示例#1
0
    public FastMove GetMove(FastBoardNode root)
    {
        moveGenTimer.Reset();
        moveSortTimer.Reset();
        moveValidateTimer.Reset();
        evalTimer.Reset();
        evalThreatsTimer.Reset();
        applyTimer.Reset();
        getMoveTimer.Restart();
        boardEvaluations = 0;
        invalidMoves     = 0;

        int color = (root.currentMove == Team.White) ? 1 : -1;

        int      bestMoveValue = -CheckmateValue * 3;
        FastMove bestMove      = FastMove.Invalid;

        for (int searchDepth = 1; searchDepth <= maxSearchDepth; searchDepth++)
        {
            int alpha = -CheckmateValue * 2; // Best move for current player
            int beta  = CheckmateValue * 2;  // Best move our opponent will let us have

            (int moveValue, FastMove move) = Search(root, searchDepth, 0, alpha, beta, color);

            bestMoveValue = moveValue;
            bestMove      = move;

            if (moveValue >= (CheckmateValue - searchDepth))
            {
                return(bestMove);
            }
        }

        return(bestMove);
    }
示例#2
0
    public FastMove GetMove(FastBoardNode root)
    {
        diagnostics = new DiagnosticInfo();
        using (diagnostics.getMove.Measure())
        {
            previousScores.Clear();

            int color          = (root.currentMove == Team.White) ? 1 : -1;
            int minSearchDepth = iterativeDeepeningEnabled ? 1 : maxSearchDepth;
            bestMove              = FastMove.Invalid;
            currentDepth          = minSearchDepth;
            cancellationRequested = false;

            for (; currentDepth <= maxSearchDepth; currentDepth++)
            {
                bestMoveThisIteration = FastMove.Invalid;
                int alpha     = -CheckmateValue * 2; // Best move for current player
                int beta      = CheckmateValue * 2;  // Best move our opponent will let us have
                int moveValue = Search(root, currentDepth, 0, alpha, beta, color);

                bestMove = bestMoveThisIteration;

                if (moveValue >= (CheckmateValue - currentDepth))
                {
                    return(bestMove);
                }
            }

            return(bestMove);
        }
    }
示例#3
0
    private void OrderMoves(FastBoardNode node, List <FastMove> moves, int plyFromRoot)
    {
        var scoredMoves = sortCache;

        scoredMoves.Clear();

        if (previousOrderingEnabled && plyFromRoot == 0 && previousScores.Count > 0)
        {
            foreach (var move in moves)
            {
                previousScores.TryGetValue(move, out int score);
                scoredMoves.Add(new ScoredMove(move, score));
            }
        }
        else
        {
            foreach (var move in moves)
            {
                scoredMoves.Add(new ScoredMove(move, MoveValuer(node, move)));
            }
        }

        scoredMoves.Sort();
        for (int i = 0; i < moves.Count; i++)
        {
            moves[i] = scoredMoves[i].move;
        }
    }
示例#4
0
    public void ThreatCountTest()
    {
        var board    = new FastBoardNode();
        var evalData = new EvaluationData();

        board[new Index(5, 'E')] = (Team.White, FastPiece.King);
        evalData.Prepare(board);
        Assert.That(evalData.White.Threats.Count, Is.EqualTo(6));
        Assert.That(evalData.Black.Threats.Count, Is.EqualTo(0));

        board[new Index(5, 'E')] = (Team.White, FastPiece.Knight);
        evalData.Prepare(board);
        Assert.That(evalData.White.Threats.Count, Is.EqualTo(12));
        Assert.That(evalData.Black.Threats.Count, Is.EqualTo(0));

        board[new Index(5, 'E')] = (Team.Black, FastPiece.Squire);
        evalData.Prepare(board);
        Assert.That(evalData.White.Threats.Count, Is.EqualTo(0));
        Assert.That(evalData.Black.Threats.Count, Is.EqualTo(6));

        board[new Index(5, 'E')] = (Team.Black, FastPiece.Pawn);
        evalData.Prepare(board);
        Assert.That(evalData.White.Threats.Count, Is.EqualTo(0));
        Assert.That(evalData.Black.Threats.Count, Is.EqualTo(2));

        board[new Index(5, 'E')] = (Team.None, FastPiece.Pawn);
        board[new Index(1, 'D')] = (Team.Black, FastPiece.Pawn);
        evalData.Prepare(board);
        Assert.That(evalData.White.Threats.Count, Is.EqualTo(0));
        Assert.That(evalData.Black.Threats.Count, Is.EqualTo(0));
    }
