本文代码基于 python3.6 和 pygame1.9.4。
五子棋比起我之前写的几款游戏来说,难度提高了不少。如果是人与人对战,那么,电脑只需要判断是否赢了就可以。如果是人机对战,那你还得让电脑知道怎么下。
我们先从简单的问题来看。
开端
画棋盘
首先肯定是要画出棋盘来,用 pygame 画出一个 19 × 19 或 15 × 15 的棋盘并不是什么难事,这在之前的文章中已经多次用到,就不赘述了。
画棋子
需要说一下的是画棋子,因为没找到什么合适的棋子图片,所以只要自己来画棋子。
我们用 pygame.draw.circle 画出来的圆形是这样的:
锯齿状十分明显,pygame.draw 中有画抗锯齿直线的函数 aaline,但是并没有 aacircle 这样的函数来画一个抗锯齿的圆。
这里就需要用到 pygame.gfxdraw 啦。pygame.gfxdraw 目前还仅是实验版本,这意味着这个 api 可能会在以后的 pygame 版本中发生变化或消失。
要绘制抗锯齿和填充形状,请首先使用函数的aa *版本,然后使用填充版本。例如:
1
2
3
4
|
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)
|
我们用这个方法在棋盘上画一个棋子试试看。
可以看到效果已明显改善。
落子
落子需要判断鼠标事件,当鼠标左键点击,获取鼠标点击的位置,然后根据棋盘的位置,计算出棋子落在棋盘的位置。
1
2
3
4
5
6
7
8
9
|
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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
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 分,最后选得分最高的位置落子。
这样不太严谨,写出来的电脑估计水平很菜,但是这个思路却是对的,落子就是要找到最值得的地方,那么我们干脆对每一个可落子的地方来做一个评估,选出最优解。
这里我们需要了解一下五子棋的几种基本棋形:连五,活四,冲四,活三,眠三,活二,眠二。
连五
顾名思义,五颗同色棋子连在一起,赢了。
活四
四颗同色棋子连在一起,并且左右两边都没有对方棋子阻挡,有两个连五点。
冲四
四颗同色棋子连在一起,并且一边有对方棋子阻挡,或者四颗棋子不是连的,当中有个空挡,这时只有一个连五点。
活三、跳活三
活三:三颗同色棋子连在一起。
跳活三:中间隔了一个空格的活三。
眠三
只能够形成冲四的三,无外乎两种情况,一是一边被挡住了,一是当中有 2 个空格。(其实我在代码中仅考虑了第一种情况,即便形成冲四,也不是什么危险局面。)
活二和眠二
活二,能够形成活三的二;眠二,能够形成眠三的二。这里就不放图了,参考活三眠三。
打分机制
理解了这些棋形,那么按我们之前的思路,就是如何打分了。
- 首先,连五肯定是不存在的,出现连五胜负已分,所以只要棋局还在进行中,就不会出现连五。那么,什么优先级最高?自然就是活四了。
- 其次是对方的“四”,对方活四,你防不防都一样输了,对方冲四,你就必须防守。
- 再次是我方的活三或冲四,活三跟冲四其实是一个级别的,对方必须防守。
- 再次是对方的活三或冲四。
以此类推下去。我们可以总结一点规律:
- 相同的棋形,我方优于对方。
- 冲四跟活三一个级别,眠三跟活二一个级别。
- 如果中间有空格的话,肯定是要比没空格的略微低级一点,但不至于降级。
基本逻辑就是这样,这一块的代码我写得也不好,整个判断写了100多行,就不贴代码了,大家可以直接下源码看。
五子棋执黑是必赢的,代码中,玩家就是执黑先手,电脑执白后手,所以,下的好是完全可以赢电脑的,不过一个小小失误也很可能被电脑翻盘。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/guliang21/article/details/86758126