C++ Negamax的实施没有';t似乎与tic-tac-toe一起工作
我已经实现了Negamax,包括alpha/beta修剪 然而,它似乎倾向于失败的举动,据我所知,这是一个无效的结果 游戏是Tic-Tac-Toe,我已经抽象了大部分游戏,所以应该很容易发现算法中的错误C++ Negamax的实施没有';t似乎与tic-tac-toe一起工作,c++,algorithm,tic-tac-toe,minimax,C++,Algorithm,Tic Tac Toe,Minimax,我已经实现了Negamax,包括alpha/beta修剪 然而,它似乎倾向于失败的举动,据我所知,这是一个无效的结果 游戏是Tic-Tac-Toe,我已经抽象了大部分游戏,所以应该很容易发现算法中的错误 #include <list> #include <climits> #include <iostream> //#define DEBUG 1 using namespace std; struct Move { int row, col;
#include <list>
#include <climits>
#include <iostream>
//#define DEBUG 1
using namespace std;
struct Move {
int row, col;
Move(int row, int col) : row(row), col(col) { }
Move(const Move& m) { row = m.row; col = m.col; }
};
struct Board {
char player;
char opponent;
char board[3][3];
Board() { }
void read(istream& stream) {
stream >> player;
opponent = player == 'X' ? 'O' : 'X';
for(int row = 0; row < 3; row++) {
for(int col = 0; col < 3; col++) {
char playa;
stream >> playa;
board[row][col] = playa == '_' ? 0 : playa == player ? 1 : -1;
}
}
}
void print(ostream& stream) {
for(int row = 0; row < 3; row++) {
for(int col = 0; col < 3; col++) {
switch(board[row][col]) {
case -1:
stream << opponent;
break;
case 0:
stream << '_';
break;
case 1:
stream << player;
break;
}
}
stream << endl;
}
}
void do_move(const Move& move, int player) {
board[move.row][move.col] = player;
}
void undo_move(const Move& move) {
board[move.row][move.col] = 0;
}
bool isWon() {
if (board[0][0] != 0) {
if (board[0][0] == board[0][1] &&
board[0][1] == board[0][2])
return true;
if (board[0][0] == board[1][0] &&
board[1][0] == board[2][0])
return true;
}
if (board[2][2] != 0) {
if (board[2][0] == board[2][1] &&
board[2][1] == board[2][2])
return true;
if (board[0][2] == board[1][2] &&
board[1][2] == board[2][2])
return true;
}
if (board[1][1] != 0) {
if (board[0][1] == board[1][1] &&
board[1][1] == board[2][1])
return true;
if (board[1][0] == board[1][1] &&
board[1][1] == board[1][2])
return true;
if (board[0][0] == board[1][1] &&
board[1][1] == board[2][2])
return true;
if (board[0][2] == board [1][1] &&
board[1][1] == board[2][0])
return true;
}
return false;
}
list<Move> getMoves() {
list<Move> moveList;
for(int row = 0; row < 3; row++)
for(int col = 0; col < 3; col++)
if (board[row][col] == 0)
moveList.push_back(Move(row, col));
return moveList;
}
};
ostream& operator<< (ostream& stream, Board& board) {
board.print(stream);
return stream;
}
istream& operator>> (istream& stream, Board& board) {
board.read(stream);
return stream;
}
int evaluate(Board& board) {
int score = board.isWon() ? 100 : 0;
for(int row = 0; row < 3; row++)
for(int col = 0; col < 3; col++)
if (board.board[row][col] == 0)
score += 1;
return score;
}
int negamax(Board& board, int depth, int player, int alpha, int beta) {
if (board.isWon() || depth <= 0) {
#if DEBUG > 1
cout << "Found winner board at depth " << depth << endl;
cout << board << endl;
#endif
return player * evaluate(board);
}
list<Move> allMoves = board.getMoves();
if (allMoves.size() == 0)
return player * evaluate(board);
for(list<Move>::iterator it = allMoves.begin(); it != allMoves.end(); it++) {
board.do_move(*it, -player);
int val = -negamax(board, depth - 1, -player, -beta, -alpha);
board.undo_move(*it);
if (val >= beta)
return val;
if (val > alpha)
alpha = val;
}
return alpha;
}
void nextMove(Board& board) {
list<Move> allMoves = board.getMoves();
Move* bestMove = NULL;
int bestScore = INT_MIN;
for(list<Move>::iterator it = allMoves.begin(); it != allMoves.end(); it++) {
board.do_move(*it, 1);
int score = -negamax(board, 100, 1, INT_MIN + 1, INT_MAX);
board.undo_move(*it);
#if DEBUG
cout << it->row << ' ' << it->col << " = " << score << endl;
#endif
if (score > bestScore) {
bestMove = &*it;
bestScore = score;
}
}
if (!bestMove)
return;
cout << bestMove->row << ' ' << bestMove->col << endl;
#if DEBUG
board.do_move(*bestMove, 1);
cout << board;
#endif
}
int main() {
Board board;
cin >> board;
#if DEBUG
cout << "Starting board:" << endl;
cout << board;
#endif
nextMove(board);
return 0;
}
该算法选择将一个棋子放置在0,1,造成保证损失,对该陷阱执行操作(无法执行任何操作以赢得或结束平局):
我很确定游戏的实现是正确的,但是算法也应该是正确的
编辑:更新了评估和nextMove
EDIT2:修复了第一个问题,尽管您的evaluate
函数计算空白,并且无法识别获胜的棋盘,但似乎仍然存在错误
编辑:
nextMove
中还有一个相对较小的问题。线路应该是
int score = -negamax(board, 0, -1, INT_MIN + 1, INT_MAX);
修正这个问题(并且评估),代码选择正确的移动
编辑:
这就解决了这个问题:
if (board.isWon() || depth <= 0) {
#if DEBUG > 1
cout << "Found winner board at depth " << depth << endl;
cout << board << endl;
#endif
return -100;
}
if(board.isWon()|深度1
coutisWon
对于玩家的赢或输都返回true。这没什么帮助。玩家的使用似乎有点滑稽
顶级循环调用“board.do_move(*it,1);”,然后调用negamax,player=1
然后,negamax将调用“board.do_move(*it,player);”,所以看起来第一个玩家实际上得到了两个移动。这并不重要,检查是否有赢意味着当前移动产生了游戏结束,因此,没有必要检查进一步的移动。但是,对于赢或输,negamax返回的分数需要有所不同。isWon
或evaluate
needs合并了这些信息…我已经在求值函数中加入了这个语义。谢谢!注意这是X的一个失败位置,所以(2,0)并不比(1,0)好。@Beta:um…,不,不是。玩得太多了?X必须在(2,0)处进行阻止,然后O必须在(1,0)处进行阻止,然后X必须在(1,2)处进行阻止若要阻止,则没有人赢。@KeithRandall,[facepalm]没关系。哦,是的!谢谢!另外,在顶级循环中,我应该将分数指定为negamax的否定值吗?我已经修复了代码,正如您所建议的,但是,我仍然得到相同的结果。我已经更新了代码以反映更改。@GeorgeJiglau,if(board.isWon()){score*=100;}
什么是0*100?我已经修正了评估
再次,但是,这只会影响到上一步赢得的游戏,不会影响当前的测试用例。太棒了!我真的没有弄明白评估应该从玩家的角度来评估棋盘,而不是从全局的角度。谢谢!
int score = -negamax(board, 0, -1, INT_MIN + 1, INT_MAX);
if (board.isWon() || depth <= 0) {
#if DEBUG > 1
cout << "Found winner board at depth " << depth << endl;
cout << board << endl;
#endif
return -100;
}