python实现五子棋人机对战游戏


Posted in Python onMarch 25, 2020

本文代码基于 python3.6 和 pygame1.9.4。

五子棋比起我之前写的几款游戏来说,难度提高了不少。如果是人与人对战,那么,电脑只需要判断是否赢了就可以。如果是人机对战,那你还得让电脑知道怎么下。

我们先从简单的问题来看。

开端

画棋盘

首先肯定是要画出棋盘来,用 pygame 画出一个 19 × 19 或 15 × 15 的棋盘并不是什么难事,这在之前的文章中已经多次用到,就不赘述了。

画棋子

需要说一下的是画棋子,因为没找到什么合适的棋子图片,所以只要自己来画棋子。
我们用 pygame.draw.circle 画出来的圆形是这样的:

python实现五子棋人机对战游戏

锯齿状十分明显,pygame.draw 中有画抗锯齿直线的函数 aaline,但是并没有 aacircle 这样的函数来画一个抗锯齿的圆。

这里就需要用到 pygame.gfxdraw 啦。pygame.gfxdraw 目前还仅是实验版本,这意味着这个 API  可能会在以后的 pygame 版本中发生变化或消失。

要绘制抗锯齿和填充形状,请首先使用函数的aa *版本,然后使用填充版本。例如:

col = (255, 0, 0)
surf.fill((255, 255, 255))
pygame.gfxdraw.aacircle(surf, x, y, 30, col)
pygame.gfxdraw.filled_circle(surf, x, y, 30, col)

我们用这个方法在棋盘上画一个棋子试试看。

python实现五子棋人机对战游戏

可以看到效果已明显改善。

落子

落子需要判断鼠标事件,当鼠标左键点击,获取鼠标点击的位置,然后根据棋盘的位置,计算出棋子落在棋盘的位置。

while True:
 for event in pygame.event.get():
 if event.type == QUIT:
 sys.exit()
 elif event.type == MOUSEBUTTONDOWN:
 pressed_array = pygame.mouse.get_pressed()
 if pressed_array[0]: # 鼠标左键点击
  mouse_pos = pygame.mouse.get_pos()
  click_point = _get_clickpoint(mouse_pos)

胜利判定

当一子落下,如何判定是否胜利?

可以肯定的是,当某一子落下的时候,如果出现了 5 连,那么落下的这颗子必定在这条 5 连线上。那么这个问题就可以简化了,我们无需全盘扫描,只需要在落子位置上横竖撇捺扫描一下,判断是否出现 5 连即可。

我们定义一个棋盘类,类中实例化一个 19 × 19 的二维数组,初始值皆为 0,表示空,用 1 表示黑子,2 表示白子。这个类对外提供一个落子方法 drop,接收参数落子方和落子坐标,如果落子后胜利,则返回胜利者,否则返回 None。

Chessman = namedtuple('Chessman', 'Name Value Color')
Point = namedtuple('Point', 'X Y')
 
BLACK_CHESSMAN = Chessman('黑子', 1, (45, 45, 45))
WHITE_CHESSMAN = Chessman('白子', 2, (219, 219, 219))
 
offset = [(1, 0), (0, 1), (1, 1), (1, -1)]
 
 
class Checkerboard:
 def __init__(self, line_points):
 self._line_points = line_points
 self._checkerboard = [[0] * line_points for _ in range(line_points)]
 
 def _get_checkerboard(self):
 return self._checkerboard
 
 checkerboard = property(_get_checkerboard)
 
 # 判断是否可落子
 def can_drop(self, point):
 return self._checkerboard[point.Y][point.X] == 0
 
 def drop(self, chessman, point):
 """
 落子
 :param chessman: 黑子/白子
 :param point:落子位置
 :return:若该子落下之后即可获胜,则返回获胜方,否则返回 None
 """
 print(f'{chessman.Name} ({point.X}, {point.Y})')
 self._checkerboard[point.Y][point.X] = chessman.Value
 
 if self._win(point):
 print(f'{chessman.Name}获胜')
 return chessman
 
 # 判断是否赢了
 def _win(self, point):
 cur_value = self._checkerboard[point.Y][point.X]
 for os in offset:
 if self._get_count_on_direction(point, cur_value, os[0], os[1]):
 return True
 
 def _get_count_on_direction(self, point, value, x_offset, y_offset):
 count = 1
 for step in range(1, 5):
 x = point.X + step * x_offset
 y = point.Y + step * y_offset
 if 0 <= x < self._line_points and 0 <= y < self._line_points and self._checkerboard[y][x] == value:
 count += 1
 else:
 break
 for step in range(1, 5):
 x = point.X - step * x_offset
 y = point.Y - step * y_offset
 if 0 <= x < self._line_points and 0 <= y < self._line_points and self._checkerboard[y][x] == value:
 count += 1
 else:
 break
 
 return count >= 5

