-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathChess.java
More file actions
349 lines (273 loc) · 13 KB
/
Copy pathChess.java
File metadata and controls
349 lines (273 loc) · 13 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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
package chess;
import java.util.ArrayList;
public class Chess {
// Project completed by Dylan Abry and Chase Moskowitz
enum Player { white, black }
private static Board board;
private static Player currentPlayer;
private static char enPassantFile = '\0';
private static int enPassantRank = -1;
/**
* Plays the next move for whichever player has the turn.
*
* @param move String for next move, e.g. "a2 a3"
*
* @return A ReturnPlay instance that contains the result of the move.
* See the section "The Chess class" in the assignment description for details of
* the contents of the returned ReturnPlay instance.
*/
public static ReturnPlay play(String move) {
move = move.trim();
ReturnPlay result = new ReturnPlay();
// Handle resign and draw logic first before looking at coordinates!!!
if (move.equalsIgnoreCase("resign")) {
result.piecesOnBoard = board.returningPieces();
result.message = (currentPlayer == Player.white
? ReturnPlay.Message.RESIGN_BLACK_WINS
: ReturnPlay.Message.RESIGN_WHITE_WINS);
start();
return result;
}
// Set up the draw request logic (execute at the end of the turn)
boolean drawRequest = move.endsWith("draw?");
if (drawRequest) move = move.replace("draw?", "");
// Begin breaking down the input into segments...
String[] coordinates = move.split(" ");
// Eliminate option of 0-1 inputs
if (coordinates.length < 2) {
result.piecesOnBoard = board.returningPieces();
result.message = ReturnPlay.Message.ILLEGAL_MOVE;
return result;
}
// Obtain starting and new ranks and values here...
char startFile = coordinates[0].charAt(0);
int startRank = Character.getNumericValue(coordinates[0].charAt(1));
char newFile = coordinates[1].charAt(0);
int newRank = Character.getNumericValue(coordinates[1].charAt(1));
// Handle the promotion input from the player
char promotionChoice = (coordinates.length == 3) ? coordinates[2].charAt(0) : 'Q';
// Assign the piece that will be moved in turn
Piece pieceToMove = board.getPiece(startFile, startRank);
// Make sure the player is moving a piece of their own!! Don't mess this up!!!!
if (pieceToMove == null || pieceToMove.isBlack != (currentPlayer == Player.black)) {
result.piecesOnBoard = board.returningPieces();
result.message = ReturnPlay.Message.ILLEGAL_MOVE;
return result;
}
/* Another desperate approach at trying to handle an en passant move... it shouldn't allow
* the pawn move diagonal if the player is not executing an en passant move...
*/
boolean epAttempt = isEnPassantAttempt(pieceToMove, startFile, startRank, newFile, newRank, board);
if (!epAttempt && !pieceToMove.canMove(startFile, startRank, newFile, newRank, board)) {
result.piecesOnBoard = board.returningPieces();
result.message = ReturnPlay.Message.ILLEGAL_MOVE;
return result;
}
// Simulate moves here to find more loop holes before executing the move...
Board tempBoard = board.copy();
Piece tempPiece = tempBoard.getPiece(startFile, startRank);
tempBoard.setPiece(newFile, newRank, tempPiece);
tempBoard.setPiece(startFile, startRank, null);
// Simulate castling before it is done so we can ensure that nothing is in the way and the correct coordinates are entered...
if (tempPiece instanceof King && Math.abs(newFile - startFile) == 2) {
if (newFile > startFile) {
Piece rook = tempBoard.getPiece('h', startRank);
tempBoard.setPiece((char)(newFile - 1), newRank, rook);
tempBoard.setPiece('h', startRank, null);
} else {
Piece rook = tempBoard.getPiece('a', startRank);
tempBoard.setPiece((char)(newFile + 1), newRank, rook);
tempBoard.setPiece('a', startRank, null);
}
}
// En passant simulation
if (tempPiece instanceof Pawn &&
newFile == enPassantFile && newRank == enPassantRank) {
int direction = tempPiece.isBlack ? -1 : 1;
tempBoard.setPiece(newFile, newRank - direction, null);
}
// King in check simulation
if (isKingInCheck(currentPlayer, tempBoard)) {
result.piecesOnBoard = board.returningPieces();
result.message = ReturnPlay.Message.ILLEGAL_MOVE;
return result;
}
// The transaction of moving the piece is now completed after all the initial checks pass!!!
board.setPiece(newFile, newRank, pieceToMove);
board.setPiece(startFile, startRank, null);
// Flag castling if it happened
if (pieceToMove instanceof King && Math.abs(newFile - startFile) == 2) {
if (newFile > startFile) {
Piece rook = board.getPiece('h', startRank);
board.setPiece((char)(newFile - 1), newRank, rook);
board.setPiece('h', startRank, null);
if (rook instanceof Rook) ((Rook) rook).rookOfficiallyMoved();
} else {
Piece rook = board.getPiece('a', startRank);
board.setPiece((char)(newFile + 1), newRank, rook);
board.setPiece('a', startRank, null);
if (rook instanceof Rook) ((Rook) rook).rookOfficiallyMoved();
}
}
// Flag en passant if it happened
if (pieceToMove instanceof Pawn &&
newFile == enPassantFile && newRank == enPassantRank) {
int direction = pieceToMove.isBlack ? -1 : 1;
board.setPiece(newFile, newRank - direction, null);
}
// These two boolean methods will prevent the king and rook from attempting to castle again
if (pieceToMove instanceof King) {
((King) pieceToMove).officiallyMoved();
}
if (pieceToMove instanceof Rook) {
((Rook) pieceToMove).rookOfficiallyMoved();
}
// Pawn promotion logic :)
if (pieceToMove instanceof Pawn && (newRank == 8 || newRank == 1)) {
Piece promotedPiece;
switch (promotionChoice) {
case 'R': promotedPiece = new Rook(newFile, newRank, pieceToMove.isBlack); break;
case 'B': promotedPiece = new Bishop(newFile, newRank, pieceToMove.isBlack); break;
case 'N': promotedPiece = new Knight(newFile, newRank, pieceToMove.isBlack); break;
default: promotedPiece = new Queen(newFile, newRank, pieceToMove.isBlack);
}
board.setPiece(newFile, newRank, promotedPiece);
}
// Nullify values for en passant and reassign them
enPassantFile = '\0';
enPassantRank = -1;
if (pieceToMove instanceof Pawn && Math.abs(newRank - startRank) == 2) {
enPassantFile = newFile;
enPassantRank = (startRank + newRank) / 2;
}
// Successful move with no draw, resign, check or checkmate
result.piecesOnBoard = board.returningPieces();
result.message = null;
// Handle check/checkmate logic, try to add stalemate if time allows
Player opponent = (currentPlayer == Player.white) ? Player.black : Player.white;
if (isKingInCheck(opponent, board)) {
if (!isAnyLegalMoves(opponent, board)) {
result.message = (opponent == Player.white)
? ReturnPlay.Message.CHECKMATE_BLACK_WINS
: ReturnPlay.Message.CHECKMATE_WHITE_WINS;
} else {
result.message = ReturnPlay.Message.CHECK;
}
} else if (!isAnyLegalMoves(opponent, board)) {
result.message = ReturnPlay.Message.STALEMATE;
}
// Handle draw after move
if (drawRequest) {
result.message = ReturnPlay.Message.DRAW;
}
// The game should reset after one of these messages passes
// Make sure the players know that white is going once the game resets, otherwise throw ILLEGAL_MOVE
if (result.message == ReturnPlay.Message.CHECKMATE_BLACK_WINS ||
result.message == ReturnPlay.Message.CHECKMATE_WHITE_WINS ||
result.message == ReturnPlay.Message.STALEMATE ||
result.message == ReturnPlay.Message.DRAW) {
System.out.println("The game is over! The board is resetting and now it is white's turn!");
start();
return result;
}
// Switch turns if game is still going
currentPlayer = (currentPlayer == Player.white) ? Player.black : Player.white;
return result;
}
// The isKingInCheck method scans all of the opponent's pieces on the board to ensure that the king isn't vulnerable for checkmate
public static boolean isKingInCheck(Player player, Board board) {
Piece king = board.locateKing(player);
if (king == null) return false;
char kingFile = king.getFile();
int kingRank = king.getRank();
// look at all opponent pieces
for (Piece piece : board.getAllPieces()) {
if (piece.isBlack != (player == Player.black)) {
if (piece.canMove(piece.getFile(), piece.getRank(), kingFile, kingRank, board)) {
return true;
}
}
}
return false;
}
// Check if the square of destination is currently occupied
public static boolean isSquareUnderAttack(char file, int rank, boolean isBlack, Board board) {
for (Piece piece : board.getAllPieces()) {
if (piece.isBlack != isBlack) {
// Special handling for pawns since they can only attack diagonal but move forward otherwise
if (piece instanceof Pawn) {
int pawnDirection = piece.isBlack ? -1 : 1;
if ((file == piece.getFile() + 1 || file == piece.getFile() - 1) &&
rank - piece.getRank() == pawnDirection) {
return true;
}
}
// All other pieces
else {
if (piece.canMove(piece.getFile(), piece.getRank(), file, rank, board)) {
return true;
}
}
}
}
return false;
}
// The leavesKingInCheck method will check if the king is left in check after the move and warn the player before the turn switches
private static boolean leavesKingInCheck(Piece piece, char newFile, int newRank, Board board) {
Board tempBoard = board.copy();
// Simulate move
tempBoard.setPiece(newFile, newRank, piece.clone());
tempBoard.setPiece(piece.getFile(), piece.getRank(), null);
// Check safety of king
Player player = piece.isBlack ? Player.black : Player.white;
return isKingInCheck(player, tempBoard);
}
// Check for an en passant attempt, which should be handled before ILLEGAL_MOVE is thrown!!
private static boolean isEnPassantAttempt(Piece p, char sf, int sr, char nf, int nr, Board b) {
if (!(p instanceof Pawn)) return false;
int dir = p.isBlack ? -1 : 1;
// Must be a diagonal step of exactly 1 rank in the pawn's forward direction!!!
if (Math.abs(nf - sf) != 1 || (nr - sr) != dir) return false;
if (!b.isEmpty(nf, nr)) return false;
return (nf == enPassantFile && nr == enPassantRank);
}
// The isAnyLegalMoves method completes a final check of moves the player can do. If this returns false, CHECKMATE!!!!
public static boolean isAnyLegalMoves(Player player, Board board) {
for (Piece piece : board.getAllPieces()) {
if (piece.isBlack == (player == Player.black)) {
char startFile = piece.getFile();
int startRank = piece.getRank();
for (char f = 'a'; f <= 'h'; f++) {
for (int r = 1; r <= 8; r++) {
boolean epassTry = isEnPassantAttempt(piece, startFile, startRank, f, r, board);
if (!epassTry && !piece.canMove(startFile, startRank, f, r, board)) {
continue;
}
Board testBoard = board.copy();
Piece testPiece = testBoard.getPiece(startFile, startRank);
testBoard.setPiece(f, r, testPiece);
testBoard.setPiece(startFile, startRank, null);
if (epassTry) {
int direction = testPiece.isBlack ? -1 : 1;
testBoard.setPiece(f, r - direction, null);
}
if (!isKingInCheck(player, testBoard)) {
return true;
}
}
}
}
}
return false;
}
/**
* This method should reset the game, and start from scratch.
*/
public static void start() {
board = new Board();
board.setBeginningPositions();
currentPlayer = Player.white;
enPassantFile = '\0';
enPassantRank = -1;
}
}