前言

上一篇文章已经有了蒙特卡洛树搜索算法了,再加上五子棋的局面状态的话,基本就是一个简单的 AI 了。这篇文章主要介绍如何实现局面状态。

局面状态

根据五子棋的玩法,局面状态应该包含如下数据:

  • 当前行棋方
  • 当前局面是否终结
  • 获胜玩家
  • 棋子位置

对于行棋方的表示,我采用 1 表示黑方,-1 表示白方,这样我就可以使用 player = -player 来交换行棋方(有点符合直觉的味道);局面是否终结自然是一个 bool 值;获胜玩家同行棋方一样,1 表示黑方获胜,-1 表示白方获胜,0 表示平局或者未终局(视局面终结状态而定);对于棋子位置,前采用了两个 std::bitset 来分别表示黑子和白子的位置。不采用字节数组表示棋盘状态是为了节省一点内存,而且根据要解决的问题,采用 bitset 也还不错。于是棋盘状态数据结构看起来如下:

class gomoku_state_t {
private:std::bitset<16*15> _black;std::bitset<16*15> _white;int _turn;int _winner;bool _gameover;
};

bitset 大小设置为 16*15 是为了方便把 xy 坐标变换为索引,这里棋盘的大小为 15x15。

局面状态有了之后,接下来就是添加必要的接口了,清单如下:

class gomoku_state_t {
public:typedef move_t action_type;void put_black(int x, int y);void put_white(int x, int y);void set_turn(int player);void play(int x, int y);bool gameover() const;int turn() const;int winner() const;bool has_stone(int x0, int x1, int y0, int y1) const;template<class _FwIt, class _Fnc>_FwIt avail_moves(_FwIt dest, _Fnc&& pred = [](int x, int y) { return true; }) const;private: // data members//...
};

其中 play 的行为是为当前行棋方在指定的位置下子,然后判断输赢,再交换行棋方。avail_moves 是根据当前局面获取可行着法,dest 是一个前向迭代器,pred 是一个筛选器。

move_t 表示一个着法,有一些函数负责获取着法的信息,比如 move_x()、move_y() 这两个函数用于获取 X Y 坐标。

小优化

在执行蒙特卡洛树搜索时,会不断地展开子节点从而占用较大内存。根据五子棋的玩法,可以帮助控制好子节点的个数。

五子棋有较强的局部性,棋子一般不会相距太远,因此在获取可行着法时不用把所有空白点位返回。我的经验是,一个点位周围 2 格范围内如果没有棋子就不用考虑。

在判断输赢时,也不用全盘查找,只用考虑每方最后一手棋 8 个方向 4 格范围内的情况,这样只用扫描 33 个点位,一方成 5 棋局就结束了(现只考虑自由规则)。

接入蒙特卡洛搜索

在前面的文章中留了 3 个函数待实现,现在就可以接入了。

std::vector<move_t> actions(const gomoku_state_t& s) {std::vector<move_t> moves;s.avail_moves(std::back_inserter(moves), [&s](int x, int y) {int minx = std::max(0, x - 2);int maxx = std::min(13, x + 2);int miny = std::max(0, y - 2);int maxy = std::min(13, y + 2);return !s.has_stone(minx, maxx, miny, maxy);});return moves;
}gomoku_state_t next(const gomoku_state_t& s, const move_t& a) {if (s.gameover()) return s;gomoku_state_t r = s;r.play(move_x(a), move_y(a));return r;
}int playout(const gomoku_state_t& s) {gomoku_state_t g = s;if (!g.gameover()) {std::vector<move_t> moves;s.avail_moves(std::back_inserter(moves));std::random_shuffle(moves.begin(), moves.end());for (auto it = moves.begin(); !g.gameover(); ++it)g.play(move_x(*it), move_y(*it));}// 因为要评估 s 的结果,所以比较 s.turn() if (s.turn() == g.winner()) return 1;else (-s.turn() == g.winner()) return -1;return 0;
}

跑起来

接入好了就可以测试一下了。在我的 i5 电脑上,每秒大约可以进行 3 万次模拟;如果每步棋 15 秒,大约需要 4GB 内存;搜索深度最大大概能达到 7 层。以下是两个蒙特卡洛法AI对弈的一局棋:

