public int Evaluate(SquareBoard board, Side side) { var enemySide = SideUtil.Opposite(side); var score = SideScore(board.GetAll(side)) - SideScore(board.GetAll(enemySide)); return(score); }
//HOWTO: make it possible to visualize search progress - let WPF to see each State and decisions? public BotMove FindBestMove(SquareBoard board, Side botSide, CancellationToken cancellation, BotOptions options = default) { this.options = options ?? new BotOptions(); this.botSide = botSide; this.playerSide = SideUtil.Opposite(botSide); this.cancellation = cancellation; return(Negamax(board, this.options.MaxDepth, Int32.MinValue + 1, Int32.MaxValue, botSide, CancellationToken.None)); }
private Point GetJumpPoint(Figure figure, Func <Point, int, Point> direction, out Figure neighbour) { neighbour = board.Get(direction(figure.Point, 1)); if (neighbour.Side == SideUtil.Opposite(figure.Side)) { var doubleStep = direction(figure.Point, 2); if (board.IsEmpty(doubleStep)) { return(doubleStep); } } return(Point.Nop); }
private void CheckForWin() { // TODO: extract it into Rules class - because there are a lot of draw cases (pretty complex) var validEnemyMoves = _rulesProvider.GetMoves(Board, SideUtil.Opposite(SideUtil.Convert(CurrentPlayer.Side))); if (validEnemyMoves.Count == 0) { _winnerIndex = CurrentPlayerIndex; Status = _winnerIndex == 0 ? GameStatus.Player1Wins : GameStatus.Player2Wins; //_isRunning = false; //TODO: potentially it's ok to undo move after game is over? // - don't stop the game with _isRunning = false // - then Undo should unmark it? // - then to be able to make Redo should store it in History // etc. } }
// NegaMax algorithm with alpha/beta, source: https://en.wikipedia.org/wiki/Negamax //TODO: append transposition tables private BotMove Negamax(SquareBoard board, int depth, int alpha, int beta, Side side, CancellationToken branchToken) { if (cancellation.IsCancellationRequested || !CanSearchDeeper(board, depth)) { return(BotMove.Empty(Estimate(board))); } var states = GetStates(board, side); if (depth == options.MaxDepth && states.Count == 1) { var singleState = states[0]; return(new BotMove(singleState.Figure, singleState.MoveSequence, 0)); } BotMove bestMove = new BotMove(Int32.MinValue); var noMoves = true; var cts = new CancellationTokenSource(); var workers = new List <Task>(); foreach (var state in states) { noMoves = false; if (cts.IsCancellationRequested) { break; } if (branchToken.IsCancellationRequested) { cts.Cancel(); break; } if (states.Count > 1 && _runningWorkersCount < options.DegreeOfParallelism && depth <= options.MaxDepth - 1) { Interlocked.Increment(ref _runningWorkersCount); workers.Add(Task.Run(() => { DoNegamax(state); Interlocked.Decrement(ref _runningWorkersCount); })); } else { DoNegamax(state); } } Task.WaitAll(workers.ToArray()); if (noMoves) { return(BotMove.Empty(Estimate(board))); } return(bestMove); void DoNegamax(State state) { if (options.IsDebug) { Log("before", board, side, state, bestMove.Score, depth, alpha, beta); } var boardAfterMove = new MoveCommandChain(state.Figure, state.Board, state.MoveSequence).Execute(); var score = -Negamax(boardAfterMove, depth - 1, -beta, -alpha, SideUtil.Opposite(side), cts.Token).Score; var shouldPrun = false; lock (locker) { if (score > bestMove.Score) { bestMove = new BotMove(state.Figure, state.MoveSequence, score); } alpha = Math.Max(alpha, bestMove.Score); //let's move cts.Cancel out of the lock scope shouldPrun = options.AllowPrunning && alpha >= beta; } if (options.IsDebug) { Log("after", board, side, state, score, depth, alpha, beta); } if (shouldPrun) { cts.Cancel(); } } }