Exemple #1
0
        private Move MiniMaxBestMove(IEnumerable <Move> validMoves, int maxMovesAhead)
        {
            // minmax algorithm plays n moves ahead and notes which move at the first level leads to the least opponent pieces remaining and the most player pieces remaining
            // which it recommends as the best move
            Board         newBoard       = _game.Board.Clone();
            MiniMaxResult playerResult   = new MiniMaxResult();
            MiniMaxResult opponentResult = new MiniMaxResult();

            MiniMax(newBoard, this, 0, 0, maxMovesAhead, playerResult, opponentResult);

            Move bestMove = playerResult.BestMovePerGeneration[1];

            // if best move takes no pieces but valid moves do take pieces, we have to pick one (any one) that takes pieces
            if (bestMove != null && bestMove.PiecesTaken == 0 && validMoves.Any(m => m.PiecesTaken > 0))
            {
                return(RandomBestMove(validMoves.Where(m => m.PiecesTaken > 0)));
            }
            // if no moves take any pieces but there are crowned pieces in the lower half of the opposite board, filter only the
            // backwards moves to encourage them to move back into the middle
            // a sufficient MiniMax depth would reveal this but would take too long :-(
            if (bestMove != null && bestMove.PiecesTaken == 0)
            {
                IEnumerable <Move> crownedMoves = validMoves.Where(m => m.PieceIsCrowned &&
                                                                   ((Colour == PieceColour.Black && m.Start.Row > 3 && m.End.Row < m.Start.Row) || (Colour == PieceColour.White && m.Start.Row <4 && m.End.Row> m.Start.Row)));
                if (crownedMoves.Count() > 0)
                {
                    return(RandomBestMove(crownedMoves));
                }
            }
            // sometimes there is no best move within n moves ahead, so just use a random of the valid moves available
            if (bestMove == null)
            {
                return(RandomBestMove(validMoves));
            }
            // otherwise, return the recommended best move
            return(bestMove);
        }
Exemple #2
0
        public Move FindBestMove(IEnumerable <Move> validMoves, int maxMovesAhead)
        {
            // minimax algorithm plays n moves ahead and notes which move at the first level leads to the least opponent pieces remaining and the most player pieces remaining
            // which it recommends as the best move
            Board newBoard = _game.Board.Clone();

            MiniMaxResult playerResult   = new MiniMaxResult();
            MiniMaxResult opponentResult = new MiniMaxResult();

            GenerateMiniMax(newBoard, _player, 0, 0, maxMovesAhead, playerResult, opponentResult);

            Move bestMove = playerResult.BestMove;

            // if no moves take any pieces but there are crowned pieces in the lower half of the opposite board, filter only the
            // backwards moves to encourage them to move back into the middle
            // a sufficient MiniMax depth would reveal this strategy but would take too long :-(
            if (bestMove != null && bestMove.PiecesTaken == 0)
            {
                IEnumerable <Move> crownedMoves = validMoves.Where(m => m.PieceIsCrowned &&
                                                                   ((_player.Colour == PieceColour.Black && m.Start.Row > 3 && m.End.Row < m.Start.Row) || (_player.Colour == PieceColour.White && m.Start.Row <4 && m.End.Row> m.Start.Row))
                                                                   );
                if (crownedMoves.Count() > 0)
                {
                    return(crownedMoves.RandomFirstOrDefault());
                }
            }

            // sometimes there is no best move within n moves ahead (all moves equally good / bad), so just use a random choice of the valid moves available
            if (bestMove == null)
            {
                return(validMoves.RandomFirstOrDefault());
            }

            // otherwise, return the recommended best move
            return(bestMove);
        }
Exemple #3
0
        private void GenerateMiniMax(Board board, Player player, int playerGeneration, int opponentGeneration, int maxGenerations, MiniMaxResult playerResult, MiniMaxResult opponentResult)
        {
            playerGeneration++;
            if (playerGeneration >= maxGenerations || playerResult.OpponentPiecesRemaining == 0)
            {
                return; // we've reached the limit, or a winning move has been found already
            }

            IEnumerable <Move> validMoves = board.ValidMovesFor(player);

            foreach (Move move in validMoves.Randomize()) // randomize the move order to avoid first-place bias for sets of equally good moves
            {
                Board newBoard = board.Clone();
                newBoard[move.Start.Row, move.Start.Column].Occupier.Move(move);

                int playerPieces   = newBoard.Squares.Count(s => s.IsOccupied && s.Occupier.Owner == player);
                int opponentPieces = newBoard.Squares.Count(s => s.IsOccupied && s.Occupier.Owner == player.Opponent);
                if (opponentPieces <= playerResult.OpponentPiecesRemaining || playerPieces > opponentResult.PlayerPiecesRemaining ||
                    playerResult.BestMovePerGeneration[playerGeneration] == null)
                {
                    // only go any further down this tree branch if there's an advantage to me compared to other branches
                    playerResult.PlayerPiecesRemaining   = playerPieces;
                    playerResult.OpponentPiecesRemaining = opponentPieces;
                    playerResult.RegisterBestMove(move, playerGeneration);

                    // now play the opponent's move set for this generation, and so on down the tree until maxGenerations is reached or tree branch is pruned
                    GenerateMiniMax(newBoard, player.Opponent, opponentGeneration, playerGeneration, maxGenerations, opponentResult, playerResult);
                }
            }
        }
Exemple #4
0
        private void MiniMax(Board board, Player player, int playerGeneration, int opponentGeneration, int maxGenerations, MiniMaxResult playerResult, MiniMaxResult opponentResult)
        {
            _timesThrough++;
            playerGeneration++;
            if (playerGeneration >= maxGenerations || playerResult.OpponentPiecesRemaining == 0)
            {
                return; // we've reached the limit, or a winning move has been found already
            }

            IEnumerable <Move> validMoves = Core.Move.ValidMovesFor(board, player);

            if (!playerResult.BestMovePerGeneration.ContainsKey(playerGeneration))
            {
                playerResult.BestMovePerGeneration.Add(playerGeneration, null);
            }

            foreach (Move move in Randomize(validMoves)) // randomize the move order to avoid first-place bias for sets of equally good moves
            {
                Board newBoard = board.Clone();
                newBoard[move.Start.Row, move.Start.Column].Occupier.Move(move);

                int playerCount   = newBoard.Squares.Count(s => s.IsOccupied && s.Occupier.Owner == player);
                int opponentCount = newBoard.Squares.Count(s => s.IsOccupied && s.Occupier.Owner == player.Opponent);
                if (opponentCount < playerResult.OpponentPiecesRemaining || playerResult.BestMovePerGeneration[playerGeneration] == null)
                {
                    playerResult.PlayerPiecesRemaining   = playerCount;
                    playerResult.OpponentPiecesRemaining = opponentCount;
                    playerResult.BestMovePerGeneration[playerGeneration] = move;
                }

                MiniMax(newBoard, player.Opponent, opponentGeneration, playerGeneration, maxGenerations, opponentResult, playerResult);
            }
        }