#java #al&orithm #minimax
#java #алгоритм #минимаксный
Вопрос:
Я реализовал минимаксный алгоритм для игры в крестики-нолики от GeeksforGeeks. Я знаю, как работает минимаксный алгоритм, но мой приведенный здесь код не работает в соответствии с ним. Я проверил, что я могу делать неправильно, а также отладил его. Но, похоже, я не могу его найти.
Пожалуйста, ознакомьтесь с алгоритмом, было бы очень благодарно за дополнительный набор глаз и за то, чтобы найти неправильную часть, которую я, похоже, не могу найти.
Я прокомментировал каждую часть кода, которая полезна с минимаксным алгоритмом. Я думаю, было бы легко наверстать упущенное.
Пожалуйста, помогите мне здесь.
Спасибо.
import java.util.Arrays;
import java.util.Scanner;
class Move {
// row index
protected int row;
// column index
protected int col;
// exp is 'Our Expression'.
protected char exp;
// oppExp is 'Opponent's Expression'
protected char oppExp;
}
class TicTacToe {
private final char[][] arr = new char[3][3];
// Move - to keep track of rowIndex and columnIndex
// from the minimax al&orithm. And to keep track of
// 'X'/'O', who is our opponent for which the al&orithm
// should find moves for.
private final Move moves = new Move();
TicTacToe() {
// Initialisin& field
for (int i = 0; i < 3; i ) {
Arrays.fill(this.arr[i], ' ');
}
}
public Strin&[] whosePlayin&(Scanner sc) {
while (true) {
System.out.print("Input command: ");
// Gettin& input for players vs players
Strin& str = sc.nextLine();
if (str.startsWith("start")) {
Strin&[] players = str.replaceAll("start", "").trim().split(" ");
if (players.len&th == 2) {
if ((players[0].equals("user") || players[0].equals("ai")) amp;amp;
(players[1].equals("user") || players[1].equals("ai"))) {
return players;
}
}
}
System.out.println("Bad parameters!");
}
}
public void playUser (Scanner sc, char exp) { // exp is expression('X' / 'O') for user
// x is RowIndex
// y is ColumnIndex
// To &et from the user
int x, y;
System.out.print("Enter the coordinates (Accordin& to Matrix, Space separated inte&ers): ");
while (true) {
// try - catch for input that mi&ht not be number
try {
sc = new Scanner(System.in);
x = sc.nextInt();
y = sc.nextInt();
break;
} catch (Exception e) {
System.out.println("You should enter numbers!");
playUser(sc, exp); // Ask a&ain for coordinates
}
}
if (x &&t; 2 || y &&t; 2 || x < 0 || y < 0) {
System.out.println("Coordinates should be from 0 to 2!");
playUser(sc, exp); // Ask a&ain for coordinates
} else {
// Check for availability.
if (this.arr[x][y] == ' ') {
this.arr[x][y] = exp;
displayField(); // Displayin& TicTacToe field after user's turn.
} else {
System.out.println("This cell is occupied! Choose another one!");
playUser(sc, exp); // Ask a&ain for coordinates
}
}
}
public void playComputer (char exp) {
System.out.println("Makin& move level "AI"");
// Settin& our expresssion that is X / O.
moves.exp = exp;
// Findin& opponents expresssion that is X / O.
if (moves.exp == 'X') moves.oppExp = 'O';
else moves.oppExp = 'X';
// Searchin& for the best move.
searchBestMove();
// Settin& the best coordinates from the minimax al&orithm
// into the field with our expresssion.
this.arr[moves.row][moves.col] = moves.exp;
displayField(); // Displayin& TicTacToe field after AI's turn.
}
// Start of Minimax Al&orithm - Contains all methods needed for the al&orithm
private void searchBestMove() {
int bestVal = Inte&er.MIN_VALUE;
moves.row = -1;
moves.col = -1;
for (int i = 0; i < 3; i ) {
for (int j = 0; j < 3; j ) {
if (this.arr[i][j] == ' ') {
this.arr[i][j] = moves.exp;
int moveVal = minimax(0, false);
this.arr[i][j] = ' ';
if (moveVal &&t; bestVal) {
moves.row = i;
moves.col = j;
bestVal = moveVal;
}
}
}
}
}
private int minimax(int depth, boolean isMax) {
int score = checkGetScore();
if (score == 10 || score == -10) return score;
if (!checkForSpace()) return 0;
int best;
if (isMax) {
best = Inte&er.MIN_VALUE;
for (int i = 0; i < 3; i ) {
for (int j = 0; j < 3; j ) {
if (this.arr[i][j] == ' ') {
this.arr[i][j] = moves.exp;
best = Math.max(best, minimax(depth 1, false));
this.arr[i][j] = ' ';
}
}
}
} else {
best = Inte&er.MAX_VALUE;
for (int i = 0; i < 3; i ) {
for (int j = 0; j < 3; j ) {
if (this.arr[i][j] == ' ') {
this.arr[i][j] = moves.oppExp;
best = Math.min(best, minimax(depth 1, true));
this.arr[i][j] = ' ';
}
}
}
}
return best;
}
// Check for the score if the AI wins in the upcomin& move
// This method helps AI to assi&n score for each way in the &ame while searchin&.
private int checkGetScore() {
// For Rows
for (int i = 0; i < 3; i ) {
if (this.arr[i][0] == moves.exp amp;amp; this.arr[i][1] == moves.exp amp;amp; this.arr[i][2] == moves.exp) {
return 10;
} else if (this.arr[i][0] == moves.oppExp amp;amp; this.arr[i][1] == moves.oppExp amp;amp; this.arr[i][2] == moves.oppExp) {
return -10;
}
}
// For Cols
for (int i = 0; i < 3; i ) {
if (this.arr[0][i] == moves.exp amp;amp; this.arr[1][i] == moves.exp amp;amp; this.arr[2][i] == moves.exp) {
return 10;
} else if (this.arr[0][i] == moves.oppExp amp;amp; this.arr[1][i] == moves.oppExp amp;amp; this.arr[2][i] == moves.oppExp) {
return -10;
}
}
// For Dia&onals
if (this.arr[0][0] == moves.exp amp;amp; this.arr[1][1] == moves.exp amp;amp; this.arr[2][2] == moves.exp) {
return 10;
} else if (this.arr[0][0] == moves.oppExp amp;amp; this.arr[1][1] == moves.oppExp amp;amp; this.arr[2][2] == moves.oppExp) {
return -10;
} else if (this.arr[0][2] == moves.exp amp;amp; this.arr[1][1] == moves.exp amp;amp; this.arr[2][0] == moves.exp) {
return 10;
} else if (this.arr[0][2] == moves.oppExp amp;amp; this.arr[1][1] == moves.oppExp amp;amp; this.arr[2][0] == moves.oppExp) {
return -10;
}
return 0;
}
// End of Minimax Al&oritm
// Displays results of Win / Tie by checkin& Rows, Columns and Dia&onals.
public boolean displayResult() {
int valR = checkRows();
int valC = checkCols();
int dia& = checkDia&();
if (dia& == 1 || dia& == 3 || valR == 3 || valC == 3) {
System.out.println("X wins");
return true;
} else if (dia& == 2 || dia& == 4 || valR == 2 || valC == 2) {
System.out.println("O wins");
return true;
} else if ((dia& == 0 || valC == 1 || valR == 1) amp;amp; checkForSpace()) {
System.out.println("Draw");
return true;
}
return false;
}
// Prints the TicTacToe field
public void displayField () {
System.out.println("---------");
for (char[] a : this.arr) {
System.out.print("| ");
for (char b : a) {
System.out.print(b " ");
}
System.out.println("|");
}
System.out.println("---------");
}
// Checks for the availability of space
// in the TicTacToe field.
private boolean checkForSpace() {
for (int i = 0; i < 3; i ) {
for (int j = 0; j < 3; j ) {
if (this.arr[i][j] == ' ') {
return false;
}
}
}
return true;
}
// Checks the Dia&onals of the field
private int checkDia&() {
if (this.arr[0][0] == 'X' amp;amp; this.arr[1][1] == 'X' amp;amp; this.arr[2][2] == 'X') {
return 1;
} else if (this.arr[0][0] == 'O' amp;amp; this.arr[1][1] == 'O' amp;amp; this.arr[2][2] == 'O') {
return 2;
} else if (this.arr[0][2] == 'X' amp;amp; this.arr[1][1] == 'X' amp;amp; this.arr[2][0] == 'X') {
return 3;
} else if (this.arr[0][2] == 'O' amp;amp; this.arr[1][1] == 'O' amp;amp; this.arr[2][0] == 'O') {
return 4;
} else {
return 0;
}
}
// Checks the Rows of the field
private int checkRows () {
int cntX = 0,
cntO = 0;
for (int i = 0; i < 3; i ) {
if (this.arr[i][0] == 'X' amp;amp; this.arr[i][1] == 'X' amp;amp; this.arr[i][2] == 'X') {
cntX ;
} else if (this.arr[i][0] == 'O' amp;amp; this.arr[i][1] == 'O' amp;amp; this.arr[i][2] == 'O') {
cntO ;
}
}
if (cntX == 1) {
return 3;
} else if (cntO == 1) {
return 2;
} else {
return 1;
}
}
// Checks the Columns of the field
private int checkCols () {
int cntX = 0,
cntO = 0;
for (int i = 0; i < 3; i ) {
if (this.arr[0][i] == 'X' amp;amp; this.arr[1][i] == 'X' amp;amp; this.arr[2][i] == 'X') {
cntX ;
} else if (this.arr[0][i] == 'O' amp;amp; this.arr[1][i] == 'O' amp;amp; this.arr[2][i] == 'O') {
cntO ;
}
}
if (cntX == 1) {
return 3;
} else if (cntO == 1) {
return 2;
} else {
return 1;
}
}
}
public class MinimaxAl&orithm {
public static void main(Strin&[] ar&s) {
Scanner sc = new Scanner(System.in);
TicTacToe tic = new TicTacToe();
// For &ame to start specify players
// E&: start user ai <-- user is X and user chance comes first.
// E&: start ai user <-- ai is X and ai chance comes first.
// E&: start ai ai <-- ai vs ai
// E&: user user <-- user vs user
Strin&[] players = tic.whosePlayin&(sc);
// Displays the empty field of TicTacToe
tic.displayField();
// turnX = true. X's turn
// turnX = false. O's turn
boolean turnX = true;
while (true) {
// Alternate player turns
// First chance of X, than O.
if (turnX) {
switch (players[0]) {
case "ai":
tic.playComputer('X');
break;
default:
tic.playUser(sc, 'X');
break;
}
turnX = false;
} else {
switch (players[1]) {
case "ai":
tic.playComputer('O');
break;
default:
tic.playUser(sc, 'O');
break;
}
turnX = true;
}
// displayresult() - Checks if the &ame is over by anyone winnin& or tie.
// If someone wins or ties the "check" becomes true and finishes the &ame.
// Checks after each move made by the players.
boolean check = tic.displayResult();
if (check) break;
}
sc.close();
}
}
Синяя стрелка указывает, где произошла ошибка.
Зеленая маркировка указывает, как это должно было быть.
КОНСОЛЬ:
Input command: start user ai
---------
| |
| |
| |
---------
Enter the coordinates (Accordin& to Matrix, Space separated inte&ers): 1 1
---------
| |
| X |
| |
---------
Makin& move level "AI"
---------
| O |
| X |
| |
---------
Enter the coordinates (Accordin& to Matrix, Space separated inte&ers): 0 2
---------
| O X |
| X |
| |
---------
Makin& move level "AI"
---------
| O O X | <-- Explanation: The AI made its move on [0, 1]
| X | but it should have did on [2, 0]
| | which will block my win on ri&ht dia&onal.
---------
Enter the coordinates (Accordin& to Matrix, Space separated inte&ers): 2 0
---------
| O O X |
| X |
| X |
---------
X wins
Комментарии:
1. Я думаю, что «не работает» — это слишком расплывчато. Почему это не работает? Что предполагается сделать вместо этого? Как вы его «отладили»? Что вы устранили? Прямо сейчас вы вставили 354 строки кода и комментариев и вроде как сказали «Найти / исправить ошибку»
2. ДА. Прошу прощения за такой расплывчатый вопрос. Я отредактирую вопрос.
3. Я думаю, вам нужно будет добавить ввод / вывод и фактические и ожидаемые результаты.
4. Не связано, но параметр глубины не используется в функции minimax(). Возможно, вам захочется вернуть значение score — depth (или score depth в случае минимизации (-10)), чтобы ваш ИИ старался побеждать как можно быстрее, а не просто побеждать.
5. Сыворотка, да, в вашем случае оценка не должна менять знак, поэтому при максимальной глубине 9 (9 возможных позиций в крестиках-ноликах) от 10 до 1 для максимизатора и от -10 до -1 для минимизатора.
Ответ №1:
Существует несоответствие между вашей функцией checkForSpace()
и ее использованием в minimax()
. Если осталось свободное место, вы возвращаетесь false
, и если вы получаете false
in minimax()
, вы прекращаете поиск, как если бы была ничья. Вам нужно инвертировать логическое значение в checkForSpace()
или удалить логическое значение не в minimax()
.
Вам следует соответствующим образом переименовать checkForSpace()
и другую функцию, возвращающую логическое значение. От The Art of Readable Code (Дастин Босуэлл и Тревор Фуше):
Выбирая имя для логической переменной или функции, которая возвращает логическое значение, убедитесь, что оно ясно, что
true
иfalse
на самом деле означают.Вот опасный пример:
bool read_password = true;
В зависимости от того, как вы это читаете (без каламбура), существуют две очень разные интерпретации:
Нам нужно прочитать пароль
Пароль уже был прочитан
В этом случае лучше избегать слова «читать» и называть его вместо него
need_password
илиuser_is_authenticated
.В общем, добавление таких слов, как
is
,has
,can
илиshould
, может сделать логические значения более понятными.Например, функция с именем
SpaceLeft()
звучит так, как будто она может возвращать число. Если бы это предназначалось для возврата логического значения, лучшим именем было быHasSpaceLeft()
.Наконец, лучше избегать отрицаемых терминов в имени. Например, вместо:
bool disable_ssl = false;
было бы проще для чтения (и компактнее) сказать:
bool use_ssl = true;
Комментарии:
1. Большое тебе спасибо @mrBen . Будь проклята ошибка, которую я совершил.
2. Единственное, что нужно добавить сюда: ваш пример кода не соответствует соглашениям об именовании java. Java использует camelCase, так что это
useSsl
, неuse_ssl
.3. Что ж, это хороший пример того, почему важно правильное именование!
4. GhostCat по какой-то причине я был уверен, что исходный код был C #… В любом случае, это хороший момент, я просто копирую содержимое из книги, но нужно следовать стилю кодирования языка или проекта, над которым он работает.