using System.Collections.Concurrent; using Connect4; namespace MinMaxAB; public class Connect4Minimax { protected static int EvaluateScoreDiff(int player, int computer) { if ((player == 0 && computer == 0) || (computer > 0 && player > 0)) return 0; if (computer > 0) { return computer switch { 1 => 1, 2 => 8, 3 => 64, _ => 0 }; } if (player > 0) { return player switch { 1 => -1, 2 => -8, 3 => -64, _ => 0 }; ; } return 0; } public static int CountSequences(GameBoard board) { int score = 0; // Считаем по горизонтали for (int col = 0; col < board.dimensions.Columns; col++) { for (int row = 0; row < board.dimensions.Rows - 3; row++) { int c = 0; int p = 0; for (int k = 0; k < 4; k++) { if (board.cells[col][row + k] == Player.Computer) c++; else if (board.cells[col][row + k] != Player.None) p++; } score += EvaluateScoreDiff(p, c); } } // По вертикали for (int col = 0; col < board.dimensions.Columns - 3; col++) { for (int row = 0; row < board.dimensions.Rows; row++) { int c = 0; int p = 0; for (int k = 0; k < 4; k++) { if (board.cells[col + k][row] == Player.Computer) c++; else if (board.cells[col + k][row] != Player.None) p++; } score += EvaluateScoreDiff(p, c); } } // По диагонали for (int col = 0; col < board.dimensions.Columns - 3; col++) { for (int row = 0; row < board.dimensions.Rows - 3; row++) { int c = 0; int p = 0; for (int k = 0; k < 4; k++) { if (board.cells[col + k][row + k] == Player.Computer) c++; else if (board.cells[col + k][row + k] != Player.None) p++; } score += EvaluateScoreDiff(p, c); } } // По второй диагонали for (int col = 3; col < board.dimensions.Columns; col++) { for (int row = 0; row < board.dimensions.Rows - 3; row++) { int c = 0; int p = 0; for (int k = 0; k < 4; k++) { if (board.cells[col - k][row + k] == Player.Computer) c++; else if (board.cells[col - k][row + k] != Player.None) p++; } score += EvaluateScoreDiff(p, c); } } return score; } private static int EvaluateMinMax(GameBoard board, int depth, bool maxPlayer = false, int alpha = int.MinValue, int beta = int.MaxValue) { var currentWinner = board.GetWinner(); // Выходим, если выиграли или проиграли 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); int best = maxPlayer ? int.MinValue : int.MaxValue; // Считаем все возможные ходы for (int i = 0; i < board.dimensions.Columns; i++) { // Если невозможно сделать ход if (!board.PlaceCoin(maxPlayer ? Player.Computer : Player.Human, i)) continue; // Итерация оппонента 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 = int.Max(alpha, best); else beta = int.Min(beta, best); if (beta <= alpha) break; } return best; } public static int GetBestMove(GameBoard board, int depth) { ConcurrentBag<(int Column, int Score)> moves = []; // Parallel.For(0, board.dimensions.Columns, new() { MaxDegreeOfParallelism = 1 }, i => Parallel.For(0, board.dimensions.Columns, i => { var newBoard = board.Clone(); if (!newBoard.PlaceCoin(Player.Computer, i)) return; moves.Add((i, EvaluateMinMax(newBoard, depth))); }); // Находим счет с максимальным счетом, с приоритетом на центральные колонки return moves.OrderByDescending(m => m.Score).ThenBy(m => int.Abs(m.Column - (board.dimensions.Columns / 2))).First().Column; } }