Connect4Minimax.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. using Connect4;
  2. namespace MinMaxAB;
  3. public class Connect4Minimax
  4. {
  5. protected static int EvaluateScoreDiff(int player, int computer)
  6. {
  7. if ((player == 0 && computer == 0) || (computer > 0 && player > 0)) return 0;
  8. if (computer > 0)
  9. {
  10. return computer switch
  11. {
  12. 1 => 1,
  13. 2 => 8,
  14. 3 => 64,
  15. _ => 0
  16. };
  17. }
  18. if (player > 0)
  19. {
  20. return player switch
  21. {
  22. 1 => -1,
  23. 2 => -8,
  24. 3 => -64,
  25. _ => 0
  26. }; ;
  27. }
  28. return 0;
  29. }
  30. public static int CountSequences(GameBoard board)
  31. {
  32. int score = 0;
  33. // Считаем по горизонтали
  34. for (int col = 0; col < board.dimensions.Columns; col++)
  35. {
  36. for (int row = 0; row < board.dimensions.Rows - 3; row++)
  37. {
  38. int c = 0;
  39. int p = 0;
  40. for (int k = 0; k < 4; k++)
  41. {
  42. if (board.cells[col][row + k] == Player.Computer) c++;
  43. else if (board.cells[col][row + k] != Player.None) p++;
  44. }
  45. score += EvaluateScoreDiff(p, c);
  46. }
  47. }
  48. // По вертикали
  49. for (int col = 0; col < board.dimensions.Columns - 3; col++)
  50. {
  51. for (int row = 0; row < board.dimensions.Rows; row++)
  52. {
  53. int c = 0;
  54. int p = 0;
  55. for (int k = 0; k < 4; k++)
  56. {
  57. if (board.cells[col + k][row] == Player.Computer) c++;
  58. else if (board.cells[col + k][row] != Player.None) p++;
  59. }
  60. score += EvaluateScoreDiff(p, c);
  61. }
  62. }
  63. // По диагонали
  64. for (int col = 0; col < board.dimensions.Columns - 3; col++)
  65. {
  66. for (int row = 0; row < board.dimensions.Rows - 3; row++)
  67. {
  68. int c = 0;
  69. int p = 0;
  70. for (int k = 0; k < 4; k++)
  71. {
  72. if (board.cells[col + k][row + k] == Player.Computer) c++;
  73. else if (board.cells[col + k][row + k] != Player.None) p++;
  74. }
  75. score += EvaluateScoreDiff(p, c);
  76. }
  77. }
  78. // По второй диагонали
  79. for (int col = 3; col < board.dimensions.Columns; col++)
  80. {
  81. for (int row = 0; row < board.dimensions.Rows - 3; row++)
  82. {
  83. int c = 0;
  84. int p = 0;
  85. for (int k = 0; k < 4; k++)
  86. {
  87. if (board.cells[col - k][row + k] == Player.Computer) c++;
  88. else if (board.cells[col - k][row + k] != Player.None) p++;
  89. }
  90. score += EvaluateScoreDiff(p, c);
  91. }
  92. }
  93. return score;
  94. }
  95. private static int EvaluateMinMax(GameBoard board, int depth, bool maxPlayer = false, int alpha = int.MinValue, int beta = int.MaxValue)
  96. {
  97. var currentWinner = board.GetWinner();
  98. // Выходим, если выиграли или проиграли
  99. if (currentWinner == Player.Computer) return (depth + 1) * 100_000;
  100. else if (currentWinner == Player.Human) return -(depth + 1) * 100_000;
  101. // Выходим, если больше некуда ходить
  102. if (!board.HasEmptyColumns()) return 0;
  103. // Последняя итерация
  104. if (depth == 0) return CountSequences(board);
  105. int best = maxPlayer ? int.MinValue : int.MaxValue;
  106. // Считаем все возможные ходы
  107. for (int i = 0; i < board.dimensions.Columns; i++)
  108. {
  109. // Если невозможно сделать ход
  110. if (!board.PlaceCoin(maxPlayer ? Player.Computer : Player.Human, i)) continue;
  111. // Итерация оппонента
  112. int x = EvaluateMinMax(board, depth - 1, !maxPlayer, alpha, beta);
  113. // Выбираем лучший (или худший) исход
  114. best = maxPlayer ? int.Max(best, x) : int.Min(best, x);
  115. // Отменяем сделанный ход
  116. board.RemoveFromTop(i);
  117. // Обновляем альфа-бета значения
  118. if (maxPlayer) alpha = int.Max(alpha, best);
  119. else beta = int.Min(beta, best);
  120. // if (beta <= alpha) i++;
  121. if (beta <= alpha) return best;
  122. }
  123. return best;
  124. }
  125. public static int GetBestMove(GameBoard board, int depth)
  126. {
  127. List<(int Column, int Score)> moves = [];
  128. // Parallel.For(0, board.dimensions.Columns, new() { MaxDegreeOfParallelism = 1 }, i =>
  129. Parallel.For(0, board.dimensions.Columns, i =>
  130. {
  131. var newBoard = board.Clone();
  132. if (!newBoard.PlaceCoin(Player.Computer, i)) return;
  133. moves.Add((i, EvaluateMinMax(newBoard, depth)));
  134. });
  135. // Находим счет с максимальным счетом, с приоритетом на центральные колонки
  136. return moves.OrderByDescending(m => m.Score).ThenBy(m => int.Abs(m.Column - (board.dimensions.Columns / 2))).First().Column;
  137. }
  138. }