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); }
public VoxelLocation(RaycastHit hit, bool modifyBySideCollided = false) { if (!modifyBySideCollided) { x = hit.x; y = hit.y; z = hit.z; } else { x = SideUtil.modify_x(hit.x, hit.side); y = SideUtil.modify_y(hit.y, hit.side); z = SideUtil.modify_z(hit.z, hit.side); } }
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. } }
public IGameMove Choose(IGameMove[] moves, SquareBoard board) { var walkMoves = moves.Select(x => x as WalkGameMove).Where(x => x != null).ToArray(); if (walkMoves.Length == 0) { return(null); } _cts = new CancellationTokenSource(TimeoutPerMoveMilliseconds); var depth = 8 * Math.Pow(TimeoutPerMoveMilliseconds / 1000, 0.4); var move = _bot.FindBestMove(board, SideUtil.Convert(Side), _cts.Token, new NegaMaxBot.BotOptions { MaxDepth = (int)depth }); return(walkMoves.FirstOrDefault(x => x.Figure == move.Figure && x.MoveSequence == move.Sequence)); }
private async Task GameLoop() { do { var validMoves = _rulesProvider.GetMoves(Board, SideUtil.Convert(CurrentPlayer.Side)); var gameMoves = new List <IGameMove>(validMoves.Values.Count); if (validMoves.Count > 0) { gameMoves.AddRange(validMoves.Flatten((f, m) => new WalkGameMove(this, f, m))); } if (_currentHistoryItem.Previous != null && _currentHistoryItem.Previous.Previous != null) { gameMoves.Add(UndoMove); } if (_currentHistoryItem.Next != null && _currentHistoryItem.Next.Next != null) { gameMoves.Add(RedoMove); } try { var move = await TryChooseMove(gameMoves); if (move == null) { return; //gameLoop was interrupted } move.Execute(); _gameStatistics.Append(move, this); } catch (Exception ex) { Error = ex?.InnerException ?? ex; Status = GameStatus.Error; _isRunning = false; break; } foreach (var player in _players) { player.GameUpdated(this); } } while (_isRunning); }
public Cell(Figure figure, GameSide playerSide) { _figure = figure; _active = figure.Side == SideUtil.Convert(playerSide); }
// 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(); } } }