UPDATE3: 我修好了。最后这是一些小错误(参见Update 1和Update2),最后一个错误非常重要:
public static List<int[]> generateMoves() {
List<int[]> possibleMoves = new ArrayList<int[]>();
if (notWon() == false) {
return possibleMoves;
}
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 3; ++col) {
if (gameBoard[row][col] == 0) {
possibleMoves.add(new int[]{row, col});
}
}
}
return possibleMoves;
}
我在这里犯了一个大错误,在行和col从0迭代到2之前,这意味着计算机实际上只有2x2字段可以使用。这解释了他的不良位置。 现在算法工作得很好,我是一个快乐的雪花。
UPDATE2: 我已经改变了条件运算符: 的从
int maxValue = (player == computerTurn) ? Integer.MAX_VALUE : Integer.MIN_VALUE;
以
int maxValue = (player == computerTurn) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
现在计算机确实进行了几次单独转弯(更改了行和列值),但它没有正确找到获胜的游戏状态(例如:)
| |
| |
| |
1evaluation值 1row 1col
| |
| X|
| |
玩家O,请输入一行(1-3):3 玩家O,请输入一栏(1-3):3
| |
| X|
| | O
19evaluation值 0row 0col
X| |
| X|
| | O
玩家O,请输入一行(1-3):3 玩家O,请输入一栏(1-3):2
X| |
| X|
| O| O
10evaluation值 1row 0col
X| |
X| X|
| O| O
玩家O,请输入一行(1-3):2 玩家O,请输入一栏(1-3):3
X| |
X| X| O
| O| O
1evaluation值 0row 1col
X| X|
X| X| O
| O| O
玩家O,请输入一行(1-3):1 玩家O,请输入一栏(1-3):3
X| X| O
X| X| O
| O| O
玩家赢了! 玩家赢了!
----游戏结束了---
处理完成,退出代码为0
更新 好的,我已经在computerInput方法中进行了三次打印输出。
System.out.println(bestMove[0] + "evaluation-value");
System.out.println(bestMove[1]+ "row");
System.out.println(bestMove[2] + "col");
这是奇怪的事情: 首次调用该函数时,它将通过所有minimax方法运行并评估最佳值。但是,它不会更改使用row = 1,col = 1初始化的行和列(col)值。如果我将它们更改为-1,则-1得到“IndexOutOfBound Exception”。这进一步证明了这些值在某种程度上没有使用minimax方法中的适当值。它们旨在在minimax方法的else语句期间重新赋值。有趣的是,控制台总是打印出评估的maxValue和row = 1,col = 1,这意味着计算机可以重复地将他的符号设置到同一个位置。这意味着计算机每转一圈都是相同的(1,1)。
我目前正在开发一个项目,该项目实现了一个带有继承AI(使用minimax)的“TicTacToe”游戏。我目前在寻找语义错误方面遇到了问题,不知怎的,计算机只进行了我的第一次转弯,我将其定义为bestRow = 1,bestCol = 1。似乎算法(miniMax),他选择了他最好的评估位置是不能正常工作的。
我的算法使用类型为int的二维数组(gameBoard)。 computerInput = 1,playerInput = -1,空表示0.我有第二个数组,我称之为consoleGameBoard,此板来自Char类型,使用X转换为gameBoard的数字条目为1,O为-1和空白( '')为0。
这是我的架构:
(0,0)(0,1)(0,2)
(1,0)(1,1)(1,2)
(2,0)(2,1)(2,2)
我真的希望有人可以帮助我... 我认为语义失误是在minimax-Method中的某个地方,因为bestRow和bestCol似乎没有更新,这意味着它们保持初始分配值(1,1)并且计算机转向基本上会重复整个时间。
我使用本教程作为miniMax算法实现的指南: https://www.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe_AI.html
Board.class和Console.class工作正常,我可以与两个人类玩家一起玩游戏而没有任何问题。语义失败在MiniMax.class中。
我的代码:
public class MiniMax extends Board {
static int searchDepth = 4;
static int computerTurn = 1;
static int playerTurn = -1;
public static int[] miniMax(int player, int depth) {
List<int[]> possibleMoves = generateMoves();
/////////////// Condition //// if condition = true /// if false = false
int maxValue = (player == computerTurn) ? Integer.MIN_VALUE : Integer.MAX_VALUE; // // conditional operator
int bestRow = 1;
int bestCol = 1;
int currentValue;
if (depth == 0 || possibleMoves.isEmpty()) {
maxValue = evaluate();
} else {
for (int[] tryMove : possibleMoves) { // for-each
gameBoard[tryMove[0]][tryMove[1]] = player;
if (player == computerTurn) {
currentValue = miniMax(playerTurn, depth - 1)[0];
if (currentValue > maxValue) {
maxValue = currentValue;
bestRow = tryMove[0];
bestCol = tryMove[1];
}
} else {
currentValue = miniMax(computerTurn, depth - 1)[0];
if (currentValue < maxValue) {
maxValue = currentValue;
bestRow = tryMove[0];
bestCol = tryMove[1];
}
}
undoMove(tryMove);
}
}
return new int[]{maxValue, bestRow, bestCol};
}
/**
* This method evaluates the expected value for each of the 8 possible connections.
* 3 rows, 3 columns and 2 diagonals. (See checkForWin for further clarification).
* It is called by the MiniMax Algorithm in order to find the best move.
*
* @return score, a product of the inherit method call.
* It connects all -1 or 1 entries and sums them up.
* <p>
* Possible (sum-)Values for "score":
* +100(3x), +10(2x), +1(1x) for Computer-Connections.
* -100(3x), -10(2x), -1 (1x) for Player-Connections.
* 0 for EMPTY-Spots.
* <p>
* Schema:
* (0,0) | (0,1) | (0,2)
* (1,0) | (1,1) | (1,2)
* (2,0) | (2,1) | (2,2)
*/
private static int evaluate() {
int score = 0;
score = score + connection(0, 0, 0, 1, 0, 2); // rowOne
score = score + connection(1, 0, 1, 1, 1, 2); // rowTwo
score = score + connection(2, 0, 2, 1, 2, 2); // rowThree
score = score + connection(0, 0, 1, 0, 2, 0); // colOne
score = score + connection(0, 1, 1, 1, 2, 1); // colTwo
score = score + connection(0, 2, 1, 2, 2, 2); // colThree
score = score + connection(0, 0, 1, 1, 2, 2); // diagonal 1
score = score + connection(0, 2, 1, 1, 2, 0); // diagonal 2
return score;
}
/**
* This method shall only be called by the evaluate() Method!
* It reviews all possible Spots by applying all the possible combinations from the evaluate() method.
* This Method connects each entry and checks is the currently active player can increase his score.
*
* @param rowOne
* @param colOne
* @param rowTwo
* @param colTwo
* @param rowThree
* @param colThree
* @return Score, a value that determines the most effective move.
*/
private static int connection(int rowOne, int colOne, int rowTwo, int colTwo, int rowThree, int colThree) {
int score = 0;
// -1 = computerInput
// 1 = userInput
// First Spot
if (gameBoard[rowOne][colOne] == computerTurn) { // First Spot = computerInput
score = 1;
} else if (gameBoard[rowOne][colOne] == playerTurn) { // First Spot = playerInput
score = -1;
}
// Second Spot
if (gameBoard[rowTwo][colTwo] == computerTurn) { // Second Spot = computerInput
if (score == 1) { // TwoConnected for computerInput
score = 10;
} else if (score == -1) {
return 0;
} else { // Spot is EMPTY
score = 1;
}
} else if (gameBoard[rowTwo][colTwo] == playerTurn) { // Second Spot = playerInput
if (score == -1) { // TwoConnected for playerInput
score = -10;
} else if (score == 1) {
return 0;
} else { // Spot is EMPTY
score = -1;
}
}
// Third Spot
if (gameBoard[rowThree][colThree] == computerTurn) { // Third Spot = computerInput
if (score > 0) { // First Spot and/or Second Spot connected for computerInput
score = score * 10;
} else if (score < 0) {
return 0;
} else { // First Spot and Second Spot are EMPTY
score = 1;
}
} else if (gameBoard[rowThree][colThree] == playerTurn) { // Third Spot = playerInput
if (score < 0) { // First Spot and/or Second Spot connected for playerInput
score = score * 10;
} else if (score > 1) {
return 0;
} else { // First Spot and Second Spot are EMPTY
score = -1;
}
}
return score;
}
/**
* Removes the last entry on the gameBoard by changing its value to zero (0) which means EMPTY.
*
* @param tryMove
*/
public static void undoMove(int[] tryMove) {
gameBoard[tryMove[0]][tryMove[1]] = 0;
}
/**
* @return possibleMoves, a ArrayList which is discovering the currently possible gameBoard situations.
*/
public static List<int[]> generateMoves() {
List<int[]> possibleMoves = new ArrayList<int[]>();
if (notWon() == false) {
return possibleMoves;
}
for (int row = 0; row < 2; ++row) {
for (int col = 0; col < 2; ++col) {
if (gameBoard[row][col] == 0) {
possibleMoves.add(new int[]{row, col});
}
}
}
return possibleMoves;
}
/**
* This method calls minimax and returns the best move possible.
*
* @return bestMove, the result of the minimax algorithm (maxValue).
*/
public static int[] computerInput() {
int[] bestMove = miniMax(computerTurn, searchDepth); // player, depth
updateBoard(computerTurn, bestMove[1], bestMove[2]);
convertToChar(bestMove[1], bestMove[2]);
return new int[]{bestMove[1], bestMove[2]}; // 0 = maxValue, 1 = row, 2 = col.
}
}
public class Board {
public static int[][] gameBoard;
private static char[][] consoleGameBoard;
private static boolean continueGame = true;
/**
* This is the constructor for the Board-Class
*/
public Board() {
gameBoard = new int[3][3];
consoleGameBoard = new char[3][3];
for (int row = 0; row < gameBoard.length; row++) {
Arrays.fill(gameBoard[row], 0);
}
} // end of constructor
public void displayBoard() {
for (int row = 0; row < consoleGameBoard.length; row++) {
for (int col = 0; col < consoleGameBoard.length; col++) {
System.out.print("\t" + consoleGameBoard[row][col]);
if (col == 0 || col == 1)
System.out.print("|");
}
System.out.print("\n_________________\n");
}
}
public static boolean updateBoard(int player, int row, int col) {
if (row >= 0 && row <= 2 && col >= 0 && col <= 2) {
if (gameBoard[row][col] != 0) {
return false;
} else {
gameBoard[row][col] = player;
return true;
}
} else {
return false;
}
}
public void userInput(int player) {
int row, col;
Scanner keyboard = new Scanner(System.in);
do {
System.out.printf("Player %s, please enter a row (1-3):", 'O');
row = keyboard.nextInt();
System.out.printf("Player %s, please enter a column (1-3):", 'O');
col = keyboard.nextInt();
} while (notValid(row, col));
updateBoard(player, row - 1, col - 1);
convertToChar(row - 1, col - 1);
}
public static boolean notValid(int row, int col) {
if (row > 3 || row < 1 || col > 3 || col < 1 || !isBlank(row, col)) {
return true;
} else {
return false;
}
}
public static boolean notFull() {
if (gameBoard[0][0] != 0 && gameBoard[0][1] != 0 && gameBoard[0][2] != 0 && gameBoard[1][0] != 0 && gameBoard[1][1] != 0 && gameBoard[1][2] != 0 &&
gameBoard[2][0] != 0 && gameBoard[2][1] != 0 && gameBoard[2][2] != 0) {
return false;
} else {
return true;
}
}
public static boolean isBlank(int row, int col) {
if (gameBoard[row - 1][col - 1] == 0) {
return true;
} else {
System.out.println(" -- INVALID! The Position is already taken! --");
System.out.println(" ------------- Make another Move -------------");
return false;
}
}
public static boolean notWon() {
// loop over each row and check for winner
for (int row = 0; row < gameBoard.length; row++) {
if (gameBoard[row][0] + gameBoard[row][1] + gameBoard[row][2] == 3 ||gameBoard[row][0] + gameBoard[row][1] + gameBoard[row][2] == -3) {
System.out.println("Player " + consoleGameBoard[row][0] + " has won!");
return continueGame = false;
}
}
// loop over each column and check for winner
for (int col = 0; col < gameBoard[0].length; col++) {
if (gameBoard[0][col] + gameBoard[1][col] + gameBoard[2][col] == 3 || gameBoard[0][col] + gameBoard[1][col] + gameBoard[2][col] == -3) {
System.out.println("Player " + consoleGameBoard[col][0] + " has won!");
return continueGame = false;
}
}
// check diagonal 1
if (gameBoard[0][0] + gameBoard[1][1] + gameBoard[2][2] == 3 || gameBoard[0][0] + gameBoard[1][1] + gameBoard[2][2] == -3) {
System.out.println("Player " + consoleGameBoard[0][0] + " has won!");
return continueGame = false;
}
// check for diagonal 2
if (gameBoard[2][0] + gameBoard[1][1] + gameBoard[0][2] == 3 ||gameBoard[2][0] + gameBoard[1][1] + gameBoard[0][2] == -3) {
System.out.println("Player " + consoleGameBoard[2][0] + " has won!");
return continueGame = false;
} else {
return continueGame = true;
}
}
public static void convertToChar(int row, int col) {
char X = 'X';
char O = 'O';
if (gameBoard[row][col] == 1) {
consoleGameBoard[row][col] = X;
} else {
consoleGameBoard[row][col] = O;
}
}
}
public class Console {
public static void main(String[] args) {
structurePack.Board myGame = new structurePack.Board();
int playerSwitch = 2;
myGame.displayBoard();
while (myGame.notWon() == true) {
if (playerSwitch % 2 == 0)
computePack.MiniMax.computerInput();
else {
myGame.userInput(-1);
}
playerSwitch++;
System.out.println("\n");
myGame.displayBoard();
myGame.notWon();
if (myGame.notWon() == false) {
System.out.println("\n");
System.out.println(" ---- The Game is OVER --- ");
break;
}
myGame.notFull();
if (myGame.notFull() == false) {
System.out.println("\n");
System.out.println(" --- This Game is a DRAW --- ");
break;
}
}
}
}