这里我定义了一个偏移量,我们一共要计算横竖撇捺 4 条线,任意一条线出现 5 连就算获胜。计算方法实际上是一样的,只是方向不同,所以定义一个偏移量数组,不同的偏移量表示不同的方向,这样就可以利用循环来实现了,节省了很多代码。

电脑落子

这就是全篇的重头戏了,要怎么教电脑下五子棋。
首先声明,我用的是相对传统的方式,不是深度学习。

五子棋就是要实现 5 连,所以,一开始,我的想法是:将所有连线保存在一个数组中,落子的时候选择最长的连线落子。但这样有个问题解决不掉,如何让电脑识别“三三”呢?

后来网上看到篇文章,使用的方法是:遍历棋盘上的空位,计算每一个位置其横竖撇捺 8 个方向上是否有己方的子,有一个就加 10 分,最后选得分最高的位置落子。

这样不太严谨,写出来的电脑估计水平很菜,但是这个思路却是对的,落子就是要找到最值得的地方,那么我们干脆对每一个可落子的地方来做一个评估,选出最优解。

这里我们需要了解一下五子棋的几种基本棋形:连五,活四,冲四,活三,眠三,活二,眠二。

连五

顾名思义,五颗同色棋子连在一起,赢了。

python实现五子棋人机对战游戏

活四

四颗同色棋子连在一起,并且左右两边都没有对方棋子阻挡,有两个连五点。

python实现五子棋人机对战游戏

冲四

四颗同色棋子连在一起,并且一边有对方棋子阻挡,或者四颗棋子不是连的,当中有个空挡,这时只有一个连五点。

python实现五子棋人机对战游戏

活三、跳活三

活三:三颗同色棋子连在一起。

python实现五子棋人机对战游戏

跳活三:中间隔了一个空格的活三。

python实现五子棋人机对战游戏

眠三

只能够形成冲四的三,无外乎两种情况,一是一边被挡住了,一是当中有 2 个空格。(其实我在代码中仅考虑了第一种情况,即便形成冲四,也不是什么危险局面。)

python实现五子棋人机对战游戏

活二和眠二

活二,能够形成活三的二;眠二,能够形成眠三的二。这里就不放图了,参考活三眠三。

打分机制

理解了这些棋形,那么按我们之前的思路,就是如何打分了。

  • 首先,连五肯定是不存在的,出现连五胜负已分,所以只要棋局还在进行中,就不会出现连五。那么,什么优先级最高?自然就是活四了。
  • 其次是对方的“四”,对方活四,你防不防都一样输了,对方冲四,你就必须防守。
  • 再次是我方的活三或冲四,活三跟冲四其实是一个级别的,对方必须防守。
  • 再次是对方的活三或冲四。

以此类推下去。我们可以总结一点规律:

  • 相同的棋形,我方优于对方。
  • 冲四跟活三一个级别,眠三跟活二一个级别。
  • 如果中间有空格的话,肯定是要比没空格的略微低级一点,但不至于降级。

基本逻辑就是这样,这一块的代码我写得也不好,整个判断写了100多行,就不贴代码了,大家可以直接下源码看。

五子棋执黑是必赢的,代码中,玩家就是执黑先手,电脑执白后手,所以,下的好是完全可以赢电脑的,不过一个小小失误也很可能被电脑翻盘。

