diff --git a/Alligator.Compact.sln b/Alligator.Compact.sln index a98084b..4f21b12 100644 --- a/Alligator.Compact.sln +++ b/Alligator.Compact.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.3.32811.315 +# Visual Studio Version 18 +VisualStudioVersion = 18.4.11620.152 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Alligator.Solver", "Alligator.Solver\Alligator.Solver.csproj", "{683DE064-2731-4553-81F5-813840788C74}" EndProject @@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Alligator.Benchmark", "Alli EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Alligator.TicTacToe", "Alligator.TicTacToe\Alligator.TicTacToe.csproj", "{28060B42-B42E-4C1C-895C-FD0B0C656553}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Alligator.ConnectFour", "Alligator.ConnectFour\Alligator.ConnectFour.csproj", "{9BB16125-E04A-4F2A-CDEA-DA954DCD6453}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,6 +47,10 @@ Global {28060B42-B42E-4C1C-895C-FD0B0C656553}.Debug|Any CPU.Build.0 = Debug|Any CPU {28060B42-B42E-4C1C-895C-FD0B0C656553}.Release|Any CPU.ActiveCfg = Release|Any CPU {28060B42-B42E-4C1C-895C-FD0B0C656553}.Release|Any CPU.Build.0 = Release|Any CPU + {9BB16125-E04A-4F2A-CDEA-DA954DCD6453}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BB16125-E04A-4F2A-CDEA-DA954DCD6453}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BB16125-E04A-4F2A-CDEA-DA954DCD6453}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BB16125-E04A-4F2A-CDEA-DA954DCD6453}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Alligator.ConnectFour/Alligator.ConnectFour.csproj b/Alligator.ConnectFour/Alligator.ConnectFour.csproj new file mode 100644 index 0000000..d715e66 --- /dev/null +++ b/Alligator.ConnectFour/Alligator.ConnectFour.csproj @@ -0,0 +1,14 @@ + + + + + + + + Exe + net10.0 + enable + enable + + + diff --git a/Alligator.ConnectFour/Configuration.cs b/Alligator.ConnectFour/Configuration.cs new file mode 100644 index 0000000..1a709f5 --- /dev/null +++ b/Alligator.ConnectFour/Configuration.cs @@ -0,0 +1,9 @@ +using Alligator.Solver; + +namespace Alligator.ConnectFour +{ + internal class Configuration : IConfiguration + { + public int MaxDepth => 13; + } +} diff --git a/Alligator.ConnectFour/Disk.cs b/Alligator.ConnectFour/Disk.cs new file mode 100644 index 0000000..091c9df --- /dev/null +++ b/Alligator.ConnectFour/Disk.cs @@ -0,0 +1,9 @@ +namespace Alligator.ConnectFour +{ + public enum Disk + { + None = 0, + Red, + Yellow + } +} diff --git a/Alligator.ConnectFour/Drop.cs b/Alligator.ConnectFour/Drop.cs new file mode 100644 index 0000000..4c94c35 --- /dev/null +++ b/Alligator.ConnectFour/Drop.cs @@ -0,0 +1,34 @@ +namespace Alligator.ConnectFour +{ + public class Drop + { + public int Column { get; } + + private static readonly Drop[] instances; + + static Drop() + { + instances = new Drop[Position.Columns]; + for (int c = 0; c < Position.Columns; c++) + { + instances[c] = new Drop(c); + } + } + + private Drop(int column) + { + Column = column; + } + + public static Drop At(int column) => instances[column]; + + public override bool Equals(object? obj) + { + return obj is Drop other && Column == other.Column; + } + + public override int GetHashCode() => Column; + + public override string ToString() => $"Col({Column})"; + } +} \ No newline at end of file diff --git a/Alligator.ConnectFour/Position.cs b/Alligator.ConnectFour/Position.cs new file mode 100644 index 0000000..fe6b28b --- /dev/null +++ b/Alligator.ConnectFour/Position.cs @@ -0,0 +1,265 @@ +using Alligator.Solver; + +namespace Alligator.ConnectFour +{ + public class Position : IPosition + { + public const int Columns = 7; + public const int Rows = 6; + public const int WinLength = 4; + + private readonly Disk[,] board; // [row, col] — row 0 is the bottom + private readonly int[] heights; + + private Disk nextDisk; + private ulong identifier; + + private readonly Stack<(int Column, ulong PrevIdentifier)> history; + + private static readonly ulong[,,] zobristTable; + private static readonly ulong zobristTurn; + + private static readonly int[,] cellWeights = + { + { 8, 9, 10, 12, 10, 9, 8 }, + { 7, 8, 9, 11, 9, 8, 7 }, + { 6, 7, 8, 10, 8, 7, 6 }, + { 5, 6, 7, 9, 7, 6, 5 }, + { 4, 5, 6, 8, 6, 5, 4 }, + { 3, 4, 5, 7, 5, 4, 3 } + }; + + static Position() + { + var rng = new Random(42); + zobristTable = new ulong[2, Rows, Columns]; + for (int color = 0; color < 2; color++) + { + for (int r = 0; r < Rows; r++) + { + for (int c = 0; c < Columns; c++) + { + zobristTable[color, r, c] = NextRandomULong(rng); + } + } + } + zobristTurn = NextRandomULong(rng); + } + + public Position() + { + board = new Disk[Rows, Columns]; + heights = new int[Columns]; + nextDisk = Disk.Red; + identifier = 0UL; + history = new Stack<(int, ulong)>(); + } + + public ulong Identifier => identifier; + + public Disk Next => nextDisk; + + public int MoveCount => history.Count; + + // Always from Red's perspective (absolute convention expected by solver) + public sbyte Value => Evaluate(); + + public void Take(Drop step) + { + int col = step.Column; + int row = heights[col]; + + history.Push((col, identifier)); + + board[row, col] = nextDisk; + heights[col] = row + 1; + + int colorIndex = nextDisk == Disk.Red ? 0 : 1; + identifier ^= zobristTable[colorIndex, row, col]; + identifier ^= zobristTurn; + + nextDisk = nextDisk == Disk.Red ? Disk.Yellow : Disk.Red; + } + + public void TakeBack() + { + var (col, prevId) = history.Pop(); + + int row = heights[col] - 1; + board[row, col] = Disk.None; + heights[col] = row; + + identifier = prevId; + nextDisk = nextDisk == Disk.Red ? Disk.Yellow : Disk.Red; + } + + public int HeightAt(int col) => heights[col]; + + public Disk DiskAt(int row, int col) => board[row, col]; + + public bool IsBoardFull => history.Count == Rows * Columns; + + public bool HasWinner() + { + if (history.Count == 0) + { + return false; + } + + var (col, _) = history.Peek(); + int row = heights[col] - 1; + Disk disk = board[row, col]; + + return CountDirection(row, col, disk, 0, 1) + CountDirection(row, col, disk, 0, -1) >= WinLength - 1 + || CountDirection(row, col, disk, 1, 0) + CountDirection(row, col, disk, -1, 0) >= WinLength - 1 + || CountDirection(row, col, disk, 1, 1) + CountDirection(row, col, disk, -1, -1) >= WinLength - 1 + || CountDirection(row, col, disk, 1, -1) + CountDirection(row, col, disk, -1, 1) >= WinLength - 1; + } + + private int CountDirection(int row, int col, Disk disk, int dRow, int dCol) + { + int count = 0; + int r = row + dRow; + int c = col + dCol; + + while (r >= 0 && r < Rows && c >= 0 && c < Columns && board[r, c] == disk) + { + count++; + r += dRow; + c += dCol; + } + + return count; + } + + private sbyte Evaluate() + { + int score = 0; + + for (int r = 0; r < Rows; r++) + { + for (int c = 0; c < Columns; c++) + { + if (board[r, c] == Disk.Red) score += cellWeights[r, c]; + else if (board[r, c] == Disk.Yellow) score -= cellWeights[r, c]; + } + } + + int redThreats = 0, yellowThreats = 0; + score += EvaluateAllWindows(ref redThreats, ref yellowThreats); + + if (redThreats >= 2) score += 80; + if (yellowThreats >= 2) score -= 80; + + // sbyte.MaxValue is reserved for wins + score = Math.Clamp(score, -126, 126); + + return (sbyte)score; + } + + private int EvaluateAllWindows(ref int redThreats, ref int yellowThreats) + { + int score = 0; + + for (int r = 0; r < Rows; r++) + { + for (int c = 0; c <= Columns - WinLength; c++) + { + score += EvaluateWindow(r, c, 0, 1, ref redThreats, ref yellowThreats); + } + } + + for (int c = 0; c < Columns; c++) + { + for (int r = 0; r <= Rows - WinLength; r++) + { + score += EvaluateWindow(r, c, 1, 0, ref redThreats, ref yellowThreats); + } + } + + for (int r = 0; r <= Rows - WinLength; r++) + { + for (int c = 0; c <= Columns - WinLength; c++) + { + score += EvaluateWindow(r, c, 1, 1, ref redThreats, ref yellowThreats); + } + } + + for (int r = WinLength - 1; r < Rows; r++) + { + for (int c = 0; c <= Columns - WinLength; c++) + { + score += EvaluateWindow(r, c, -1, 1, ref redThreats, ref yellowThreats); + } + } + + return score; + } + + private int EvaluateWindow(int startRow, int startCol, int dRow, int dCol, + ref int redThreats, ref int yellowThreats) + { + int red = 0, yellow = 0; + int minDistance = int.MaxValue; + + for (int i = 0; i < WinLength; i++) + { + int r = startRow + i * dRow; + int c = startCol + i * dCol; + Disk d = board[r, c]; + if (d == Disk.Red) red++; + else if (d == Disk.Yellow) yellow++; + else + { + int distance = r - heights[c]; + if (distance < minDistance) minDistance = distance; + } + } + + if (red > 0 && yellow > 0) return 0; + + if (red == 3) + { + if (minDistance == 0) redThreats++; + return ScoreThreeInRow(minDistance); + } + if (yellow == 3) + { + if (minDistance == 0) yellowThreats++; + return -ScoreThreeInRow(minDistance); + } + if (red == 2) return ScoreTwoInRow(minDistance); + if (yellow == 2) return -ScoreTwoInRow(minDistance); + + return 0; + } + + private static int ScoreThreeInRow(int distanceToPlayable) + { + return distanceToPlayable switch + { + 0 => 50, + 1 => 20, + 2 => 8, + _ => 3 + }; + } + + private static int ScoreTwoInRow(int distanceToPlayable) + { + return distanceToPlayable switch + { + 0 => 8, + 1 => 4, + _ => 2 + }; + } + + private static ulong NextRandomULong(Random rng) + { + byte[] buffer = new byte[8]; + rng.NextBytes(buffer); + return BitConverter.ToUInt64(buffer, 0); + } + } +} diff --git a/Alligator.ConnectFour/Program.cs b/Alligator.ConnectFour/Program.cs new file mode 100644 index 0000000..28cc028 --- /dev/null +++ b/Alligator.ConnectFour/Program.cs @@ -0,0 +1,169 @@ +using Alligator.Solver; + +namespace Alligator.ConnectFour +{ + class Program + { + static void Main(string[] args) + { + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine("Hello Connect Four!"); + Console.WriteLine(); + + IRules rules = new Rules(); + IConfiguration solverConfiguration = new Configuration(); + + SolverProvider solverFactory = new SolverProvider(rules, solverConfiguration, SolverLog); + ISolver solver = solverFactory.Create(); + + Position position = new Position(); + IList history = new List(); + bool aiStep = true; + + while (rules.LegalStepsAt(position).Any()) + { + PrintPosition(position); + Drop next; + + if (aiStep) + { + while (true) + { + try + { + next = AiStep(history, solver); + position.Take(next); + break; + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + } + } + else + { + while (true) + { + try + { + next = HumanStep(position); + position.Take(next); + break; + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + } + } + history.Add(next); + aiStep = !aiStep; + } + + PrintPosition(position); + + if (!rules.IsGoal(position)) + { + Console.WriteLine("Game over, DRAW!"); + } + else + { + Console.WriteLine(string.Format("Game over, {0} WON!", aiStep ? "human" : "ai")); + } + + Console.ReadKey(); + } + + private static Drop HumanStep(Position position) + { + Console.Write("Your turn! Enter column (0-6): "); + while (true) + { + try + { + string input = Console.ReadLine() ?? string.Empty; + int col = int.Parse(input.Trim()); + + if (col < 0 || col >= Position.Columns) + { + throw new ArgumentOutOfRangeException($"Column must be between 0 and {Position.Columns - 1}."); + } + if (position.HeightAt(col) >= Position.Rows) + { + throw new InvalidOperationException($"Column {col} is full."); + } + + return Drop.At(col); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + Console.Write("Try again (0-6): "); + } + } + } + + private static Drop AiStep(IList history, ISolver solver) + { + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine("AI is thinking..."); + + var next = solver.OptimizeNextStep(history); + + Console.WriteLine(string.Format("AI drops into column {0}", next.Column)); + Console.ForegroundColor = ConsoleColor.White; + + return next; + } + + private static void PrintPosition(Position position) + { + Console.WriteLine(); + + // Column headers + Console.Write(" "); + for (int c = 0; c < Position.Columns; c++) + { + Console.Write(string.Format(" {0}", c)); + } + Console.WriteLine(); + + // Board — row 5 (top) to row 0 (bottom) + for (int r = Position.Rows - 1; r >= 0; r--) + { + Console.Write(" "); + for (int c = 0; c < Position.Columns; c++) + { + Disk disk = position.DiskAt(r, c); + switch (disk) + { + case Disk.None: + Console.Write(" ."); + break; + case Disk.Red: + Console.ForegroundColor = ConsoleColor.Red; + Console.Write(" X"); + Console.ForegroundColor = ConsoleColor.White; + break; + case Disk.Yellow: + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write(" O"); + Console.ForegroundColor = ConsoleColor.White; + break; + } + } + Console.WriteLine(); + } + Console.WriteLine(); + } + + private static void SolverLog(string message) + { + var prevColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(string.Format("[SolverLog] {0}", message)); + Console.ForegroundColor = prevColor; + } + } +} diff --git a/Alligator.ConnectFour/Rules.cs b/Alligator.ConnectFour/Rules.cs new file mode 100644 index 0000000..8f050d6 --- /dev/null +++ b/Alligator.ConnectFour/Rules.cs @@ -0,0 +1,47 @@ +using Alligator.Solver; + +namespace Alligator.ConnectFour +{ + public class Rules : IRules + { + public Position InitialPosition() + { + return new Position(); + } + + public IEnumerable LegalStepsAt(Position position) + { + if (position.HasWinner() || position.IsBoardFull) + { + yield break; + } + + int center = Position.Columns / 2; + + if (position.HeightAt(center) < Position.Rows) + { + yield return Drop.At(center); + } + + for (int offset = 1; offset <= Position.Columns / 2; offset++) + { + int left = center - offset; + int right = center + offset; + + if (left >= 0 && position.HeightAt(left) < Position.Rows) + { + yield return Drop.At(left); + } + if (right < Position.Columns && position.HeightAt(right) < Position.Rows) + { + yield return Drop.At(right); + } + } + } + + public bool IsGoal(Position position) + { + return position.HasWinner(); + } + } +} diff --git a/Alligator.Solver/Algorithms/AlphaBetaSolver.cs b/Alligator.Solver/Algorithms/AlphaBetaSolver.cs index cc69bc4..73431b5 100644 --- a/Alligator.Solver/Algorithms/AlphaBetaSolver.cs +++ b/Alligator.Solver/Algorithms/AlphaBetaSolver.cs @@ -9,19 +9,20 @@ internal class AlphaBetaSolver : ISolver private readonly IRules rules; private readonly ISearchManager searchManager; private readonly Action logger; - - private const int maxDepth = 7; // TODO: magic number! + private readonly int maxDepth; public AlphaBetaSolver( AlphaBetaPruning alphaBetaPruning, IRules rules, ISearchManager searchManager, - Action logger) + Action logger, + int maxDepth) { this.alphaBetaPruning = alphaBetaPruning ?? throw new ArgumentNullException(nameof(alphaBetaPruning)); this.rules = rules ?? throw new ArgumentNullException(nameof(rules)); this.searchManager = searchManager ?? throw new ArgumentNullException(nameof(searchManager)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.maxDepth = maxDepth; } public TStep OptimizeNextStep(IList history) diff --git a/Alligator.Solver/IConfiguration.cs b/Alligator.Solver/IConfiguration.cs index cd520e2..7d67275 100644 --- a/Alligator.Solver/IConfiguration.cs +++ b/Alligator.Solver/IConfiguration.cs @@ -5,5 +5,10 @@ /// public interface IConfiguration { + /// + /// Maximum search depth for iterative deepening (exclusive upper bound). + /// The solver searches at even depths: 2, 4, 6, ... up to the largest even less than this value. + /// + int MaxDepth => 7; } } \ No newline at end of file diff --git a/Alligator.Solver/SolverProvider.cs b/Alligator.Solver/SolverProvider.cs index 27b2b24..cc69bf2 100644 --- a/Alligator.Solver/SolverProvider.cs +++ b/Alligator.Solver/SolverProvider.cs @@ -47,7 +47,8 @@ internal ISolver Create(ICacheTables cacheTables, IHeur new AlphaBetaPruning(rules, cacheTables, heuristicTables, searchManager), rules, searchManager, - logger); + logger, + solverConfiguration.MaxDepth); } } } \ No newline at end of file diff --git a/Alligator.Test/Alligator.Test.csproj b/Alligator.Test/Alligator.Test.csproj index c1d895d..e1413b8 100644 --- a/Alligator.Test/Alligator.Test.csproj +++ b/Alligator.Test/Alligator.Test.csproj @@ -21,6 +21,7 @@ + diff --git a/Alligator.Test/ConnectFourTests.cs b/Alligator.Test/ConnectFourTests.cs new file mode 100644 index 0000000..a277d75 --- /dev/null +++ b/Alligator.Test/ConnectFourTests.cs @@ -0,0 +1,156 @@ +using Alligator.ConnectFour; +using Alligator.Solver; + +namespace Alligator.Test +{ + [TestClass] + public class ConnectFourTests + { + private Rules rules = null!; + private SolverProvider solverFactory = null!; + + [TestInitialize] + public void Setup() + { + rules = new Rules(); + solverFactory = new SolverProvider(rules, new ConnectFourConfiguration()); + } + + [TestMethod] + public void InitialPosition_has_7_legal_moves() + { + var position = rules.InitialPosition(); + var moves = rules.LegalStepsAt(position).ToList(); + + Assert.AreEqual(Position.Columns, moves.Count); + } + + [TestMethod] + public void HasWinner_detects_vertical_win() + { + var position = new Position(); + // Red drops in column 0 four times, Yellow drops in column 1 + position.Take(Drop.At(0)); // Red + position.Take(Drop.At(1)); // Yellow + position.Take(Drop.At(0)); // Red + position.Take(Drop.At(1)); // Yellow + position.Take(Drop.At(0)); // Red + position.Take(Drop.At(1)); // Yellow + position.Take(Drop.At(0)); // Red — 4 in column 0 + + Assert.IsTrue(position.HasWinner()); + } + + [TestMethod] + public void HasWinner_detects_horizontal_win() + { + var position = new Position(); + // Red: cols 0,1,2,3. Yellow: cols 0,1,2 (row above) + position.Take(Drop.At(0)); // Red r0c0 + position.Take(Drop.At(0)); // Yellow r1c0 + position.Take(Drop.At(1)); // Red r0c1 + position.Take(Drop.At(1)); // Yellow r1c1 + position.Take(Drop.At(2)); // Red r0c2 + position.Take(Drop.At(2)); // Yellow r1c2 + position.Take(Drop.At(3)); // Red r0c3 — 4 horizontal + + Assert.IsTrue(position.HasWinner()); + } + + [TestMethod] + public void HasWinner_detects_diagonal_win() + { + var position = new Position(); + // Build a diagonal for Red: (0,0), (1,1), (2,2), (3,3) + position.Take(Drop.At(0)); // Red r0c0 + position.Take(Drop.At(1)); // Yellow r0c1 + position.Take(Drop.At(1)); // Red r1c1 + position.Take(Drop.At(2)); // Yellow r0c2 + position.Take(Drop.At(2)); // Red r1c2 + position.Take(Drop.At(3)); // Yellow r0c3 + position.Take(Drop.At(2)); // Red r2c2 + position.Take(Drop.At(3)); // Yellow r1c3 + position.Take(Drop.At(3)); // Red r2c3 + position.Take(Drop.At(4)); // Yellow r0c4 + position.Take(Drop.At(3)); // Red r3c3 — diagonal win + + Assert.IsTrue(position.HasWinner()); + } + + [TestMethod] + public void No_legal_moves_after_win() + { + var position = new Position(); + position.Take(Drop.At(0)); + position.Take(Drop.At(1)); + position.Take(Drop.At(0)); + position.Take(Drop.At(1)); + position.Take(Drop.At(0)); + position.Take(Drop.At(1)); + position.Take(Drop.At(0)); // Red wins vertically + + var moves = rules.LegalStepsAt(position).ToList(); + Assert.AreEqual(0, moves.Count); + } + + [TestMethod] + public void TakeBack_restores_position() + { + var position = new Position(); + var idBefore = position.Identifier; + + position.Take(Drop.At(3)); + Assert.AreNotEqual(idBefore, position.Identifier); + + position.TakeBack(); + Assert.AreEqual(idBefore, position.Identifier); + Assert.AreEqual(0, position.HeightAt(3)); + } + + [TestMethod] + public void Solver_finds_winning_move_in_connect_four() + { + // Red has 3 in a row at bottom (cols 0,1,2), column 3 is open + // Solver should find the winning drop + var position = new Position(); + position.Take(Drop.At(0)); // Red + position.Take(Drop.At(0)); // Yellow + position.Take(Drop.At(1)); // Red + position.Take(Drop.At(1)); // Yellow + position.Take(Drop.At(2)); // Red + position.Take(Drop.At(2)); // Yellow + // Red's turn — dropping in col 3 wins + + var solver = solverFactory.Create(); + var history = new List(); + // Replay moves as history + history.Add(Drop.At(0)); history.Add(Drop.At(0)); + history.Add(Drop.At(1)); history.Add(Drop.At(1)); + history.Add(Drop.At(2)); history.Add(Drop.At(2)); + + var bestMove = solver.OptimizeNextStep(history); + Assert.AreEqual(3, bestMove.Column); + } + + [TestMethod] + public void Solver_blocks_opponent_winning_move() + { + // Yellow has 3 in a row at bottom (cols 0,1,2), Red must block col 3 + var history = new List + { + Drop.At(6), Drop.At(0), // Red col6, Yellow col0 + Drop.At(6), Drop.At(1), // Red col6, Yellow col1 + Drop.At(5), Drop.At(2), // Red col5, Yellow col2 + // Red's turn — must block Yellow at col 3 + }; + + var solver = solverFactory.Create(); + var bestMove = solver.OptimizeNextStep(history); + Assert.AreEqual(3, bestMove.Column); + } + + private class ConnectFourConfiguration : IConfiguration + { + } + } +} diff --git a/Alligator.Test/SolverTests.cs b/Alligator.Test/SolverTests.cs index 6d7a35c..16b066c 100644 --- a/Alligator.Test/SolverTests.cs +++ b/Alligator.Test/SolverTests.cs @@ -16,11 +16,11 @@ private static int Solve(TreeNode root, int maxDepth = 7) var rules = new TreeRules(root); var cacheTables = new CacheTables(); var heuristicTables = new HeuristicTables(); - var searchManager = new SearchManager(maxDepth - 1); - var alphaBeta = new AlphaBetaPruning( - rules, cacheTables, heuristicTables, searchManager); - var solver = new AlphaBetaSolver( - alphaBeta, rules, searchManager, _ => { }); +var searchManager = new SearchManager(maxDepth - 1); +var alphaBeta = new AlphaBetaPruning( + rules, cacheTables, heuristicTables, searchManager); +var solver = new AlphaBetaSolver( + alphaBeta, rules, searchManager, _ => { }, maxDepth); return solver.OptimizeNextStep([]); }