示例#5
0
    private int MoveValuer(FastBoardNode node, FastMove move)
    {
        int value = 1000; // Start high to always exceed invalid move scores of 0

        var mover         = node[move.start];
        int attackerValue = GetPieceValue(mover.piece);

        EvaluationData.BoardCollection us;
        EvaluationData.BoardCollection them;
        if (node.currentMove == Team.White)
        {
            us   = evaluationData.White;
            them = evaluationData.Black;
        }
        else
        {
            us   = evaluationData.Black;
            them = evaluationData.White;
        }

        // Devalue moving into threatened hexes
        if (them.Threats[move.target])
        {
            value -= attackerValue / 2;
        }

        // Value moving threatened pieces
        if (them.Threats[move.start])
        {
            value += attackerValue / 2;
        }

        if (move.moveType == MoveType.Move)
        {
            // TODO: what else?
        }
        else if (move.moveType == MoveType.Attack)
        {
            bool isFree = !them.Threats[move.target];
            var  victim = node[move.target];
            if (isFree)
            {
                value += GetPieceValue(victim.piece) / 2;
            }
            else
            {
                int attackValue = GetPieceValue(victim.piece) - attackerValue;
                value += attackValue / 10;
            }
        }
        else if (move.moveType == MoveType.EnPassant)
        {
            value += 5;
        }

        return(value);
    }
示例#6
0
    Task <HexAIMove> IHexAI.GetMove(Game game)
    {
        var root = new FastBoardNode(game);

        return(Task.Run(() =>
        {
            return GetMove(root).ToHexMove();
        }));
    }
示例#7
0
    public string ToString(FastBoardNode node)
    {
        string promoteMsg = promoteTo == FastPiece.Pawn ? string.Empty : $" to {promoteTo}";
        string sFrom      = ((Index)start).GetKey() + $"({node[start]})";
        string sTo        = ((Index)target).GetKey();

        if (moveType == MoveType.Attack || moveType == MoveType.Defend)
        {
            sTo += $" ({node[target]})";
        }

        return($"{moveType} {sFrom} -> {sTo}{promoteMsg}");
    }
示例#8
0
 static void AddThreatRays(ref BitsBoard threats, FastBoardNode node, FastIndex[][] rays)
 {
     foreach (var ray in rays)
     {
         foreach (var move in ray)
         {
             threats[move] = true;
             if (node[move].team != Team.None)
             {
                 break;
             }
         }
     }
 }
示例#9
0
    public void PiecesCountTest()
    {
        var board    = new FastBoardNode();
        var evalData = new EvaluationData();

        evalData.Prepare(board);
        Assert.That(evalData.White.Pieces.Count, Is.EqualTo(0));
        Assert.That(evalData.Black.Pieces.Count, Is.EqualTo(0));

        board[new Index(1, 'A')] = (Team.White, FastPiece.King);
        evalData.Prepare(board);
        Assert.That(evalData.White.Pieces.Count, Is.EqualTo(1));
        Assert.That(evalData.Black.Pieces.Count, Is.EqualTo(0));

        board[new Index(9, 'A')] = (Team.Black, FastPiece.King);
        evalData.Prepare(board);
        Assert.That(evalData.White.Pieces.Count, Is.EqualTo(1));
        Assert.That(evalData.Black.Pieces.Count, Is.EqualTo(1));
    }
示例#10
0
    public int EvaluateTerminalBoard(FastBoardNode node, int plyFromRoot)
    {
        diagnostics.terminalBoardEvaluations++;
        bool whiteIsChecking = node.currentMove != Team.White && node.IsChecking(Team.White);

        if (whiteIsChecking)
        {
            return(CheckmateValue - plyFromRoot);
        }

        bool blackIsChecking = node.currentMove != Team.Black && node.IsChecking(Team.Black);

        if (blackIsChecking)
        {
            return(-CheckmateValue + plyFromRoot);
        }

        // Either stalemate, or 50 move rule draw
        return(DrawValue);
    }
