Kaynağa Gözat

Optimizations

Vsevolod Levitan 4 ay önce
ebeveyn
işleme
34a2fa0ef5
5 değiştirilmiş dosya ile 82 ekleme ve 154 silme
  1. 26 5
      Extensions.cs
  2. 24 26
      FourInARow/GameBoard.cs
  3. 1 1
      FourInARow/Player.cs
  4. 28 118
      MinMaxABFourInARowBoard.cs
  5. 3 4
      Program.cs

+ 26 - 5
Extensions.cs

@@ -1,19 +1,40 @@
+using FourInARow;
+
 namespace MinMaxAB;
 
 public static class Extensions
 {
     public static T ChooseRandom<T>(this T[] source) => source[Random.Shared.Next(0, source.Length)];
-    public static T[,] Fill<T>(int cols, int rows, T value)
+    public static Player[][] Fill(int cols, int rows, Player value)
     {
-        T[,] array = new T[cols, rows];
-        for (int i = 0; i < array.GetLength(0); i++)
+        Player[][] array = new Player[cols][];
+        for (int i = 0; i < cols; i++)
         {
-            for (int j = 0; j < array.GetLength(1); j++)
+            var arr = new Player[rows];
+            for (int j = 0; j < rows; j++)
             {
-                array[i, j] = value;
+                arr[j] = value;
             }
+            array[i] = arr;
         }
 
         return array;
     }
+
+    public static Player[][] CopyArrayBuiltIn(this Player[][] source)
+    {
+        var len = source.Length;
+        var dest = new Player[len][];
+
+        for (var x = 0; x < len; x++)
+        {
+            var inner = source[x];
+            var ilen = inner.Length;
+            var newer = new Player[ilen];
+            Array.Copy(inner, newer, ilen);
+            dest[x] = newer;
+        }
+
+        return dest;
+    }
 }

+ 24 - 26
FourInARow/GameBoard.cs

