五子棋AI算法

之前说想写一些比较大型的算法,想了半天,还是觉得写五子棋的AI比较合适。一则因为自己研究过这个,有一些基础,二则尽管现在网上有很多五子棋AI算法的资料,但是确实都有些晦涩难懂。就想着借这个机会,凭自己的理解,尽量的讲通俗一些。然而,这个算法确实有些复杂,想要通俗一些需要较大的篇幅,一篇博客难以讲完,这里就分很多个章节,一点一点的深入探讨。为了让文章更加通俗一些,我会略去一部分很简单但是占用篇幅很长的代码,改为用几行注释说明。

框架的搭建

首先,我们计划是做一个五子棋AI,也就是说让玩家和这个AI对下。整个游戏的框架是这样的:

其中,棋盘是一个Object,存放当前的棋局情况,通知每个Player“轮到你下棋了”、“对方下了什么棋”、“游戏结束,XXX获胜”等消息,并且从每个Player那里获取他下了什么棋。两个Player分别是人类玩家和AI。Player的基类应该是一个interface,里面只有三个方法。人类玩家和AI是它的子类,分别实现这三个方法。

public interface Player {
   Point play();
   void display(Point p);
   void notifyWinner(int color);
}
  • 1
  • 2
  • 3
  • 4
public final class Point {
   public final int x;
   public final int y;
   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
   @Override
   public String toString() {return "(" + x + "," + y + ")";}
}

解释一下:

  • play 方法,告知“轮到你下棋了”,并且返回一个Point,也就是下一步下的棋。对于人类玩家,则就是阻塞,等待玩家在界面上选取一个点,并且将这个点的坐标返回。对于AI,则是直接开始用我们的AI算法进行计算,并返回计算结果。
  • void display(Point p) 方法,告知“对方下了什么棋”。对于人类玩家,则就是将对方下了的棋在界面上显示出来。对于AI,则是将对方下了的棋记在AI的缓存中,以便后续的计算。
  • void notifyWinner(int color) 方法,告知“游戏结束,XXX玩家赢了”。对于人类玩家,则就是在界面上展示谁赢了的文字及特效,并且从此之后再点击棋盘就不再有反应了。对于AI,则是通知AI不要再计算了。

当然了,如果打算连续下多盘棋,可能还需要一个reset方法,通知人类玩家和AI清空当前棋盘。当然了,这个和我们的算法关系不大就不列出来了。
然后,我们的interface Player需要两个实现类,分别叫做HumanPlayerRobotPlayer,这个RobotPlayerplay方法将是五子棋AI算法的核心内容,后面会花费大量篇幅进行讲解。
接下来就是我们的棋盘:

public abstract class Constant {
   public static int MAX_LEN = 15;
}
  • 1
  • 2
public class ChessBoard {
   private byte[][] board = new byte[Constant.MAX_LEN][Constant.MAX_LEN];
   private Player[] players = new Player[2];
   private int whoseTurn = 0;
   private int count = 0;
   private boolean isEnd = false;

   private boolean checkForWin(Point p) {
      /* 因为篇幅问题,此处省略十几行代码 */
      /* 这个函数就是在下完每一步棋时调用,只需要判断以这步棋若形成五连珠即可判定获胜 */
      return false;
   }

   public void play() {
      if (isEnd) return;
      Point p = players[whoseTurn].play(); //调用Player的play方法,获取下一步下的棋
      if (board[p.y][p.x] != 0) //严谨,以防万一
         throw new IllegalArgumentException(p.toString() + board[p.y][p.x]);
      board[p.y][p.x] = (byte) (whoseTurn + 1);
      System.out.println((whoseTurn == 0 ? "黑" : "白") + p.toString()); //打印日志
      if (++count == Constant.MAX_LEN * Constant.MAX_LEN) //严谨,如果棋盘下满了游戏结束
         isEnd = true;
      if (checkForWin(p)) //如果下了这步棋后赢了,游戏结束
         isEnd = true;
      whoseTurn = 1 - whoseTurn; //切换当前下棋的人
      players[whoseTurn].display(p); //调用Player的display方法,告知他对方下了哪步棋
      if (isEnd) { //如果下完这一步棋后有一方赢了,则调用Player的notifyWinner方法通知
         players[0].notifyWinner(2 - whoseTurn);
         players[1].notifyWinner(2 - whoseTurn);
      }
   }

