-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHarderAI.java
More file actions
258 lines (224 loc) · 9.01 KB
/
Copy pathHarderAI.java
File metadata and controls
258 lines (224 loc) · 9.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/**
* HarderAI.java
*
* @author Deep Patel
*
* REMARKS:
* A slightly stronger AI that uses a simple minimax search over legal moves.
*
* Notes about this codebase:
* - Human pieces are represented with Piece.getWhosePiece() == true.
* - AI pieces are represented with Piece.getWhosePiece() == false.
* - The game ends when a King is captured (see ChessGameLogic.movePiece).
*/
import java.util.ArrayList;
import java.util.List;
public class HarderAI implements ChessPlayer {
// Keep the search shallow so it stays fast in this simple implementation.
private static final int MAX_DEPTH = 2; // ply depth: AI move + human reply
// Constructor
HarderAI() {}
/**
* Choose a move for the AI side (whosePiece == false) using minimax.
*
* @param lastHumanMove the last move made by the human (not required for minimax)
* @param board the current board position
* @return the move to be made by the AI
*/
@Override
public Move makeMove(Move lastHumanMove, Board board) {
List<Move> moves = generateLegalMoves(board, /*isHumanTurn=*/false);
// Fallback: if somehow no moves exist, just return a forfeiting move.
if (moves.isEmpty()) {
return new Move(1, 1, 1, 1, false, true);
}
Move bestMove = moves.get(0);
int bestScore = Integer.MIN_VALUE;
for (Move m : moves) {
Board next = copyBoard(board);
applyMove(next, m, /*isHumanTurn=*/false);
int score = minimax(next, MAX_DEPTH - 1, /*isMaximizing=*/false);
if (score > bestScore) {
bestScore = score;
bestMove = m;
}
}
// If the move is a pawn promotion for the AI, tell ChessGameLogic what to promote to.
Piece fromPiece = board.getBox(bestMove.getFromRow() - 1, bestMove.getFromCol() - 1).getPiece();
if (fromPiece instanceof Pawn && bestMove.getToRow() == 8) {
bestMove.setPiece(new Queen(false));
} else {
bestMove.setPiece(fromPiece);
}
return bestMove;
}
/**
* Minimax search.
*
* @param board current position
* @param depth remaining depth
* @param isMaximizing true when it's AI's turn, false when it's human's turn
*/
private int minimax(Board board, int depth, boolean isMaximizing) {
// Terminal conditions
if (depth == 0 || isKingMissing(board, /*humanKing=*/true) || isKingMissing(board, /*humanKing=*/false)) {
return evaluate(board);
}
boolean isHumanTurn = !isMaximizing; // maximizing = AI (false pieces)
List<Move> moves = generateLegalMoves(board, isHumanTurn);
if (moves.isEmpty()) {
return evaluate(board);
}
if (isMaximizing) {
int best = Integer.MIN_VALUE;
for (Move m : moves) {
Board next = copyBoard(board);
applyMove(next, m, /*isHumanTurn=*/false);
best = Math.max(best, minimax(next, depth - 1, false));
}
return best;
} else {
int best = Integer.MAX_VALUE;
for (Move m : moves) {
Board next = copyBoard(board);
applyMove(next, m, /*isHumanTurn=*/true);
best = Math.min(best, minimax(next, depth - 1, true));
}
return best;
}
}
/**
* Material-only evaluation.
* Positive scores are good for the AI (false pieces).
*/
private int evaluate(Board board) {
// If a king is captured, heavily reward/punish.
boolean humanKingMissing = isKingMissing(board, true);
boolean aiKingMissing = isKingMissing(board, false);
if (humanKingMissing && !aiKingMissing) {
return 100000;
}
if (aiKingMissing && !humanKingMissing) {
return -100000;
}
int score = 0;
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
Piece p = board.getBox(r, c).getPiece();
if (p == null) continue;
int v = pieceValue(p);
// AI pieces (false) are positive; human (true) are negative.
score += p.getWhosePiece() ? -v : v;
}
}
return score;
}
private int pieceValue(Piece p) {
if (p instanceof Pawn) return 100;
if (p instanceof Knight) return 320;
if (p instanceof Bishop) return 330;
if (p instanceof Rook) return 500;
if (p instanceof Queen) return 900;
if (p instanceof King) return 20000;
return 0;
}
private boolean isKingMissing(Board board, boolean humanKing) {
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
Piece p = board.getBox(r, c).getPiece();
if (p instanceof King && p.getWhosePiece() == humanKing) {
return false;
}
}
}
return true;
}
/**
* Generate all legal moves for the side whose turn it is.
*
* @param board current board
* @param isHumanTurn true for human side (whosePiece == true), false for AI side
*/
private List<Move> generateLegalMoves(Board board, boolean isHumanTurn) {
List<Move> moves = new ArrayList<>();
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
Piece piece = board.getBox(r, c).getPiece();
if (piece == null) continue;
if (piece.getWhosePiece() != isHumanTurn) continue;
// Pawn validation depends on "turn" in this codebase.
if (piece instanceof Pawn) {
((Pawn) piece).setTurn(isHumanTurn);
}
int fromRow = r + 1;
int fromCol = c + 1;
for (int tr = 1; tr <= 8; tr++) {
for (int tc = 1; tc <= 8; tc++) {
if (tr == fromRow && tc == fromCol) continue;
Piece dest = board.getBox(tr - 1, tc - 1).getPiece();
// Can't capture your own piece
if (dest != null && dest.getWhosePiece() == isHumanTurn) continue;
Move m = new Move(fromRow, fromCol, tr, tc, false, false);
// For AI pawn promotions, tell the engine to promote to queen.
if (!isHumanTurn && piece instanceof Pawn && tr == 8) {
m.setPiece(new Queen(false));
} else {
m.setPiece(piece);
}
if (piece.isValidMove(board, m)) {
moves.add(m);
}
}
}
}
}
return moves;
}
/**
* Apply a move to the board (used for search only).
* This mirrors the core behavior in ChessGameLogic.movePiece, but without UI.
*/
private void applyMove(Board board, Move move, boolean isHumanTurn) {
Piece piece = board.getBox(move.getFromRow() - 1, move.getFromCol() - 1).getPiece();
Piece destPiece = board.getBox(move.getToRow() - 1, move.getToCol() - 1).getPiece();
// Promote pawns automatically during search so the evaluation makes sense.
boolean doPromotion = (piece instanceof Pawn)
&& ((isHumanTurn && move.getToRow() == 1) || (!isHumanTurn && move.getToRow() == 8));
if (doPromotion) {
// Default: queen promotion.
board.getBox(move.getToRow() - 1, move.getToCol() - 1).setPiece(new Queen(isHumanTurn));
} else {
board.getBox(move.getToRow() - 1, move.getToCol() - 1).setPiece(piece);
}
board.getBox(move.getFromRow() - 1, move.getFromCol() - 1).setPiece(null);
// (destPiece is intentionally unused here; capture is implied by overwriting)
if (destPiece instanceof King) {
// Nothing else required; evaluation checks for missing kings.
}
}
/**
* Create a deep copy of the board (new Piece instances) for search.
*/
private Board copyBoard(Board original) {
Board b = new Board();
// Override the reset position with the actual position.
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
Piece p = original.getBox(r, c).getPiece();
b.getBox(r, c).setPiece(clonePiece(p));
}
}
return b;
}
private Piece clonePiece(Piece p) {
if (p == null) return null;
boolean side = p.getWhosePiece();
if (p instanceof Pawn) return new Pawn(side);
if (p instanceof Rook) return new Rook(side);
if (p instanceof Knight) return new Knight(side);
if (p instanceof Bishop) return new Bishop(side);
if (p instanceof Queen) return new Queen(side);
if (p instanceof King) return new King(side);
return null;
}
}