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