/* Take a move object and return true if the A and B positions are distinct from each other. */ private bool IsMovePositionsDistinct(FormedMove move) { Tuple<int, int> posA = move.PosA; Tuple<int, int> posB = move.PosB; return !posB.Equals(posA); }
/* Takes a move which so far meets the requirements for a Movement type move on the chess board. This function checks that one of the moving piece's rays covers this move object (The piece itself can move in this direction/way) and that the way is not blocked by another piece. */ private bool IsPieceMovementLegal(FormedMove move, ChessPosition cpm) { bool isLegalMovement = true; Tuple<int, int> posA = move.PosA; Tuple<int, int> posB = move.PosB; Tile tileA = cpm.Board[posA.Item1, posA.Item2]; List<List<Tuple<int, int>>> movementRays; // get the rays for a piece of type=piece.Val and location=posA movementRays = GetPieceRay(tileA.piece.Val, posA); ; List<Tuple<int, int>> moveRayUsed = null; foreach (List<Tuple<int, int>> ray in movementRays) { // for pawns the rays are comprise TWO forward positions (from first move) // so if the pawn has already .MovedOnce don not consider the second position in the ray // as the pawn can no longer legally move to that position if ((tileA.piece.Val == EGamePieces.WhitePawn || tileA.piece.Val == EGamePieces.BlackPawn) && (tileA.piece.MovedOnce)) { ; if (ray[0].Equals(posB)) { ; moveRayUsed = ray; break; } } // for all other cases consider the entirety of the ray else { if (ray.Contains(posB)) { moveRayUsed = ray; break; } } } if (moveRayUsed != null) // check there are no intermediate Pieces blocking the movement foreach (Tuple<int, int> tilePos in moveRayUsed) { Tile tileAtPosition = cpm.Board[tilePos.Item1, tilePos.Item2]; if (!tileAtPosition.IsEmpty()) { isLegalMovement = false; break; } if (tilePos.Equals(posB)) break; } else isLegalMovement = false; return isLegalMovement; }
/* Takes a move object and a chess board object and returns true if the tile on the B position of the move is empty. */ private bool IsMoveBEmpty(FormedMove move, ChessPosition cpm, ref Tile tileB) { tileB = cpm.Board[move.PosB.Item1, move.PosB.Item2]; return tileB.IsEmpty(); }
/* Takes a move object and a chess board object and returns true if the piece on the B position of the move belongs to the other player. TODO: replace with alternate call to previous functions*/ private bool IsMoveBPieceOtherPlayer(FormedMove move, ChessPosition cpm, ref Tile tileB) { bool result = false; tileB = cpm.Board[move.PosB.Item1, move.PosB.Item2]; if (!tileB.IsEmpty()) if (!cpm.Player.Owns(tileB.piece)) result = true; return result; }
/* Takes a move which so far meets the requirements for an EnPassant type move on the chess board. This function checks that one of the moving pawn's diagonal rays covers this move object (The piece itself can move in this direction/way) and that the corresponding enpassant capture tile for this movement contains an enemy pawn piece to capture.*/ private bool IsEnPassantCaptureLegal(FormedMove move, ChessPosition cpm) { bool isLegalEnPassantCapture = true; Tuple<int, int> posA = move.PosA; Tuple<int, int> posB = move.PosB; Tile tileA = cpm.Board[posA.Item1, posA.Item2]; List<List<Tuple<int, int>>> epMovementRays; // get attack ray for the pawn on tileA // these are the movement rays in the case of EP epMovementRays = GetPieceRayPawnCapture(tileA.piece.Val, posA); List<Tuple<int, int>> moveRayUsed = null; foreach(List<Tuple<int, int>> ray in epMovementRays) { if (ray.Contains(posB)) { moveRayUsed = ray; break; } } // so at this point: // have posA and posB and it is a valid enpassant-type // movement (diagonal to empty square) if (moveRayUsed != null) { // continue checking ep position int epX = posB.Item2; int epY = posA.Item1; Tuple<int, int> posEP = Tuple.Create(epY, epX); // if this computed ep square between posA and posB does not // match the currently valid EnPassantSq, then this is not // a valid EP capture if (!posEP.Equals(cpm.EnPassantSq)) isLegalEnPassantCapture = false; } else isLegalEnPassantCapture = false; return isLegalEnPassantCapture; }
/* Takes a move object and a chess board object and returns true if the piece on the A position of the move belongs to the current player. */ private bool IsMoveAPieceCurPlayer(FormedMove move, ChessPosition cpm, ref Tile tileA) { bool result = false; tileA = cpm.Board[move.PosA.Item1, move.PosA.Item2]; if (!tileA.IsEmpty()) if (cpm.Player.Owns(tileA.piece)) result = true; return result; }
/* Takes an input string from a view / source and returns true if it is in the correct chess board move format e.g. 'A5 A6'. The corresponding move object is also created and passed to the non-local move variable. */ public bool ValidateInput(string input, ref FormedMove move) { // these lists contain only the valid file and ranks // and are indexed such that a file/rank will correspond to the col/row pos List<char> validFiles = "ABCDEFGH".ToList(); List<int> validRanks = new List<int>(new int[] { 8, 7, 6, 5, 4, 3, 2, 1 }); bool validMove = false; string[] chessBoardLocs = input.Split(); Tuple<int, int> chessBoardPos1; Tuple<int, int> chessBoardPos2; if (chessBoardLocs.Length == 2) { string chessBoardLoc1 = chessBoardLocs[0]; string chessBoardLoc2 = chessBoardLocs[1]; { char loc1File = Char.ToUpper(chessBoardLoc1[0]); int loc1Rank = (int)Char.GetNumericValue(chessBoardLoc1[1]); char loc2File = Char.ToUpper(chessBoardLoc2[0]); int loc2Rank = (int)Char.GetNumericValue(chessBoardLoc2[1]); if (validFiles.Contains(loc1File) && validRanks.Contains(loc1Rank) && (validFiles.Contains(loc2File) && validRanks.Contains(loc2Rank))) { int loc1Col = validFiles.IndexOf(loc1File); int loc1Row = validRanks.IndexOf(loc1Rank); int loc2Col = validFiles.IndexOf(loc2File); int loc2Row = validRanks.IndexOf(loc2Rank); chessBoardPos1 = Tuple.Create<int, int>(loc1Row, loc1Col); chessBoardPos2 = Tuple.Create<int, int>(loc2Row, loc2Col); move = new FormedMove(chessBoardPos1, chessBoardPos2); validMove = true; } } } return validMove; }
/* Apply a capture type move to the board. This capture has already been validated by the evaluator and found to be legal. There are additional actions to be made if the moving piece is a pawn and fits certain criteria. The captured piece is also added to the piecesCapd List property of this ChessPosition.*/ private void applyCapture(FormedMove move) { Tuple<int, int> frSqPos = move.PosA; Tuple<int, int> toSqPos = move.PosB; Tile toSqTl = getTile(toSqPos); Tile frSqTl = getTile(frSqPos); Piece mvPiece = frSqTl.piece; // if moving piece is a pawn do additional actions // if (mvPiece.Val == EGamePieces.WhitePawn || mvPiece.Val == EGamePieces.BlackPawn) { // if there exists a non-empty value in PromotionSelection // this means the capturing piece is also pawn being promoted if (move.PromotionSelection != EGamePieces.empty) mvPiece.Val = move.PromotionSelection; // in the cases where a pawns first move is a capture, need to // set this to prevent the 2 move option only applying to first 'movement' mvPiece.MovedOnce = true; } // add the captured to the list addToCaptured(toSqTl.piece.Val); // move the piece updatePosWithPiece(toSqPos, mvPiece); updatePosWithPiece(frSqPos, null); }
/* The Entry point for a move application, this method selects the appropriate type of move to make based on the moveType property. */ public void applyMove(FormedMove move) { switch (move.MoveType) { case EChessMoveTypes.Castle: applyCastle(move); break; case EChessMoveTypes.Capture: applyCapture(move); break; case EChessMoveTypes.Movement: applyMovement(move); break; case EChessMoveTypes.EpMovement: applyEnPassantCapture(move); break; default: throw new ArgumentException($"Unexpected value: {move.MoveType}, for moveType"); } }
/* Take a valid-format move object and check if it may be legally applied to the current chess game. It must pass the following checks: -the locations are distinct and -the A tile contains a piece of the current player Also one of the following: -the B tile contains a piece of the current player (further castle checks) or -the B tile contains a piece of the opponent player (further capture checks) or -the B tile is empty: -(further en passant checks) -(further movement checks) Finally it must check, if the move were to be applied, that it does not leave the current player's king in check.*/ public bool IsValidMove(ref FormedMove move, ChessPositionModel cpm, ref List<Tuple<int, int>> kingCheckedBy) { bool validMove = false; Tile tileA = new Tile(); Tile tileB = new Tile(); if (IsMovePositionsDistinct(move)) { if (IsMoveAPieceCurPlayer(move, cpm, ref tileA)) { // check if its a castling if (IsMoveBPieceCurPlayer(move, cpm, ref tileB)) { validMove = false; // IsLegalCastle? - this involves calls to isKingInCheck move.MoveType = EChessMoveTypes.Castle; } // check if its a capture else if (IsMoveBPieceOtherPlayer(move, cpm, ref tileB)) { validMove = IsCaptureLegal(move, cpm); move.MoveType = EChessMoveTypes.Capture; } // check if its a movement else if (IsMoveBEmpty(move, cpm, ref tileB)) { if (validMove = IsPieceMovementLegal(move, cpm)) { move.MoveType = EChessMoveTypes.Movement; } else if (validMove = IsEnPassantCaptureLegal(move, cpm)) { move.MoveType = EChessMoveTypes.EpMovement; } } } } // if its a valid move so far, check if there is a pawn promotion, // then apply the move to a copy of the chess position and // finally check it passes a check test if (validMove) { // if there is a pawn promotion the player is prompted for the promotion piece and it is added to the move object MoveIncludesPawnPromotion(ref move, cpm); // a copy of the chess position is made so that the move may // be applied in order for the king-check to be checked without // causing the chess position to update the display ChessPosition cpCopy = cpm.getChessPositionCopy(); cpCopy.applyMove(move); // after this application of the move if (IsKingInCheck(cpCopy, ref kingCheckedBy)) validMove = false; } return validMove; }
/* Apply a movement type move to the board. This move has already been validated by the evaluator and found to be legal. There are additional actions to be made if the moving piece is a pawn and fits certain criteria. */ private void applyMovement(FormedMove move) { Tuple<int, int> frSqPos = move.PosA; Tuple<int, int> toSqPos = move.PosB; Tile frSqTl = getTile(frSqPos); Tile toSqTl = getTile(toSqPos); Piece mvPiece = frSqTl.piece; // if moving piece is a pawn do additional actions // if (mvPiece.Val == EGamePieces.WhitePawn || mvPiece.Val == EGamePieces.BlackPawn) { // if it has moved two squares on its first move it can be captured En Passant if (!mvPiece.MovedOnce && mvTwoTiles(frSqPos, toSqPos)) this.EnPassantSq = toSqPos; // if there exists a non-empty value in PromotionSelection // this means the moving piece is a pawn being promoted if (move.PromotionSelection != EGamePieces.empty) mvPiece.Val = move.PromotionSelection; mvPiece.MovedOnce = true; } // move the piece updatePosWithPiece(toSqPos, mvPiece); updatePosWithPiece(frSqPos, null); }
/* Apply an En Passant type move to the board. This move has already been validated by the evaluator and found to be legal and there exists an opponent piece on the EnPassant Square which can be captured. As in a normal capture, the piece captured En Passant is also added to the piecesCapd List property of this ChessPosition.*/ private void applyEnPassantCapture(FormedMove move) { Tuple<int, int> frSqPos = move.PosA; Tuple<int, int> toSqPos = move.PosB; // Compute En Passant Square int epSqRank = frSqPos.Item1; int epSqFile = toSqPos.Item2; Tuple<int, int> epSqPos = Tuple.Create(epSqRank, epSqFile); Tile frSqTl = getTile(frSqPos); Tile toSqTl = getTile(toSqPos); Tile epSqTl = getTile(epSqPos); Piece mvPiece = frSqTl.piece; // move the pawn updatePosWithPiece(toSqPos, mvPiece); updatePosWithPiece(frSqPos, null); // add the captured to the list addToCaptured(epSqTl.piece.Val); updatePosWithPiece(epSqPos, null); }
/* Apply a Castle type move to the board. This move has already been validated by the evaluator and found to be legal. */ private void applyCastle(FormedMove move) { }
/* This function receives a move object and the chess game and checks if the move also includes a pawn promotion, if it does it prompts the player for the selection piece, and adds it to the FormedMove. If it does not it ends silently TODO: could change it back to return bool, ref the selection piece then add it to move in the calling code ~~style issue*/ private void MoveIncludesPawnPromotion(ref FormedMove move, ChessPosition cpm) { Piece mvPiece = cpm.Board[move.PosA.Item1, move.PosA.Item2].piece; Tuple<int, int> posB = move.PosB; // if moving piece is a pawn and posB isHighRank if ((mvPiece.Val == EGamePieces.WhitePawn || mvPiece.Val == EGamePieces.BlackPawn) && (IsHighRank(posB))) { // Safe invoke for popup onto main thread via viewRef PromotionSelectionPopup(ref move, mvPiece); } }
/* Takes a move which so far meets the requirements for a Capture type move on the chess board. This function checks that the one of the capturer piece's rays covers this move object (The piece itself can move in this way) and that the way is not blocked by another piece en route. Note: This is very similar to the IsPieceMovementLegal function, only difference is that the check for pieces on a tile is moved after the check for the final tile (since the final tile will have a piece on it: the piece being captured, and dont want the code to consider that as an intermediate piece which would block the capture. Other difference is that if the capturer piece is of type pawn, a unique set of pawn capture rays are gotten (Pawn only piece to use different rays for capture and movement). */ private bool IsCaptureLegal(FormedMove move, ChessPosition cpm) { bool isLegalCapture = true; Tuple<int, int> posA = move.PosA; Tuple<int, int> posB = move.PosB; Tile tileA = cpm.Board[posA.Item1, posA.Item2]; List<List<Tuple<int, int>>> captureRays; if (tileA.piece.Val == EGamePieces.BlackPawn || tileA.piece.Val == EGamePieces.WhitePawn) captureRays = GetPieceRayPawnCapture(tileA.piece.Val, posA); else captureRays = GetPieceRay(tileA.piece.Val, posA); List<Tuple<int, int>> captureRayUsed = null; foreach (List<Tuple<int, int>> ray in captureRays) { if (ray.Contains(posB)) { captureRayUsed = ray; break; } } if (captureRayUsed != null) // check there are no intermediate Pieces blocking the capture movement foreach (Tuple<int, int> position in captureRayUsed) { if (position.Equals(posB)) break; Tile tileAtPosition = cpm.Board[position.Item1, position.Item2]; if (!tileAtPosition.IsEmpty()) { isLegalCapture = false; break; } } else isLegalCapture = false; return isLegalCapture; }
/* Make a thread safe invoke on the viewRef in order to use it as the parent argument to showDialog for the promotion selection form. This is called by Move includes pawn promotion. */ private void PromotionSelectionPopup(ref FormedMove move, Piece mvPiece) { if (this.viewRef.InvokeRequired) { PromotionSelectionPopupCallback d = new PromotionSelectionPopupCallback(PromotionSelectionPopup); this.viewRef.Invoke(d, new object[] { move, mvPiece }); } else { EGamePieces promotionPiece; EGamePieces promotionPieceDefault; View.PromotionSelection promotionSelection = new View.PromotionSelection(mvPiece); promotionPieceDefault = promotionSelection.DefaultPiece; // (queen) // remove the ability to x close the dialog before a piece is selected promotionSelection.ControlBox = false; if (promotionSelection.ShowDialog(this.viewRef) == System.Windows.Forms.DialogResult.OK) { promotionPiece = promotionSelection.SelectedPiece; promotionSelection.Dispose(); move.PromotionSelection = promotionPiece; } else move.PromotionSelection = promotionPieceDefault; } }
/* This is the Game Loop for a Tic Tac Toe Game. This is run while a game is in progress in a seperate thread. When the game meets an ending criteria (winner) this thread will end naturally. This thread may also be terminated externally. */ private void TicTacToeGameLoop() { FormedMove move; string winner = null; // change to player bool isMaxTurns = false; while (true) { // check if a winner has been found or reached max turns if (tictactoeModel.IsWinningPosition(ref winner) || tictactoeModel.IsMaxTurns(ref isMaxTurns)) break; this.Message = $"Player {tictactoeModel.Player.PlayerValue}, make your move"; move = null; // check if display has provided a move if (gameInput != null) { move = new FormedMove(gameInput); if (tictactoeModel.IsValidMove(move)) { tictactoeModel.applyMove(move); tictactoeModel.ChangePlayer(); } else this.Message = "The move was not valid"; gameInput = null; } } // at this point game has ended if (winner != null) this.Message = $"Player {winner} has won the game!"; else if (isMaxTurns) this.Message = "Draw, no more moves can be made"; }