更多关于python游戏的精彩文章请点击查看以下专题:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python实现2048小游戏
Mar 30 Python
Python中用format函数格式化字符串的用法
Apr 08 Python
Django发送html邮件的方法
May 26 Python
Python下rrdtool模块的基本使用方法
Nov 13 Python
Python基于TCP实现会聊天的小机器人功能示例
Apr 09 Python
Python 中包/模块的 `import` 操作代码
Apr 22 Python
Django-Model数据库操作(增删改查、连表结构)详解
Jul 17 Python
使用python将excel数据导入数据库过程详解
Aug 27 Python
Window版下在Jupyter中编写TensorFlow的环境搭建
Apr 10 Python
parser.add_argument中的action使用
Apr 20 Python
python3操作redis实现List列表实例
Aug 04 Python
Python使用plt.boxplot()函数绘制箱图、常用方法以及含义详解
Aug 14 Python
PyCharm 创建指定版本的 Django(超详图解教程)
Jun 18 #Python
使用python实现简单五子棋游戏
Jun 18 #Python
Pycharm新手教程(只需要看这篇就够了)
Jun 18 #Python
pyqt5 获取显示器的分辨率的方法
Jun 18 #Python
PyQt5 实现字体大小自适应分辨率的方法
Jun 18 #Python
Python3网络爬虫中的requests高级用法详解
Jun 18 #Python
在Qt5和PyQt5中设置支持高分辨率屏幕自适应的方法
Jun 18 #Python
You might like
php switch语句多个值匹配同一代码块应用示例
2014/07/29 PHP
laravel框架的安装与路由实例分析
2019/10/11 PHP
身份证号码前六位所代表的省,市,区, 以及地区编码下载
2007/04/12 Javascript
jQuery 1.4 15个你应该知道的新特性(译)
2010/01/24 Javascript
JQuery1.4+ Ajax IE8 内存泄漏问题
2010/10/15 Javascript
自己动手开发jQuery插件教程
2011/08/25 Javascript
jquery设置元素的readonly和disabled的写法
2013/09/22 Javascript
js获取客户端外网ip的简单实例
2013/11/21 Javascript
js中的json对象详细介绍
2014/10/29 Javascript
Active控件问题小结(附解决办法)
2016/06/09 Javascript
基于JavaScript表单脚本(详解)
2017/10/18 Javascript
webpack+react+antd脚手架优化的方法
2018/04/02 Javascript
vue实现个人信息查看和密码修改功能
2018/05/06 Javascript
angular.js实现列表orderby排序的方法
2018/10/02 Javascript
Vue对象赋值视图不更新问题及解决方法
2019/06/03 Javascript
JS面向对象编程实现的Tab选项卡案例详解
2020/03/03 Javascript
JS实现iframe中子父页面跨域通讯的方法分析
2020/03/10 Javascript
用JS实现选项卡
2020/03/23 Javascript
解决Vue keep-alive 调用 $destory() 页面不再被缓存的情况
2020/10/30 Javascript
Python用Bottle轻量级框架进行Web开发
2016/06/08 Python
python内置函数:lambda、map、filter简单介绍
2017/11/16 Python
python 反向输出字符串的方法
2018/07/16 Python
从0开始的Python学习016异常
2019/04/08 Python
python多进程并发demo实例解析
2019/12/13 Python
PyTorch笔记之scatter()函数的使用
2020/02/12 Python
Python从文件中读取数据的方法步骤
2020/11/18 Python
英国最大的宝石首饰超市:QP Jewellers
2018/09/23 全球购物
Saks Fifth Avenue澳洲/亚太地区:萨克斯第五大道精品百货店
2019/06/09 全球购物
乌克兰珠宝大卖场:Zlato.ua
2020/09/27 全球购物
食品厂厂长岗位职责
2014/01/30 职场文书
手机银行营销方案
2014/03/14 职场文书
专家推荐信模板
2014/05/09 职场文书
培训督导岗位职责
2015/04/10 职场文书
2015学校年度工作总结
2015/05/11 职场文书
《坐井观天》教学反思
2016/02/18 职场文书
大学生创业,为什么都会选择快餐饮?
2019/08/08 职场文书