1、摘要
通过python的pygame库,实现一款支持人机对战和人人对战的五子棋小游戏。在游戏初始界面会有5秒的规则介绍,五秒后自动进入人机对战模式。玩家可根据游戏中的提示按下Q键切换为初始人人对战对接,按下E键切换为初始人机对战界面。在游戏进行过程中,程序会记录并显示黑白两子的累计获胜局数,提高玩家体验。
完整代码:https://download.csdn.net/download/weixin_45817309/14989460
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具体实现
# 统计某方向棋子权重值
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具体实现
#通过横竖撇捺四个方向计算是否五子连珠
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具体实现
# 获取鼠标点击位置,传入参数为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、不足
在代码的编写过程中,由于许多基础知识的不熟练,导致了很多基础语法都需要上网去查相关的用法。此外,虽然对人机对战中的机器人进行了很多版的迭代,也参考了网络上前人使用的设置权值的方式进行判断,但是还是能够比较轻松地胜过电脑。对于电脑落子的算法还有很大提升空间。