C++ 换位表导致测试失败(但在游戏中运行良好)

C++ 换位表导致测试失败(但在游戏中运行良好),c++,artificial-intelligence,minimax,C++,Artificial Intelligence,Minimax,我在Tictoe minmax算法中添加了一个换位表 int AI::findBestMove() { hash = tTable->recalculateHash(); int bestMove = minMax().second; return bestMove; } std::pair<int, int> AI::minMax(int reverseDepth, std::pair<int, int> bestScoreMove, p

我在Tictoe minmax算法中添加了一个换位表

int AI::findBestMove()
{
    hash = tTable->recalculateHash();
    int bestMove = minMax().second;
    return bestMove;
}

std::pair<int, int> AI::minMax(int reverseDepth, std::pair<int, int> bestScoreMove, player currentPlayer, int alpha, int beta, int lastPlay)
{
    Entry e = (*tTable)[hash];
    if (e && e.depth == reverseDepth)
            return e.scoreMove;
    if (reverseDepth == 0)
        return { 0, -2 };
    else if (field->canDrawOrWin() && lastPlay != -1)
    {
        if (field->hasWon(lastPlay))
            return { evaluateScore(currentPlayer), -1 };
        else if (field->isDraw())
            return { 0, -1 };
    }
    bestScoreMove.first = currentPlayer == player::AI ? INT_MIN : INT_MAX;
    for (int i = 0; i < field->size(); i++)
    {
        if ((*field)[i] == player::None && field->isCoordWorthChecking(i))
        {
            (*field)[i] = currentPlayer;
            hash = tTable->calculateHash(hash, i);
            std::pair<int, int> scoreMove = minMax(reverseDepth - 1, bestScoreMove, getOpponent(currentPlayer), alpha, beta, i);
            if (currentPlayer == player::AI)
            {
                alpha = std::max(alpha, scoreMove.first);
                if (bestScoreMove.first < scoreMove.first)
                    bestScoreMove = { scoreMove.first, i };
            }
            else
            {
                beta = std::min(beta, scoreMove.first);
                if (bestScoreMove.first > scoreMove.first)
                    bestScoreMove = { scoreMove.first, i };
            }
            hash = tTable->calculateHash(hash, i);
            (*field)[i] = player::None;
            if (beta <= alpha)
                break;
        }
    }
    tTable->placeEntry(hash, bestScoreMove, reverseDepth);
    return bestScoreMove;
}
int AI::findBestMove()
{
hash=tTable->重新计算hash();
int bestMove=minMax().秒;
返回最佳移动;
}
std::pair AI::minMax(int reverseDepth,std::pair bestScoreMove,player currentPlayer,int alpha,int beta,int lastPlay)
{
条目e=(*tTable)[hash];
如果(e&&e.depth==反向深度)
返回e.scoreMove;
如果(反向深度==0)
返回{0,-2};
else if(field->canDrawOrWin()&&lastPlay!=-1)
{
如果(场->哈斯旺(最后一场))
返回{evaluateScore(currentPlayer),-1};
else if(字段->isDraw())
返回{0,-1};
}
bestScoreMove.first=currentPlayer==player::AI?INT\u MIN:INT\u MAX;
对于(int i=0;isize();i++)
{
如果((*field)[i]==player::None&&field->isCoordWorthChecking(i))
{
(*字段)[i]=当前播放器;
hash=tTable->calculateHash(散列,i);
std::pair scoreMove=minMax(反向第1步,最佳得分移动,获取对手(当前玩家),alpha,beta,i);
如果(currentPlayer==player::AI)
{
alpha=std::max(alpha,scoreMove.first);
if(bestScoreMove.firstscoreMove.first)
bestScoreMove={scoreMove.first,i};
}
hash=tTable->calculateHash(散列,i);
(*字段)[i]=播放器::无;
if(beta placeEntry(hash、bestScoreMove、reverseDepth);
返回并移动;
}
为了测试它,我做了一个验收测试,玩每一个可能的棋盘,并检查人类的胜利

TEST(AcceptanceTest, EveryBoard)
{
    int winstate = 0;
    std::shared_ptr<Field> field = std::make_shared<Field>(4);
    AI ai(field);
    playEveryBoard(ai, field, winstate);
    std::cout <<"Human wins: " << winstate << std::endl;
}
void playEveryBoard(AI& ai, std::shared_ptr<Field> f, int& winstate)
{
    int bestMove = 0;
    auto it = f->begin();
    while (true)
    {
        it = std::find(it, f->end(), player::None);
        if (it == f->end())
            break;
        *it = player::Human;
        if (f->hasWon())
            winstate++;
        EXPECT_TRUE(!f->hasWon());

        bestMove = ai.findBestMove();
        if (bestMove == -1)//TIE
        {
            *it = player::None;
            break;
        }
        (*f)[bestMove] = player::AI;
        if (f->hasWon())//AI WIN
        {
            *it = player::None;
            (*f)[bestMove] = player::None;
            break;
        }

        playEveryBoard(ai, f, winstate);

        *it = player::None;
        (*f)[bestMove] = player::None;
        if (it == f->end())
            break;
        it++;
    }
}
测试(每个板的验收测试)
{
int-winstate=0;
std::shared_ptr field=std::make_shared(4);
AI(现场);
playEveryBoard(ai、field、winstate);
std::cout haswen())
winstate++;
期望为真(!f->haswen());
bestMove=ai.findBestMove();
if(bestMove==-1)//平局
{
*它=播放器::无;
打破
}
(*f)[bestMove]=玩家::AI;
如果(f->haswen())//AI-WIN
{
*它=播放器::无;
(*f)[bestMove]=玩家::无;
打破
}
playEveryBoard(ai、f、winstate);
*它=播放器::无;
(*f)[bestMove]=玩家::无;
如果(it==f->end())
打破
it++;
}
}
直到我添加了换位表,测试才返回任何松动状态。为了测试松动状态出现时,我做了一个测试,播放松动字段的每个排列,但它从未找到松动状态,什么会导致AI仅在EveryBoard测试中松动

TEST(LoosePossible, AllPermutations)
{
    std::vector<int> loosingField = { 2, 3, 7, 11, 12, 13, 15 };
    do{
        std::shared_ptr<Field> field = std::make_shared<Field>(4);
        AI *ai = new AI(field);
        for (auto i : loosingField)
        {
            if ((*field)[i] != player::None || field->hasWon())
                break;
            (*field)[i] = player::Human;
            EXPECT_TRUE(!field->hasWon());
            (*field)[ai->findBestMove()] = player::AI;
        }
        delete ai;
    } while (next_permutation(loosingField.begin(), loosingField.end()));
 }
测试(可能,所有排列)
{
向量loosingField={2,3,7,11,12,13,15};
做{
std::shared_ptr field=std::make_shared(4);
AI*AI=新AI(字段);
用于(自动i:松动字段)
{
如果((*field)[i]!=player::None | | field->haswun())
打破
(*field)[i]=玩家::人;
EXPECT_TRUE(!field->haswen());
(*字段)[ai->findBestMove()]=player::ai;
}
删除ai;
}while(next_置换(loosingField.begin(),loosingField.end());
}

我发现至少有两个地方可能会出现这些错误

这方面存在一个潜在问题:

Entry e = (*tTable)[hash];
if (e && e.depth == reverseDepth)
        return e.scoreMove;
除了检查转置表是否存储相同深度的搜索结果外,还需要检查表中存储的边界是否与表中的边界兼容

我将此作为回答以下问题的一部分:

在转置表中存储值时,还需要存储搜索过程中使用的alpha和beta边界。当在节点中间搜索时返回值时,它是真值的上限(因为值=beta)、真值的下限(因为值=alpha)或节点的实际值(alpha 测试这一点的方法是修改
AI::minMax
。当从换位表返回值时,将标志设置为true。然后,每次返回值时,如果换位表标志为true,则将要返回的值与在换位表中找到的值进行比较。如果它们不相同,则有点不对劲

此外,minimax通常用于零和游戏,这意味着两个玩家的分数之和应加为0。我不知道所有返回值在代码中的含义,但有时返回
{0,-1}
,有时返回
{0,-2}
。这是有问题的,因为现在你有了一个非零和游戏,很多理论都不成立了

特别是,最大玩家可能会将
{0,-1}
{0,-2}
视为相同,但最小玩家不会。因此,如果移动顺序以任何方式发生变化,您可能会看到它们的顺序不同,因此树根的值将不稳定


顺便说一句,这是多人游戏中的一个基本问题。实际上,当一名玩家是国王制造者时,这一问题就会出现。他们自己无法赢得游戏,但他们可以决定谁赢。

谢谢你的anwser Nathan,他将对此进行调查。-1和-2是不可能的