private void getOptimalCell(byte i_PlayerIndex, out Board.Cell o_ResultMove) { const byte k_FirstHumanMoveIndex = 0; const byte k_LastUnblockingEmptyCell = 0; Random rnd = new Random(); Board.Cell targetedCell = this.gameLogic.Players[GetOtherPlayerIndex(i_PlayerIndex)].m_MovesList[k_FirstHumanMoveIndex]; List <Board.Cell> unblockingEmptyCells = this.getUnblockingEmptyCells(targetedCell); if (unblockingEmptyCells.Count != k_LastUnblockingEmptyCell) { o_ResultMove = unblockingEmptyCells[rnd.Next(unblockingEmptyCells.Count)]; if (!this.gameLogic.checkIfPlayerLost(o_ResultMove, i_PlayerIndex)) { unblockingEmptyCells.Remove(o_ResultMove); } } else { List <Board.Cell> freeCellsInBoard = this.getFreeCellsInBoard(); o_ResultMove = freeCellsInBoard[rnd.Next(freeCellsInBoard.Count)]; while (this.gameLogic.checkIfPlayerLost(o_ResultMove, i_PlayerIndex)) { const int k_LastComputerOptionalCell = 1; if (freeCellsInBoard.Count == k_LastComputerOptionalCell) { break; } freeCellsInBoard.Remove(o_ResultMove); o_ResultMove = freeCellsInBoard[rnd.Next(freeCellsInBoard.Count)]; } } }
/// <summary> /// Set the text (Nought or Cross) for a button, and then /// disable it so user cannot click it again during this game. /// </summary> /// <param name="button">Button to set</param> /// <param name="cell">Nought, Cross or Empty</param> private void SetButtonCell(Button button, Board.Cell cell) { switch (cell) { case Board.Cell.Computer: { button.Text = Board.CellToString(cell); button.Enabled = false; break; } case Board.Cell.Human: { button.Text = Board.CellToString(cell); button.Enabled = false; break; } case Board.Cell.Empty: { button.Text = Board.CellToString(cell); button.Enabled = true; break; } } }
private bool checkDiagonalLose(Board.Cell i_LastMove, byte i_PlayerIndex, eSign i_PlayerSign) { bool minorDiagonalLoseFlag = true; bool majorDiagonalLoseFlag = true; for (byte colRowIndex = 0; colRowIndex < this.Board.BoardSize; colRowIndex++) { if (i_LastMove != this.Board[(byte)(this.m_BoardSize - colRowIndex - 1), colRowIndex]) { if (minorDiagonalLoseFlag && this.Board[(byte)(this.Board.BoardSize - colRowIndex - 1), colRowIndex].m_CellSign != i_PlayerSign) { minorDiagonalLoseFlag = false; } } if (i_LastMove != this.Board[colRowIndex, colRowIndex]) { if (majorDiagonalLoseFlag && this.Board[colRowIndex, colRowIndex].m_CellSign != i_PlayerSign) { majorDiagonalLoseFlag = false; } } } bool minorOrMajorDiagonalLose = minorDiagonalLoseFlag || majorDiagonalLoseFlag; if (minorOrMajorDiagonalLose) { this.Players[i_PlayerIndex].RoundStatus = minorDiagonalLoseFlag ? eRoundStatus.MinorDiagonalLose : eRoundStatus.MajorDiagonalLose; this.Players[GetOtherPlayerIndex(i_PlayerIndex)].RoundStatus = eRoundStatus.Win; } return(minorOrMajorDiagonalLose); }
/// <summary> /// Calculate the node /// </summary> /// <param name="board">Current board state</param> /// <param name="currentPosition">Position in board to set</param> /// <param name="currentMove">Current move (either Computer or Human)</param> /// <param name="depth">Current depth. Used for choosing earliest winning/losing move</param> public MiniMaxNode(Board board, int currentPosition, Board.Cell currentMove, int depth) { // Create the child nodes list this.ChildNodes = new List <MiniMaxNode>(); // Save the index to the cell we are updating. This way we always know what move // this node corresponds to this.UpdatedCellIndex = currentPosition; // Set the cell to Computer/Human board.Cells[this.UpdatedCellIndex] = currentMove; if (board.GetState() == Board.State.ComputerWins) { // If Computer wins, give it a high score, but deduct the current depth // (So that quick wins have higher scores than lengthy wins) this.Score = 10 - depth; } else if (board.GetState() == Board.State.HumanWins) { // If Human wins, give it a low score, but add the current depth // (So that quick loses have lower scores than lengthy loses) this.Score = -10 + depth; } else if (board.GetState() == Board.State.Draw) { // If it's a draw, just set to neutral this.Score = 0; } else { // Invert the player var nextMove = currentMove == Board.Cell.Computer ? Board.Cell.Human : Board.Cell.Computer; for (var i = 0; i < Board.BoardSize; i++) { // For each empty cell if (board.Cells[i] == Board.Cell.Empty) { // Clone the board var childBoard = board.Clone(); // Calculate child moves var miniMaxNode = new MiniMaxNode(childBoard, i, nextMove, depth + 1); ChildNodes.Add(miniMaxNode); } } if (currentMove == Board.Cell.Computer) { // Since this isn't a terminal node, give the current node the best score // amongst the child nodes.. this.Score = this.ChildNodes.OrderBy(n => n.Score).First().Score; } else { // ..or for Human moves, give it the worst score amongst the children this.Score = this.ChildNodes.OrderBy(n => n.Score).Last().Score; } } }
private bool checkIfPlayerLost(Board.Cell i_LastMove, byte i_PlayerIndex) { eSign playerSign = this.Players[i_PlayerIndex].Sign; bool gameFinishedFlag = this.checkRowOrColLose(i_LastMove, i_PlayerIndex, playerSign); if (!gameFinishedFlag && this.isCellOnDiagonal(i_LastMove)) { gameFinishedFlag = this.checkDiagonalLose(i_LastMove, i_PlayerIndex, playerSign); } return(gameFinishedFlag); }
internal void HandleComputerMove() { this.m_CurrentNumOfMoves++; Board.Cell computerMove = this.r_GameLogic.GetAIMove(this.m_PlayerIndexTurn); this.r_GameLogic.RegisterMove(computerMove, this.m_PlayerIndexTurn); this.MarkCellWithComputerSign(computerMove, this.m_PlayerIndexTurn); this.m_IsGameFinished = this.r_GameLogic.CheckEndGame(computerMove, this.m_CurrentNumOfMoves, this.m_PlayerIndexTurn); this.m_PlayerIndexTurn = GameLogic.GetOtherPlayerIndex(this.m_PlayerIndexTurn); if (this.m_IsGameFinished) { this.showResults(this.m_PlayerIndexTurn); this.resetRound(); } this.boldPlayer(k_Player1Index); }
internal bool CheckEndGame(Board.Cell i_CurrentMove, byte i_CurrentNumOfMoves, byte i_PlayerIndex) { bool isGameFinished = false; if (this.checkIfPlayerLost(i_CurrentMove, i_PlayerIndex)) { isGameFinished = true; this.Players[GetOtherPlayerIndex(i_PlayerIndex)].Score++; } if (!isGameFinished) { isGameFinished = this.handleFullBoard(i_CurrentNumOfMoves); } return(isGameFinished); }
internal void RegisterMove(Board.Cell i_CurrentMove, byte i_PlayerIndex) { this.Board[i_CurrentMove] = i_CurrentMove; this.Board[i_CurrentMove].m_CellSign = this.Players[i_PlayerIndex].Sign; if (this.Players[i_PlayerIndex].PlayerType == ePlayerType.Human) { this.Players[i_PlayerIndex].m_MovesList.Add(i_CurrentMove); } CellChosenEventArgs e = new CellChosenEventArgs { m_CellSign = i_CurrentMove.m_CellSign, m_CellIndex = Board.GetCellIndexInBoard(i_CurrentMove) }; OnCellChosen(e); }
internal void MarkCellWithComputerSign(Board.Cell i_ComputerMove, byte i_PlayerIndex) { bool found = false; string cellCoordinates = $"{i_ComputerMove.m_CellRow},{i_ComputerMove.m_CellCol}"; while (!found) { foreach (Button cellButton in this.r_CellButtons) { if (cellButton.Name == cellCoordinates) { found = true; this.changeCellButtonAppearance(cellButton, i_ComputerMove.m_CellSign); break; } } } }
private List <Board.Cell> getUnblockingEmptyCells(Board.Cell i_TargetedCell) { List <Board.Cell> optionalCells = new List <Board.Cell>(); for (byte rowIndex = 0; rowIndex < this.gameLogic.m_BoardSize; rowIndex++) { for (byte colIndex = 0; colIndex < this.gameLogic.m_BoardSize; colIndex++) { if (this.gameLogic.Board[rowIndex, colIndex].m_CellSign == eSign.Empty) { if (rowIndex != i_TargetedCell.m_CellRow && colIndex != i_TargetedCell.m_CellCol && !this.gameLogic.isCellOnDiagonal(this.gameLogic.Board[rowIndex, colIndex])) { optionalCells.Add(this.gameLogic.Board[rowIndex, colIndex]); } } } } return(optionalCells); }
private bool checkRowOrColLose(Board.Cell i_LastMove, byte i_PlayerIndex, eSign i_PlayerSign) { bool rowLoseFlag = true; bool colLoseFlag = true; byte currentCol = i_LastMove.m_CellCol; byte currentRow = i_LastMove.m_CellRow; for (byte colRowIndex = 0; colRowIndex < this.Board.BoardSize && (colLoseFlag || rowLoseFlag); colRowIndex++) { if (i_LastMove != this.Board[colRowIndex, currentCol]) { if (colLoseFlag && this.Board[colRowIndex, currentCol].m_CellSign != i_PlayerSign) { colLoseFlag = false; } } if (i_LastMove != this.Board[currentRow, colRowIndex]) { if (rowLoseFlag && this.Board[currentRow, colRowIndex].m_CellSign != i_PlayerSign) { rowLoseFlag = false; } } } bool rowOrColLoseFlag = rowLoseFlag || colLoseFlag; if (rowOrColLoseFlag) { this.Players[i_PlayerIndex].RoundStatus = rowLoseFlag ? eRoundStatus.RowLose : eRoundStatus.ColLose; this.Players[GetOtherPlayerIndex(i_PlayerIndex)].RoundStatus = eRoundStatus.Win; } return(rowOrColLoseFlag); }
private void getRandomCell(out Board.Cell o_ResultMove) { Random rnd = new Random(); o_ResultMove = new Board.Cell((byte)rnd.Next(this.gameLogic.m_BoardSize), (byte)rnd.Next(this.gameLogic.m_BoardSize)); }
internal bool CheckCellLegality(Board.Cell o_ResultMove) { return(this.Board.IsCellInRange(o_ResultMove) && this.Board.IsCellUsed(o_ResultMove)); }
private bool isCellOnDiagonal(Board.Cell i_LastMove) { return(i_LastMove.m_CellRow == i_LastMove.m_CellCol || i_LastMove.m_CellRow + i_LastMove.m_CellCol + 1 == this.Board.BoardSize); }