MinMaxABFourInARowBoard.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. using FourInARow;
  2. namespace MinMaxAB;
  3. public class MinMaxABFourInARowBoard
  4. {
  5. protected int EvaluateScoreDiff((int player, int computer) scores)
  6. {
  7. //Console.WriteLine($"V: {scores.computer} {scores.player}");
  8. if (scores.player == 0 && scores.computer == 0) return 0;
  9. if (scores.computer > 0)
  10. {
  11. return scores.computer switch
  12. {
  13. 1 => 1,
  14. 2 => 10,
  15. 3 => 100,
  16. _ => 0
  17. };
  18. }
  19. if (scores.player > 0)
  20. {
  21. return scores.player switch
  22. {
  23. 1 => -1,
  24. 2 => -10,
  25. 3 => -100,
  26. _ => 0
  27. }; ;
  28. }
  29. return 0;
  30. }
  31. // public int CountSequences(GameBoard board, Player player)
  32. // {
  33. // int score = 0;
  34. // int hscore = 0;
  35. // int vscore = 0;
  36. // int dscore = 0;
  37. // int rscore = 0;
  38. // for (int col = 0; col < board.dimensions.Columns; col++)
  39. // {
  40. // for (int row = 0; row < board.dimensions.Rows; row++)
  41. // {
  42. // if (row < board.dimensions.Columns - 4)
  43. // {
  44. // int computer = 0;
  45. // int p = 0;
  46. // int c2 = 0;
  47. // int p2 = 0;
  48. // for (int k = 0; k < 4; k++)
  49. // {
  50. // if (board.cells[col, row + k] == player) computer++;
  51. // else if (board.cells[col, row + k] != Player.None) p++;
  52. // if (col < board.dimensions.Rows - 4)
  53. // {
  54. // if (board.cells[col + k, row + k] == player) c2++;
  55. // else if (board.cells[col + k, row + k] != Player.None) p2++;
  56. // }
  57. // }
  58. // hscore += EvaluateScoreDiff((p, computer));
  59. // dscore += EvaluateScoreDiff((p2, c2));
  60. // }
  61. // if (col < board.dimensions.Rows - 4)
  62. // {
  63. // int computer = 0;
  64. // int p = 0;
  65. // for (int k = 0; k < 4; k++)
  66. // {
  67. // if (board.cells[col + k, row] == player) computer++;
  68. // else if (board.cells[col + k, row] != Player.None) p++;
  69. // }
  70. // vscore += EvaluateScoreDiff((p, computer));
  71. // }
  72. // if (col >= 3 && row < board.dimensions.Columns - 4)
  73. // {
  74. // int computer = 0;
  75. // int p = 0;
  76. // for (int k = 0; k < 4; k++)
  77. // {
  78. // if (board.cells[col - k, row + k] == player) computer++;
  79. // else if (board.cells[col - k, row + k] != Player.None) p++;
  80. // }
  81. // rscore += EvaluateScoreDiff((p, computer));
  82. // }
  83. // }
  84. // }
  85. // return score;
  86. // }
  87. public int CountSequences(GameBoard board, Player player)
  88. {
  89. int score = 0;
  90. // Direction vectors for all 4 directions:
  91. // Horizontal, Vertical, Diagonal, Reverse Diagonal
  92. int[,] directions = new int[,] {
  93. { 0, 1 }, // Horizontal: right
  94. { 1, 0 }, // Vertical: down
  95. { 1, 1 }, // Diagonal: down-right
  96. { -1, 1 } // Reverse Diagonal: up-right
  97. };
  98. for (int row = 0; row < board.dimensions.Rows; row++)
  99. {
  100. for (int col = 0; col < board.dimensions.Columns; col++)
  101. {
  102. // Check all 4 directions from current position
  103. for (int dir = 0; dir < 4; dir++)
  104. {
  105. int rowDir = directions[dir, 0];
  106. int colDir = directions[dir, 1];
  107. // Check if we can fit a sequence of 4 in this direction
  108. int endRow = row + (3 * rowDir);
  109. int endCol = col + (3 * colDir);
  110. if (endRow >= 0 && endRow < board.dimensions.Rows &&
  111. endCol >= 0 && endCol < board.dimensions.Columns)
  112. {
  113. int aiCount = 0;
  114. int humanCount = 0;
  115. // Check the sequence of 4 cells in current direction
  116. for (int k = 0; k < 4; k++)
  117. {
  118. int currentRow = row + (k * rowDir);
  119. int currentCol = col + (k * colDir);
  120. if (board.cells[currentCol, currentRow] == player)
  121. aiCount++;
  122. else if (board.cells[currentCol, currentRow] != Player.None)
  123. humanCount++;
  124. }
  125. // Score the sequence
  126. score += EvaluateScoreDiff((humanCount, aiCount));
  127. }
  128. }
  129. }
  130. }
  131. return score;
  132. }
  133. private int EvaluateMinMax(GameBoard board, Player player, int depth, bool maxPlayer = false, int alpha = int.MinValue, int beta = int.MaxValue)
  134. {
  135. var currentWinner = board.GetWinner();
  136. if (currentWinner == player) return (depth + 1) * 100_000;
  137. if (currentWinner! == GameBoard.Opposite(player)) return -(depth + 1) * 100_000;
  138. if (!board.HasEmptyColumns()) return 0;
  139. //if (depth == 0) return CountSequences(board, player);
  140. if (depth == 0)
  141. {
  142. return CountSequences(board, player); ;
  143. }
  144. int best = maxPlayer ? int.MinValue : int.MaxValue;
  145. for (int i = 0; i < board.dimensions.Columns; i++)
  146. {
  147. if (!board.PlaceCoin(maxPlayer ? player : GameBoard.Opposite(player), i)) continue;
  148. int x = EvaluateMinMax(board, player, depth - 1, !maxPlayer, alpha, beta);
  149. best = maxPlayer ? Math.Max(best, x) : Math.Min(best, x);
  150. board.RemoveFromTop(i);
  151. if (maxPlayer) alpha = Math.Max(alpha, best);
  152. else beta = Math.Min(beta, best);
  153. if (beta <= alpha) i++;
  154. }
  155. return best;
  156. }
  157. public int GetBestMove(GameBoard board, Player player, int depth)
  158. {
  159. List<(int Column, int Score)> moves = [];
  160. // Parallel.For(0, board.dimensions.Columns, new ParallelOptions() { MaxDegreeOfParallelism = 1 }, i =>
  161. Parallel.For(0, board.dimensions.Columns, i =>
  162. {
  163. var newBoard = (board.Clone() as GameBoard)!;
  164. if (!newBoard.PlaceCoin(player, i)) return;
  165. moves.Add((i, EvaluateMinMax(newBoard, player, depth)));
  166. });
  167. // for (int i = 0; i < board.dimensions.Columns; i++)
  168. // {
  169. // Console.Write($"\rAI Thinking {(float)i / board.dimensions.Columns:p0}...");
  170. // if (!PlaceCoin(player, i)) continue;
  171. // moves.Add((i, EvaluateMinMax(player, depth, maxPlayer, alpha, beta)));
  172. // RemoveFromTop(i);
  173. // }
  174. int maxScore = moves.Max(move => move.Score);
  175. var bestMoves = moves.Where(move => move.Score == maxScore).OrderBy(move => Math.Abs(move.Column - board.dimensions.Columns / 2)).ToArray();
  176. // Console.WriteLine(string.Join(" | ", moves.OrderBy(m => m.Column).Select(m => $"{m.Column}:{m.Score}")));
  177. return bestMoves.ChooseRandom().Column;
  178. }
  179. }