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; } }