再写围棋的MC模拟

  有了棋串的数据结构后,落子就变得很高效了,接下来要生成随机棋步。

  以9x9棋盘为例,直接生成0~80的随机数进行模拟会很低效,因为它们并不都是合法落子点——有3类落子点是非法的:1. 非空点 2. 劫争点 3. 自杀点。

  自然想在模拟过程中增量维护这3类点集,前两类很容易维护,难点在于自杀点的增量维护。

  着手写自杀点的维护后,才发现问题出乎意料的复杂。我先写了个IsSuiside函数,通过试下作为最终的裁决,而这又涉及到对象的拷贝,太过昂贵。于是想尽量少用,通过位运算来判定多数情况。然后随着测试的进行,陆续发现多处的位运算判定不可靠,不得不用IsSuiside函数替代……

  最后的效率可想而知,9 x 9棋盘模拟1w局,用时约40秒——这是不可授受的。

  回过头来思考,越发觉得这件事并不必要做。多数情况下,自杀点本身并无价值(除了打劫时也许可作劫材),即使允许自杀,在UCT搜索过程中,自杀点自然会冷下来——用过多的资源判定自杀点划不来。

  于是修改程序,让规则允许自杀,不过为了尽早结束棋局,不允许自填眼位,也不允许填对方眼位而不提子。增量维护眼位集合要简单得多。

  测试下来要快很多,9 x 9棋盘,2.3秒模拟1万局,CPU是2.4G core 2,编译器是clang,开O3优化级。

  Simulate函数:

template <BoardLen BOARD_LEN>
PointIndex
MCSimulator<BOARD_LEN>::Simulate(const BoardInGm<BOARD_LEN> &input_board) const
{
    BoardInGm<BOARD_LEN> bingm;
    bingm.Copy(input_board);

    do {
        PlayerColor last_player = bingm.LastPlayer();
        PlayerColor cur_player = OppstColor(last_player);
        const auto &playable = bingm.PlayableIndexes(cur_player);

        std::bitset<BLSq<BOARD_LEN>()> noko_plbl(playable);
        PointIndex ko = bingm.KoIndex();
        if (ko != BoardInGm<BOARD_LEN>::NONE) {
            std::bitset<BLSq<BOARD_LEN>()> kobits;
            kobits.set();
            kobits.reset(ko);
            noko_plbl &= kobits;
        }

        PointIndex play_c = noko_plbl.count();
        if (play_c > 0) {
            PointIndex rand = this->Rand(play_c - 1);
            PointIndex cur_indx =
                GetXst1<BLSq<BOARD_LEN>()>(noko_plbl, rand);
            bingm.PlayMove(Move(cur_player, cur_indx));
        } else {
            bingm.Pass(cur_player);
        }
    } while(bingm.PlayableIndexes(BLACK_PLAYER).count() > 0 ||
            bingm.PlayableIndexes(WHITE_PLAYER).count() > 0);

    return bingm.BlackRegion();
}

  忽然想试试19路标准棋盘下,双方随机落子黑棋能赢多少,测试函数:

template <BoardLen BOARD_LEN>
void MCSimulator<BOARD_LEN>::TEST()
{
    int begin = clock();
    int sum = 0;
    const int a = 10000;
    for (int i=0; i<a; ++i) {
        BoardInGm<TEST_LEN> b;
        b.Init();
        auto &mcs = MCSimulator<TEST_LEN>::Ins();
        int r = mcs.Simulate(b);
        sum += r;
    }
    int end = clock();
    printf("time = %f\n", (float)(end - begin) / 1000000);
    printf("simulate complte.\n");
    printf("average black = %f\n", (float)sum / a);
}

  19路的模拟速度有点慢,28秒1w局。看来随机落子的先行优势并不明显……

  代码:https://github.com/chncwang/FooGo

原文地址:https://www.cnblogs.com/qswang/p/2815632.html