TicTacToeAI with Minimax Algorithm

huangapple go评论108阅读模式
英文:

TicTacToeAI with Minimax Algorithm

问题

我已经实现了井字游戏的Minimax算法,参考自GeeksForGeeks。我知道Minimax算法是如何工作的,但是我的代码似乎不按照它的方式工作。我已经反复检查可能出错的地方,也进行了调试。但似乎我找不到问题所在。

请查看算法,非常感谢额外的一双眼睛,以找到我找不到的错误部分。

我已经对代码的每个部分进行了注释,有助于理解。请帮助我解决问题。

谢谢。

  1. import java.util.Arrays;
  2. import java.util.Scanner;
  3. // ... 省略了一些代码 ...
  4. // 检查游戏结果,显示胜利者或平局。
  5. public boolean displayResult() {
  6. int valR = checkRows();
  7. int valC = checkCols();
  8. int diag = checkDiag();
  9. if (diag == 1 || diag == 3 || valR == 3 || valC == 3) {
  10. System.out.println("X wins");
  11. return true;
  12. } else if (diag == 2 || diag == 4 || valR == 2 || valC == 2) {
  13. System.out.println("O wins");
  14. return true;
  15. } else if ((diag == 0 || valC == 1 || valR == 1) && checkForSpace()) {
  16. System.out.println("Draw");
  17. return true;
  18. }
  19. return false;
  20. }
  21. // ... 其他部分的代码 ...

蓝色箭头指示发生错误的地方。
绿色标记说明应该是正确的方式。
TicTacToeAI with Minimax Algorithm

控制台输出:

  1. Input command: start user ai
  2. ---------
  3. | |
  4. | |
  5. | |
  6. ---------
  7. Enter the coordinates (According to Matrix, Space separated integers): 1 1
  8. ---------
  9. | |
  10. | X |
  11. | |
  12. ---------
  13. Making move level "AI"
  14. ---------
  15. | O |
  16. | X |
  17. | |
  18. ---------
  19. Enter the coordinates (According to Matrix, Space separated integers): 0 2
  20. ---------
  21. | O X |
  22. | X |
  23. | |
  24. ---------
  25. Making move level "AI"
  26. ---------
  27. | O O X | <-- 说明: AI [0, 1] 位置上进行了移动,但应该在 [2, 0] 上进行移动,
  28. | X | 这将阻止我在右对角线上获胜。
  29. | |
  30. ---------
  31. Enter the coordinates (According to Matrix, Space separated integers): 2 0
  32. ---------
  33. | O O X |
  34. | X |
  35. | X |
  36. ---------
  37. X wins

希望这对你有所帮助。如果你有任何其他问题,请随时提问。

英文:

I have implemented Minimax Algorithm for Tic Tac Toe Game from GeeksForGeeks. I know how the Minimax Algorithm works but my code here doesn't work according to it. I have checked and checked for the things I might be doing wrong and also have debugged it. But it seems, I am not able to find it.

Please look into the algorithm, it would be much thankful for extra set of eyes and to find the incorrect part which I can't seem to find.

I have commented every part of the code that is helpful with Minimax Algorithm. I think it would be easy to catch up.

Please help me out here.

