Python实现支持人机对战的五子棋软件(超详细)

完整工程下载链接

利用pygame实现一个支持双人对战以及人机对战的小游戏

最终效果展示

在这里插入图片描述

总体框架介绍

windows.py负责处理素材图片以及将图片导入pygame。menu.py负责各级菜单的具体功能实现,包括菜单的绘制,点击效果,鼠标移动效果,点击返回等等。easypc.py是实现简单人机对战的主要程序,我采用了最简单的打分表,没有录入棋谱,也没有用博弈论决策树alphabeta剪枝,但是同样达到了较好的效果。judgewin.py主要实现对终局棋盘胜负判断。因为python无法直接实现跨文件的全局变量传输,(global 关键字可以定义一个变量为全局变量,但是这个仅限于在一个模块(py文件)中调用全局变量,在另外一个py文件 再次使用 global x 也是无法访问到的)所以我专门定义了一个全局变量管理模块gloval.py来管理全局变量。
在这里插入图片描述

具体功能以及算法思想

一、主界面与棋盘设计

  1. 采用了图形化的界面,一共有四张图片组成,分别是背景图片,棋盘图片,黑子图片,白字图片,菜单为绘图函数绘制。
  2. 在上部可以看到程序和作者信息:五子棋 by Ace Cheney。
  3. 当鼠标碰到菜单时,鼠标图标会由指针变成准星,同时方框以及字体变黑。
  4. 当点击游戏说明时,会出现游戏的相关介绍。
  5. 当点击开始游戏时,会进入二级菜单。
  6. 当点击双人人对战时,会进入三级菜单。此时鼠标移动到棋盘,棋子会显示出来。
  7. 当点击人机对战时,会进入四级菜单,让玩家选择是否先手。
  8. 当点击悔棋,产生悔棋效果。
  9. 当点击返回上级菜单时,返回上级菜单。
  10. 当落子后,会在每个落子上显示棋子的序号,且在最后一个棋子上面显示方框。

二、移位与胜负判定

  • 对于移位操作,首先要判断棋子是佛在棋盘内,如果在棋盘内,则显示棋子,如果不在棋盘内,则不显示棋子。
  • 对于在棋盘内的情况,需要先找到每个横纵交叉点的位置,即相对于棋盘和游戏界面的横纵坐标。
  • 之后利用round函数四舍五入鼠标点击的坐标值,取整且转换后的结果作为落子的坐标
  • 判断此落子位置是否为空,如果不为空则说明已经有落子,不能继续落子。
  • 对于胜负判定。分成三种情况,和棋,白棋获胜,黑棋获胜。
  • 当棋盘上每个交叉点都有落子,且不满足白棋获胜或者黑棋获胜,则判为和棋。
  • 当横向,纵向,左斜,右斜有五个白子或者五个黑子,则判为白子胜,或者黑子胜。

三、棋型价值设计

在实现人机对战功能时,核心思想是电脑计算出棋盘上每个格子的分数,之后选择分数最高的格子落子,这也就意味着需要通过一张打分表来进行评分。打分规则具体设计如下。

  • 一个交叉点视作一个位置,五个连续的位置视为一个组。
  • 计算每个组的分数,做累加,就得到总体得分。
  • 当没有棋子时,该位置得到7分。
  • 当有一个己方棋子时,该位置得到35分
  • 当有两个己方棋子时,该位置得到800分
  • 当有三个己方棋子时,该位置得到15000分
  • 当有四个己方棋子时,该位置得到800000分
  • 当有一个对方棋子时,该位置得到15分
  • 当有两个对方棋子时,该位置得到400分
  • 当有三个对方棋子时,该位置得到8000分
  • 当有四个对方棋子时,该位置得到100000分
  • 当双方棋子都在棋盘存在时,该位置得0分。
  • 在此基础上,遍历整个棋盘,找到整个棋盘上评分最高,次高,第三高的位置。
  • 在分数误差50以内,在最高,次高,第三高内随机选择一个位置落子。
  • 在分数误差100以内,在最高,次高内随机选择一个位置落子。
    最后三条是为了在玩家进行落子的时候走出不同的落子,以增加游戏性。

四、人机模式和双人模式的设计

  • 在主菜单中设计人机模式和双人模式菜单
  • 在双人模式中,黑方先落子,之后白方再落子,此视为一个回合。
  • 在人机模式中,分为两个子菜单。分别是电脑先手,和玩家先手。
  • 如果是电脑先手,则默认下在棋盘的最中央,载入相关信息后,按照人落子,电脑落子的先后顺序,视为一个回合。
  • 如果是人先手,则在人落子后,计算棋盘上空余格点的分数,之后按照相应要求落子。