示例#11
0
    public void Prepare(FastBoardNode node)
    {
        White.Clear();
        Black.Clear();

        for (byte b = 0; b < node.positions.Length; ++b)
        {
            var             piece = node.positions[b];
            BoardCollection board;
            if (piece.team == Team.None)
            {
                continue;
            }
            else if (piece.team == Team.White)
            {
                board = White;
            }
            else
            {
                board = Black;
            }

            board.Pieces[b]      = true;
            board.MaterialValue += GetMaterialValue(piece.piece);

            if (piece.piece == FastPiece.Pawn)
            {
                board.Pawns[b] = true;
            }
            else
            {
                AddThreats(ref board.Threats, node, b);
            }
        }

        White.PawnThreats = White.Pawns.Shift(HexNeighborDirection.UpLeft) | White.Pawns.Shift(HexNeighborDirection.UpRight);
        Black.PawnThreats = Black.Pawns.Shift(HexNeighborDirection.DownLeft) | Black.Pawns.Shift(HexNeighborDirection.DownRight);

        White.Threats = White.Threats | White.PawnThreats;
        Black.Threats = Black.Threats | Black.PawnThreats;
    }
 static void AddRayMoves(List <FastMove> moves, FastIndex start, Team team, FastBoardNode boardNode, FastIndex[] ray, bool generateQuiet = true)
 {
     foreach (var target in ray)
     {
         var occupant = boardNode[target];
         if (occupant.team == Team.None)
         {
             if (generateQuiet)
             {
                 moves.Add(new FastMove(start, target, MoveType.Move)); // empty
             }
         }
         else
         {
             if (occupant.team != team)
             {
                 moves.Add(new FastMove(start, target, MoveType.Attack)); // rip and tear
             }
             break;
         }
     }
 }