这盘棋下得还算可以,黑棋 43 转折点,黑棋胜率由此急剧下落;白棋 44 一套连招还挺意外的。从这盘棋可以看到,除非有无限次的模拟,否则纯粹的蒙特卡洛法会因为水平线效应而错过最优解。要改善这种状况,就需要更准确的局面评估算法,这样即使少量的模拟,也能让搜索更准确。事实上,即使是加入了神经网络评估的蒙特卡洛算法,也有可能会无法看到某个陷阱。

整个速度应该还能进一步提高,比较明显的比如 playout 中 shuffle 与 play 可以合并;再一个,如果不涉及多线程,每次获取 avail_moves 时可以免去创建临时的 std::vector。

附:

胜负判断相关代码

// 判断某个方向上是否有 5 子连珠
bool win_line( const std::bitset<16 * 15>& stones, std::size_t start, std::size_t stop, std::size_t stride )
{int cnt = 0;for( ; cnt < 5 && start != stop; start += stride )if( stones.test[start] )cnt++;elsecnt = 0;return cnt >= 5;
}bool win_line_at(const std::bitset<16 * 15>& stones, std::size_t p, std::size_t da, std::size_t db, std::size_t stride) {return win_line( stones, p - da*stride, p + stride*(db + 1), stride );
}// 判断某一方是否赢棋
bool win( const std::bitset<16 * 15>& stones )
{// 水平方向for( std::size_t row = 0; row < 240; row += 16 )if( win_line( stones, row, row + 15, 1 ) )return true;// 垂直方向for( std::size_t col = 0; col < 15; col++ )if( win_line( stones, col, col + 240, 16 ) )return true;// ‘\’ 方向对角线上半部分for( std::size_t s = 0, e = 255; s != 11; s++, e -= 16 )if( win_line( stones, s, e, 17 ) )return true;// ‘\’ 方向对角线下半部分for( std::size_t s = 16, e = 254; s != 176; s += 16, e-- )if( win_line( stones, s, e, 17 ) )return true;// ‘/’ 方向对角线上半部分for( std::size_t s = 4, e = 79; s != 15; s++, e += 16 )if( win_line( stones, s, e, 15 ) )return true;// ‘/’ 方向对角线下半部分for( std::size_t s = 30, e = 240; s != 190; s += 16, e++ )if( win_line( stones, s, e, 15 ) )return true;return false;
}// 判断 x, y 位置周围是否存在赢棋
bool win( const std::bitset<16 * 15>& stones, int x, int y )
{constexpr std::size_t _4 = 4;std::size_t x0 = static_cast<std::size_t>(x);std::size_t y0 = static_cast<std::size_t>(y);std::size_t x1 = 14 - x0;std::size_t y1 = 14 - y0;std::size_t p = y0 * 16 + x0;// 水平方向x0 = std::min( x0, _4 ); x1 = std::min( x1, _4 );if( win_line_at( stones, p, x0, x1, 1 ) )return true;// 垂直方向y0 = std::min( y0, _4 ); y1 = std::min( y1, _4 );if( win_line_at( stones, p, y0, y1, 16 ) )return true;// ‘\’ 方向std::size_t ma = std::min( x0, y0 ); std::size_t mb = std::min( x1, y1 );if( win_line_at( stones, p, ma, mb, 17 ) )return true;// ‘/’ 方向ma = std::min( x1, y0 ); mb = std::min( x0, y1 );if( win_line_at( stones, p, ma, mb, 15 ) )return true;return false;
}

