|
@@ -0,0 +1,216 @@
|
|
|
+using FourInARow;
|
|
|
+
|
|
|
+namespace MinMaxAB;
|
|
|
+
|
|
|
+public class MinMaxABFourInARowBoard
|
|
|
+{
|
|
|
+ protected int EvaluateScoreDiff((int player, int computer) scores)
|
|
|
+ {
|
|
|
+ //Console.WriteLine($"V: {scores.computer} {scores.player}");
|
|
|
+
|
|
|
+ if (scores.player == 0 && scores.computer == 0) return 0;
|
|
|
+
|
|
|
+ if (scores.computer > 0)
|
|
|
+ {
|
|
|
+ return scores.computer switch
|
|
|
+ {
|
|
|
+ 1 => 1,
|
|
|
+ 2 => 10,
|
|
|
+ 3 => 100,
|
|
|
+ _ => 0
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (scores.player > 0)
|
|
|
+ {
|
|
|
+ return scores.player switch
|
|
|
+ {
|
|
|
+ 1 => -1,
|
|
|
+ 2 => -10,
|
|
|
+ 3 => -100,
|
|
|
+ _ => 0
|
|
|
+ }; ;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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)
|
|
|
+ {
|
|
|
+ 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
|
|
|
+ };
|
|
|
+
|
|
|
+ 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);
|
|
|
+
|
|
|
+ if (endRow >= 0 && endRow < board.dimensions.Rows &&
|
|
|
+ endCol >= 0 && endCol < board.dimensions.Columns)
|
|
|
+ {
|
|
|
+ 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)
|
|
|
+ aiCount++;
|
|
|
+ else if (board.cells[currentCol, currentRow] != Player.None)
|
|
|
+ humanCount++;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Score the sequence
|
|
|
+ score += EvaluateScoreDiff((humanCount, aiCount));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return score;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int EvaluateMinMax(GameBoard board, Player player, 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 (!board.HasEmptyColumns()) return 0;
|
|
|
+
|
|
|
+ //if (depth == 0) return CountSequences(board, player);
|
|
|
+ if (depth == 0)
|
|
|
+ {
|
|
|
+ return CountSequences(board, player); ;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
+
|
|
|
+ int x = EvaluateMinMax(board, player, depth - 1, !maxPlayer, alpha, beta);
|
|
|
+ best = maxPlayer ? Math.Max(best, x) : Math.Min(best, x);
|
|
|
+ board.RemoveFromTop(i);
|
|
|
+
|
|
|
+ if (maxPlayer) alpha = Math.Max(alpha, best);
|
|
|
+ else beta = Math.Min(beta, best);
|
|
|
+
|
|
|
+ if (beta <= alpha) i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return best;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int GetBestMove(GameBoard board, Player player, 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)));
|
|
|
+ });
|
|
|
+ // 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;
|
|
|
+ }
|
|
|
+}
|