示例#13
0
    static void AddThreats(ref BitsBoard threats, FastBoardNode node, byte index)
    {
        var piece = node[index];

        switch (piece.piece)
        {
        case FastPiece.King:
            threats = threats | PrecomputedMoveData.kingThreats[index];
            return;

        case FastPiece.Knight:
            threats = threats | PrecomputedMoveData.knightThreats[index];
            return;

        case FastPiece.Squire:
            threats = threats | PrecomputedMoveData.squireThreats[index];
            return;

        case FastPiece.Bishop:
            AddThreatRays(ref threats, node, PrecomputedMoveData.bishopRays[index]);
            return;

        case FastPiece.Rook:
            AddThreatRays(ref threats, node, PrecomputedMoveData.rookRays[index]);
            return;

        case FastPiece.Queen:
            AddThreatRays(ref threats, node, PrecomputedMoveData.bishopRays[index]);
            AddThreatRays(ref threats, node, PrecomputedMoveData.rookRays[index]);
            return;

        case FastPiece.None:
        case FastPiece.Pawn:     // Pawn handled by caller
        default:
            return;
        }
    }
 public static void AddAllPossibleKnightMoves(List <FastMove> moves, FastIndex start, Team team, FastBoardNode boardNode, bool generateQuiet = true)
 {
     foreach (var target in PrecomputedMoveData.knightMoves[start.ToByte()])
     {
         if (boardNode.TryGetPiece(target, out (Team team, FastPiece piece)occupier))
         {
             if (occupier.team != team)
             {
                 moves.Add(new FastMove(start, target, MoveType.Attack));
             }
         }
         else if (generateQuiet)
         {
             moves.Add(new FastMove(start, target, MoveType.Move));
         }
     }
 }
    public static void AddAllPossiblePawnMoves(List <FastMove> moves, FastIndex start, Team team, FastBoardNode boardNode, bool generateQuiet = true)
    {
        Team enemy = team.Enemy();

        HexNeighborDirection leftAttackDir   = HexNeighborDirection.UpLeft;
        HexNeighborDirection leftPassantDir  = HexNeighborDirection.DownLeft;
        HexNeighborDirection rightAttackDir  = HexNeighborDirection.UpRight;
        HexNeighborDirection rightPassantDir = HexNeighborDirection.DownRight;
        HexNeighborDirection forwardDir      = HexNeighborDirection.Up;

        if (team != Team.White)
        {
            leftAttackDir   = HexNeighborDirection.DownLeft;
            leftPassantDir  = HexNeighborDirection.UpLeft;
            rightAttackDir  = HexNeighborDirection.DownRight;
            rightPassantDir = HexNeighborDirection.UpRight;
            forwardDir      = HexNeighborDirection.Down;
        }

        if (start.TryGetNeighbor(leftAttackDir, out var leftAttack))
        {
            var leftVictim = boardNode[leftAttack];
            if (leftVictim.team == enemy)
            {
                moves.Add(new FastMove(start, leftAttack, MoveType.Attack));
            }
            if (leftVictim.team == Team.None)
            {
                if (start.TryGetNeighbor(leftPassantDir, out var leftPassant) && boardNode.PassantableIndex == leftPassant)
                {
                    moves.Add(new FastMove(start, leftAttack, MoveType.EnPassant));
                }
            }
        }

        if (start.TryGetNeighbor(rightAttackDir, out var rightAttack))
        {
            var rightVictim = boardNode[rightAttack];
            if (rightVictim.team == enemy)
            {
                moves.Add(new FastMove(start, rightAttack, MoveType.Attack));
            }
            if (rightVictim.team == Team.None)
            {
                if (start.TryGetNeighbor(rightPassantDir, out var rightPassant) && boardNode.PassantableIndex == rightPassant)
                {
                    moves.Add(new FastMove(start, rightAttack, MoveType.EnPassant));
                }
            }
        }

        if (!generateQuiet)
        {
            return;
        }

        // One forward
        bool hasForward = start.TryGetNeighbor(forwardDir, out var forward);

        if (hasForward && !boardNode.IsOccupied(forward))
        {
            if (forward.TryGetNeighbor(forwardDir, out var twoForward))
            {
                moves.Add(new FastMove(start, forward, MoveType.Move));

                // Two forward on 1st move
                if (PawnIsAtStart(team, (Index)start) && !boardNode.IsOccupied(twoForward))
                {
                    moves.Add(new FastMove(start, twoForward, MoveType.Move));
                }
            }
            else
            {
                // if two squares forward doesn't exist, that means we're on the last rank for this pawn.
                moves.Add(new FastMove(start, forward, MoveType.Move, FastPiece.Queen));
                moves.Add(new FastMove(start, forward, MoveType.Move, FastPiece.Rook));
                moves.Add(new FastMove(start, forward, MoveType.Move, FastPiece.Knight));
                moves.Add(new FastMove(start, forward, MoveType.Move, FastPiece.Squire));
            }
        }
    }
 public static void AddAllPossibleQueenMoves(List <FastMove> moves, FastIndex start, Team team, FastBoardNode boardNode, bool generateQuiet = true)
 {
     AddAllPossibleRookDirectionalMoves(moves, start, team, boardNode, generateQuiet);
     AddAllPossibleBishopMoves(moves, start, team, boardNode, generateQuiet);
 }
    public static void AddAllPossibleMoves(List <FastMove> moves, FastIndex start, FastPiece piece, Team team, FastBoardNode boardNode, bool generateQuiet = true)
    {
        switch (piece)
        {
        case FastPiece.King:
            AddAllPossibleKingMoves(moves, start, team, boardNode, generateQuiet);
            return;

        case FastPiece.Queen:
            AddAllPossibleQueenMoves(moves, start, team, boardNode, generateQuiet);
            return;

        case FastPiece.Rook:
            AddAllPossibleRookMoves(moves, start, team, boardNode, generateQuiet);
            return;

        case FastPiece.Knight:
            AddAllPossibleKnightMoves(moves, start, team, boardNode, generateQuiet);
            return;

        case FastPiece.Bishop:
            AddAllPossibleBishopMoves(moves, start, team, boardNode, generateQuiet);
            return;

        case FastPiece.Squire:
            AddAllPossibleSquireMoves(moves, start, team, boardNode, generateQuiet);
            return;

        case FastPiece.Pawn:
            AddAllPossiblePawnMoves(moves, start, team, boardNode, generateQuiet);
            return;

        default:
            throw new ArgumentException($"Unhandled piece type: {piece}", nameof(piece));
        }
    }
    public static void AddAllPossibleRookMoves(List <FastMove> moves, FastIndex start, Team team, FastBoardNode boardNode, bool generateQuiet = true)
    {
        AddAllPossibleRookDirectionalMoves(moves, start, team, boardNode, generateQuiet);

        if (generateQuiet)
        {
            foreach (var target in PrecomputedMoveData.kingMoves[start.ToByte()])
            {
                if (boardNode.TryGetPiece(target, out (Team team, FastPiece piece)occupier))
                {
                    if (occupier.team == team && Contains(DefendableTypes, occupier.piece))
                    {
                        moves.Add(new FastMove(start, target, MoveType.Defend));
                    }
                }
            }
        }
    }
 public static void AddAllPossibleRookDirectionalMoves(List <FastMove> moves, FastIndex start, Team team, FastBoardNode boardNode, bool generateQuiet = true)
 {
     FastIndex[][] rookRays = PrecomputedMoveData.rookRays[start.ToByte()];
     foreach (var directionalIndexes in rookRays)
     {
         AddRayMoves(moves, start, team, boardNode, directionalIndexes, generateQuiet);
     }
 }
 public static void AddAllPossibleBishopMoves(List <FastMove> moves, FastIndex start, Team team, FastBoardNode boardNode, bool generateQuiet = true)
 {
     FastIndex[][] bishopRays = PrecomputedMoveData.bishopRays[start.ToByte()];
     foreach (var ray in bishopRays)
     {
         AddRayMoves(moves, start, team, boardNode, ray, generateQuiet);
     }
 }
