/// <summary> /// Recursively search for the best move for the given board /// </summary> /// <param name="depth">How many recursive iterations you want to take(each iteration is 1 turn of 1 of the players, so steps ahead is depth/2</param> /// <param name="tiles">The Tile to iterate over</param> /// <param name="initialSequence">Initial sequence of moves, which is used as state to know what sequence </param> /// <param name="priority"></param> /// <param name="max"></param> /// <param name="currentPlayer"></param> /// <param name="enemyPlayer"></param> /// <returns></returns> public MinimaxResult GetBestTurn(BoardTileCollection tiles, IPlayer currentPlayer) { #if DEBUG this._amountOfCheckups = 0; #endif IPlayer enemyPlayer = this.Game.GetEnemyPlayer(currentPlayer); var results = new List <MinimaxResult>(); //get all possible initial moves for the player var checkerMovePairs = currentPlayer.GetAllPossibleMoves(Game, enemyPlayer, tiles); Parallel.ForEach(checkerMovePairs, (pair, loopState) => { List <MoveSequence> possibleSequences = null; Turn turn = pair.Value; foreach (var move in turn.Moves) //for each initial move of the checker { if (move is AttackMove attMove) { //all possible followup move sequences possibleSequences = GetPossibleMoveSequences(turn, attMove); } else if (move is WalkMove) { possibleSequences = new List <MoveSequence> { new MoveSequence(turn, new List <Move> { move }) }; } //in case theres only 1 possible move, we dont need to calculate anything, so cut of. if (checkerMovePairs.Count == 1 && possibleSequences.Count == 1) { var seq = possibleSequences.First(); results.Add(new MinimaxResult(seq.Moves, seq.Turn, 0)); loopState.Break(); } foreach (var sequence in possibleSequences) { BoardTileCollection clonedTiles = (BoardTileCollection)tiles.Clone(); float prio = sequence.Moves[0].Priority; foreach (var mov in sequence.Moves) { Board.TakeMove(clonedTiles, mov); } //now we need to call search the move tree for the value of the sequence. var sequenceValue = GetValueOfMoveSequence(this.MaxDepth - 1, clonedTiles, prio, enemyPlayer, currentPlayer, false); var result = new MinimaxResult(sequence.Moves, sequence.Turn, sequenceValue); lock (_resultAddLock) results.Add(result); } } }); #if DEBUG Console.WriteLine($"amount of checkups: {this._amountOfCheckups}"); #endif return(ChooseBestResult(results)); }
protected float GetValueOfMoveSequence(int depth, BoardTileCollection tiles, float priority, IPlayer currentPlayer, IPlayer enemyPlayer, int alpha, int beta) { if (depth == 0) { return(priority); } var checkerMovePairs = currentPlayer.GetAllPossibleMoves(Game, enemyPlayer, tiles); //This paralelisation is increasing the performance quite a lot, but since its called recursively it's maybe not smart to run it in from the start to the end? //I expect this would create quite a bit of overhead by creating millions of tasks. Parallel.ForEach(checkerMovePairs, (pair) => { #if DEBUG lock (this._amountOfCheckupsLock) this._amountOfCheckups++; #endif List <MoveSequence> possibleSequences = null; Turn turn = pair.Value; foreach (var move in turn.Moves) { if (move is AttackMove attMove) { possibleSequences = GetPossibleMoveSequences(turn, attMove); } else { possibleSequences = new List <MoveSequence> { new MoveSequence(turn, new List <Move> { move }) }; } foreach (var sequence in possibleSequences) { BoardTileCollection clonedTiles = (BoardTileCollection)tiles.Clone(); int prio = sequence.Moves[0].Priority; foreach (var mov in sequence.Moves) { Board.TakeMove(clonedTiles, mov); } priority -= prio; //now we need to call the same method from the enemy perception again // recursively collect all sequences for the followup moves var furtherMovesValue = GetValueOfMoveSequence(depth - 1, clonedTiles, -priority, enemyPlayer, currentPlayer, -beta, -alpha); } } }); return(priority); }
/// <summary> /// Recursively search for the best move for the given board /// </summary> /// <param name="depth">How many recursive iterations you want to take(each iteration is 1 turn of 1 of the players, so steps ahead is depth/2</param> /// <param name="tiles">The Tile to iterate over</param> /// <param name="initialSequence">Initial sequence of moves, which is used as state to know what sequence </param> /// <param name="priority"></param> /// <param name="max"></param> /// <param name="currentPlayer"></param> /// <param name="enemyPlayer"></param> /// <returns></returns> public MinimaxResult GetBestTurn(BoardTileCollection tiles, IPlayer currentPlayer) { IPlayer enemyPlayer = this.Game.GetEnemyPlayer(currentPlayer); var results = new List <MinimaxResult>(); //get all possible initial moves for the player var checkerMovePairs = currentPlayer.GetAllPossibleMoves(Game, enemyPlayer, tiles); //loop variables Parallel.ForEach(checkerMovePairs, (pair) => { //foreach(var pair in checkerMovePairs) { //for each checker List <MoveSequence> possibleSequences = null; Turn turn = pair.Value; foreach (var move in turn.Moves) //for each initial move of the checker { if (move is AttackMove attMove) { //all possible followup move sequences possibleSequences = GetPossibleMoveSequences(turn, attMove); } else if (move is WalkMove) { possibleSequences = new List <MoveSequence> { new MoveSequence(turn, new List <Move> { move }) }; } //Parallel.ForEach(possibleSequences, (sequence) => { // Not useful, its max 2 moves foreach (var sequence in possibleSequences) { BoardTileCollection clonedTiles = (BoardTileCollection)tiles.Clone(); float prio = sequence.Moves[0].Priority; foreach (var mov in sequence.Moves) { Board.TakeMove(clonedTiles, mov); } //now we need to call the same method from the enemy perception again var sequenceValue = GetBestMoveSequence(this.MaxDepth, clonedTiles, prio, enemyPlayer, currentPlayer); var result = new MinimaxResult(sequence.Moves, sequence.Turn, sequenceValue); results.Add(result); } //}); } }); //} return(ChooseBestResult(results)); }
protected float GetBestMoveSequence(int depth, BoardTileCollection tiles, float priority, IPlayer currentPlayer, IPlayer enemyPlayer, int alpha, int beta) { if (depth == 0) { return(priority); } var checkerMovePairs = currentPlayer.GetAllPossibleMoves(Game, enemyPlayer, tiles); //Loop variables //stores all the generated move sequences, instantiated outside loop to prevent unneeded creating of variables List <MoveSequence> possibleSequences = null; foreach (var pair in checkerMovePairs) { Turn turn = pair.Value; foreach (var move in turn.Moves) { if (move is AttackMove attMove) { possibleSequences = GetPossibleMoveSequences(turn, attMove); } else { possibleSequences = new List <MoveSequence> { new MoveSequence(turn, new List <Move> { move }) }; } foreach (var sequence in possibleSequences) { BoardTileCollection clonedTiles = (BoardTileCollection)tiles.Clone(); int prio = sequence.Moves[0].Priority; foreach (var mov in sequence.Moves) { Board.TakeMove(clonedTiles, mov); } priority += prio; //now we need to call the same method from the enemy perception again // recursively collect all sequences for the followup moves var furtherMovesValue = GetBestMoveSequence(depth - 1, clonedTiles, -priority, enemyPlayer, currentPlayer, -beta, -alpha); } } } return(priority); }
public void GetCrossChecksForBoardTiles_CreateABoardWithWordBoingAndUsingBoingDawg_AssertThatCrossChecksAreCorrectBeforeAndAfterTransposition() { Board board = new(3, 7); board.PlaceCharTile(2, 2, 'B'); board.PlaceCharTile(2, 3, 'O'); board.PlaceCharTile(2, 4, 'I'); board.PlaceCharTile(2, 5, 'N'); board.PlaceCharTile(2, 6, 'G'); BoardAnchorCollector boardAnchorCollector = new(); BoardTileCollection boardAnchors = boardAnchorCollector.GetAnchors(board); BoardCrossCheckCollector boardCrossCheckCollector = new(board, UnitTestGlobals.BoingDawgWithAlphabet); Dictionary <BoardTile, HashSet <char> > crossChecksForNormalBoard = boardCrossCheckCollector.GetCrossChecksForBoardTiles(boardAnchors); Assert.IsTrue(crossChecksForNormalBoard.Count == 12); Assert.IsTrue(crossChecksForNormalBoard[board.GetBoardTileAtCoordinates(1, 2)].Count == 2); Assert.IsTrue(crossChecksForNormalBoard[board.GetBoardTileAtCoordinates(1, 3)].Count == 17); Assert.IsTrue(crossChecksForNormalBoard[board.GetBoardTileAtCoordinates(1, 4)].Count == 13); Assert.IsTrue(crossChecksForNormalBoard[board.GetBoardTileAtCoordinates(1, 5)].Count == 5); Assert.IsTrue(crossChecksForNormalBoard[board.GetBoardTileAtCoordinates(1, 6)].Count == 2); Assert.IsTrue(crossChecksForNormalBoard[board.GetBoardTileAtCoordinates(3, 2)].Count == 5); Assert.IsTrue(crossChecksForNormalBoard[board.GetBoardTileAtCoordinates(3, 3)].Count == 16); Assert.IsTrue(crossChecksForNormalBoard[board.GetBoardTileAtCoordinates(3, 4)].Count == 6); Assert.IsTrue(crossChecksForNormalBoard[board.GetBoardTileAtCoordinates(3, 5)].Count == 5); Assert.IsTrue(crossChecksForNormalBoard[board.GetBoardTileAtCoordinates(3, 6)].Count == 3); BoardTransposer transposer = new(board); transposer.TransposeBoard(); Dictionary <BoardTile, HashSet <char> > crossChecksForTransposedBoard = boardCrossCheckCollector.GetCrossChecksForBoardTiles(boardAnchors); Assert.IsTrue(crossChecksForTransposedBoard[board.GetBoardTileAtCoordinates(1, 2)].Count == 0); Assert.IsTrue(crossChecksForTransposedBoard[board.GetBoardTileAtCoordinates(7, 2)].Count == 1); transposer.TransposeBoard(); Assert.IsTrue(board.RowCount == 3); Assert.IsTrue(board.ColumnCount == 7); Assert.IsTrue(board.GetBoardTileAtCoordinates(2, 3).CharTile.Letter == 'O'); Assert.IsTrue(board.GetBoardTileAtCoordinates(2, 3).X == 2); Assert.IsTrue(board.GetBoardTileAtCoordinates(2, 3).Y == 3); }
protected float GetValueOfMoveSequence(int depth, BoardTileCollection tiles, float priority, IPlayer currentPlayer, IPlayer enemyPlayer) => GetValueOfMoveSequence(depth, tiles, priority, currentPlayer, enemyPlayer, int.MaxValue, int.MinValue);
public IEnumerable <IChecker> GetPlayerOwnedCheckers(BoardTileCollection tiles) => tiles.Where(t => t.Checker != null) .Select(t => t.Checker).Where(c => c.Owner == this.PlayerNumber);
protected float GetValueOfMoveSequence(int depth, BoardTileCollection tiles, float priority, IPlayer currentPlayer, IPlayer enemyPlayer, bool max) { #warning DE BUG dat ai niet goed kijkt komt denk ik omdat ik de checker niet copy aan het begin, dus de checker data (is king, enz.) kan op de ene plek al geupdatet worden, en in de andere tree vervolgens verkeert berekent. FIX DIT. //Exit condition if (depth == 0) { return(priority); } float bestPrio = priority; var checkerMovePairs = currentPlayer.GetAllPossibleMoves(Game, enemyPlayer, tiles); //This paralelisation is increasing the performance quite a lot, but since its called recursively it's maybe not smart to run it in from the start to the end? //I expect this would create quite a bit of overhead by creating millions of tasks. Parallel.ForEach(checkerMovePairs, (pair) => { #region DEBUG #if DEBUG lock (this._amountOfCheckupsLock) this._amountOfCheckups++; #endif #endregion List <MoveSequence> possibleSequences; Turn turn = pair.Value; foreach (var move in turn.Moves) { if (move is AttackMove attMove) { possibleSequences = GetPossibleMoveSequences(turn, attMove); } else { possibleSequences = new List <MoveSequence> { new MoveSequence(turn, new List <Move> { move }) }; } List <float> betterFoundPrios = new List <float>(); foreach (MoveSequence sequence in possibleSequences) { BoardTileCollection clonedTiles = (BoardTileCollection)tiles.Clone(); int prio = sequence.Moves[0].Priority; foreach (var mov in sequence.Moves) { //TODO: atm it doesnt update checker to king when looking in the future. for this we probarly need to clone checker to. Board.TakeMove(clonedTiles, mov); } float newPrio = max ? priority + prio : priority - prio; //now we need to call the same method from the enemy perception again // recursively collect all sequences for the followup moves var furtherMovesValue = GetValueOfMoveSequence(depth - 1, clonedTiles, newPrio, enemyPlayer, currentPlayer, !max); //if(max) { lock (_addSequenceLock) { if (furtherMovesValue > bestPrio) { betterFoundPrios.Add(furtherMovesValue); } } //} //else { // lock(_addSequenceLock) { // if(furtherMovesValue < bestPrio) { // bestPrio = furtherMovesValue; // } // } //} //results.Add(furtherMovesValue); //return furtherMovesValue; } //get the best of the fould prios if (betterFoundPrios.Any()) { lock (_addSequenceLock) { bestPrio = betterFoundPrios.Max(); } } } }); return(bestPrio); }
/// <summary> /// Recursively search for the best move for the given board /// </summary> /// <param name="depth">How many recursive iterations you want to take(each iteration is 1 turn of 1 of the players, so steps ahead is depth/2</param> /// <param name="tiles">The Tile to iterate over</param> /// <param name="initialSequence">Initial sequence of moves, which is used as state to know what sequence </param> /// <param name="priority"></param> /// <param name="max"></param> /// <param name="currentPlayer"></param> /// <param name="enemyPlayer"></param> /// <returns></returns> public MinimaxResult GetBestTurn(BoardTileCollection tiles, IPlayer currentPlayer) { IPlayer enemyPlayer = this.Game.GetEnemyPlayer(currentPlayer); var results = new List <MinimaxResult>(); //get all possible initial moves for the player var checkerMovePairs = currentPlayer.GetAllPossibleMoves(Game, enemyPlayer, tiles); //loop variables List <MoveSequence> possibleSequences = null; foreach (var pair in checkerMovePairs) //for each checker { Turn turn = pair.Value; foreach (var move in turn.Moves) //for each initial move of the checker { if (move is AttackMove attMove) { //all possible followup move sequences possibleSequences = GetPossibleMoveSequences(turn, attMove); } else if (move is WalkMove) { possibleSequences = new List <MoveSequence> { new MoveSequence(turn, new List <Move> { move }) }; } foreach (var sequence in possibleSequences) { BoardTileCollection clonedTiles = (BoardTileCollection)tiles.Clone(); float prio = sequence.Moves[0].Priority; foreach (var mov in sequence.Moves) { Board.TakeMove(clonedTiles, mov); } //now we need to call the same method from the enemy perception again var sequenceValue = GetBestMoveSequence(this.MaxDepth, clonedTiles, prio, enemyPlayer, currentPlayer); var result = new MinimaxResult(sequence.Moves, sequence.Turn, sequenceValue); results.Add(result); } } } #if DEBUG var str = ""; foreach (var result in results) { str += $"move: {result.Moves.First().StartLocation} => {result.Moves.Last().EndLocation}, value:{result.TurnValue}\n"; } Console.WriteLine($"Turn: {this.Game.Turn}"); Console.WriteLine(str); #endif if (results.Count() == 0) { return(null); } IEnumerable <MinimaxResult> bestResults = ((this.MaxDepth & 0b1) == 0b1) ? results.Where(res => res.TurnValue == results.Max(r => r.TurnValue)) : results.Where(res => res.TurnValue == results.Min(r => r.TurnValue)); var random = new Random(); var ind = random.Next(0, bestResults.Count()); var chosenResult = bestResults.ElementAtOrDefault(ind); return(chosenResult); }