/// <summary> /// Get directions where there's a playable move /// </summary> /// <param name="position">Position to check</param> /// <returns>List of directions</returns> public List <IntPosition> GetNeighborsDirections(IntPosition position) { List <IntPosition> directionsList = new List <IntPosition>(); Player currentPlayer = playerTurn; Player oppositePlayer = GetOppositePlayer(currentPlayer); for (int rowDelta = -1; rowDelta <= 1; rowDelta++) { for (int columnDelta = -1; columnDelta <= 1; columnDelta++) { IntPosition nextPosition = new IntPosition(position.Column + columnDelta, position.Row + rowDelta); if (IsPositionValid(nextPosition)) { int slotContentId = gameBoard[nextPosition.Column, nextPosition.Row]; if ((position.Row != nextPosition.Row || position.Column != nextPosition.Column) && slotContentId == (int)oppositePlayer && IsPositionValid(nextPosition)) { directionsList.Add(new IntPosition(columnDelta, rowDelta)); } } } } return(directionsList); }
/// <summary> /// Get a list of positions where a pawn has to be flipped, given the position where a new pawn is to be placed. /// This is basically the effect of a new move on the board. /// NB: the list does not contain the new pawn. /// </summary> /// <param name="pawnPosition">Pawn position</param> /// <returns>List of positions where to flip pawns</returns> public List <IntPosition> GetPawnsToFlip(IntPosition pawnPosition) { List <IntPosition> pawnsToFlip = new List <IntPosition>(); List <IntPosition> directionsList = GetNeighborsDirections(pawnPosition); foreach (IntPosition direction in directionsList) { List <IntPosition> currentPath = new List <IntPosition>(); Player currentPlayer = playerTurn; Player oppositePlayer = GetOppositePlayer(currentPlayer); IntPosition currentPosition = pawnPosition; currentPosition += direction; while (IsPositionValid(currentPosition) && gameBoard[currentPosition.Column, currentPosition.Row] == (int)oppositePlayer) { currentPath.Add(currentPosition); currentPosition += direction; } if (IsPositionValid(currentPosition) && gameBoard[currentPosition.Column, currentPosition.Row] == (int)currentPlayer) { pawnsToFlip.AddRange(currentPath); } } return(pawnsToFlip); }
/// <summary> /// Implementation of negamax algorithm with alpha beta pruning. A node is represented by a game board. /// </summary> /// <param name="nodeBoard">A particular game state</param> /// <param name="depth">The maximum tree depth</param> /// <param name="parentValue">The parent node value</param> /// <param name="maximizingPlayer">Whether node is a maximizimer or minimizer. True if maximizer. </param> /// <returns>A tuple containing the value as it's first item and the position corresponding to the best /// predicted play as it's second item. </returns> private Tuple <int, IntPosition> AlphaBeta(BoardIA nodeBoard, int depth, int parentValue, bool maximizingPlayer) { if (depth == 0 || nodeBoard.IsTerminal()) { var heuristicValue = nodeBoard.GetHeuristicValue(); return(new Tuple <int, IntPosition>(heuristicValue, new IntPosition(-1, -1))); } else { int bestValue = maximizingPlayer ? -int.MaxValue : int.MaxValue; IntPosition bestMove = new IntPosition(-1, -1); var childPositions = nodeBoard.GetAllPossibleMoves(); foreach (var child in childPositions) { var childValue = AlphaBeta(PosToBoard(child, nodeBoard), depth - 1, bestValue, !maximizingPlayer); int minOrMax = maximizingPlayer ? 1 : -1; if (childValue.Item1 * minOrMax > bestValue * minOrMax) { bestValue = childValue.Item1; bestMove = child; if (bestValue * minOrMax > parentValue * minOrMax) { break; } } } return(new Tuple <int, IntPosition>(bestValue, bestMove)); } }
/// <summary> /// Initialize all the data of the game /// </summary> /// <param name="gridSize">Size of the board</param> /// <param name="initialPawnsPosition">Initial position of the pawns (top left corner)</param> public void InitAll(IntPosition gridSize, IntPosition initialPawnsPosition) { playerTurn = Player.Black; InitPlayersData(); InitGameBoard(gridSize, initialPawnsPosition); InitTimer(); moveHistory = new List <Tuple <List <IntPosition>, Player> >(); }
/// <summary> /// Check whether the given position is legal. /// </summary> /// <param name="column">Column number of the position to check</param> /// <param name="line">Line number of the position to check</param> /// <param name="isWhite">True if player turn is white</param> /// <returns>True if move is legal</returns> public bool IsPlayable(int column, int line, bool isWhite) { Player currentPlayer = PlayerTurn; PlayerTurn = isWhite ? Player.White : Player.Black; IntPosition positionToCheck = new IntPosition(column, line); List <IntPosition> playableSlots = GetAllPossibleMoves(); PlayerTurn = currentPlayer; return(playableSlots.Contains(positionToCheck)); }
/// <summary> /// Check if a move is stable /// </summary> /// <param name="position">Position to check stability</param> /// <returns>True : stable, False : not stable</returns> private bool IsMoveStable(IntPosition position, bool isWhite) { int[,] gameBoardSave = CopyArray(GameBoard); List <IntPosition> result = new List <IntPosition>(); int playerId = isWhite ? (int)Player.White : (int)Player.Black; GameBoard[position.Column, position.Row] = playerId; List <IntPosition> pawnsToFlip = GetPawnsToFlip(position); foreach (IntPosition pawnPos in pawnsToFlip) { GameBoard[pawnPos.Column, pawnPos.Row] = (int)(GetOppositePlayer((Player)playerId)); } // Tests valid moves for the opposite players SwitchPlayer(); for (int rowDelta = -1; rowDelta <= 1; rowDelta++) { for (int columnDelta = -1; columnDelta <= 1; columnDelta++) { if (!(columnDelta == 0 && rowDelta == 0)) { try { IntPosition nextPosition = new IntPosition(position.Column + columnDelta, position.Row + rowDelta); while (IsPositionValid(nextPosition) && GameBoard[nextPosition.Column, nextPosition.Row] == playerId) { nextPosition = new IntPosition(nextPosition.Column + columnDelta, nextPosition.Row + rowDelta); } if (IsPositionValid(nextPosition) && GameBoard[nextPosition.Column, nextPosition.Row] == (int)GetOppositePlayer((Player)playerId)) { if (IsPossibleMove(nextPosition, new IntPosition(-columnDelta, -rowDelta), result)) { GameBoard = gameBoardSave; SwitchPlayer(); return(false); } } } catch (Exception exc) { Console.WriteLine(exc.Message); } } } } SwitchPlayer(); GameBoard = gameBoardSave; return(true); }
/// <summary> /// Play a move and alter the board accordingly. Returns true if the move was legal. /// </summary> /// <param name="column">The column corresponding to the position of the move</param> /// <param name="line">The line corresponding to the position of the move</param> /// <param name="isWhite">True if the current player turn is white</param> /// <returns>True if move was legal.</returns> public bool PlayMove(int column, int line, bool isWhite) { PlayerTurn = isWhite ? Player.White : Player.Black; bool IsMoveValid = IsPlayable(column, line, isWhite); if (IsMoveValid) { IntPosition slotPosition = new IntPosition(column, line); List <IntPosition> pawnsToFlip = GetPawnsToFlip(slotPosition); pawnsToFlip.Add(slotPosition); UpdateSlots(pawnsToFlip); } return(IsMoveValid); }
/// <summary> /// Gets the most recent move stored and plays it backwards. /// </summary> /// <returns> /// A tuple containing the coordinates of the affected slots, which /// player the move belongs to and the coordinates of the pawn that /// was added to the board (which now has to be removed). /// </returns> public Tuple <List <IntPosition>, Player, IntPosition> UndoMove() { var lastMove = moveHistory.Last(); List <IntPosition> slotsToUndo = lastMove.Item1; Player moveAuthor = lastMove.Item2; IntPosition lastPawnPosition = slotsToUndo.Last(); slotsToUndo.RemoveAt(slotsToUndo.Count - 1); foreach (var position in slotsToUndo) { GameBoard[position.Column, position.Row] = (int)GetOppositePlayer(moveAuthor); } GameBoard[lastPawnPosition.Column, lastPawnPosition.Row] = (int)SlotContent.Nothing; moveHistory.RemoveAt(moveHistory.Count - 1); return(Tuple.Create(slotsToUndo, moveAuthor, lastPawnPosition)); }
/// <summary> /// Init. the game board /// </summary> /// <param name="gridSize">Size of the board</param> /// <param name="initialPawnsPosition">Initial position of the pawns (top left corner)</param> public void InitGameBoard(IntPosition gridSize, IntPosition initialPawnsPosition) { gameBoard = new int[gridSize.Column, gridSize.Row]; for (int row = 0; row < Rows; row++) { for (int column = 0; column < Columns; column++) { gameBoard[column, row] = (int)SlotContent.Nothing; } } gameBoard[initialPawnsPosition.Column, initialPawnsPosition.Row] = (int)SlotContent.White; gameBoard[initialPawnsPosition.Column + 1, initialPawnsPosition.Row] = (int)SlotContent.Black; gameBoard[initialPawnsPosition.Column, initialPawnsPosition.Row + 1] = (int)SlotContent.Black; gameBoard[initialPawnsPosition.Column + 1, initialPawnsPosition.Row + 1] = (int)SlotContent.White; }
/// <summary> /// Creates an AIBoard class given the position of a move to play /// and the board where the move has to be played. /// </summary> /// <param name="position">The position of the pawn to play on the board</param> /// <param name="sourceBoard">The board where the move takes place</param> /// <returns>A new AIBoard with the updated game state</returns> private BoardIA PosToBoard(IntPosition position, BoardIA sourceBoard) { int[,] gameState = new int[9, 7]; for (int i = 0; i < 9; i++) { for (int j = 0; j < 7; j++) { gameState[i, j] = sourceBoard.GameBoard[i, j]; } } BoardIA newBoard = new BoardIA { GameBoard = gameState, PlayerTurn = sourceBoard.PlayerTurn }; newBoard.PlayMove(position.Column, position.Row, sourceBoard.PlayerTurn == Player.White); newBoard.SwitchPlayer(); return(newBoard); }
/// <summary> /// Walk the game board to get all possible moves for the current player. /// </summary> /// <returns> /// A list containing the position of each playable slot. /// </returns> public List <IntPosition> GetAllPossibleMoves() { List <IntPosition> possibleMovesList = new List <IntPosition>(); for (int rowIndex = 0; rowIndex < Rows; rowIndex++) { for (int columnIndex = 0; columnIndex < Columns; columnIndex++) { IntPosition currentSlot = new IntPosition(columnIndex, rowIndex); int slotContentId = gameBoard[currentSlot.Column, currentSlot.Row]; if (slotContentId == (int)playerTurn) { List <IntPosition> currentSlotPossibleMovesList = GetNeighborsDirections(currentSlot); List <IntPosition> movements = new List <IntPosition>(); foreach (IntPosition direction in currentSlotPossibleMovesList) { if (IsPossibleMove(currentSlot, direction, movements)) { IntPosition possibleMove = movements[movements.Count - 1]; possibleMovesList.Add(possibleMove); } movements.Clear(); } } } } if (possibleMovesList.Count == 0) { SkipCurrentPlayerTurn(); } else { CurrentPlayerData.HasSkippedLastTurn = false; } return(possibleMovesList); }
/// <summary> /// Check if there's a valid move in the given direction, if true, the "positions" list is filled with all the positions of the valid move /// </summary> /// <param name="pawnPosition">Position to check</param> /// <param name="direction">Direction to check</param> /// <param name="positions">Empty list given by the user, filled with severals position if there's a valid move</param> /// <returns></returns> public bool IsPossibleMove(IntPosition pawnPosition, IntPosition direction, List <IntPosition> positions) { IntPosition currentPosition = pawnPosition; bool result = false; Player currentPlayer = playerTurn; Player oppositePlayer = GetOppositePlayer(currentPlayer); positions.Add(pawnPosition); currentPosition += direction; while (IsPositionValid(currentPosition) && gameBoard[currentPosition.Column, currentPosition.Row] == (int)oppositePlayer) { positions.Add(currentPosition); currentPosition += direction; } if (result = IsPositionValid(currentPosition) && (gameBoard[currentPosition.Column, currentPosition.Row] == (int)SlotContent.Nothing)) { positions.Add(currentPosition); } return(result); }
/// <summary> /// Overloaded constructor: can define the size of the board and the initial position of the pawns (top left corner) /// </summary> /// <param name="gridSize">Size of the board</param> /// <param name="initialPawnsPosition">Initial position of the pawns (top left corner)</param> public OthelloLogic(IntPosition gridSize, IntPosition initialPawnsPosition) { InitAll(gridSize, initialPawnsPosition); }
/// <summary> /// Check if the position is inside the board /// </summary> /// <param name="position">Position to check</param> /// <returns>True if the position is inside board, false otherwise</returns> public bool IsPositionValid(IntPosition position) { return(position.Row >= 0 && position.Row < Rows && position.Column >= 0 && position.Column < Columns); }