基于 pygame 的五子棋小游戏

1、摘要

  通过python的pygame库,实现一款支持人机对战和人人对战的五子棋小游戏。在游戏初始界面会有5秒的规则介绍,五秒后自动进入人机对战模式。玩家可根据游戏中的提示按下Q键切换为初始人人对战对接,按下E键切换为初始人机对战界面。在游戏进行过程中,程序会记录并显示黑白两子的累计获胜局数,提高玩家体验。

完整代码https://github.com/lgc0208/Gobang

2、程序框架

2.1 board.py 负责棋盘类的实现

2.1.1 __int__:构造函数,定义棋盘成员

2.1.2 _getBoard(self):返回_board成员

2.1.3 ifDropChess(self, point):判断是否可以落子

2.1.4 countDirection(self, point, value, offsetX, offsetY):判断横竖撇捺四个方向是否达成五子连珠

2.1.5win(self, point):判断是否胜利

2.1.6dropChess(self, chessMan, point):落子

2.2 machine.py 负责人机对战中机器类的实现

2.2.1 __int__:构造函数,定义机器类成员

2.2.2 getRivalDrop(self, point):得到对手落子位置

2.2.3 getPiece(self, point, offsetX, offsetY, TorF):判断所给位置方向上两格内的落子情况

2.2.4 getDirectionScore(self, point, offsetX, offsetY):统计所给方向上的棋子权重值

2.2.5 getPointScore(self, point):统计机器落子优先级

2.2.6 machineDrop(self):机器落子

2.3 五子棋.py 负责串联起整个程序的实现

2.3.1 printText(screen, font, x, y, text, textColor = (255, 255, 255)):文字打印函数:在屏幕的(x,y)处打印文字,文字颜色默认为白色

2.3.2 drawBoard(screen):刻画棋盘

2.3.3 drawChess(screen, Point, pieceColor):绘制棋子

2.3.4 drawChessInformation(screen, pos, color):绘制填充的圆形

2.3.5 getNextRunner(currentRunner):返回下一个执子方

2.3.6 drawInfomation(screen, font, currentRunner, SumOfBlackWin, SumOfWhiteWin):绘制信息栏

2.3.7 getClick(clickPlace):获取鼠标点击位置

2.3.8 main():主函数

3、关键代码实现

3.1 getDirectionScore(self, point, offsetX, offsetY)

3.1.1基本思想

  先设定偏移量offset分别为(1, 0), (0, 1), (1, 1), (1, -1),便于统计横竖撇捺四个方向上的棋子权重值。再根据每个方向上是否有棋子、棋子的种类和个数、棋子之间的空格数分别设定不同的权重值。根据权重值可以实现机器与人对战的效果。

3.1.2具体实现

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# 统计某方向棋子权重值
def getDirectionScore(self, point, offsetX, offsetY):
countSelf = 0 # 落子处我方连续子数
countOpposite = 0 # 落子处对方连续子数
spaceSelf = None # 我方连续子中有无空格
spaceOpposite = None # 对方连续子中有无空格
blockSelf = 0 # 我方连续子两端有无阻挡
blockOpposite = 0 # 对方连续子两端有无阻挡