Thank you.

  1. import java.util.Arrays;
  2. import java.util.Scanner;
  3. class Move {
  4. // row index
  5. protected int row;
  6. // column index
  7. protected int col;
  8. // exp is 'Our Expression'.
  9. protected char exp;
  10. // oppExp is 'Opponent's Expression'
  11. protected char oppExp;
  12. }
  13. class TicTacToe {
  14. private final char[][] arr = new char[3][3];
  15. // Move - to keep track of rowIndex and columnIndex
  16. // from the minimax algorithm. And to keep track of
  17. // 'X'/'O', who is our opponent for which the algorithm
  18. // should find moves for.
  19. private final Move moves = new Move();
  20. TicTacToe() {
  21. // Initialising field
  22. for (int i = 0; i < 3; i++) {
  23. Arrays.fill(this.arr[i], ' ');
  24. }
  25. }
  26. public String[] whosePlaying(Scanner sc) {
  27. while (true) {
  28. System.out.print("Input command: ");
  29. // Getting input for players vs players
  30. String str = sc.nextLine();
  31. if (str.startsWith("start")) {
  32. String[] players = str.replaceAll("start", "").trim().split(" ");
  33. if (players.length == 2) {
  34. if ((players[0].equals("user") || players[0].equals("ai")) &&
  35. (players[1].equals("user") || players[1].equals("ai"))) {
  36. return players;
  37. }
  38. }
  39. }
  40. System.out.println("Bad parameters!");
  41. }
  42. }
  43. public void playUser (Scanner sc, char exp) { // exp is expression('X' / 'O') for user
  44. // x is RowIndex
  45. // y is ColumnIndex
  46. // To get from the user
  47. int x, y;
  48. System.out.print("Enter the coordinates (According to Matrix, Space separated integers): ");
  49. while (true) {
  50. // try - catch for input that might not be number
  51. try {
  52. sc = new Scanner(System.in);
  53. x = sc.nextInt();
  54. y = sc.nextInt();
  55. break;
  56. } catch (Exception e) {
  57. System.out.println("You should enter numbers!");
  58. playUser(sc, exp); // Ask again for coordinates
  59. }
  60. }
  61. if (x > 2 || y > 2 || x < 0 || y < 0) {
  62. System.out.println("Coordinates should be from 0 to 2!");
  63. playUser(sc, exp); // Ask again for coordinates
  64. } else {
  65. // Check for availability.
  66. if (this.arr[x][y] == ' ') {
  67. this.arr[x][y] = exp;
  68. displayField(); // Displaying TicTacToe field after user's turn.
  69. } else {
  70. System.out.println("This cell is occupied! Choose another one!");
  71. playUser(sc, exp); // Ask again for coordinates
  72. }
  73. }
  74. }
  75. public void playComputer (char exp) {
  76. System.out.println("Making move level \"AI\"");
  77. // Setting our expresssion that is X / O.
  78. moves.exp = exp;
  79. // Finding opponents expresssion that is X / O.
  80. if (moves.exp == 'X') moves.oppExp = 'O';
  81. else moves.oppExp = 'X';
  82. // Searching for the best move.
  83. searchBestMove();
  84. // Setting the best coordinates from the minimax algorithm
  85. // into the field with our expresssion.
  86. this.arr[moves.row][moves.col] = moves.exp;
  87. displayField(); // Displaying TicTacToe field after AI's turn.
  88. }
  89. // Start of Minimax Algorithm - Contains all methods needed for the algorithm
  90. private void searchBestMove() {
  91. int bestVal = Integer.MIN_VALUE;
  92. moves.row = -1;
  93. moves.col = -1;
  94. for (int i = 0; i < 3; i++) {
  95. for (int j = 0; j < 3; j++) {
  96. if (this.arr[i][j] == ' ') {
  97. this.arr[i][j] = moves.exp;
  98. int moveVal = minimax(0, false);
  99. this.arr[i][j] = ' ';
  100. if (moveVal > bestVal) {
  101. moves.row = i;
  102. moves.col = j;
  103. bestVal = moveVal;
  104. }
  105. }
  106. }
  107. }
  108. }
  109. private int minimax(int depth, boolean isMax) {
  110. int score = checkGetScore();
  111. if (score == 10 || score == -10) return score;
  112. if (!checkForSpace()) return 0;
  113. int best;
  114. if (isMax) {
  115. best = Integer.MIN_VALUE;
  116. for (int i = 0; i < 3; i++) {
  117. for (int j = 0; j < 3; j++) {
  118. if (this.arr[i][j] == ' ') {
  119. this.arr[i][j] = moves.exp;
  120. best = Math.max(best, minimax(depth + 1, false));
  121. this.arr[i][j] = ' ';
  122. }
  123. }
  124. }
  125. } else {
  126. best = Integer.MAX_VALUE;
  127. for (int i = 0; i < 3; i++) {
  128. for (int j = 0; j < 3; j++) {
  129. if (this.arr[i][j] == ' ') {
  130. this.arr[i][j] = moves.oppExp;
  131. best = Math.min(best, minimax(depth + 1, true));
  132. this.arr[i][j] = ' ';
  133. }
  134. }
  135. }
  136. }
  137. return best;
  138. }
  139. // Check for the score if the AI wins in the upcoming move
  140. // This method helps AI to assign score for each way in the game while searching.
  141. private int checkGetScore() {
  142. // For Rows
  143. for (int i = 0; i < 3; i++) {
  144. if (this.arr[i][0] == moves.exp && this.arr[i][1] == moves.exp && this.arr[i][2] == moves.exp) {
  145. return 10;
  146. } else if (this.arr[i][0] == moves.oppExp && this.arr[i][1] == moves.oppExp && this.arr[i][2] == moves.oppExp) {
  147. return -10;
  148. }
  149. }
  150. // For Cols
  151. for (int i = 0; i < 3; i++) {
  152. if (this.arr[0][i] == moves.exp && this.arr[1][i] == moves.exp && this.arr[2][i] == moves.exp) {
  153. return 10;
  154. } else if (this.arr[0][i] == moves.oppExp && this.arr[1][i] == moves.oppExp && this.arr[2][i] == moves.oppExp) {
  155. return -10;
  156. }
  157. }
  158. // For Diagonals
  159. if (this.arr[0][0] == moves.exp && this.arr[1][1] == moves.exp && this.arr[2][2] == moves.exp) {
  160. return 10;
  161. } else if (this.arr[0][0] == moves.oppExp && this.arr[1][1] == moves.oppExp && this.arr[2][2] == moves.oppExp) {
  162. return -10;
  163. } else if (this.arr[0][2] == moves.exp && this.arr[1][1] == moves.exp && this.arr[2][0] == moves.exp) {
  164. return 10;
  165. } else if (this.arr[0][2] == moves.oppExp && this.arr[1][1] == moves.oppExp && this.arr[2][0] == moves.oppExp) {
  166. return -10;
  167. }
  168. return 0;
  169. }
  170. // End of Minimax Algoritm
  171. // Displays results of Win / Tie by checking Rows, Columns and Diagonals.
  172. public boolean displayResult() {
  173. int valR = checkRows();
  174. int valC = checkCols();
  175. int diag = checkDiag();
  176. if (diag == 1 || diag == 3 || valR == 3 || valC == 3) {
  177. System.out.println("X wins");
  178. return true;
  179. } else if (diag == 2 || diag == 4 || valR == 2 || valC == 2) {
  180. System.out.println("O wins");
  181. return true;
  182. } else if ((diag == 0 || valC == 1 || valR == 1) && checkForSpace()) {
  183. System.out.println("Draw");
  184. return true;
  185. }
  186. return false;
  187. }
  188. // Prints the TicTacToe field
  189. public void displayField () {
  190. System.out.println("---------");
  191. for (char[] a : this.arr) {
  192. System.out.print("| ");
  193. for (char b : a) {
  194. System.out.print(b + " ");
  195. }
  196. System.out.println("|");
  197. }
  198. System.out.println("---------");
  199. }
  200. // Checks for the availability of space
  201. // in the TicTacToe field.
  202. private boolean checkForSpace() {
  203. for (int i = 0; i < 3; i++) {
  204. for (int j = 0; j < 3; j++) {
  205. if (this.arr[i][j] == ' ') {
  206. return false;
  207. }
  208. }
  209. }
  210. return true;
  211. }
  212. // Checks the Diagonals of the field
  213. private int checkDiag() {
  214. if (this.arr[0][0] == 'X' && this.arr[1][1] == 'X' && this.arr[2][2] == 'X') {
  215. return 1;
  216. } else if (this.arr[0][0] == 'O' && this.arr[1][1] == 'O' && this.arr[2][2] == 'O') {
  217. return 2;
  218. } else if (this.arr[0][2] == 'X' && this.arr[1][1] == 'X' && this.arr[2][0] == 'X') {
  219. return 3;
  220. } else if (this.arr[0][2] == 'O' && this.arr[1][1] == 'O' && this.arr[2][0] == 'O') {
  221. return 4;
  222. } else {
  223. return 0;
  224. }
  225. }
  226. // Checks the Rows of the field
  227. private int checkRows () {
  228. int cntX = 0,
  229. cntO = 0;
  230. for (int i = 0; i < 3; i++) {
  231. if (this.arr[i][0] == 'X' && this.arr[i][1] == 'X' && this.arr[i][2] == 'X') {
  232. cntX++;
  233. } else if (this.arr[i][0] == 'O' && this.arr[i][1] == 'O' && this.arr[i][2] == 'O') {
  234. cntO++;
  235. }
  236. }
  237. if (cntX == 1) {
  238. return 3;
  239. } else if (cntO == 1) {
  240. return 2;
  241. } else {
  242. return 1;
  243. }
  244. }
  245. // Checks the Columns of the field
  246. private int checkCols () {
  247. int cntX = 0,
  248. cntO = 0;
  249. for (int i = 0; i < 3; i++) {
  250. if (this.arr[0][i] == 'X' && this.arr[1][i] == 'X' && this.arr[2][i] == 'X') {
  251. cntX++;
  252. } else if (this.arr[0][i] == 'O' && this.arr[1][i] == 'O' && this.arr[2][i] == 'O') {
  253. cntO++;
  254. }
  255. }
  256. if (cntX == 1) {
  257. return 3;
  258. } else if (cntO == 1) {
  259. return 2;
  260. } else {
  261. return 1;
  262. }
  263. }
  264. }
  265. public class MinimaxAlgorithm {
  266. public static void main(String[] args) {
  267. Scanner sc = new Scanner(System.in);
  268. TicTacToe tic = new TicTacToe();
  269. // For game to start specify players
  270. // Eg: start user ai <-- user is X and user chance comes first.
  271. // Eg: start ai user <-- ai is X and ai chance comes first.
  272. // Eg: start ai ai <-- ai vs ai
  273. // Eg: user user <-- user vs user
  274. String[] players = tic.whosePlaying(sc);
  275. // Displays the empty field of TicTacToe
  276. tic.displayField();
  277. // turnX = true. X's turn
  278. // turnX = false. O's turn
  279. boolean turnX = true;
  280. while (true) {
  281. // Alternate player turns
  282. // First chance of X, than O.
  283. if (turnX) {
  284. switch (players[0]) {
  285. case "ai":
  286. tic.playComputer('X');
  287. break;
  288. default:
  289. tic.playUser(sc, 'X');
  290. break;
  291. }
  292. turnX = false;
  293. } else {
  294. switch (players[1]) {
  295. case "ai":
  296. tic.playComputer('O');
  297. break;
  298. default:
  299. tic.playUser(sc, 'O');
  300. break;
  301. }
  302. turnX = true;
  303. }
  304. // displayresult() - Checks if the game is over by anyone winning or tie.
  305. // If someone wins or ties the "check" becomes true and finishes the game.
  306. // Checks after each move made by the players.
  307. boolean check = tic.displayResult();
  308. if (check) break;
  309. }
  310. sc.close();
  311. }
  312. }

