public ChessMove(BoardSquare start, BoardSquare end, char pieceMoved, bool isCapture, Piece promotedPiece) { this.startSquare = start; this.endSquare = end; this.pieceMoved = pieceMoved; this.checkIndicator = '\0'; this.isCapture = isCapture; if (promotedPiece != null) this.promotedPiece = promotedPiece.Notational; // Determine if the move resulted in castling if (this.pieceMoved == (char)Piece.PieceNotation.King && start.X - end.X == 2) this.castleNotation = CASTLE_QUEEN; else if (this.pieceMoved == (char)Piece.PieceNotation.King && start.X - end.X == -2) this.castleNotation = CASTLE_KING; else this.castleNotation = String.Empty; }
public bool IsBetweenPoints(BoardSquare endPointA, BoardSquare endPointB) { BoardSquare maxVals = BoardSquare.Empty, minVals = BoardSquare.Empty; maxVals.X = Math.Max(endPointA.X, endPointB.X); maxVals.Y = Math.Max(endPointA.Y, endPointB.Y); minVals.X = Math.Min(endPointA.X, endPointB.X); minVals.Y = Math.Min(endPointA.Y, endPointB.Y); /*if (endPointA.X > endPointB.X) { maxVals.X = endPointA.X; minVals.X = endPointB.X; } else { minVals.X = endPointA.X; maxVals.X = endPointB.X; } if (endPointA.Y > endPointB.Y) { maxVals.Y = endPointA.Y; minVals.Y = endPointB.Y; } else { minVals.Y = endPointA.Y; maxVals.Y = endPointB.Y; }*/ if (this.X < minVals.X || this.X > maxVals.X || this.Y < minVals.Y || this.Y > maxVals.Y) return false; // Avoid divide by zero, the line goes straight up if (endPointB.X == endPointA.X) { return endPointB.X == endPointB.X; } else { double slope = (endPointB.Y - endPointA.Y) / (endPointB.X - endPointA.X); // The line is not a clear diagonal or straight across if (slope != 0 && Math.Abs(slope) != 1) return false; else { double yInt = endPointB.Y - slope * endPointB.X; return (this.Y == slope * this.X + yInt); } } }
/* * Determines if the last move caused the game to end * * First, check if the king is in check, and store it in a boolean variable. * * If the king is in check, check if the attack is preventable. This is done * by seeing it the king can either move away safely or can neutralize all * attacks (both the primary attack and discoveryCheck if it occured). If the * king is in check and the conditions checking if the attempted capture * is preventable, checkmate has occured, otherwise the game is not over and the * King is simply in check. * * If the king is not in check, determine if any of the various draw conditions are * met, including by repetition, redundancy (x moves without a capture or pawn movement), * stalemate (where a player can not make a legal move) and by material (where neither side * has enough material to checkmate). * * If none of those conditions are met, the game can still be played, and is * considered to have normal status. */ private void IsGameOver(ChessMove.MoveStatusCodes moveType, BoardSquare directAttacker) { // Check if king is in check (discovered, normal) // Be sure to account for rook when castled, removed pawn in en passant // If one or the other, check for blocks, capture, move out of way // If both, check move out of way // If neither, check other pieces can move (stalemate) // Check draw by repetition, inaction (no capture/movement), and material // Set gameStatus internal variable as appropriate BoardSquare currKingPos = this.kingPos[(int)(whiteTurn ? Sides.white : Sides.black)]; BitBoard futureMoves = this.board[directAttacker].getAllMoves(this.board, (short)directAttacker.X, (short)directAttacker.Y); bool isDirectCheck = futureMoves.Contains(currKingPos); bool isDiscoverCheck; this.attackingPieces[(int)AttackTypes.discovery] = ExtrapolateAttack((short)this.startCoord.X, (short)this.startCoord.Y, (short)currKingPos.X, (short)currKingPos.Y); this.attackingPieces[(int)AttackTypes.regular] = isDirectCheck ? endCoord : BoardSquare.Empty; isDiscoverCheck = this.attackingPieces[(int)AttackTypes.discovery] != BoardSquare.Empty; if (!isDiscoverCheck) { BoardSquare lastKingPos = this.kingPos[(int)(whiteTurn ? Sides.black : Sides.white)]; if (moveType == ChessMove.MoveStatusCodes.queenCastle) { isDiscoverCheck = this[lastKingPos.Y, lastKingPos.X + 1].getAllMoves(this.board, (short)(this.endCoord.X + 1), (short)this.endCoord.Y).Contains(currKingPos); if (isDiscoverCheck) this.attackingPieces[(int)AttackTypes.discovery] = new BoardSquare(lastKingPos.X + 1, lastKingPos.Y); } else if (moveType == ChessMove.MoveStatusCodes.queenCastle) { isDiscoverCheck = this[lastKingPos.Y, lastKingPos.X - 1].getAllMoves(this.board, (short)(this.endCoord.X - 1), (short)this.endCoord.Y).Contains(currKingPos); if (isDiscoverCheck) this.attackingPieces[(int)AttackTypes.discovery] = new BoardSquare(lastKingPos.X - 1, lastKingPos.Y); } else if (moveType == ChessMove.MoveStatusCodes.enPassant) { // Check captured square this.attackingPieces[(int)AttackTypes.discovery] = ExtrapolateAttack((short)endCoord.X, (short)startCoord.Y, (short)currKingPos.X, (short)currKingPos.Y); isDiscoverCheck = this.attackingPieces[(int)AttackTypes.discovery] != BoardSquare.Empty; } } if (isDirectCheck || isDiscoverCheck) { this.gameStatus = GameStatusCodes.check; // If king can't move safely, check alternatives if (this.board[currKingPos].getAllMoves(this.board, (short)currKingPos.X, (short)currKingPos.Y) == BitBoard.Empty) { // Double attack, can only get out of it by moving if (isDirectCheck && isDiscoverCheck) this.gameStatus = GameStatusCodes.checkMate; else { BoardSquare attackSquare = BoardSquare.Empty; bool canBlockCap = false; if (isDirectCheck) attackSquare = this.attackingPieces[(int)AttackTypes.regular]; else attackSquare = this.attackingPieces[(int)AttackTypes.discovery]; // Check for blocking, capture foreach (BoardSquare sq in this.board) { Piece p = this.board[sq]; if (p.Colour == this.board[currKingPos].Colour) { BitBoard bbMoves = p.getAllMoves(this.board, (short)sq.X, (short)sq.Y); foreach (BoardSquare sqMove in bbMoves) { // Can block or capture if (sqMove == attackSquare || (!(board[sq] is King) && sqMove.IsBetweenPoints(currKingPos, attackSquare)) || board[sq] is Pawn && ((Pawn)board[sq]).MayEnPassant(this.board, (short)sq.X, (short)sq.Y, (short)sqMove.X, (short)sqMove.Y)) { canBlockCap = true; break; } } if (canBlockCap) break; } } if (!canBlockCap) this.gameStatus = GameStatusCodes.checkMate; } } } else { gameStatus = GameStatusCodes.normal; //Check for draw conditions (stalemate, repetition, inactivity (no capture/pawn), material if (this.positions.isDrawByMoves()) this.gameStatus = GameStatusCodes.forcedDraw; else if (this.lastMove.IsCapture && IsDrawByMaterial()) this.gameStatus = GameStatusCodes.drawByMaterial; else { bool isStalemate = true; char nextMoveCol = whiteTurn ? Piece.NOTATION_W : Piece.NOTATION_B; foreach (BoardSquare sq in this.board) if (this.board[sq].Colour == nextMoveCol && this.board[sq].getAllMoves(this.board, (short)sq.X, (short)sq.Y) != BitBoard.Empty) { isStalemate = false; break; } if (isStalemate) this.gameStatus = GameStatusCodes.stalemate; } } }
/* * Standard set of actions that occur when a valid turn takes takes place. * * The two arguments, currPos and newPos, are character representations * of the original and new positions of the moved piece respectively * * First, the board array is updated. * Second, switch internal track of move, and record the last move. * Third, update the array of repeatable board positions, clear discoverCheckPiece, and increment the move count for draws * Fourth of all, if the piece that moved was the king, update his board position in the array * Lastly, check to see if this move causes the game to end */ private void DoTurn(ChessMove.MoveStatusCodes moveType) { BoardSquare directAttacker = endCoord; switch (moveType) { case ChessMove.MoveStatusCodes.enPassant: board[lastMove.EndSquare.Y, lastMove.EndSquare.X] = null; break; case ChessMove.MoveStatusCodes.kingCastle: board.movePiece(Board.NUM_FILES-1, startCoord.Y, startCoord.X + 1, startCoord.Y); directAttacker = new BoardSquare(startCoord.X + 1, startCoord.Y); break; case ChessMove.MoveStatusCodes.queenCastle: board.movePiece(0, startCoord.Y, startCoord.X - 1, startCoord.Y); directAttacker = new BoardSquare(startCoord.X - 1, startCoord.Y); break; } // Update internal board position and move trackers lastMove = new ChessMove(startCoord, endCoord, board[startCoord].Notational, board[endCoord] != null); allMoves.Add(lastMove); board.movePiece(startCoord.X, startCoord.Y, endCoord.X, endCoord.Y); // Switch who's move it is and record move whiteTurn = !whiteTurn; // Update internal move-by-move trackers positions.add(lastMove, board); // Update position of king if king moved if (lastMove.PieceMoved == (char)Piece.PieceNotation.King) { kingPos[Convert.ToInt32(whiteTurn)] = endCoord; ((King)board[endCoord]).HasNotMoved = false; } if (moveType != ChessMove.MoveStatusCodes.pawnPromote) IsGameOver(moveType, directAttacker); }
public void SetPromotedValue(BoardSquare square, Piece newPiece) { if (square.Y == (whiteTurn ? 0 : Board.NUM_ROWS - 1)) { Piece endSquare = board[square]; if (endSquare != null && endSquare is Pawn) { board[square] = newPiece; IsGameOver(ChessMove.MoveStatusCodes.pawnPromote, this.endCoord); } } }
/* * Based on the current position and attempted new position of a piece, determines if the move * is valid. Owing to View-Layer checking, it is known that the starting coordinate holds a piece * pf the colour who's turn it is to move, and the ending coordinate does not hold a piece of the * same colour. * * Checking starts off by taking the two board coordinates, passed in as 2 character arrays, and * converting them to integers so they can be accurately used to reference the 2-D board array * holding all the pieces. * * First to be checked is if performing the move would leave the player in check. Since a cardinal * rule of chess involves not ending your turn while you yourself are in check, if true is returned by * this function, it is an invalid move, all other checking is ignored, and the appropriate flag is * returned. * * Next check to see if the move is a traditional movement of a piece by checking if the square it's moving * to is unoccupied, if the movement follows that piece's traditional movement pattern, and that no pieces * lie in the way of the movement. If it is, it is a valid movement, and some piece-specific checking occurs. * If it was a king , indicate that he has moved (meaning that he can no longer castle). If it was a pawn, reset * the draw counters for draw by repetition and movement, and check and see if the pawn has reached the far side * of the board. If it did, perform the turn and return the appropriate flag. If not, piece specific checking * ends, the turn is performed, and the movement is a typical movement. * * If the movement is not a typical movement, checking occurs to see if it is a traditional capture. This means * that the square the piece is moving to is occupied, the movement follows the piece's traditional capture * movement pattern, and that there are no pieces in the way. If it is, the same actions occur here as do if * a regular movement has occured, with the exception that the draw counters are always reset, instead of just * if a pawn was moved. * * If none of the above conditions are met, exception checking occurs. * First, if the piece moved was a pawn, checking for en Passant is dealt with. If the movement was valid as * being en Passant, reset the draw counters, set the square contents of the captured piece to null manually * (since the square was never clicked on, doTurn can not handle doing that), perform the turn, and return * the appropriate flag. * Second, checking for castling occurs, by seeing if the square moved to is null and castling is allowed for * that piece. If it is, the current rank and file of the involved rook is calculated, as is the ending * X-coordinate of the rook. Then it is tested if the involved rook is actually on the square that it * needs to be on, that there is nothing in between the rook and king, and that the square the * king is moving over on it's way to it's ending position is not under attack. If all of these conditions * are met, it is a valid castling, the turn is performed, and the appropriate value is returned. * * If none of the conditions are met, it is an invalid move, and a flag is returned indicating this. */ public ChessMove.MoveStatusCodes IsValidTurn(char[] currPos, char[] newPos) { startCoord = new BoardSquare(currPos[0], currPos[1]); endCoord = new BoardSquare(newPos[0], newPos[1]); Piece startPiece = board[startCoord]; Piece endPiece = board[endCoord]; ChessMove.MoveStatusCodes resultant = ChessMove.MoveStatusCodes.invalidMove; // Clicked on own piece, nothing in way of desired move if (startPiece != null && startPiece.Colour == (whiteTurn ? Piece.NOTATION_W : Piece.NOTATION_B) && (endPiece == null || endPiece.Colour != startPiece.Colour) /* && IsPieceFreeToMove((short)startCoord.X, (short)startCoord.Y, (short)endCoord.X, (short)endCoord.Y) */) { BitBoard b = startPiece.getAllMoves(this.board, (short)startCoord.X, (short)startCoord.Y); if (b.Contains(endCoord)) { resultant = ChessMove.MoveStatusCodes.normalMovement; if (startPiece is King) { if (startCoord.X - endCoord.X == -2) resultant = ChessMove.MoveStatusCodes.kingCastle; else if (startCoord.X - endCoord.X == 2) resultant = ChessMove.MoveStatusCodes.queenCastle; } else if (startPiece is Pawn) { if (endCoord.Y == ((Pawn)startPiece).GetLastRow()) resultant = ChessMove.MoveStatusCodes.pawnPromote; else if (endCoord.X - startCoord.X != 0 && this[endCoord.Y, endCoord.X] == null) resultant = ChessMove.MoveStatusCodes.enPassant; } DoTurn(resultant); } } return resultant; }
public ChessMove(BoardSquare start, BoardSquare end, char pieceMoved, bool isCapture) : this(start, end, pieceMoved, isCapture, null) { }