文学起点网
当前位置: 首页 文学百科

python五子棋编程教程(Python游戏五子棋之人机对战)

时间:2023-08-01 作者: 小编 阅读量: 4 栏目名: 文学百科

开端画棋盘首先肯定是要画出棋盘来,用pygame画出一个19×19或15×15的棋盘并不是什么难事,这在之前的文章中已经多次用到,就不赘述了。pygame.gfxdraw目前还仅是实验版本,这意味着这个API可能会在以后的pygame版本中发生变化或消失。这个类对外提供一个落子方法drop,接收参数落子方和落子坐标,如果落子后胜利,则返回胜利者,否则返回None。活四四颗同色棋子连在一起,并且左右两边都没有对方棋子阻挡,有两个连五点。

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

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

开端

画棋盘

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

画棋子

需要说一下的是画棋子,因为没找到什么合适的棋子图片,所以只要自己来画棋子。

我们用 pygame.draw.circle 画出来的圆形是这样的:

锯齿状十分明显, 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)

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

加群:960410445 即可获取源码!

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

落子

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

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.Xstep * x_offset y = point.Ystep * 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 分,最后选得分最高的位置落子。

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

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

连五

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

活四

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

冲四

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

活三、跳活三

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

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

眠三

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

活二和眠二

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

打分机制

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

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

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

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

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

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

    推荐阅读
  • 红枫树苗怎么养(红枫树苗的养殖方法)

    红枫树苗怎么养种植时间红枫树苗适合在春季三至五月份,和秋季九月份进行种植,此时的气候较为凉爽,并且空气中的湿度较高,能够提高红枫树种子的发芽速度,让植株生长的更加健壮。处理种子在培育红枫树苗前,要先将种子浸泡到清水中,等到半小时后,将漂浮在水面的干瘪种子挑出,然后将剩余的红枫树种子继续浸泡一天,等到种子露白后,再将其捞出放置在阴凉环境下晾干备用。

  • 荔枝授粉的正确方法(如何提高荔枝授粉率及注意事项)

    荔枝授粉的正确方法利用昆虫授粉:传粉以蜜蜂最佳,可在荔枝园放蜂,蜜蜂活动半径一般300米左右,果园内每3~5亩左右放置一群蜜蜂效果好。在蜂蝇传粉期间应暂停喷施农药。摇落凋谢的花朵和积水,可减少花穗积水而造成的花穗霉烂死亡,减少霜霉疫病侵染。可在每天上午8~10时、下午3~4时,应向树冠及花穗喷清水,同时地面淋、灌水,以增加空气湿度,有降温和保湿作用,有利于授粉。

  • 有虐有甜的现代言情小说(我心中的言情小说TOP泡沫之夏)

    尽管她不喜欢欧辰,但是因为他能让养父母不被公司开除,能保证他们一家衣食无忧,尹夏沫可以讨好欧辰。洛熙对尹夏沫患得患失,最终两人还是分开了。虽然尹夏沫不爱欧辰,但是她并没有因为欧辰逼迫自己嫁给他而怨恨他,而是作为一个妻子照顾他。尹夏沫最终爱上欧辰弟弟去世后,欧辰的悉心照顾让尹夏沫最终爱上了欧辰,尹夏沫因为怀孕走出了弟弟去世的阴影,最终一家三口幸福的生活。

  • 仙女果怎样储存(仙女果储存的方法)

    仙女果怎样储存阴凉处晾晒保存姑娘果最常见的保存方法就是用袋子装起来,放在阴凉、通风、干燥的地方晾晒保存。加工保存姑娘果也可以加工制成蜜饯、果酱以及果酒,延长其保存时间。姑娘果的存放环境应干燥,所以不用放冰箱,平时放在阴凉、通风处,晾晒着就可以放很久,不剥皮不用水洗的前提下,放半个月都没问题。但如果姑娘果去了表皮,且已经用水洗过了,晾晒保存容易坏,可放入冰箱冷藏延长食用时间。

  • 口袋妖怪漆黑的魅影5.0二周目下(口袋妖怪-漆黑的魅影5.0EX)

    资源名称:口袋妖怪-漆黑的魅影5.0EX资源版本:V和雨资源大小:9.63MB资源介绍:内置作弊菜单索罗亚种族值增加

  • 母亲节送什么花好(母亲节送什么花好最适合最便宜)

    康乃馨是世界上许多文化中“母亲节”的官方鲜花,最初康乃馨是用来纪念去世的母亲,如今,康乃馨多用来表示“感谢”。康乃馨的含义包括迷恋,与众不同和爱。浅红色康乃馨经常被用来表达敬佩;而深红色的则表达了更深的爱和情感;粉红色康乃馨通常是表示感谢的象征。在20世纪初期,康乃馨成为母亲节的正式花。

  • 云南马铃薯种产量(云南德宏马铃薯收获忙)

    3月16日,农民使用机械设备收获马铃薯。近日,云南德宏傣族景颇族自治州芒市风平镇迎来马铃薯收获季,田间地头呈现一派繁忙的早春收获景象。新华社记者王冠森摄3月16日,农民展示收获的马铃薯。

  • 白灼虾怎么去虾线(在家做虾如何去虾线)

    将虾壳穿破,然后轻轻地将黑线挑出注意,最好不要挑断黑线,以免多次挑黑线剜出虾的肉,现在小编就来说说关于白灼虾怎么去虾线?下面内容希望能帮助到你,我们来一起看看吧!白灼虾怎么去虾线将虾壳穿破,然后轻轻地将黑线挑出。虾壳含钙量很高,可以保留。在用牙签刺壳时,尽量不要连同虾壳一起去掉。要是做虾仁则例外。去线过程中,有时出现尾部的黑线难以挑出,用水冲洗。

  • 3d打印可以用于骨头手术吗(为患者量身定制)

    而且一穿少则3个月,多则一年甚至几年。幸运的是,陈女士接受了3D打印人工椎体置换,目前颈部功能已恢复正常。像陈女士一样受益于这项新技术的患者有很多。人体脊柱当中和周围走行着重要的脊髓、神经和血管,长久以来,脊柱肿瘤的外科治疗过程,充满了未知和险境,稍有差池,患者就会永久瘫痪甚至失去生命。刘忠军带领团队,多年致力于脊柱肿瘤的治疗研究。就在前不久,34岁的林女士因车祸面临截肢。

  • 徐州苏宁广场是位于市中心吗(徐州第三座苏宁广场开工)

    徐州彭城苏宁广场2017年4月,徐州彭城苏宁广场盛大开业,以50万方磅礴之姿,266米巅峰高度,刷新城市天际线。自开业以来,徐州彭城苏宁广场持续引入品牌首店、旗舰店,多次联合市政府、商务部门开展消费促进活动,是徐州乃至整个淮海经济区的潮流最前线与客流汇聚地,收获了极佳的服务口碑。