【发布时间】:2015-11-03 06:54:28
【问题描述】:
所以在过去的 3 周里,我一直致力于这个项目。我设法让 minimax 函数在早期用于 3x3 板,但是当我尝试将它用于 4x4 板时开始出现问题,即 Java 堆空间错误。从那时起,在 Alpha beta pruning 的帮助下,我设法从 aprox 中减少了 minimax 函数中所需的 minimax 调用的数量。 59000 到 16000 到 11000,然后最后到 8000 次调用(这是假设一个插槽已经被填满的板的初始最小最大调用)。然而,现在的问题是该方法只能在 4x4 游戏中继续运行。它只是不停地自称,没有错误,没有结果,什么都没有。从理论上讲,按照我的看法,我的功能应该适用于任意板尺寸,唯一的问题是内存。现在,由于我已经大大减少了函数的内存贪婪,我希望它能够工作。嗯,它适用于 3x3。但是,它不适用于 4x4。 简要说明该函数的作用: 该函数返回一个大小为 2 的数组,其中包含所有可能的下一步移动中最有利的下一步移动以及预期从该移动中获得的分数。评分系统很简单。 +10 表示 O 胜,-10 表示 X 胜,0 表示平局。该函数当然是递归的。在其中,您会发现某些快捷方式可以减少对其自身的所需调用次数。例如,如果轮到 X 并且返回的分数是 -10(这是 X 的最佳分数),则退出循环,即停止从该状态观察其他潜在的移动。这是 State 类的代码:
private String [] state; //Actual content of the board
private String turn; //Whose turn it is
private static int n; //Size of the board
public State(int n) {
state = new String[n*n];
for(int i = 0; i < state.length; i++) {
state[i] = "-";
}
State.n = n;
}
public int[] newminimax47(int z) {
int bestScore = (turn == "O") ? +9 : -9; //X is minimizer, O is maximizer
int bestPos = -1;
int currentScore;
int lastAdded = z;
if(isGameOver() != "Not Gameover") {
bestScore= score();
}
else {
int i = 0;
for(int x:getAvailableMoves()) {
if(turn == "X") { //X is minimizer
setX(x);
currentScore = newminimax47(x)[0];
if(i == 0) {
bestScore = currentScore;
bestPos = x;
if(bestScore == -10)
break;
}
else if(currentScore < bestScore) {
bestScore = currentScore;
bestPos = x;
if(bestScore == -10)
break;
}
}
else if(turn == "O") { //O is maximizer
setO(x);
currentScore = newminimax47(x)[0];
if(i == 0) {
bestScore = currentScore;
bestPos = x;
if(bestScore == 10)
break;
}
else if(currentScore > bestScore) {
bestScore = currentScore;
bestPos = x;
if(bestScore == 10)
break;
}
}
i++;
}
}
revert(lastAdded);
return new int [] {bestScore, bestPos};
}
newminimax47() 使用的补函数:
isGameOver():
public String isGameOver() {
if(n == 3) {
//Rows 1 to 3
if((state[0] != "-") && (state[0] == state[1]) && (state[1] == state[2]))
return (state[0] == "X") ? "X Won" : "O Won";
else if((state[3] != "-") && (state[3] == state[4]) && (state[4] == state[5]))
return (state[3] == "X") ? "X Won" : "O Won";
else if((state[6] != "-") && (state[6] == state[7]) && (state[7] == state[8]))
return (state[6] == "X") ? "X Won" : "O Won";
//Columns 1 to 3
else if((state[0] != "-")&&(state[0] == state[3]) && (state[3] == state[6]))
return (state[0] == "X") ? "X Won" : "O Won";
else if((state[1] != "-") && (state[1] == state[4]) && (state[4] == state[7]))
return (state[1] == "X") ? "X Won" : "O Won";
else if((state[2] != "-") && (state[2] == state[5]) && (state[5] == state[8]))
return (state[2] == "X") ? "X Won" : "O Won";
//Diagonals
else if((state[0] != "-") && (state[0]==state[4]) && (state[4] == state[8]))
return (state[0] == "X") ? "X Won" : "O Won";
else if((state[6] != "-") && (state[6] == state[4]) && (state[4] == state[2]))
return (state[6] == "X") ? "X Won" : "O Won";
//Checking if draw
else if((state[0] != "-") && (state[1]!="-") && (state[2] != "-") && (state[3]!="-") &&
(state[4] != "-") && (state[5] != "-") && (state[6] != "-") && (state[7] != "-") &&
(state[8] != "-"))
return "Draw";
else
return "Not Gameover";
}
else {
//Rows 1 to 4
if((state[0] != "-") && (state[0] == state[1]) && (state[1] == state[2]) && (state[2] == state[3]))
return (state[0] == "X") ? "X Won" : "O Won";
else if((state[4] != "-") && (state[4] == state[5]) && (state[5]==state[6]) && (state[6] == state[7]))
return (state[4] == "X") ? "X Won" : "O Won";
else if((state[8] != "-") && (state[8] == state[9]) && (state[9]==state[10]) && (state[10] == state[11]))
return (state[8] == "X") ? "X Won" : "O Won";
else if((state[12] != "-") && (state[12] == state[13]) &&(state[13] == state[14]) && (state[14] == state[15]))
return (state[12] == "X") ? "X Won" : "O Won";
//Columns 1 to 4
else if((state[0] != "-") && (state[0] == state[4]) && (state[4] == state[8]) && (state[8] == state[12]))
return (state[0] == "X") ? "X Won" : "O Won";
else if((state[1] != "-") && (state[1] == state[5]) && (state[5] == state[9]) && (state[9] == state[13]))
return (state[1] == "X") ? "X Won" : "O Won";
else if((state[2] != "-") && (state[2] == state[6]) && (state[6] == state[10]) && (state[10] == state[14]))
return (state[2] == "X") ? "X Won" : "O Won";
else if((state[3] != "-") && (state[3] == state[7]) && (state[7] == state[11]) && (state[11] == state[15]))
return (state[3] == "X") ? "X Won" : "O Won";
//Diagonale
else if((state[0] != "-") && (state[0] == state[5]) && (state[5] == state[10]) && (state[10] == state[15]))
return (state[0] == "X") ? "X Won" : "O Won";
else if((state[12] != "-") && (state[12] == state[9]) && (state[9] == state[6]) && (state[6] == state[3]))
return (state[0] == "X") ? "X Won" : "O Won";
//Pruefe ob Gleichstand
else if((state[0] != "-") && (state[1] != "-") && (state[2] != "-") && (state[3]!="-") &&
(state[4] != "-") && (state[5] != "-") && (state[6] != "-") && (state[7] != "-") &&
(state[8] != "-") && (state[9] != "-") && (state[10] != "-") && (state[11] != "-") &&
(state[12] != "-") && (state[13] != "-") && (state[14] != "-") && (state[15] != "-"))
return "Draw";
else
return "Not Gameover";
}
}
请原谅 isGameOver() 方法的生硬,它只是检查棋盘的状态(即获胜、平局、游戏未结束)
getAvailableMoves() 方法:
public int[] getAvailableMoves() {
int count = 0;
int i = 0;
for(int j = 0; j < state.length; j++) {
if(state[j] == "-")
count++;
}
int [] availableSlots = new int[count];
for(int j = 0; j < state.length; j++){
if(state[j] == "-")
availableSlots[i++] = j;
}
return availableSlots;
}
此方法仅返回一个包含所有可用下一步移动的 int 数组(关于当前状态),如果没有可用移动或游戏结束,则返回空数组。
score() 方法:
public int score() {
if(isGameOver() == "X Won")
return -10;
else if(isGameOver() == "O Won")
return +10;
else
return 0;
}
setO()、setX() 和 revert():
public void setX(int i) {
state[i] = "X";
turn = "O";
lastAdded = i;
}
public void setO(int i) {
state[i] = "O";
turn = "X";
lastAdded = i;
}
public void revert(int i) {
state[i] = "-";
if(turn == "X")
turn = "O";
else
turn = "X";
}
对于 3x3 游戏,我的主要方法如下所示:
public static void main(String args[]) {
State s = new State(3);
int [] ScoreAndRecommendedMove = new int[2];
s.setX(8);
ScoreAndRecommendedMove = s.newminimax47(8);
System.out.println("Score: "+ScoreAndRecommendedMove[0]+" Position: "+ ScoreAndRecommendedMove[1]);
}
在这个游戏中,X 在第 8 个位置开始了游戏。这种情况下的方法将返回
Score: 0 Position: 4
这意味着 O 最有希望的举动是在位置 4,在最坏的情况下将产生 0 分(即平局)。
下图旨在说明 newminimax47() 的工作原理。在这种情况下,我们的起始状态(板)被赋予数字 1。注意:数字表示创建所考虑状态的优先级。 1 在 2 之前创建,2 在 3 之前创建,3 在 4 之前创建等等。
在这种情况下,最终返回状态1的分数和位置将是
Score: 0 Position: 6
来自状态 8。
注意:您看到的代码只是实际 State 类的 sn-ps。这些 sn-ps 本身应该允许您毫无问题地重新创建和使用 newminimax47 函数(至少对于 3x3)。您可能发现的任何错误都不是真正的错误,它们根本不包含在我在这里复制的 sn-ps 中,并且代码应该在没有它们的情况下工作。例如 setO 和 setX 函数中的 lastAdded 变量不包含在此处的 sn-ps 中,但我刚刚意识到您不需要它就可以使用 minimax 函数,因此您可以将其注释掉。
【问题讨论】:
-
将z参数赋予minmax方法的目的是什么?
-
你的 setX() 和 setO() 方法使用了一个变量 'lastAdded' 但它是未定义的...... newMinimax47() 中只有一个同名的局部变量
-
在 3x3 棋盘上,第一步有 9 种可能性,第二步有 8 种可能性,依此类推,总共有
9! = 362880步。对于 4x4 棋盘,总移动数为16! = 20922789888000。所以这个算法可能没什么问题,你只需要在一台非常非常快的计算机上运行它:) -
分数不只取决于你当前的棋盘状态吗?在这种情况下,您可以拥有 3^16 种不同的状态(“O”、“X”或空)。你可以使用记忆来加速递归。
标签: java algorithm loops tic-tac-toe minimax