# 如果是 1 表示是边上是我方子,2 表示敌方子, 0表示无子
flagPositive = self.getPiece(point, offsetX, offsetY, True)
if flagPositive != 0: # 传入的偏移方向上若存在棋子
for i in range(1, 6): # 循环判断该方向连着几个棋子
x = point.X + i * offsetX
y = point.Y + i * offsetY
# 若加上偏移量后仍在棋盘内
if 0 <= x < self._pointNumber and 0 <= y < self._pointNumber:
if flagPositive == 1: # 若该偏移方向两格内有我方棋子
if self._board[y][x] == self._my.Value: #若该位置有我方棋子
countSelf += 1 # 我方连续棋子数+1
if spaceSelf is False: # 若已经出现过空格,且探测到我方棋子
spaceSelf = True # 空格出现在我方连续棋子之间
elif self._board[y][x] == self._rival.Value: # 若该位置是敌方棋子
blockOpposite += 1 # 敌方棋子受阻挡+1
break # 落子后我方安全,跳出循环
else: # 若该位置不存在棋子
if spaceSelf is None: # 第一次检测到空格时生效
spaceSelf = False # 表示存在空格但不在白子之间
else:
break # 遇到第二个空格退出循环
elif flagPositive == 2: # 若该偏移方向上有敌方棋子
if self._board[y][x] == self._my.Value:
blockOpposite += 1 # 敌方受阻挡+1
break # 我方安全,跳出循环
elif self._board[y][x] == self._rival.Value: # 该位置存在敌方棋子
countOpposite += 1 # 敌方连续棋子数+1
if spaceOpposite is False: # 若第二次出现空格
spaceOpposite = True # 对方连续棋子内出现空格事件为
else:
if spaceOpposite is None: # 若第一次出现空格
spaceOpposite = False # 敌方连续棋子内还未出现空格
else:
break # 若在出现敌方棋子前又出现空格,我方安全,跳出循环
else: # 偏移后触碰到棋盘边界
if flagPositive == 1: # 若为己方棋子
blockSelf += 1 # 己方棋子被堵塞量+1
elif flagPositive == 2: # 若为敌方棋子
blockOpposite += 1 # 敌方棋子被堵塞量+1

if spaceSelf is False: # 若己方连续棋子内不存在空格
spaceSelf = None # 重置
if spaceOpposite is False: # 若对方连续棋子内不存在空格
spaceOpposite = None # 重置

# 将设定的偏移量里的X,Y增量取反,重复上述操作
flagNegative = self.getPiece(point, -offsetX, -offsetY, True)
if flagNegative != 0:
for i in range(1, 6):
x = point.X - i * offsetX
y = point.Y - i * offsetY
if 0 <= x < self._pointNumber and 0 <= y < self._pointNumber:
if flagNegative == 1:
if self._board[y][x] == self._my.Value:
countSelf += 1
if spaceSelf is False:
spaceSelf = True
elif self._board[y][x] == self._rival.Value:
blockOpposite += 1
break
else:
if spaceSelf is None:
spaceSelf = False
else:
break # 遇到第二个空格退出
elif flagNegative == 2:
if self._board[y][x] == self._my.Value:
blockOpposite += 1
break
elif self._board[y][x] == self._rival.Value:
countOpposite += 1
if spaceOpposite is False:
spaceOpposite = True
else:
if spaceOpposite is None:
spaceOpposite = False
else:
break
else:
if flagNegative == 1:
blockSelf += 1
elif flagNegative == 2:
blockOpposite += 1
'''
权重值划分:
(己方连续四子>敌方连续四子)>(己方连续三子无阻挡>敌方连续三子无阻挡)>(己方连续三子有一个阻挡&&己方连续两子无阻挡
>敌方连续三子有阻挡&&敌方连续两子无阻挡)>(己方连续两子有阻挡>敌方连续两子有阻挡)
无空格>有空格,两种情况应在同一数量级(紧跟在括号后)
优先级量化 8 10 80 100 800 1000 8000 10000 五组(当数值相近的时候会变成人工智障,不知为啥)
'''
score = 0 # 初始化权重值,判断落子选择的优先级
if countSelf == 4: # 若己方连续四子
score = 10000 # 优先级参考备注
elif countOpposite == 4: # 若敌方连续四子
score = 8000 # 优先级参考备注
elif countSelf == 3: # 若我方连续三子
if blockSelf == 0: # 若我方连续三子无阻挡
score = 1000 # 优先级参考备注
elif blockSelf == 1: # 若我方连续三子中有一个阻挡
score = 100 # 优先级参考备注
else:
score = 0 # 优先级最低
elif countOpposite == 3: # 若敌方连续三子
if blockOpposite == 0: # 若敌方连续三子无阻挡
score = 800 # 优先级参考备注
elif blockOpposite == 1: # 若敌方连续三子中有一个阻挡
score = 80 # 优先级参考备注
else:
score = 0 # 优先级最低
elif countSelf == 2: # 若己方连续两子
if blockSelf == 0: # 若己方两子间没有阻挡
score = 100 # 优先级参考备注
elif blockSelf == 1: # 若两子间有一个阻挡
score = 80 # 优先级参考备注
else:
score = 0 # 优先级最低
elif countOpposite == 2: # 若敌方连续两子
if blockOpposite == 0: # 若敌方两子间没有阻挡
score = 10 # 优先级参考备注
elif blockOpposite == 1: # 若敌方两子间有一个阻挡
score = 8 # 优先级参考备注
else:
score = 0 # 优先级为0
elif countSelf == 1: # 若己方只有单个落子
score = 10 # 优先级参考备注
elif countOpposite == 1: # 若对方只有单个落子
score = 8 # 优先级参考备注
else:
score = 0 # 优先级最低