   public static void main(String[] args) {
      ChessBoard b = new ChessBoard();
      b.players[0] = new HumanPlayer(1); //这里我从构造函数中传入了颜色,例如1表示执黑,2表示执白
      b.players[1] = new RobotPlayer(2);
      while (b.getWinner() == null) {
         b.play();
      }
   }
}

棋盘的代码确实很简单易懂,也做了很多注释,就不多介绍了。
接下来,就只剩下HumanPlayerRobotPlayer的实现了。

人类玩家

人类玩家无非就是实现三个方法:

  • play 方法,阻塞等待玩家点击棋盘上的一个点并返回这个点
  • display 方法,将AI下的棋展示在界面上
  • notifyWinner 方法,显示一行字“你赢(输)了”

这段代码与本文无关,就不贴出来了,我把我做的这个丑陋的界面贴出来展示一下,哈哈。

(不要吐槽我的界面,这不是重点。)

RobotPlayer

AI才是重点内容,涉及了大量的算法和数学知识,博弈树、评估函数、极大极小值搜索、启发式搜索、α-β剪枝等等,将会占用大量的篇幅。从下篇博客开始,将对此逐一展开。

原文地址:https://www.cnblogs.com/ko88/p/11741782.html

时间: 2024-10-08 14:40:03

五子棋AI算法的相关文章

五子棋AI算法第二篇-极大极小值搜索算法

AI实现的基本思路-极大极小值搜索算法 五子棋看起来有各种各样的走法,而实际上把每一步的走法展开,就是一颗巨大的博弈树.在这个树中,从根节点为0开始,奇数层表示电脑可能的走法,偶数层表示玩家可能的走法. 假设电脑先手,那么第一层就是电脑的所有可能的走法,第二层就是玩家的所有可能走法,以此类推. 我们假设平均每一步有50种可能的走法,那么从根节点开始,往下面每一层的节点数量是上一层的 50被,假设我们进行4层思考,也就是电脑和玩家各走两步,那么这颗博弈树的最后一层的节点数为 50^4 = 625W

五子棋AI算法-算杀

关于剪枝问题 前面讲到的通过Alpha-Beta剪枝和启发式搜索可以将4层搜索的平均时间降低到1秒以下.只有这两个优化方式其实目前最多可以做到6层搜索,就是把AI和玩家各向后推算三步. 6层搜索的棋力其实相当弱,碰到经常玩五子棋的人基本都会输,更不要说对五子棋有研究的玩家.以目前的平均一个节点有50个子节点的搜索方式,把搜索效率提高50倍则可以增加一层搜索深度.而除了前面讲到的AlphaBeta剪枝和启发式搜索,其他的剪枝算法基本都是非安全剪枝.也就是说后序我们会使用一些非安全的剪枝算法来提升搜

五子棋AI算法第三篇-Alpha Beta剪枝

剪枝是必须的 上一篇讲了极大极小值搜索,其实单纯的极大极小值搜索算法并没有实际意义. 可以做一个简单的计算,平均一步考虑 50 种可能性的话,思考到第四层,那么搜索的节点数就是 50^4 = 6250000,在我的酷睿I7的电脑上一秒钟能计算的节点不超过 5W 个,那么 625W 个节点需要的时间在 100 秒以上.电脑一步思考 100秒肯定是不能接受的,实际上最好一步能控制在 5 秒 以内. 顺便说一下层数的问题,首先思考层数必须是偶数.因为奇数节点是AI,偶数节点是玩家,如果AI下一个子不考