五、游戏状态

在不同游戏状态下进行相同的操作可能会产生不同的结果,所以处理事件响应时要先判断游戏状态,再作出相应的反馈,这里我把游戏状态一共分成七类。

  • 1为主菜单状态
  • 2为人机和双人对战选择状态
  • 3为人机对战先后手选择状态
  • 4为双人对战状态
  • 5为人机对战,而且人先手状态
  • 6位人机对战,而且电脑先手状态
  • 7为游戏结束状态,即当一方赢棋或者和棋的状态

代码详解

wuziqi. py (核心模块)

主函数
  • 首先需要导入pygame,sys,numpy模块,以及自己的模块,在主函数中主要完成三件事,分别是素材的预处理和pygame的初始化,这两步都在自己创建的windows模块中
  • main()函数放在循环中,因为每次重新开始游戏,都会重新执行一次main函数。
#--coding:utf-8--
import pygame as pg
from sys import exit #用exit来退出程序
import windows as wd
import menu
import gloval 
import numpy
import judgewin
import easy_pc
gloval.init()
def main():
	wd.Image_PreProcessing()
	wd.windows()
	mainloop()
if __name__=='__main__':
	while 1:
		main()
		
程序主循环 : mainloop()
  • while restart == 0之前,是进行各种参数的初始化。将在用到每个参数时详细说明,这里不过多赘述,下面介绍循环内的详细过程

  • gamestate是游戏状态的标识符,默认为1。1为主菜单状态,2为人机和双人对战选择菜单状态,3为人机对战先后手选择菜单,4,5,6为对战菜单,7为结束游戏菜单,以下图为例,这是对战菜单状态,也就是说gamestate==4或者5,6,在这种状态下才会显示“悔棋”菜单,“重新开始”菜单,而且当光标移到棋盘上时,会将光标变成棋子的图标,而在其他状态下kennel不会显示“悔棋”菜单和“重新开始”菜单,而且当光标移动到棋盘上时,外观不会发生改变。
    在这里插入图片描述

  • 之后按照画背景,画菜单,顺序运行两个函数,这样可以保证棋盘和菜单显示在背景图片之上。

  • 在画好界面后,需要实现相关的功能,这些功能全部都是通过鼠标反馈的,包括鼠标的移动和点击事件。比如,当鼠标在不同游戏状态下移动到棋盘或者移动到菜单位置上,光标的样式都会不同。点击菜单,落子,也会产生不同的反馈。这些功能全部是通过drawpress和drawmove两个函数实现的。

  • 之后判断是否赢棋,以及更新赢棋后的界面。

  • 在完成一帧内的所有操作以及判断后,刷新界面,查看restart的状态以判断用户是否退出程序或者点击重新开始

def mainloop():
	#设置默认光标样式
	pg.mouse.set_cursor(*pg.cursors.arrow)
	gloval.setval('restart',0)
	global restart
	
	restart=gloval.getval('restart')
	gloval.setval('press_intro', 0)
	gloval.setval('press_regret', 0)
	global gamestate 
	gamestate=1
	gloval.setval('gamestate', 1)
	global press,button
	press=(0,0,0)
	button=(132,71,34)#字体颜色
	global screen
	screen=gloval.getval('screen')
	global imBackground
	imBackground=gloval.getval('imBackground')
	global imChessboard
	imChessboard=gloval.getval('imChessboard')
	global imBlackPiece
	imBlackPiece=gloval.getval('imBlackPiece')
	global imWhitePiece
	imWhitePiece=gloval.getval('imWhitePiece')
	global whiteround #回合,黑子先走,whiteround为-1
	whiteround=[-1]
	global chess_array#储存双方落子信息,0246先手,1357后手
	chess_array=[]
	global	chess_num
	chess_num=0
	global piece_x,piece_y,piece
	FPS = 60
	piece_x=[]
	piece_y=[]
	piece=[]
	
	while restart == 0:
		#刷新gamestate
		gamestate=gloval.getval('gamestate')
		#画背景,左上角是坐标	
		drawbg()	
		#画菜单
		drawmenu()
		#鼠标事件按键情况
		drawpress()
		# 画鼠标移动的相关效果
		drawmove()
		#判断是否赢棋
		judgewin.check_win(chess_array)
		#刷新画面
		pg.display.update() 
		#调整游戏帧数
		FPSClock=pg.time.Clock()
		FPSClock.tick(FPS)
		restart=gloval.getval('restart')
画背景 : drawbg()
  • 背景图片从窗口的(0,0)位置,也就是最左上角开始放置,因为签名图像剪切使背景图片大小等于窗口大小,所以铺满整个窗口。
  • 棋盘图片显示同理。