示例#21
0
    int Search(FastBoardNode node, int searchDepth, int plyFromRoot, int alpha, int beta, int color)
    {
        if (searchDepth == 0)
        {
            using (diagnostics.quiescence.Measure())
                return(QuiescenceSearch(node, plyFromRoot, alpha, beta, color));
        }

        List <FastMove> moves;

        using (diagnostics.moveGen.Measure())
        {
            moves = moveCache[searchDepth];
            moves.Clear();
            node.AddAllPossibleMoves(moves, node.currentMove);
        }

        evaluationData.Prepare(node);

        using (diagnostics.moveSort.Measure())
        {
            OrderMoves(node, moves, plyFromRoot);
        }

        bool isTerminal = true;
        int  value      = int.MinValue;

        foreach (var move in moves)
        {
            if (cancellationRequested)
            {
                return(0);
            }

            using (diagnostics.apply.Measure())
                node.DoMove(move);

            bool isKingVulnerable;
            using (diagnostics.moveValidate.Measure())
                isKingVulnerable = node.IsChecking(node.currentMove);

            if (isKingVulnerable)
            {
                diagnostics.invalidMoves++;
                using (diagnostics.apply.Measure())
                    node.UndoMove(move);
                continue;
            }

            isTerminal = false;
            int currentValue = -Search(node, searchDepth - 1, plyFromRoot + 1, -beta, -alpha, -color);

            if (previousOrderingEnabled && plyFromRoot == 0)
            {
                previousScores[move] = currentValue;
            }

            using (diagnostics.apply.Measure())
                node.UndoMove(move);

            if (currentValue > value)
            {
                if (plyFromRoot == 0)
                {
                    bestMoveThisIteration = move;
                }
                value = currentValue;
            }
            alpha = Math.Max(alpha, value);
            if (alpha >= beta)
            {
                diagnostics.searchCutoff++;
                break;
            }
        }

        if (isTerminal)
        {
            value = color * EvaluateTerminalBoard(node, plyFromRoot);
        }

        return(value);
    }
