【问题标题】:Transposition Table is causing tests to fail(but runs fine in game)转置表导致测试失败(但在游戏中运行良好)
【发布时间】:2020-07-10 08:25:10
【问题描述】:

我在井字游戏 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;
}

为了测试它,我做了一个验收测试,玩所有可能的棋盘并检查人类获胜

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++;
    }
}

在我添加转置表之前,测试从未返回任何丢失状态,为了测试丢失状态何时出现我做了一个测试,播放了丢失字段的每个排列,但它从未找到丢失状态,可能导致 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()));
 }

【问题讨论】:

    标签: c++ artificial-intelligence minimax


    【解决方案1】:

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

    这一行有一个潜在的问题:

    Entry e = (*tTable)[hash];
    if (e && e.depth == reverseDepth)
            return e.scoreMove;
    

    除了检查转置表是否存储了相同深度的搜索结果外,还需要检查表中存储的边界与表中的边界是否兼容。

    我在回复another question时提到了这个问题:

    在转置表中存储值时,还需要存储搜索期间使用的 alpha 和 beta 边界。当您在搜索中间的节点处获得一个值时,它要么是真值的上限(因为 value = beta),要么是真值的下限(因为 value = alpha)或节点的实际值(阿尔法

    测试这个的方法是修改AI::minMax。当您从转置表返回值时,将标志设置为 true。然后,每次返回值时,如果转置表标志为真,则将要返回的值与在转置表中找到的值进行比较。如果它们不相同,则说明有问题。

    此外,极小极大通常用于零和游戏,这意味着两个玩家的得分之和应为 0。我不知道所有返回值在您的代码中的含义,但有时您正在返回{0, -1},有时返回{0, -2}。这是有问题的,因为现在你有一个非零和游戏,并且大部分理论都崩溃了。

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

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

    【讨论】:

    • 感谢您的 anwser Nathan,将对此进行调查。 -1 和 -2 从未实际使用过,它们被存储为移动部分,分数始终为 0,这对我来说只是一个信息,所以我可以知道分数是平局还是搜索太浅
    • 所以我可以只存储一个枚举来描述该值是否为真、上限或下限,并基于if enum == true 然后return value; else if enum == lowerbound and value &gt;= beta 然后return value; 等。这是正确的吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-25
    • 2017-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-12
    相关资源
    最近更新 更多