我试图在Russel Norvig的人工智能书中给出了用于井字游戏的极小极大算法。它拥有除了将bestMove返回给用户的方式之外的所有内容。我努力回归bestMove,但无法决定何时选择bestMove。帮忙,有人吗?
moveT MiniMax(stateT state)
{
moveT bestMove;
max_move(state,bestMove);
return bestMove;
}
int max_move(stateT state,int & bestMove)
{
int v = -10000;
if(GameIsOver(state))
{
return EvaluateStaticPosition(state);
}
vector<moveT> moveList;
GenerateMoveList(state, moveList);
int nMoves = moveList.size();
for(int i = 0 ; i < nMoves ; i++)
{
moveT move = moveList[i];
MakeMove(state, move);
int curValue = min_move(state,bestMove);
if(curValue > v)
{
v = curValue;
bestMove = move;
}
RetractMove(state, move);
}
return v;
}
int min_move(stateT state, int &bestMove)
{
int v = 10000;
if(GameIsOver(state))
{
return EvaluateStaticPosition(state);
}
vector<moveT> moveList;
GenerateMoveList(state, moveList);
int nMoves = moveList.size();
for(int i = 0 ; i < nMoves; i++)
{
moveT move = moveList[i];
MakeMove(state, move);
int curValue = max_move(state,depth+1,bestMove);
if(curValue < v)
{
curValue = v;
}
RetractMove(state, move);
}
return v;
}
P.S。:还有其他伪代码用于查找minmax值。然而,他们只专注于井字游戏,我试图将其扩展到其他游戏。感谢。
更新:整个代码可以在这里找到:http://ideone.com/XPswCl
答案 0 :(得分:7)
在最简单的minimax版本中,第一个玩家希望最大化他的分数,第二个玩家希望最小化第一个玩家的分数。
由于第一和第二玩家都只关心第一个玩家的得分,EvaluateStaticPosition
应该返回一个值,表明第一个玩家的棋盘状态有多好。它的转变是不相关的。
int EvaluateStaticPosition(stateT state)
{
if(CheckForWin(state, FIRST_PLAYER))
{
return WINNING_POSITION;
}
if(CheckForWin(state, Opponent(FIRST_PLAYER)))
{
return LOSING_POSITION;
}
return NEUTRAL_POSITION;
}
现在,当你想要第一个玩家最佳的移动时,请拨打MaxMove。如果您想要最适合第二个玩家的移动,请致电MinMove。
moveT MiniMax(stateT state)
{
moveT bestMove;
int i = 0;
if (state.whoseTurn == FIRST_PLAYER){
i = MaxMove(state, bestMove);
}
else{
i = MinMove(state,bestMove);
}
cout<<"i is "<<i<<endl;
return bestMove;
}
最后,您在MinMove
和MaxMove
内部遇到了一些问题。如果您在其中任何一个中分配curRating
,则不应将bestMove
作为第二个参数传递给MaxMove
或MinMove
。然后它会将对手的最佳移动到bestMove
,这是没有意义的。相反,声明一个opponentsBestMove
对象并将其作为第二个参数传递。 (您实际上不会使用该对象,甚至不会在之后查看其值,但这没关系)。通过此更改,您永远不会在bestMove
内向MinMove
分配任何内容,因此您应该在if(curRating < v)
块中进行此操作。
int MaxMove(stateT state, moveT &bestMove)
{
if(GameIsOver(state))
{
return EvaluateStaticPosition(state);
}
vector<moveT> moveList;
GenerateMoveList(state, moveList);
int nMoves = moveList.size();
int v = -1000;
for(int i = 0 ;i<nMoves; i++)
{
moveT move = moveList[i];
MakeMove(state, move);
moveT opponentsBestMove;
int curRating = MinMove(state, opponentsBestMove);
if (curRating > v)
{
v = curRating;
bestMove = move;
}
RetractMove(state, move);
}
return v;
}
int MinMove(stateT state, moveT &bestMove)
{
if(GameIsOver(state))
{
return EvaluateStaticPosition(state);
}
vector<moveT>moveList;
GenerateMoveList(state, moveList);
int nMoves = moveList.size();
int v = 1000;
for(int i = 0 ; i<nMoves; i++)
{
moveT move = moveList[i];
MakeMove(state , move);
moveT opponentsBestMove;
int curRating = MaxMove(state,opponentsBestMove);
if(curRating < v)
{
v = curRating;
bestMove = move;
}
RetractMove(state, move);
}
return v;
}
此时你应该有一个无与伦比的AI!
The final position looks like this:
O | O | X
---+---+---
X | X | O
---+---+---
O | X | X
Cat's game.
另一种方法利用了tic-tac-toe是零和游戏的事实。换句话说,在游戏结束时,玩家的得分总和将等于零。对于双人游戏,这意味着一个玩家的得分将始终是另一个玩家的得分。这对我们来说很方便,因为最小化其他玩家的分数与最大化自己的分数相同。因此,不是一个玩家最大化他的分数而一个玩家最小化另一个玩家的分数,我们可以让两个玩家都试图最大化他们自己的分数。
将EvaluateStaticPosition
更改回其原始形式,以便根据当前玩家的电路板状态有多少给出分数。
int EvaluateStaticPosition(stateT state)
{
if(CheckForWin(state, state.whoseTurn))
{
return WINNING_POSITION;
}
if(CheckForWin(state, Opponent(state.whoseTurn)))
{
return LOSING_POSITION;
}
return NEUTRAL_POSITION;
}
删除MinMove
,因为我们只关心最大化。
重写MaxMove
,以便选择让对手得分最差的移动。最佳动作的得分是其他球员最差得分的负值。
int MaxMove(stateT state, moveT &bestMove)
{
if(GameIsOver(state))
{
return EvaluateStaticPosition(state);
}
vector<moveT> moveList;
GenerateMoveList(state, moveList);
int nMoves = moveList.size();
int v = -1000;
for(int i = 0 ;i<nMoves; i++)
{
moveT move = moveList[i];
MakeMove(state, move);
moveT opponentsBestMove;
int curRating = -MaxMove(state, opponentsBestMove);
if (curRating > v)
{
v = curRating;
bestMove = move;
}
RetractMove(state, move);
}
return v;
}
由于MaxMove
用于两个玩家,我们不再需要区分MiniMax
功能中的玩家。
moveT MiniMax(stateT state)
{
moveT bestMove;
int i = 0;
i = MaxMove(state, bestMove);
cout<<"i is "<<i<<endl;
return bestMove;
}
答案 1 :(得分:4)
好吧,看起来MiniMax
正确地为你选择它,只需用初始状态和深度调用它。 (除非根据状态的第一个玩家是第二个玩家,否则你应该在MiniMax中调用min_move。)
编辑: 是的,我忽略了一些东西,bestMove目前没有多大意义。在max_move中的程序中,您可以像这样更改循环:
for(int i = 0 ; i < nMoves ; i++)
{
moveT move = moveList[i];
MakeMove(state, move);
int new_value = min_move(state, depth+1);
if(new_value > v)
{
v=new_value;
}
RetractMove(state, move);
}
之后你可以想一想bestMove的意思吗?我的想法是你有兴趣找到一个“最好的”系列动作来进行井字游戏。为此你需要一个向量,甚至更好stack。但这也意味着将std::stack<int>* best_moves
作为最后一个参数。
对于堆栈实现,在min_move中返回下一步移动,如果它们的值最佳,则会将move
推到best_moves
堆栈的顶部。当然,在游戏结束时你只需返回空堆栈。需要采用OOP方法将其正确拉出,我会在有一段时间的时候这样做。
如果您只需要最好的下一步那么我建议您将min_move和max_moe的返回类型更改为某个结构,如下所示:
struct Value_move{
int value;
moveT best_move;
};
然后max_move的新实现如下所示:
const int MOVE_INVALID = -12345;
const int MOVE_NOTHING = -12346;
Value_move max_move(stateT state, int depth)
{
Value_move best;
best.value = -10000; best.best_move = MOVE_INVALID;
if(GameIsOver(state))
{
best.value = EvaluateStaticPosition(state);
best.best_move = MOVE_NOTHING;
return best;
}
vector<moveT> moveList;
GenerateMoveList(state, moveList);
int nMoves = moveList.size();
for(int i = 0 ; i < nMoves ; i++)
{
moveT move = moveList[i];
MakeMove(state, move);
Value_move curr = min_move(state, depth+1);
if(curr.value > best.value)
{
best.value = curr.value;
best.best_move = move;
}
RetractMove(state, move);
}
return v;
}
您只需要在MiniMax函数中获取返回结构中的best_move字段。
REMARK:
你不得不承认,虽然这在很多方面都不像c ++程序,而是c程序。否则,CapitalCamelCase中的所有函数都应该是类方法,你应该通过(const)ref而不是value传递状态 - 但只有当状态实际上是typedef后面的指针时,这整个代码才有意义。
答案 2 :(得分:0)
您的代码找到正确的值,但然后通过传递相同的引用来覆盖它。
int curValue = min_move(state,bestMove);
应该成为
moveT nextMove; // No need to actually do anything with this value
int curValue = min_move(state,nextMove);
您还需要在min_move函数中进行相同类型的更改。
注意:在min_move
中,您的代码调用max_move
的参数多于您为该函数定义的参数。