示例#22
0
    public int EvaluateBoard(FastBoardNode node, int plyFromRoot)
    {
        if (node.plySincePawnMovedOrPieceTaken >= 100)
        {
            return(0); // automatic draw due to 50 move rule.
        }
        int  boardValue      = 0;
        bool whiteIsChecking = node.currentMove != Team.White && node.IsChecking(Team.White);

        if (whiteIsChecking)
        {
            boardValue += CheckBonusValue;
        }

        bool blackIsChecking = node.currentMove != Team.Black && node.IsChecking(Team.Black);

        if (blackIsChecking)
        {
            boardValue -= CheckBonusValue;
        }

        if (!node.HasAnyValidMoves(node.currentMove))
        {
            if (whiteIsChecking)
            {
                return(CheckmateValue - plyFromRoot);
            }
            else if (blackIsChecking)
            {
                return(-CheckmateValue + plyFromRoot);
            }
            else
            {
                return(DrawValue);
            }
        }

        /*
         * for (byte i = 0; i < node.positions.Length; i++)
         * {
         *  var piece = node.positions[i];
         *  if (piece.team == Team.None)
         *      continue;
         *
         *  var valuedPosition = FastIndex.FromByte(i);
         *  if (piece.team == Team.Black)
         *      valuedPosition = valuedPosition.Mirror();
         *
         *  int pieceValue = GetPieceValue(piece.piece);
         *
         *  if (pawnValueMapEnabled && piece.piece == FastPiece.Pawn)
         *  {
         *      pieceValue += pawnValueMap[valuedPosition.HexId];
         *  }
         *
         *  boardValue += TeamMults[(byte)piece.team] * pieceValue;
         * }
         */

        using (diagnostics.evalThreats.Measure())
        {
            boardValue += evaluationData.White.Threats.Count - evaluationData.Black.Threats.Count;
            boardValue += (evaluationData.White.PawnThreats.Count * 2) - (evaluationData.Black.PawnThreats.Count * 2);
            boardValue += evaluationData.White.MaterialValue - evaluationData.Black.MaterialValue;

            var whiteAttacks = evaluationData.Black.Pieces & evaluationData.White.Threats;
            var blackAttacks = evaluationData.White.Pieces & evaluationData.Black.Threats;
            boardValue += whiteAttacks.Count - blackAttacks.Count;

            var whiteDefended = evaluationData.White.Pieces & evaluationData.White.Threats;
            var blackDefended = evaluationData.Black.Pieces & evaluationData.Black.Threats;
            boardValue += whiteDefended.Count - blackDefended.Count;

            var whiteHangingPieces = blackAttacks & ~evaluationData.White.Threats;
            var blackHangingPieces = whiteAttacks & ~evaluationData.Black.Threats;
            boardValue += (blackHangingPieces.Count - whiteHangingPieces.Count) * 100;
        }

        return(boardValue);
    }
示例#23
0
    public HexAIMove GetMove(Game game)
    {
        var root = new FastBoardNode(game);

        return(GetMove(root).ToHexMove());
    }
示例#24
0
    int QuiescenceSearch(FastBoardNode node, int plyFromRoot, int alpha, int beta, int color)
    {
        int eval;

        evaluationData.Prepare(node);

        using (diagnostics.quiescenceEval.Measure())
            eval = color * EvaluateBoard(node, plyFromRoot);

        if (!quiescenceSearchEnabled)
        {
            return(eval);
        }

        if (eval >= beta)
        {
            diagnostics.quiescenceCutoff++;
            return(beta);
        }

        if (eval > alpha)
        {
            alpha = eval;
        }

        List <FastMove> moves;

        using (diagnostics.quiescenceMoveGen.Measure())
        {
            moves = new List <FastMove>(10);
            node.AddAllPossibleMoves(moves, node.currentMove, generateQuiet: false);
        }

        using (diagnostics.quiescenceMoveSort.Measure())
            OrderMoves(node, moves, -1);

        bool maybeTerminal = true;
        int  value         = int.MinValue;

        foreach (var move in moves)
        {
            if (cancellationRequested)
            {
                return(0);
            }

            using (diagnostics.quiescenceApply.Measure())
                node.DoMove(move);

            bool isKingVulnerable;
            using (diagnostics.quiescenceMoveValidate.Measure())
                isKingVulnerable = node.IsChecking(node.currentMove);
            if (isKingVulnerable)
            {
                diagnostics.invalidMoves++;
                using (diagnostics.quiescenceApply.Measure())
                    node.UndoMove(move);
                continue;
            }

            maybeTerminal = false;
            int currentValue = -QuiescenceSearch(node, plyFromRoot + 1, -beta, -alpha, -color);

            using (diagnostics.quiescenceApply.Measure())
                node.UndoMove(move);

            if (currentValue > value)
            {
                value = currentValue;
            }
            alpha = Math.Max(alpha, value);
            if (alpha >= beta)
            {
                diagnostics.quiescenceCutoff++;
                break;
            }
        }

        // No non-quiet moves were found from this position
        if (maybeTerminal)
        {
            return(eval);
        }

        return(value);
    }