def drawbg():
	##画背景
	screen.blit(imBackground,(0,0)) 
	global chessboard_start_x
	global chessboard_start_y
	chessboard_start_x=50			#(1024-(1024-540))/2
	chessboard_start_y=(768-540)/2		
	screen.blit(imChessboard,(chessboard_start_x,chessboard_start_y))
画菜单 : drawmenu()

对于每一种游戏状态下的菜单都不相同,具体实现方法见menu模块

def drawmenu():
    ##画菜单
	if gamestate==1:				
		menu.menu1()	#画’开始游戏‘,‘游戏说明’,’结束游戏‘按钮
	elif gamestate==2:				
		menu.menu2()	#画 ‘人机对战’,‘双人对战’,'返回上级菜单',‘结束游戏’
	elif gamestate==3:				
		menu.menu3()	#画 ‘玩家先手’,‘电脑先手’,'返回上级菜单',‘结束游戏’
	elif gamestate==4 or gamestate==5 or gamestate==6 :
		menu.menu4()	#画‘悔棋’,‘重新开始’,‘结束游戏’按钮
	elif gamestate==7:				
		menu.menu7()	#画‘重新开始’,‘结束游戏’按钮
		
鼠标移动 :drawmove()
  • 通过pygame的mouse函数获取鼠标的坐标
  • 在4,5,6游戏状态下(都是对战状态),当光标移动到棋盘时,光标才会变成白子或者黑子。
  • 在对应的游戏状态下,光标移动到菜单,会得到相应的反馈,具体实现见menu模块。
	##画鼠标移动的相关效果
def drawmove():

	gloval.setval('mouse_x', pg.mouse.get_pos()[0])	# 鼠标的坐标
	gloval.setval('mouse_y', pg.mouse.get_pos()[1])
	mouse_x,mouse_y = pg.mouse.get_pos()			# 棋子跟随鼠标移动
	if chessboard_start_x<mouse_x<chessboard_start_x+540 and chessboard_start_y<mouse_y<chessboard_start_y+540 and (gamestate==4 or gamestate== 5 or gamestate== 6):
		if whiteround[chess_num]==1:
			screen.blit(imWhitePiece,(mouse_x-16,mouse_y-16))
		else:
			screen.blit(imBlackPiece,(mouse_x-16,mouse_y-16))
	elif gamestate==1:
		menu.movemenu1()
	elif gamestate==2:
		menu.movemenu2()
	elif gamestate==3:
		menu.movemenu3()
	elif gamestate==4  or gamestate==5 or gamestate==6 :
		menu.movemenu4()
	elif gamestate==7:
		menu.movemenu7()
		
鼠标点击:drawpress()
  • 在 for event in pg.event.get()之前为初始化操作,在用到时将会详细说明,下面将详细说明循环内的步骤
  • 首先,通过pygame的event.get获取鼠标点击事件
  • 当把程序X掉时通过pg.quit关闭窗口,通过sys.exit结束程序
  • pygame.mousebuttondown获取鼠标点击事件
  • 在两种情况下点击鼠标会得到反馈,一种是点击菜单,另一种是在对战状态下点击棋盘进行落子。
  • 点击棋盘某个位置进行落子时分为两种情况:当双人对战时,只需要返回鼠标点击的位置,进行后续处理,当人机对战时,因为我默认电脑先手落子于棋盘正中间,之后进行“先人后机”的过程,因此都只需要返回鼠标点击位置,之后在进行一步电脑落子即可。
  • ifem是ifempty的缩写,用于判断落子点是否为空位,如果是空位才能落子,否则不能落子
  • 在点击菜单时,同样需要判断游戏当前状态,执行对应操作,具体实现见menu模块
  • 在落子成功后,需要画棋子和上面的数字,以及最后一个棋子上面的框框。
  • pressed_x,pressed_y是鼠标点击的坐标
def drawpress():

	global whiteround
	global chess_array
	global d
	global chess_num
	global piece_x,piece_y,piece
	press_intro=gloval.getval('press_intro')
	press_regret=gloval.getval('press_regret')
	d = (518-22)/14  			 	#(1,1)的实际坐标为(22,22),(15,15)的实际坐标为(518,518),有14个间隔
	for event in pg.event.get():	#获取鼠标点击事件
		if event.type==pg.QUIT:
			pg.quit()
			exit()
		if event.type == pg.MOUSEBUTTONDOWN:
			gloval.setval('pressed_x', event.pos[0])
			gloval.setval('pressed_y', event.pos[1])
			pressed_x
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ace Cheney

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值