@@ -38,6 +38,14 @@ public class Chessboard {
3838 // Square that can be targeted by an en passant capture
3939 private Position enPassantTarget ;
4040
41+ // Track if kings and rooks have moved (for castling)
42+ private boolean whiteKingMoved = false ;
43+ private boolean blackKingMoved = false ;
44+ private boolean whiteKingsideRookMoved = false ;
45+ private boolean whiteQueensideRookMoved = false ;
46+ private boolean blackKingsideRookMoved = false ;
47+ private boolean blackQueensideRookMoved = false ;
48+
4149 public Chessboard () {
4250 board = GetInitMatrixBoard ();
4351 invalid = new ChessPiece (ChessPieceType .Invalid , Color .None );
@@ -78,6 +86,10 @@ public ChessPiece movePiece(Position source, Position target, Color player, Ches
7886 target .col == enPassantTarget .col &&
7987 Math .abs (source .col - target .col ) == 1 ;
8088
89+ // Check for castling
90+ boolean isCastling = sourcePosition .type () == ChessPieceType .King &&
91+ Math .abs (target .col - source .col ) == 2 ;
92+
8193 // reset en passant target; will be set again if this move is a double step
8294 enPassantTarget = null ;
8395
@@ -96,9 +108,53 @@ public ChessPiece movePiece(Position source, Position target, Color player, Ches
96108 return captured ;
97109 }
98110
111+ // Handle castling - move both king and rook
112+ if (isCastling ) {
113+ int rookSourceCol = target .col > source .col ? 7 : 0 ; // Kingside or queenside
114+ int rookTargetCol = target .col > source .col ? target .col - 1 : target .col + 1 ;
115+
116+ ChessPiece rook = board [source .row ][rookSourceCol ];
117+ board [source .row ][source .col ] = emptySpace ;
118+ board [target .row ][target .col ] = sourcePosition ;
119+ board [source .row ][rookSourceCol ] = emptySpace ;
120+ board [source .row ][rookTargetCol ] = rook ;
121+
122+ // Mark king as moved
123+ if (player == Color .White ) {
124+ whiteKingMoved = true ;
125+ } else {
126+ blackKingMoved = true ;
127+ }
128+
129+ return emptySpace ;
130+ }
131+
99132 board [source .row ][source .col ] = emptySpace ;
100133 board [target .row ][target .col ] = sourcePosition ;
101134
135+ // Track king and rook moves for castling eligibility
136+ if (sourcePosition .type () == ChessPieceType .King ) {
137+ if (player == Color .White ) {
138+ whiteKingMoved = true ;
139+ } else {
140+ blackKingMoved = true ;
141+ }
142+ } else if (sourcePosition .type () == ChessPieceType .Rock ) {
143+ if (player == Color .White ) {
144+ if (source .row == 0 && source .col == 0 ) {
145+ whiteQueensideRookMoved = true ;
146+ } else if (source .row == 0 && source .col == 7 ) {
147+ whiteKingsideRookMoved = true ;
148+ }
149+ } else {
150+ if (source .row == 7 && source .col == 0 ) {
151+ blackQueensideRookMoved = true ;
152+ } else if (source .row == 7 && source .col == 7 ) {
153+ blackKingsideRookMoved = true ;
154+ }
155+ }
156+ }
157+
102158 if (sourcePosition .type () == ChessPieceType .Pawn && Math .abs (target .row - source .row ) == 2 ) {
103159 enPassantTarget = new Position ((source .row + target .row ) / 2 , source .col );
104160 }
@@ -321,6 +377,13 @@ public Position[] getValidMoves(Position position) {
321377
322378 ChessPiece chessPiece = board [position .row ][position .col ];
323379 Position [] candidateMoves = getCandidateMoves (position , chessPiece );
380+
381+ // For kings, add castling moves before filtering
382+ if (chessPiece .type () == ChessPieceType .King ) {
383+ List <Position > withCastling = new ArrayList <>(Arrays .asList (candidateMoves ));
384+ addCastlingMoves (position , withCastling , chessPiece .color ());
385+ candidateMoves = withCastling .toArray (Position []::new );
386+ }
324387
325388 // Filter out moves that would leave the player's king in check
326389 return filterMovesLeavingKingInCheck (position , candidateMoves , chessPiece .color ());
@@ -346,7 +409,7 @@ private Position[] getCandidateMoves(Position position, ChessPiece chessPiece) {
346409 return getValidMovesQueen (position , chessPiece );
347410 }
348411 case King -> {
349- return getValidMovesKing (position , chessPiece );
412+ return getValidMovesKingBasic (position , chessPiece );
350413 }
351414 case Rock -> {
352415 return getValidMovesRock (position , chessPiece );
@@ -371,8 +434,19 @@ private Position[] getCandidateMoves(Position position, ChessPiece chessPiece) {
371434 */
372435 private Position [] filterMovesLeavingKingInCheck (Position from , Position [] candidateMoves , Color playerColor ) {
373436 List <Position > legalMoves = new ArrayList <>();
437+ ChessPiece piece = board [from .row ][from .col ];
438+ boolean isKing = piece .type () == ChessPieceType .King ;
374439
375440 for (Position to : candidateMoves ) {
441+ // Castling moves are already validated and don't need simulation
442+ // (king moves 2 squares horizontally)
443+ boolean isCastlingMove = isKing && Math .abs (to .col - from .col ) == 2 ;
444+
445+ if (isCastlingMove ) {
446+ legalMoves .add (to );
447+ continue ;
448+ }
449+
376450 // Simulate the move
377451 ChessPiece captured = simulateMove (from , to );
378452
@@ -421,7 +495,114 @@ private Position[] getValidMovesBishop(Position position, ChessPiece chessPiece)
421495 return valid .toArray (Position []::new );
422496 }
423497
424- private Position [] getValidMovesKing (Position position , ChessPiece chessPiece ) {
498+ /**
499+ * Adds castling moves if conditions are met.
500+ * Castling is allowed if:
501+ * 1. King hasn't moved
502+ * 2. Rook hasn't moved
503+ * 3. No pieces between king and rook
504+ * 4. King is not in check
505+ * 5. King doesn't move through check
506+ * 6. King doesn't land in check
507+ */
508+ private void addCastlingMoves (Position kingPos , List <Position > valid , Color color ) {
509+ // Check if king is in check - can't castle out of check
510+ if (isKingInCheck (color )) {
511+ return ;
512+ }
513+
514+ if (color == Color .White ) {
515+ // White kingside castling
516+ if (!whiteKingMoved && !whiteKingsideRookMoved &&
517+ kingPos .row == 0 && kingPos .col == 4 ) {
518+ if (canCastleKingside (0 , color )) {
519+ valid .add (new Position (0 , 6 ));
520+ }
521+ }
522+ // White queenside castling
523+ if (!whiteKingMoved && !whiteQueensideRookMoved &&
524+ kingPos .row == 0 && kingPos .col == 4 ) {
525+ if (canCastleQueenside (0 , color )) {
526+ valid .add (new Position (0 , 2 ));
527+ }
528+ }
529+ } else {
530+ // Black kingside castling
531+ if (!blackKingMoved && !blackKingsideRookMoved &&
532+ kingPos .row == 7 && kingPos .col == 4 ) {
533+ if (canCastleKingside (7 , color )) {
534+ valid .add (new Position (7 , 6 ));
535+ }
536+ }
537+ // Black queenside castling
538+ if (!blackKingMoved && !blackQueensideRookMoved &&
539+ kingPos .row == 7 && kingPos .col == 4 ) {
540+ if (canCastleQueenside (7 , color )) {
541+ valid .add (new Position (7 , 2 ));
542+ }
543+ }
544+ }
545+ }
546+
547+ /**
548+ * Check if kingside castling is possible (no pieces between, king doesn't move through check).
549+ */
550+ private boolean canCastleKingside (int row , Color color ) {
551+ // Check squares between king and rook are empty
552+ if (board [row ][5 ].type () != ChessPieceType .Empty ||
553+ board [row ][6 ].type () != ChessPieceType .Empty ) {
554+ return false ;
555+ }
556+
557+ // Check king doesn't move through check (squares f1/f8 and g1/g8)
558+ return !isSquareUnderAttack (new Position (row , 5 ), color ) &&
559+ !isSquareUnderAttack (new Position (row , 6 ), color );
560+ }
561+
562+ /**
563+ * Check if queenside castling is possible (no pieces between, king doesn't move through check).
564+ */
565+ private boolean canCastleQueenside (int row , Color color ) {
566+ // Check squares between king and rook are empty
567+ if (board [row ][1 ].type () != ChessPieceType .Empty ||
568+ board [row ][2 ].type () != ChessPieceType .Empty ||
569+ board [row ][3 ].type () != ChessPieceType .Empty ) {
570+ return false ;
571+ }
572+
573+ // Check king doesn't move through check (squares d1/d8 and c1/c8)
574+ // Note: b1/b8 doesn't need to be safe, only the king's path
575+ return !isSquareUnderAttack (new Position (row , 3 ), color ) &&
576+ !isSquareUnderAttack (new Position (row , 2 ), color );
577+ }
578+
579+ /**
580+ * Check if a square is under attack by the opponent.
581+ */
582+ private boolean isSquareUnderAttack (Position square , Color defendingColor ) {
583+ Color attackingColor = getOpposite (defendingColor );
584+
585+ for (int r = 0 ; r < board .length ; r ++) {
586+ for (int c = 0 ; c < board [r ].length ; c ++) {
587+ ChessPiece piece = board [r ][c ];
588+ if (piece .color () == attackingColor ) {
589+ Position [] moves = getCandidateMoves (new Position (r , c ), piece );
590+ for (Position move : moves ) {
591+ if (move .row == square .row && move .col == square .col ) {
592+ return true ;
593+ }
594+ }
595+ }
596+ }
597+ }
598+ return false ;
599+ }
600+
601+ /**
602+ * Gets basic king moves (one square in any direction) without castling.
603+ * Used by getCandidateMoves to avoid infinite recursion.
604+ */
605+ private Position [] getValidMovesKingBasic (Position position , ChessPiece chessPiece ) {
425606 if (chessPiece .type () != ChessPieceType .King ) {
426607 return new Position [0 ];
427608 }
0 commit comments