Connect4Minimax.cs 5.4 KB

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