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); }
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); } }
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; } }
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)); }
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); }
Task <HexAIMove> IHexAI.GetMove(Game game) { var root = new FastBoardNode(game); return(Task.Run(() => { return GetMove(root).ToHexMove(); })); }
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}"); }
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; } } } }
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)); }
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); }
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; } } }
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); } }
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); }
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); }
public HexAIMove GetMove(Game game) { var root = new FastBoardNode(game); return(GetMove(root).ToHexMove()); }
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); }