The blue arrow indicates where the mistake happened.
The green marking specifies how it should have been.
TicTacToeAI with Minimax Algorithm

CONSOLE:

  1. Input command: start user ai
  2. ---------
  3. | |
  4. | |
  5. | |
  6. ---------
  7. Enter the coordinates (According to Matrix, Space separated integers): 1 1
  8. ---------
  9. | |
  10. | X |
  11. | |
  12. ---------
  13. Making move level "AI"
  14. ---------
  15. | O |
  16. | X |
  17. | |
  18. ---------
  19. Enter the coordinates (According to Matrix, Space separated integers): 0 2
  20. ---------
  21. | O X |
  22. | X |
  23. | |
  24. ---------
  25. Making move level "AI"
  26. ---------
  27. | O O X | <-- Explanation: The AI made its move on [0, 1]
  28. | X | but it should have did on [2, 0]
  29. | | which will block my win on right diagonal.
  30. ---------
  31. Enter the coordinates (According to Matrix, Space separated integers): 2 0
  32. ---------
  33. | O O X |
  34. | X |
  35. | X |
  36. ---------
  37. X wins

答案1

得分: 2

在你的函数checkForSpace()和它在minimax()中的使用之间存在不匹配。如果还有空间,你返回false,如果在minimax()中得到false,你会停止搜索,就好像存在平局一样。你需要在checkForSpace()中反转布尔值,或者在minimax()中删除逻辑非。