五子棋AI - 局面状态相关推荐

  1. java五子棋AI算法人机对战(春物彩羽版可下载试玩PC端)

    五子棋AI算法 前言: 坐标西安,写于疫情封城期间.改进了之前写的基于极大极小值策略AI五子棋游戏,是用java实现的,采用了java老旧的jframe窗体和绘图类.写好之后整理成了这篇博客. 游戏采 ...

  2. 五子棋AI - 蒙特卡洛树搜索

    动机 自高中时代做了一个带简单AI的五子棋游戏后,一直以来实现一个更加厉害的五子棋AI算是我的小目标.之前也尝试过使用 MinMax 算法,最终结果不甚理想.当然并不是算法问题,而是搭配这个算法需要许 ...

  3. 基于博弈树的五子棋 AI 算法及其 C++ 实现

    基于博弈树的五子棋 AI 算法及其 C++ 实现 摘要 一   五子棋的游戏规则 二   五子棋对弈的算法描述 2.1 博弈树搜索算法 2.2 α ─ β 剪枝 2.3 估价函数 三   五子棋对弈的 ...

  4. 五子棋AI设计(转载)

    五子棋AI设计(转载) 原始链接:https://blog.csdn.net/pi9nc/article/details/10858411 2013-09-01 22:15:52 pi9nc 阅读数 ...

  5. python五子棋ai棋力最高_棋力最强的五子棋 App 是什么?

    插嘴说下这个五子棋大师 编程我不懂,这个五子棋大师的计算尚可,应该在五步以上,除去掌握比较熟练的棋型,大部分情况下我没有它算的远,如果正常和它下的话,想赢最高级,很吃力. 但是我还是可以轻松虐它,因为 ...

  6. 【五子棋AI循序渐进】发布一个完整的有一定棋力的版本(含源码)

    本博文来自于:http://www.cnblogs.com/zcsor/archive/2012/12/25/2832820.html 经过这半年左右的学习和探索,现在对五子棋AI有了一定的认识,给大 ...

  7. 纳尼,五子棋AI居然这么简单?

    前言 最近一直在写博文.虽然没有人看,但是我写博文,并不是为了提高自己的人气,而是为了锻炼自己的表达能力和写代码的能力. 正文 演示 项目简介 好啦,我先来介绍一下本次的项目吧. EasyX是c++的 ...

  8. 课程设计书五子棋AI算法及其实现

    五子棋AI,能根据棋盘局势判断棋子应落在何处获胜,主要算法有权值法和博弈树法两种实现方案. 权值法 在数理统计中,有一种名为蒙特卡洛法的方法常被使用,其主要内容为:根据事件出现的概率估计某些特征,并将 ...

  9. python五子棋ai棋力最高_【五子棋AI循序渐进】发布一个完整的有一定棋力的版本(含源码)...

    本博文来自于:http://www.cnblogs.com/zcsor/archive/2012/12/25/2832820.html 经过这半年左右的学习和探索,现在对五子棋AI有了一定的认识,给大 ...

最新文章

  1. PostSharp AOP编程:3.PostSharp的LocationInterceptionAspect类基本组成
  2. 20172304 《程序设计与数据结构》第四周学习总结
  3. 前端JavaScripts基础知识点
  4. 重读《从菜鸟到测试架构师》--黑色的盒子里有什么(中)
  5. 28.卷1(套接字联网API)---原始套接字
  6. 从个人应用到企业级应用——数字钱包市场调研分析报告
  7. html如何添加阿里图标,CSS引入阿里iconfont图标步骤
  8. 中国石油大学(北京)本科毕业论文答辩PPT模板
  9. LCA的 Trajan 算法
  10. Android 去掉Power键屏保功能,但保留长按关机功能。
  11. 全球与中国泄漏吸收枕头市场深度研究分析报告
  12. 警告:integer division in floating-point context
  13. regulator框架
  14. form表单的action属性设置相对路径
  15. 数据处理技巧(3):excel打开txt数据,出现多个数据在同一个单元格的解决办法
  16. vue-video播放器
  17. Oday安全 11.5利用未启用SafeSEH模块绕过SafeSEH一节注记
  18. Lua服务器框架 crossover
  19. 2022年百度竞价推广效果不好了?怎么做?
  20. 幼儿园装备论文计算机网络,计算机专业毕业论文幼儿园网站的设计与维护.doc...

热门文章

  1. 小说的逻辑与反逻辑_林冲的“走”——小说内部的逻辑与反逻辑(毕飞宇)
  2. 李沐《动手学深度学习》d2l——安装和使用
  3. 走近板球运动·体育项目
  4. 小米火箭_如何掌握火箭联赛的空中技巧
  5. 全志CQR40平台SDK文件夹分析记录
  6. 基于nacos的分布式服务治理
  7. Chawt.com 免费给全球发送短信
  8. 南开19计算机应用基础,南开19秋《计算机应用基础》在线作业答案
  9. 倾听客户声音 华为无线亮相美国CTIA展
  10. 欧睿国际:海尔互联空调全球销量第一