-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGame.java
More file actions
546 lines (504 loc) · 21.7 KB
/
Game.java
File metadata and controls
546 lines (504 loc) · 21.7 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
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Game implements Runnable{
private boolean gameOver = false;
/* Local elements of state. The above determines whether or not the game is
* still in play. A value of false disables all buttons, and occurs
* concurrently with the termination of all pieces from state. StartSquare
* and endSquare serve self-evident purposes in executing moves, as does
* chosenPiece. Because moves are judged to be legal in regards to check
* retroactively, another element of state, limboPiece, is necessary to
* ensure that any piece taken during an illegal move can still be
* replaced when the move is reset. The chessboard is initialized here for
* convenience, and the toMove variable ensures that white starts the game.
*/
private Square startSquare;
private Square endSquare;
private Piece chosenPiece;
private Piece limboPiece;
private Chessboard board = new Chessboard();
private Shade toMove = Shade.WHITE;
//The array defined here is used later in the prompt for pawn promotion.
private Object[] options = {"Knight","Bishop","Rook","Queen"};
/* The below method checks for checkmate or stalemate by determining
* whether or not the color whose turn it is to move has any valid
* moves remaining. It functions using the same algorithm as the movement
* protocol in the mouse listener added to each square. Whether or not the
* mate is a checkmate or a stalemate is determined later on.
*/
private boolean mateCheck(Player p1,Player p2){
//A temporary holding cell for the coordinates of a piece before it is
//moved, so that it may be moved back in the event a move is invalid.
Coord temp;
int n = 1;
if(toMove == Shade.BLACK){
for(Piece pc: p2.getPieces()){
if (pc instanceof PassantShadow) continue;
temp = pc.getCoord();
for(Coord cd:pc.getMoves()){
if(board.getSquare(cd).isOccupied()) limboPiece = board.getSquare(cd).getOccupant();
if(board.movePiece(pc, cd)){
if(!(p1.getSquaresAttacked().contains(p2.getKingCoord()))){
System.out.print("Found move for " + pc.toString() + " from " + board.getSquare(temp).toString() + "\n");
board.getSquare(temp).place(pc);
if(limboPiece != null) limboPiece.revive();
return false;
}
board.getSquare(temp).place(pc);
if(limboPiece != null) limboPiece.revive();
System.out.print(n + " moves evaluated\n");
n++;
}
limboPiece = null;
}
}
return true;
} else {
for(Piece pc: p1.getPieces()){
if (pc instanceof PassantShadow) continue;
temp = pc.getCoord();
for(Coord cd:pc.getMoves()){
if(board.getSquare(cd).isOccupied()) limboPiece = board.getSquare(cd).getOccupant();
if(board.movePiece(pc, cd)){
if(!(p2.getSquaresAttacked().contains(p1.getKingCoord()))){
System.out.print("Found move for " + pc.toString() + " at " + board.getSquare(cd).toString());
board.getSquare(temp).place(pc);
if(limboPiece != null) limboPiece.revive();
return false;
}
board.getSquare(temp).place(pc);//movePiece(pc, temp);
if(limboPiece != null) limboPiece.revive();
}
limboPiece = null;
}
}
return true;
}
}
public void toggleMove(){
if (toMove == Shade.BLACK) toMove = Shade.WHITE;
else toMove = Shade.BLACK;
}
//Method is passed the shade of the losing player and both players, so that it can terminate play.
private void victory(Shade sh, Player p1, Player p2){
for(Piece p:p1.getPieces()){
p.kill();
}
for(Piece p:p2.getPieces()){
p.kill();
}
for(Square sqr:board.getAllSquares()){
if(sh == Shade.WHITE) sqr.setBackground(Color.BLACK);
else sqr.setBackground(Color.WHITE);
}
}
//Method is passed both players, allowing it to terminate play.
public void draw(Player p1, Player p2){
for(Piece p:p1.getPieces()){
p.kill();
}
for(Piece p:p2.getPieces()){
p.kill();
}
for(Square sqr:board.getAllSquares()){
sqr.setBackground(Color.CYAN);
}
}
@Override
public void run() {
JFrame frame = new JFrame("CIChess: A Chess Program by Cam Cogan");
//White (p1) and black (p2) are initialized
final Player p1 = new Player(Shade.WHITE,board);
final Player p2 = new Player(Shade.BLACK,board);
//The main GUI components are initialized, including the board and button panel at right.
final JPanel grid = new JPanel();
final JPanel buttonPanel = new JPanel();
final JButton declareMate = new JButton("Declare Mate");
final JButton offerDraw = new JButton("Offer Draw");
final JButton resignButton = new JButton("Resign");
final JButton partyButton = new JButton("Party Colors");
//Configuring the button panel.
buttonPanel.setLayout(new GridLayout(4,1));
buttonPanel.add(declareMate);
buttonPanel.add(offerDraw);
buttonPanel.add(resignButton);
buttonPanel.add(partyButton);
buttonPanel.setBorder(BorderFactory.createEtchedBorder());
//Displaying the square objects in the correct order on the grid requires
//the unorthodox indexing loop seen below, as the squares are added to
//the grid from left to right, top row to bottom row.
for(int i = 0; i < 8; i++){
for(int j = 0; j < 8; j++){
final Square sq = board.getSquare(new Coord(j,7-i));
grid.add(sq);
if(sq.isOccupied()) sq.add(sq.getOccupant());
else sq.removeAll();
}
}
//This method uses the mateCheck method, then declares victory for the
//proper player, declares a stalemate, or returns to play, letting the
//players know it was unsuccessful in finding a mate.
declareMate.addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
if(!gameOver){
if(mateCheck(p1,p2)){
if(toMove == Shade.BLACK){
if(p1.getSquaresAttacked().contains(p2.getKingCoord())){
victory(toMove,p1,p2);
toggleMove();
JOptionPane.showMessageDialog(grid, toMove + " WINS BY CHECKMATE!");
gameOver = true;
} else {
draw(p1, p2);
JOptionPane.showMessageDialog(grid, "Stalemate!");
gameOver = true;
}
} else {
if(p2.getSquaresAttacked().contains(p1.getKingCoord())){
victory(toMove,p1,p2);
toggleMove();
JOptionPane.showMessageDialog(grid, toMove + " WINS BY CHECKMATE!");
gameOver = true;
} else {
draw(p1, p2);
JOptionPane.showMessageDialog(grid, "Stalemate!");
gameOver = true;
}
}
} else {
JOptionPane.showMessageDialog(grid, "No mates detected.");
}
}
}
});
//A simple button that terminates play if a draw is agreed upon.
offerDraw.addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
if(!gameOver){
toggleMove();
int n = JOptionPane.showConfirmDialog(grid, toMove + ": Do you accept the draw?",
"Draw Offered",JOptionPane.YES_NO_OPTION);
if(n == 0){
draw(p1, p2);
JOptionPane.showMessageDialog(grid, "Draw declared!");
gameOver = true;
} else {
toggleMove();
}
}
}
});
//A button functionally identical to the one above, but which declares victory rather than a draw.
resignButton.addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
if(!gameOver){
int n = JOptionPane.showConfirmDialog(grid, toMove + ": Are you sure you wish to resign?",
"Resign?",JOptionPane.YES_NO_OPTION);
if(n == 0){
victory(toMove,p1,p2);
toggleMove();
JOptionPane.showMessageDialog(grid, toMove + " WINS BY RESIGNATION!");
gameOver = true;
}
}
}
});
//A button which iterates over the squares on the board and changes the color scheme to
//magenta-green instead of black-white. A boolean field helps it keep track of its current
//mode.
partyButton.addMouseListener(new MouseAdapter(){
private boolean state = false;
Square sqare;
public void mouseClicked(MouseEvent e){
if(!gameOver){
for(int i = 0; i < 8; i++){
for(int j = 0; j < 8; j++){
sqare = board.getSquare(new Coord(j,7-i));
if(!state){
if ((i+j)%2 ==0) sqare.setBackground(Color.GREEN);
else sqare.setBackground(Color.MAGENTA);
} else {
if ((i+j)%2 ==0) sqare.setBackground(Color.white);
else sqare.setBackground(Color.gray);
}
}
}
}
state = !(state);
grid.repaint();
}
});
/** The main mouse listener for the game, added to the grid to allow the
* existence of one listener for the board rather than 64. The main event
* loop of the entire game is contained within this listener.
*
* A mousePressed event assigns the correct state to the startSquare and
* chosenPiece fields. The search for the correct square is dependent on
* the invariant that each square is 75 pixels by 75 pixels.
*
* A mouseReleased event assigns the endSquare and limboPiece fields
* using the same algorithm as above. The program then performs a series
* of checks:
* -Is the start square or chosen piece selected null? If so, the action is discarded.
* -Can the chosenPiece piece be moved to the square endSquare from its present location? If not, the action is discarded.
* -If so, will this move leave the piece's king in check? If so, the action is discarded.
* -If the piece is a king, is it castling? If so, the graphical representation of the rook is also adjusted.
* -If the piece is a pawn, is it being promoted? If so, the piece is replaced with a piece of the user's choice.
* -If the piece is a pawn, is it is moving two spaces? If so, leave a PassantShadow object in its wake on the board.
* -If the piece is a pawn, is it capturing en passant? If so, remove the graphical representation of the pawn captured.
*
* Following this series of evaluations, the program then performs a set of events regardless of the color of the piece,
* though the protocol below is technically stemmed by color:
* -The color to move next has its current attacks cleared from the board, and the color that has just moved
* has its attacks applied to the board.
* -The toMove variable is toggled so that the next player may move.
* -startSquare and endSquare are cleared of any graphical representations of pieces.
* -The graphical representation of chosenPiece is added to endSquare.
* -PassantShadows are cleared for the color about to move
* -The board is repainted.
* -The console prints helper messages.
* -The state of the variables startSquare, endSquare, chosenPiece, and limboPiece are all reset to null.
* -PassantShadows are cleared from players' rosters.
*/
grid.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e){
Point start = e.getPoint();
int x = start.x;
int y = start.y;
x /= 75;
y = 7 - (y/75);
startSquare = board.getSquare(new Coord(x,y));
if(!startSquare.isOccupied()) chosenPiece = null;
if(startSquare.occupantShade() == toMove){
chosenPiece = startSquare.getOccupant();
} else chosenPiece = null;
System.out.print("Start square: " + startSquare.toString() + "\n");
if(chosenPiece == null) System.out.print("Piece was not selected; startSquare.isOccupied returned false.\n");
else System.out.print("Chosen piece: " + chosenPiece.toString() + "\n");
}
public void mouseReleased(MouseEvent e){
Point end = e.getPoint();
int x = end.x;
int y = end.y;
x /= 75;
y = 7 - (y/75);
endSquare = board.getSquare(new Coord(x,y));
if (endSquare.isOccupied() || endSquare.hasPassant()) limboPiece = endSquare.getOccupant();
if(!(startSquare == null || chosenPiece == null)){
if(board.movePiece(chosenPiece, endSquare.getCoords())){
if(toMove == Shade.WHITE){
if(!(p2.getSquaresAttacked().contains(p1.getKingCoord()))){
if (chosenPiece instanceof King){
if (startSquare.getCoords().vectorTo(endSquare.getCoords()).equals(new Coord(2,0))){
Square rookSquare = board.getSquare(new Coord(5,0));
rookSquare.removeAll();
rookSquare.add(rookSquare.getOccupant());
} else if (startSquare.getCoords().vectorTo(endSquare.getCoords()).equals(new Coord(-2,0))){
Square rookSquare = board.getSquare(new Coord(3,0));
rookSquare.removeAll();
rookSquare.add(rookSquare.getOccupant());
}
}
if (chosenPiece instanceof Pawn){
if (endSquare.getCoords().getRank() == 0 || endSquare.getCoords().getRank() == 7){
int n = JOptionPane.showOptionDialog(grid,"To what would you like to promote that pawn?",
"Pawn Promotion",JOptionPane.YES_NO_CANCEL_OPTION,JOptionPane.QUESTION_MESSAGE,
null,options,options[3]);
chosenPiece.kill();
endSquare.removeAll();
switch (n){
case 0:
Knight nite = new Knight(Shade.WHITE,new int[]{endSquare.getCoords().getFile(),
endSquare.getCoords().getRank()},board);
p1.addPromotedPiece(nite);
chosenPiece = nite;
break;
case 1:
Bishop b = new Bishop(Shade.WHITE,new int[]{endSquare.getCoords().getFile(),
endSquare.getCoords().getRank()},board);
p1.addPromotedPiece(b);
chosenPiece = b;
break;
case 2:
Rook r = new Rook(Shade.WHITE,new int[]{endSquare.getCoords().getFile(),
endSquare.getCoords().getRank()},board);
p1.addPromotedPiece(r);
chosenPiece = r;
break;
case 3:
Queen q = new Queen(Shade.WHITE,new int[]{endSquare.getCoords().getFile(),
endSquare.getCoords().getRank()},board);
p1.addPromotedPiece(q);
chosenPiece = q;
break;
}
} else if (startSquare.getCoords().vectorTo(endSquare.getCoords()).getRank() == 2){
PassantShadow ps =
new PassantShadow(Shade.WHITE,
new int[]{endSquare.getCoords().getFile(),endSquare.getCoords().getRank()-1},
board,(Pawn) chosenPiece);
board.movePiece(ps, ps.getCoord());
p1.getPieces().add(ps);
} else if (limboPiece instanceof PassantShadow){
board.getSquare(new Coord(endSquare.getCoords().getFile(),endSquare.getCoords().getRank()-1)).removeAll();
}
}
board.applyAttack(p1);
toggleMove();
startSquare.removeAll();
endSquare.removeAll();
endSquare.add(chosenPiece);
endSquare.repaint();
for(Square sqwr: board.getAllSquares()){
if(sqwr.hasPassant() && sqwr.occupantShade() == Shade.BLACK) sqwr.clear();
}
grid.repaint();
System.out.print("End square: " + endSquare.toString() + "\n");
System.out.print("To move: " + toMove + "\n");
startSquare = null;
endSquare = null;
chosenPiece = null;
limboPiece = null;
p2.clearPassant();
} else {
if(!startSquare.place(chosenPiece)) System.out.print("Problem placing chosenPiece");//movePiece(chosenPiece, startSquare.getCoords());
endSquare.clear();
if(limboPiece != null) limboPiece.revive();
limboPiece = null;
System.out.print("Invalid move, King under attack.\n");
}
} else {
if(!(p1.getSquaresAttacked().contains(p2.getKingCoord()))){
if (chosenPiece instanceof King){
if (startSquare.getCoords().vectorTo(endSquare.getCoords()).equals(new Coord(2,0))){
Square rookSquare = board.getSquare(new Coord(5,7));
rookSquare.removeAll();
rookSquare.add(rookSquare.getOccupant());
} else if (startSquare.getCoords().vectorTo(endSquare.getCoords()).equals(new Coord(-2,0))){
Square rookSquare = board.getSquare(new Coord(3,7));
rookSquare.removeAll();
rookSquare.add(rookSquare.getOccupant());
}
}
if (chosenPiece instanceof Pawn){
if (endSquare.getCoords().getRank() == 0 || endSquare.getCoords().getRank() == 7){
int n = JOptionPane.showOptionDialog(grid,"To what would you like to promote that pawn?",
"Pawn Promotion",JOptionPane.YES_NO_CANCEL_OPTION,JOptionPane.QUESTION_MESSAGE,
null,options,options[3]);
chosenPiece.kill();
endSquare.removeAll();
switch (n){
case 0:
Knight nite = new Knight(Shade.BLACK,new int[]{endSquare.getCoords().getFile(),
endSquare.getCoords().getRank()},board);
p2.addPromotedPiece(nite);
chosenPiece = nite;
break;
case 1:
Bishop b = new Bishop(Shade.BLACK,new int[]{endSquare.getCoords().getFile(),
endSquare.getCoords().getRank()},board);
p2.addPromotedPiece(b);
chosenPiece = b;
break;
case 2:
Rook r = new Rook(Shade.BLACK,new int[]{endSquare.getCoords().getFile(),
endSquare.getCoords().getRank()},board);
p2.addPromotedPiece(r);
chosenPiece = r;
break;
case 3:
Queen q = new Queen(Shade.BLACK,new int[]{endSquare.getCoords().getFile(),
endSquare.getCoords().getRank()},board);
p2.addPromotedPiece(q);
chosenPiece = q;
break;
}
} else if (startSquare.getCoords().vectorTo(endSquare.getCoords()).getRank() == -2){
PassantShadow ps =
new PassantShadow(Shade.BLACK,
new int[]{endSquare.getCoords().getFile(),endSquare.getCoords().getRank()+1},
board,(Pawn) chosenPiece);
board.movePiece(ps, ps.getCoord());
p2.getPieces().add(ps);
} else if (limboPiece instanceof PassantShadow){
board.getSquare(new Coord(endSquare.getCoords().getFile(),endSquare.getCoords().getRank()+1)).removeAll();
}
}
board.applyAttack(p2);
toggleMove();
startSquare.removeAll();
endSquare.removeAll();
endSquare.add(chosenPiece);
endSquare.repaint();
for(Square sqwr: board.getAllSquares()){
if(sqwr.hasPassant() && sqwr.occupantShade() == Shade.WHITE) sqwr.clear();
}
grid.repaint();
System.out.print("End square: " + endSquare.toString() + "\n");
System.out.print("To move: " + toMove + "\n");
startSquare = null;
endSquare = null;
chosenPiece = null;
limboPiece = null;
p1.clearPassant();
} else {
if(!startSquare.place(chosenPiece)) System.out.print("Problem placing chosenPiece");
endSquare.clear();
if(limboPiece != null) limboPiece.revive();
limboPiece = null;
System.out.print("Invalid move, King under attack.\n");
}
}
}
}
}
});
grid.setLayout(new GridLayout(8,8));
frame.setLayout(new BorderLayout());
frame.add(grid, BorderLayout.CENTER);
frame.add(buttonPanel,BorderLayout.LINE_END);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
JOptionPane.showMessageDialog(frame,
"Hello and welcome to CIChess! This is a two-player chess game developed as a homework\n" +
"project for CIS 120. The rules of CIChess are identical to those of normal chess.\n" +
"Simply find a partner, pick a color, and trade off making moves with your opponent.\n" +
"Any move valid in chess is also valid here, including en passant as well as promotion.\n" +
"To actually move pieces, simply click and drag them. There won't be any visual piece preview,\n" +
"but there are a number of helpful outputs in the console to aid you.\n\n" +
"If a mate situation is reached, simply hit the 'Declare Mate' button at right\n" +
"and the game will check for you whether or not a checkmate or stalemate has been \n" +
"reached. If you wish to resign, hit the 'Resign' button. Meanwhile, if you're feeling\n" +
"diplomatic and wish to offer a draw, that option can be found at right as well. Hit the\n" +
"'Party Colors' button to add some zazz to your game. The board will turn the color of the\n" +
"winning player when a win or loss has occurred, or cyan if there is a draw.\n\n" +
"The impressive features of this game consist almost entirely of the accuracy with which\n" +
"it renders the rules of chess. Pieces are bound to only move in a certain fashion, and \n" +
"cannot move through pieces of any color, barring knights. Pieces of the same color cannot\n" +
"take one another, and kings cannot move into check. Pawns can only move forward, unless they\n" +
"can capture, including capturing en passant, and can only move forward two spaces if it is\n" +
"the piece's first move. Kings can castle with rooks, but only if intervening squares are neither\n" +
"occupied nor under attack by opposing pieces. Castling is also invalid if either piece involved\n" +
"has moved during the game. No rule is left unimplemented, and players are bound to follow them.\n\n" +
"IMPORTANT GRAPHICAL CONSIDERATIONS: Keep the window at present size, as stretching it will\n" +
"distort the controls of the game. Additionally, when pawns are promoted, they seem to \n" +
"disappear. Fear not! Minimizing and restoring the screen will display the piece to which you've\n" +
"promoted said pawn.\n\n" +
"With that, best of luck! This game took some time to develop, so you should feel free to spend\n" +
"about as much time enjoying it.");
}
public static void main(String[] args){
SwingUtilities.invokeLater(new Game());
}
}