棋串的数据结构

  Monte Carlo局面评估+UCT博弈树搜索是现代计算机围棋的主流。这种方法对算法的效率有很高要求,因此须要在Mone Carlo模拟过程中保存棋串(直线相连的同色棋子集合)信息,提高落子效率。

  棋串须要满足以下需求:

  1. 频繁查询某棋子所在的“棋串”。
  2. 频繁创建棋串。
  3. 频繁查询棋串的“气”。
  4. 频繁合并两棋串。
  5. 移除棋串,并枚举其中的棋子。

  因此用链表实现的并查集是很合适的数据结构:

struct Node {
    PointIndex next_, list_head_;
};
struct List {
    PointIndex tail_, len_;
    AirSet air_set_;
};

Node nodes_[FOO_SQUARE(BOARD_LEN)];
List lists_[FOO_SQUARE(BOARD_LEN)];

  创建只有一颗棋子的棋串:

template <BoardLen BOARD_LEN>
void
ChainSet<BOARD_LEN>::CreateList(PointIndex node_i,
                                const ChainSet<BOARD_LEN>::AirSet &air_set)
{
    nodes_[node_i].list_head_ = node_i;
    lists_[node_i].tail_ = node_i;
    lists_[node_i].len_ = 1;
    lists_[node_i].air_set_ = air_set;
}

  查询棋子所在棋串:

PointIndex GetListHead(PointIndex node_i) const {
    return nodes_[node_i].list_head_;
}

  合并两棋串:

template <BoardLen BOARD_LEN>
PointIndex ChainSet<BOARD_LEN>::MergeLists(PointIndex list_a, PointIndex list_b)
{
    FOO_ASSERT(list_a != list_b);
    if (lists_[list_a].len_ > lists_[list_b].len_) swap(list_a, list_b);

    for (int i=list_a; ; i=nodes_[i].next_) {
        nodes_[i].list_head_ = list_b;
        if (i == lists_[list_a].tail_) break;
    }

    nodes_[lists_[list_b].tail_].next_ = list_a;
    lists_[list_b].tail_ = lists_[list_a].tail_;
    lists_[list_b].len_ += lists_[list_a].len_;
    lists_[list_b].air_set_ |= lists_[list_a].air_set_;
    return list_b;
}

  移除棋串:

template <BoardLen BOARD_LEN>
void ChainSet<BOARD_LEN>::RemoveList(PointIndex head)
{
    for (int i=head; ; i=nodes_[i].next_) {
        nodes_[i].list_head_ = ChainSet<BOARD_LEN>::NONE_LIST;
        if (i == lists_[head].tail_) break;
    }
}

  枚举棋子:

template <BoardLen BOARD_LEN>
PntIndxVector ChainSet<BOARD_LEN>::GetPiecesOfAChain(PointIndex list_i) const
{
    const List *pl = lists_ + list_i;
    PntIndxVector v(pl->len_);
    int vi = 0;

    for (int i=list_i; ; i=nodes_[i].next_) {
        v[vi++] = i;
        if (i == pl->tail_) break;
    }

    return v;
}

  棋串的“气”可以按位存储,这样既省内存,又增效率。比如,“气”的数量,就是2进制中1的数量;合并两棋串时也须要合并它们的“气”,则作位或运算。这个数据结构不必自己实现:

typedef std::bitset<FOO_SQUARE(BOARD_LEN)> AirSet;

  当然还须要写一些高层接口供落子函数调用。

  贴张落子函数的test case:

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

posted @ 2012-12-04 18:10  chncwang  阅读(1764)  评论(4编辑  收藏  举报