if spaceSelf or spaceOpposite: # 若己方或对方连续棋子内存在空格
score /= 2 # 优先级降低

return score # 返回优先级

3.2 countDirection(self, point, value, offsetX, offsetY)

3.2.1基本思想

  设定循环,判断棋子的横竖撇捺八个方向是否存在五个相同的棋子,若存在,则获胜。

3.2.2具体实现

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
#通过横竖撇捺四个方向计算是否五子连珠
def countDirection(self, point, value, offsetX, offsetY):
count = 1 # 计算连珠个数

# 判断所下棋子右侧是否五子连珠
for i in range(1, 5):
x = point.X + i*offsetX
y = point.Y + i*offsetY
if 0 <= x < self._linePoints \
and 0 <= y < self._linePoints \
and value == self._board[y][x]:
count += 1
else:
break

# 判断所下棋子左侧是否五子连珠
for i in range(1, 5):
x = point.X - i*offsetX
y = point.Y - i*offsetY
if 0 <= x < self._linePoints \
and 0 <= y < self._linePoints \
and value == self._board[y][x]:
count += 1
else:
break

judgeWin = (count >= 5) # 判断是否达成五子连珠,达成为True
return judgeWin # 返回判断结果

3.3 getClick(clickPlace)

3.2.1基本思想

  通过pygame库获取鼠标的点击位置,并根据棋盘建立时设置的长、宽、高将其转换为棋盘上的坐标,从而方便进行棋子的绘制。

3.2.2具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 获取鼠标点击位置,传入参数为pygame库获取的鼠标点击位置
def getClick(clickPlace):
placeX = clickPlace[0] - BOARD_START_PLACE # 点击的位置在棋盘中的横坐标
placeY = clickPlace[1] - BOARD_START_PLACE # 点击的位置在棋盘中的纵坐标
if placeX < -INSIDE_WIDTH or placeY < -INSIDE_WIDTH: # 若越界
return None
x = placeX // POINT_SIZE # 以棋子的大小为单位计算
y = placeY // POINT_SIZE

# 修正点击位置,当用户点击位置与交点有偏差时自动修正
if placeX % POINT_SIZE > PIECE_RADIUS_LEFT:
x += 1
if placeY % POINT_SIZE > PIECE_RADIUS_LEFT:
y += 1
if x >= POINT_NUMBER or y >= POINT_NUMBER: # 恰好在中间位置时不修正
return None
return Point(x, y) # 返回游戏区的坐标

4、不足

  在代码的编写过程中,由于许多基础知识的不熟练,导致了很多基础语法都需要上网去查相关的用法。此外,虽然对人机对战中的机器人进行了很多版的迭代,也参考了网络上前人使用的设置权值的方式进行判断,但是还是能够比较轻松地胜过电脑。对于电脑落子的算法还有很大提升空间。

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 lgc0208@foxmail.com
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信