@@ -5,57 +5,58 @@ namespace FourInARow;
 
 public class GameBoard : ICloneable
 {
-    public Player[,] cells;
+    public Player[][] cells;
     public readonly GameDimensions dimensions;
 
-    protected int lastPlayedRow;
-    protected int lastPlayedColumn;
-
     public GameBoard(GameDimensions dim)
     {
         cells = Extensions.Fill(dim.Columns, dim.Rows, Player.None);
         dimensions = dim;
     }
 
+    public GameBoard(GameDimensions dim, Player[][] data)
+    {
+        dimensions = dim;
+        cells = data;
+    }
+
     private bool cacheInvalidated = false;
     private Player winnerCache = Player.None;
 
     public bool PlaceCoin(Player player, int column)
     {
         int row = 0;
-        while (row < dimensions.Rows && cells[column, row] == Player.None)
+        while (row < dimensions.Rows && cells[column][row] == Player.None)
         {
             row++;
         }
 
         if (row == 0)
             return false;
-        cells[column, row - 1] = player;
+        cells[column][row - 1] = player;
         cacheInvalidated = true;
-        lastPlayedRow = row;
-        lastPlayedColumn = column;
         return true;
     }
 
     public bool RemoveFromTop(int column)
     {
         int row = 0;
-        while (row < dimensions.Rows && cells[column, row] == Player.None)
+        while (row < dimensions.Rows && cells[column][row] == Player.None)
         {
             row++;
         }
 
         if (row == dimensions.Rows)
             return false;
-        cells[column, row] = Player.None;
+        cells[column][row] = Player.None;
         cacheInvalidated = true;
         return true;
     }
 
     public static Player Opposite(Player player) => player switch
     {
-        Player.Player => Player.Computer,
-        Player.Computer => Player.Player,
+        Player.Human => Player.Computer,
+        Player.Computer => Player.Human,
         _ => Player.None
     };
 
@@ -69,7 +70,7 @@ public class GameBoard : ICloneable
         {
             for (int j = 0; j < dimensions.Rows; j++)
             {
-                if (cells[i, j] == Player.None)
+                if (cells[i][j] == Player.None)
                     continue;
 
                 bool h = i + 3 < dimensions.Columns;
@@ -83,18 +84,18 @@ public class GameBoard : ICloneable
 
                 for (int k = 1; k < 4; k++)
                 {
-                    h = h && cells[i, j] == cells[i + k, j];
-                    v = v && cells[i, j] == cells[i, j + k];
-                    d = d && cells[i, j] == cells[i + k, j + k];
-                    bd = bd && cells[i, j] == cells[i - k, j + k];
+                    h = h && cells[i][j] == cells[i + k][j];
+                    v = v && cells[i][j] == cells[i][j + k];
+                    d = d && cells[i][j] == cells[i + k][j + k];
+                    bd = bd && cells[i][j] == cells[i - k][j + k];
                     if (!h && !v && !d && !bd)
                         break;
                 }
 
                 if (h || v || d || bd)
                 {
-                    winnerCache = cells[i, j];
-                    return cells[i, j];
+                    winnerCache = cells[i][j];
+                    return cells[i][j];
                 }
             }
         }
@@ -107,7 +108,7 @@ public class GameBoard : ICloneable
     {
         for (int x = 0; x < dimensions.Columns; x++)
         {
-            if (cells[x, 0] == Player.None) return true;
+            if (cells[x][0] == Player.None) return true;
         }
 
         return false;
@@ -115,7 +116,7 @@ public class GameBoard : ICloneable
 
     protected char PlayerChar(Player player) => player switch
     {
-        Player.Player => 'O',
+        Player.Human => 'O',
         Player.Computer => 'X',
         _ => ' ',
     };
@@ -133,7 +134,7 @@ public class GameBoard : ICloneable
         for (int row = 0; row < dimensions.Rows; row++)
         {
             builder.Append("| ");
-            for (int column = 0; column < dimensions.Columns; column++) builder.Append(PlayerChar(cells[column, row])).Append(" | ");
+            for (int column = 0; column < dimensions.Columns; column++) builder.Append(PlayerChar(cells[column][row])).Append(" | ");
             builder.AppendLine(sep);
         }
         return builder.ToString();
@@ -141,10 +142,7 @@ public class GameBoard : ICloneable
 
     public object Clone()
     {
-        var newBoard = new GameBoard(dimensions)
-        {
-            cells = (cells.Clone() as Player[,])!
-        };
+        var newBoard = new GameBoard(dimensions, cells.CopyArrayBuiltIn());
         return newBoard;
     }
 }

+ 1 - 1
FourInARow/Player.cs

@@ -3,6 +3,6 @@ namespace FourInARow;
 public enum Player
 {
     None,
-    Player,
+    Human,
     Computer
 }

+ 28 - 118
MinMaxABFourInARowBoard.cs

@@ -4,15 +4,13 @@ namespace MinMaxAB;
 
 public class MinMaxABFourInARowBoard
 {
-    protected int EvaluateScoreDiff((int player, int computer) scores)
+    protected static int EvaluateScoreDiff(int player, int computer)
     {
-        //Console.WriteLine($"V: {scores.computer} {scores.player}");
+        if (player == 0 && computer == 0) return 0;
 
-        if (scores.player == 0 && scores.computer == 0) return 0;
-
-        if (scores.computer > 0)
+        if (computer > 0)
         {
-            return scores.computer switch
+            return computer switch
             {
                 1 => 1,
                 2 => 10,
@@ -21,9 +19,9 @@ public class MinMaxABFourInARowBoard
             };
         }
 
-        if (scores.player > 0)
+        if (player > 0)
         {
-            return scores.player switch
+            return player switch
             {
                 1 => -1,
                 2 => -10,
@@ -35,97 +33,26 @@ public class MinMaxABFourInARowBoard
         return 0;
     }
 
-    // public int CountSequences(GameBoard board, Player player)
-    // {
-    //     int score = 0;
-    //     int hscore = 0;
-    //     int vscore = 0;
-    //     int dscore = 0;
-    //     int rscore = 0;
-
-    //     for (int col = 0; col < board.dimensions.Columns; col++)
-    //     {
-    //         for (int row = 0; row < board.dimensions.Rows; row++)
-    //         {
-    //             if (row < board.dimensions.Columns - 4)
-    //             {
-    //                 int computer = 0;
-    //                 int p = 0;
-    //                 int c2 = 0;
-    //                 int p2 = 0;
-
-    //                 for (int k = 0; k < 4; k++)
-    //                 {
-    //                     if (board.cells[col, row + k] == player) computer++;
-    //                     else if (board.cells[col, row + k] != Player.None) p++;
-
-    //                     if (col < board.dimensions.Rows - 4)
-    //                     {
-    //                         if (board.cells[col + k, row + k] == player) c2++;
-    //                         else if (board.cells[col + k, row + k] != Player.None) p2++;
-    //                     }
-    //                 }
-
-    //                 hscore += EvaluateScoreDiff((p, computer));
-    //                 dscore += EvaluateScoreDiff((p2, c2));
-    //             }
-
-    //             if (col < board.dimensions.Rows - 4)
-    //             {
-    //                 int computer = 0;
-    //                 int p = 0;
-
-    //                 for (int k = 0; k < 4; k++)
-    //                 {
-    //                     if (board.cells[col + k, row] == player) computer++;
-    //                     else if (board.cells[col + k, row] != Player.None) p++;
-    //                 }
-
-    //                 vscore += EvaluateScoreDiff((p, computer));
-    //             }
-
-    //             if (col >= 3 && row < board.dimensions.Columns - 4)
-    //             {
-    //                 int computer = 0;
-    //                 int p = 0;
-
-    //                 for (int k = 0; k < 4; k++)
-    //                 {
-    //                     if (board.cells[col - k, row + k] == player) computer++;
-    //                     else if (board.cells[col - k, row + k] != Player.None) p++;
-    //                 }
-
-    //                 rscore += EvaluateScoreDiff((p, computer));
-    //             }
-    //         }
-    //     }
-    //     return score;
-    // }
-
-    public int CountSequences(GameBoard board, Player player)
+    public int CountSequences(GameBoard board)
     {
         int score = 0;
 
-        // Direction vectors for all 4 directions:
-        // Horizontal, Vertical, Diagonal, Reverse Diagonal
         int[,] directions = new int[,] {
-        { 0, 1 },  // Horizontal: right
-        { 1, 0 },  // Vertical: down
-        { 1, 1 },  // Diagonal: down-right
-        { -1, 1 }  // Reverse Diagonal: up-right
+        { 0, 1 },
+        { 1, 0 },
+        { 1, 1 },
+        { -1, 1 }
     };
 
         for (int row = 0; row < board.dimensions.Rows; row++)
         {
             for (int col = 0; col < board.dimensions.Columns; col++)
             {
-                // Check all 4 directions from current position
                 for (int dir = 0; dir < 4; dir++)
                 {
                     int rowDir = directions[dir, 0];
                     int colDir = directions[dir, 1];
 
-                    // Check if we can fit a sequence of 4 in this direction
                     int endRow = row + (3 * rowDir);
                     int endCol = col + (3 * colDir);
 
@@ -135,20 +62,18 @@ public class MinMaxABFourInARowBoard
                         int aiCount = 0;
                         int humanCount = 0;
 
-                        // Check the sequence of 4 cells in current direction
                         for (int k = 0; k < 4; k++)
                         {
                             int currentRow = row + (k * rowDir);
                             int currentCol = col + (k * colDir);
 
-                            if (board.cells[currentCol, currentRow] == player)
+                            if (board.cells[currentCol][currentRow] == Player.Computer)
                                 aiCount++;
-                            else if (board.cells[currentCol, currentRow] != Player.None)
+                            else if (board.cells[currentCol][currentRow] != Player.None)
                                 humanCount++;
                         }
 
-                        // Score the sequence
-                        score += EvaluateScoreDiff((humanCount, aiCount));
+                        score += EvaluateScoreDiff(humanCount, aiCount);
                     }
                 }
             }
@@ -157,32 +82,28 @@ public class MinMaxABFourInARowBoard
         return score;
     }
 
-    private int EvaluateMinMax(GameBoard board, Player player, int depth, bool maxPlayer = false, int alpha = int.MinValue, int beta = int.MaxValue)
+    private int EvaluateMinMax(GameBoard board, int depth, bool maxPlayer = false, int alpha = int.MinValue, int beta = int.MaxValue)
     {
         var currentWinner = board.GetWinner();
 
-        if (currentWinner == player) return (depth + 1) * 100_000;
-        if (currentWinner! == GameBoard.Opposite(player)) return -(depth + 1) * 100_000;
+        if (currentWinner == Player.Computer) return (depth + 1) * 100_000;
+        else if (currentWinner == Player.Human) return -(depth + 1) * 100_000;
         if (!board.HasEmptyColumns()) return 0;
 
-        //if (depth == 0) return CountSequences(board, player);
-        if (depth == 0)
-        {
-            return CountSequences(board, player); ;
-        }
+        if (depth == 0) return CountSequences(board);
 
         int best = maxPlayer ? int.MinValue : int.MaxValue;
 
         for (int i = 0; i < board.dimensions.Columns; i++)
         {
-            if (!board.PlaceCoin(maxPlayer ? player : GameBoard.Opposite(player), i)) continue;
+            if (!board.PlaceCoin(maxPlayer ? Player.Computer : Player.Human, i)) continue;
 
-            int x = EvaluateMinMax(board, player, depth - 1, !maxPlayer, alpha, beta);
-            best = maxPlayer ? Math.Max(best, x) : Math.Min(best, x);
+            int x = EvaluateMinMax(board, depth - 1, !maxPlayer, alpha, beta);
+            best = maxPlayer ? int.Max(best, x) : int.Min(best, x);
             board.RemoveFromTop(i);
 
-            if (maxPlayer) alpha = Math.Max(alpha, best);
-            else beta = Math.Min(beta, best);
+            if (maxPlayer) alpha = int.Max(alpha, best);
+            else beta = int.Min(beta, best);
 
             if (beta <= alpha) i++;
         }
@@ -190,27 +111,16 @@ public class MinMaxABFourInARowBoard
         return best;
     }
 
-    public int GetBestMove(GameBoard board, Player player, int depth)
+    public int GetBestMove(GameBoard board, int depth)
     {
         List<(int Column, int Score)> moves = [];
-        // Parallel.For(0, board.dimensions.Columns, new ParallelOptions() { MaxDegreeOfParallelism = 1 }, i =>
         Parallel.For(0, board.dimensions.Columns, i =>
         {
             var newBoard = (board.Clone() as GameBoard)!;
-            if (!newBoard.PlaceCoin(player, i)) return;
-            moves.Add((i, EvaluateMinMax(newBoard, player, depth)));
+            if (!newBoard.PlaceCoin(Player.Computer, i)) return;
+            moves.Add((i, EvaluateMinMax(newBoard, depth)));
         });
-        // for (int i = 0; i < board.dimensions.Columns; i++)
-        // {
-        //     Console.Write($"\rAI Thinking {(float)i / board.dimensions.Columns:p0}...");
-        //     if (!PlaceCoin(player, i)) continue;
-        //     moves.Add((i, EvaluateMinMax(player, depth, maxPlayer, alpha, beta)));
-        //     RemoveFromTop(i);
-        // }
-
-        int maxScore = moves.Max(move => move.Score);
-        var bestMoves = moves.Where(move => move.Score == maxScore).OrderBy(move => Math.Abs(move.Column - board.dimensions.Columns / 2)).ToArray();
-        // Console.WriteLine(string.Join(" | ", moves.OrderBy(m => m.Column).Select(m => $"{m.Column}:{m.Score}")));
-        return bestMoves.ChooseRandom().Column;
+
+        return moves.MaxBy(m => m.Score).Column;
     }
 }

+ 3 - 4
Program.cs

@@ -11,7 +11,6 @@ internal class Program
 
     private static void Main()
     {
-
         var board = new GameBoard(new(7, 6));
         var minmax = new MinMaxABFourInARowBoard();
         while (true)
@@ -30,7 +29,7 @@ internal class Program
 
         var sw = new Stopwatch();
         sw.Start();
-        var move = minmax.GetBestMove(board, Player.Computer, depth);
+        var move = minmax.GetBestMove(board, depth);
         sw.Stop();
         Console.WriteLine($"Decision made in {sw.Elapsed}");
 
@@ -60,9 +59,9 @@ internal class Program
         Console.WriteLine(board);
 
         Console.Write("Your move: ");
-        board.PlaceCoin(Player.Player, int.Parse(Console.ReadLine()!) - 1);
+        board.PlaceCoin(Player.Human, int.Parse(Console.ReadLine()!) - 1);
 
-        if (board.GetWinner() == Player.Player)
+        if (board.GetWinner() == Player.Human)
         {
             Console.WriteLine("Human Wins");
             Environment.Exit(0);