人机ai五子棋 ——五子棋AI算法之Java实现

人机ai五子棋 下载:chess.jar (可直接运行) 源码:https://github.com/xcr1234/chess 其实机器博弈最重要的就是打分,分数也就是权重,把棋子下到分数大的地方,我获胜的概率就更大. 而在下棋过程中,大部分的点的得分都很小,或者接近,因此无需对每一个点都打分,只需要在我方附近(进攻)或者敌方附近(防守)的几个点进行打分. 具体原理大家可以看源码中的注释,说明的很清楚. 参考 http://blog.csdn.net/pi9nc/article/details

五子棋AI算法-迭代加深

前面讲到了算杀,其实在算杀之前应该讲一下迭代加深.因为这些文章是我边做边写的一些笔记,所以顺序上可能不是那么严谨. 按照前面的所有算法实现之后(当然不包括算杀),会发现一个比较严重的问题,就是电脑在自己已经胜券在握的情况下(有双三之类的棋可以走),竟然会走一些冲四之类的棋来调戏玩家.这种走法出现的本质就是因为现在的AI只比较最终结果,并没有考虑到路径长短.所以很容易出现在6层搜索到一个双三,其实在4层的时候也有一个双三,因为分数一样,AI会随机选择一个走法.就导致了明明可以两步赢的棋,AI非要走

五子棋AI算法-Zobrist

这个博客不是把五子棋算法研究透彻之后再写的,而是一边研究算法一边写代码,同时一边写博客,所以有些博文的顺序不太对,比如 Zobrist 其实应该放在算杀之前就讲的.不过这并没有大的影响,总体上的顺序是OK的. 另外,这一系列博客讲的五子棋代码其实是一个开源的项目,源码地址:https://github.com/lihongxun945/gobang 由于是边写代码边写博客,所以博客中的代码不是最新的,甚至是有bug的,所以源码请尽量参考上述开源项目中的代码.比如之前讲极大极小值搜索改为负极大值的

五子棋AI算法-重构代码

为什么需要重构 之前的代码有很多松散的模块组合在一起.在把 Zobrist 集成进去时,会发现全部需要走棋的操作其实都需要进行一次 Zobrist 异或操作.另外在逻辑上,其实很多模块都是可以合并到同一个类的,所以这次把代码进行了一次大的重构.所以如果发现博客说的一些模块找不到了也是很正常的,因为大部分模块都被移到了 Board 类中. 这次重构主要的工作就是 把AI相关的代码分成了四个模块: Board ,所有和棋子相关的操作都在这里,包括打分,判断胜负,zobrist缓存,启发函数等. ne

H5版俄罗斯方块(3)---游戏的AI算法

前言: 算是"long long ago"的事了, 某著名互联网公司在我校举行了一次"lengend code"的比赛, 其中有一题就是"智能俄罗斯方块". 本着一向甘做分母, 闪耀分子的绿叶精神, 着着实实地打了一份酱油. 这次借学习H5的机会, 再来重温下俄罗斯方块的AI编写. 本系列的文章链接如下: 1). 需求分析和目标创新 2). 游戏的基本框架和实现 这些博文和代码基本是同步的, 并不确定需求是否会改变, 进度是否搁置, 但期翼自己能

棋牌源码搭建教程之棋牌游戏AI算法

棋牌游戏客户端实现采用Flash 9开发,服务端采用Win32+VC6开发(基于IOCP),数据库网关采用Win32+VC6开发(基于IOCP,MySQL5实现了处理线程池和数据库连接池).虽然服务器端去年就已经完成,但相应的机器人AI算法一直没有能力去实现.今天把它拿到Blog上来希望有机会和感兴趣的兄弟们探讨下. Kevin在他的Blog上给出了他的实现,其给出的算法思想是用宽度优先生成一棵搜索树,再根据玩牌的技巧进行剪枝与判权,机器人的AI能够像养成类游戏那样,实现在蹂躏下慢慢成长,水平逐