你应该重新命名checkForSpace()和其他返回布尔值的函数。来自《可读代码的艺术》(Dustin Boswell和Trevor Foucher)的建议:

当选择布尔变量的名称或返回布尔值的函数的名称时,确保真正理解了truefalse的含义。

这里有一个危险的例子:

  1. bool read_password = true;

根据你如何阅读它(没有双关意思),有两种非常不同的解释:

  • 我们需要读取密码

  • 密码已经被读取

在这种情况下,最好避免使用单词“read”,而是将其命名为need_passworduser_is_authenticated等。

一般来说,添加像ishascanshould这样的词可以使布尔值更加清晰。

例如,名为SpaceLeft()的函数听起来可能会返回一个数字。如果它的目的是返回一个布尔值,一个更好的名称将是HasSpaceLeft()

最后,最好避免在名称中使用否定性术语。例如,不要使用:

  1. bool disable_ssl = false;

阅读起来会更容易(并且更紧凑),而应该使用:

  1. bool use_ssl = true;
英文:

There a mismatch between your function checkForSpace() and its use in minimax(). If there is space left you return false and if you get a false in minimax() you stop the search as if there is a tie. You need to invert the boolean in checkForSpace() or remove the logical not in minimax().

You should propably rename checkForSpace() and other function that return a boolean. From The Art of Readable Code (Dustin Boswell and Trevor Foucher):
> When picking a name for a boolean variable or a function that returns a boolean, be sure it’s clear what true and false really mean.
>
> Here’s a dangerous example:
>
> bool read_password = true;
>
> Depending on how you read it (no pun intended), there are two very different interpretations:
>
> - We need to read the password
>
> - The password has already been read
>
> In this case, it’s best to avoid the word "read," and name it need_password or user_is_authenticated instead.
>
> In general, adding words like is, has, can, or should can make booleans more clear.
>
> For example, a function named SpaceLeft() sounds like it might return a number. If it were meant to return a boolean, a better name would be HasSpaceLeft().
>
> Finally, it’s best to avoid negated terms in a name. For example, instead of:
>
> bool disable_ssl = false;
>
> it would be easier to read (and more compact) to say:
>
> bool use_ssl = true;

huangapple
  • 本文由 发表于 2020年8月10日 14:40:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/63335279.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定