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,pressed_y=event.pos[0],event.pos[1]
			#第一種情況,人人對戰
			if chessboard_start_x<pressed_x<chessboard_start_x+540 and chessboard_start_y<pressed_y<chessboard_start_y+540 and gamestate==4 :
				player_pos_chess(pressed_x,pressed_y)	
			#第二種情況,玩家先手	
			elif chessboard_start_x<pressed_x<chessboard_start_x+540 and chessboard_start_y<pressed_y<chessboard_start_y+540 and gamestate==5 :
				ifem=player_pos_chess(pressed_x, pressed_y) 	#玩家下棋
				if ifem!=False:
					pc_pos_chess() #電腦下棋
			#第三種情況,電腦先手
			elif chessboard_start_x<pressed_x<chessboard_start_x+540 and chessboard_start_y<pressed_y<chessboard_start_y+540 and gamestate==6:
				ifem=player_pos_chess(pressed_x, pressed_y) 	#玩家下棋
				if ifem!=False:
					pc_pos_chess() #電腦下棋
			#第四種情況,點擊菜單
			
			else:									
				if gamestate==1:
					menu.pressmenu1()
				elif gamestate==2:
					menu.pressmenu2()
				elif gamestate==3:
					if 6==menu.pressmenu3():
						pc_pos_chess()
				elif gamestate==4 or gamestate==5 or gamestate==6 :
					menu.pressmenu4()
				elif gamestate==7:
					menu.pressmenu7()
	restart=gloval.getval('restart')#是否重啓遊戲		
	if chess_array :				#畫棋子和棋子上面的數字不爲空,有落子
		draw_chess(chess_num,whiteround)	#畫棋子和上面的數字
	if press_intro==1: 						#遊戲簡介信息
		draw_intro_text()	
	if press_regret==1:				#悔棋
		regret()
	draw_chesscross(chess_num)#畫最後一個落子位置

遊戲簡介顯示 :draw_intro_text()

  • 當在主菜單頁面點擊“遊戲說明”時顯示相關內容。我在使用系統默認字體的時候總是報錯,所以使用了網上下載的字體文件。

  • render的pygame官方定義文檔如下,button是我定義的顏色,button=(132,71,34)
    在這裏插入圖片描述

  • blit函數是把圖片放到對象上screen.blit(image,position)表示將image放置到screen對象上,位置的兩個座標由position確定,pygame官方文檔對blit的解釋如下
    在這裏插入圖片描述

def draw_intro_text():
	my_font=pg.font.Font('mufont.ttf',25)
	text1=my_font.render("雙方分別使用黑白兩色的棋子,",True,button)
	text2=my_font.render("下在棋盤直線與橫線的交叉點上,",True,button)
	text3=my_font.render("先形成五子連線者獲勝。",True,button)
	screen.blit(text1,(640,100))	
	screen.blit(text2,(640,140))	
	screen.blit(text3,(640,180))	

悔棋 : regret ()

  • 實現方法:黑白雙方每一次落子,chess_num加一,如果chess_num不爲0則代表有落子,可以進行悔棋。
  • 當雙人對戰時,只需要悔一步棋,也就是把最後一步落子的相關信息,落子位置全部刪除,同時落子方更換,比如現在是白方回合選擇了悔棋,在悔棋後將是黑方回合
  • 當人機對戰時,需要一次性悔兩步,一步是玩家,一步是電腦,因爲電腦不存在悔棋,連續悔棋兩步纔會重新輪到玩家回合。
  • 至於chess_array,piece_x,piece_y,piece,whiteround的具體含義,將會在後面詳細介紹。
def regret():
	global chess_num,chess_array,piece_y,piece_x,piece,whiteround
	if chess_num!=0:  #刪除所有儲存的數組
		if gamestate==4:
			del chess_array[-1]
			del piece_x[-1]
			del piece_y[-1]
			del piece[-1]
			del whiteround[-1]
			chess_num=chess_num-1
		if gamestate==5 or gamestate==6:
			del chess_array[-1]
			del chess_array[-1]
			del piece_x[-1]
			del piece_y[-1]
			del piece_x[-1]
			del piece_y[-1]
			del piece[-1]
			del piece[-1]
			del whiteround[-1]
			del whiteround[-1]
			chess_num=chess_num-2
	gloval.setval('press_regret', 0)

畫棋子上的數字 :draw_chess()

  • 實現的效果是在每個棋子上面標註數字,黑子用白字標,白子用黑字標,方便玩家看清落子順序,分析棋局。效果如下:

  • chess_num儲存了棋面上的棋子總數。

  • 不管是人機對戰還是雙人對戰,第一個落子的總是黑子。

  • whileround間隔儲存1和-1,1代表白子回合,-1代表黑子回合。

  • 以黑子落子爲例,screen.blit(imBlackPiece,(piece_x[i]-16,piece_y[i]-16))將黑子的中心恰好放到網格線的交叉點上。

  • i從0開始計數,所以第i 個子上的數字爲i+1。
    將數字恰好放到棋子上,而且先顯示棋子,後顯示數字,這樣數字就不會被棋子覆蓋。

def draw_chess(chess_num,whiteround):
	my_font=pg.font.Font('mufont.ttf',18)
	for i in range(chess_num):
		
		if whiteround[i]==-1:				#黑子
			screen.blit(imBlackPiece,(piece_x[i]-16,piece_y[i]-16))
			text_w=my_font.render(str(i+1),True,(255,255,255))
			screen.blit(text_w,(piece_x[i]-7,piece_y[i]-12))
		else:								#白子
			screen.blit(imWhitePiece,(piece_x[i]-16,piece_y[i]-16))	
			text_b=my_font.render(str(i+1),True,(0,0,0))
			screen.blit(text_b,(piece_x[i]-7,piece_y[i]-12))

畫最後一個棋子的落子位置:draw_chesscross()

  • 如果有落子,那麼就畫一個方框吧最後一個棋子圍住就好,button是自定義的顏色元祖,pygame中draw.rect的解釋文檔如下:
    在這裏插入圖片描述
  • 第三個參數Rect=[A,B,C,D],其中ABCD必須是整數,AB存儲起始位置,CD存儲矩形長寬,width爲矩形邊框的粗細。
  • piece_x和piece_y兩個列表按照落子順序存儲了棋子的橫縱座標。

def draw_chesscross(chess_num):
	if chess_num!=0:
		pg.draw.rect(screen,button,[int(piece_x[chess_num-1]-16),int(piece_y[chess_num-1]-16),33,33],2)
	
		
	##二維座標轉一維索引值

一維索引和二維座標間的轉換:array2index(),index2array()

	##二維座標值轉一維索引
def array2index(array):
	return (array[0]-1)*15+array[1]

	
	##一維索引值轉二維座標
def index2array(index):
	i,j = int((index-index%15)/15+1),int(index%15)
	if j == 0 :
		i -= 1
		j = 15
	return i,j

判斷是否爲空:if_isempty

輸入變量是落子的橫縱座標,和piece中存儲的棋面上已落子的座標列表對比

	##判斷是否爲空
def if_isempty(i,j):
	if [i,j] not in  piece:
		return True
	else:
		return False
座標位置:getpos(),getrealpos()
  • getpos()的作用是吧mousebuttondown返回的座標pressed_x,pressed_y轉變爲棋盤上的位置,比如(1,3),(1,15)如下圖所示。

在這裏插入圖片描述

  • 因爲玩家落子不會正好點擊到網格點上,所以getpos()在實現轉化的時候需要進行近似處理。也就是說點在點在每個紅框內,就會round()到相應的棋盤位置。
    -在這裏插入圖片描述
  • getrealpos()的作用是把棋盤上的位置轉化爲真實的位置,可以理解成getpos的逆操作,return的值就是screen.blit()落子輸入的位置參數。
	##得到棋盤上棋子的數組位置
def getpos(pressed_x,pressed_y):
	mouse_chessboard_x = pressed_x-chessboard_start_x# 鼠標在棋盤中的座標
	mouse_chessboard_y = pressed_y-chessboard_start_y
	i_tmp = round((mouse_chessboard_y-22)/d)+1	# 計算鼠標最接近的格點
	j_tmp = round((mouse_chessboard_x-22)/d)+1
	if i_tmp in range(1,16) and j_tmp in range(1,16):#1到15判斷標號是否有效
		chess_i = i_tmp
		chess_j = j_tmp
		return chess_i,chess_j
		
	##二維座標轉化爲棋盤內的真實座標(計算棋子在棋盤中的位置)(計算棋子的實際位置)
def getrealpos(array):    											
	piece_chessboard_x,piece_chessboard_y = 22+(array[1]-1)*d,22+(array[0]-1)*d
	piece_x,piece_y = piece_chessboard_x+chessboard_start_x,piece_chessboard_y+chessboard_start_y
	return piece_x,piece_y

玩家落子:player_pos_chess()

  • 傳入參數是鼠標點擊的座標位置
  • 執行的操作有:將轉化好的相對於棋盤的落子位置存入chess_array,判斷落子位置是否爲空如果爲空則存入相關信息,如果不爲空則刪除重複的數組
def player_pos_chess(pressed_x,pressed_y):
	global whiteround
	global chess_array
	global chess_num
	global piece_x,piece_y,piece
	chess_array.append(list(getpos(pressed_x,pressed_y)))#記錄位置,黑棋白棋數組交替
	piece_i,piece_j=getrealpos(chess_array[chess_num])#已處理的位置
	isempty=if_isempty(piece_i,piece_j)
	
	if isempty==True:		#如果這個地方沒有棋子		
		whiteround.append(-whiteround[chess_num])
		piece_x.append(piece_i)
		piece_y.append(piece_j)
		piece.append([piece_i,piece_j])
		chess_num+=1
		gloval.setval("chess_array", chess_array)
		return 1
	else:								#	如果這個地方有棋子
		del chess_array[chess_num]		#刪除那個重複的數組		
		return 0

電腦落子:pc_pos_chess()

電腦落子的處理步驟和玩家落子相比唯一的不同是不需要處理鼠標點擊信息,因爲電腦通過算法得到的落子位置就是相對於棋盤的位置,由pc_pressed()得到。

def pc_pos_chess():
	global whiteround
	global chess_array
	global chess_num
	global piece_x,piece_y,piece
	pc_pressed=easy_pc.find_maxscore(chess_array,whiteround[-1])
	chess_array.append(pc_pressed)#記錄電腦下棋位置
	piece_i,piece_j=getrealpos(chess_array[chess_num])#已處理的位置
	isempty=if_isempty(piece_i,piece_j)
	print(isempty)
	if isempty==True:									#如果這個地方沒有棋子
		whiteround.append(-whiteround[chess_num])
		piece_x.append(piece_i)
		piece_y.append(piece_j)
		piece.append([piece_i,piece_j])
		chess_num+=1
		gloval.setval("chess_array", chess_array)
	else:								#	如果這個地方有棋子
		del chess_array[chess_num]		#刪除那個重複的數組	

windows. py (預處理模塊)

導入模塊

  • PIL模塊用於修改素材尺寸,gloval模塊用於管理文件間全局變量。
#--coding:utf-8--
from PIL import Image 
import pygame as pg
from sys import * #用exit來退出程序
import gloval 
gloval.init()

素材處理 : Image_PreProcessing()

  • 因爲棋盤,棋子,背景顏色的素材均來源於網絡,大小不一定符合要求,所有圖片在使用之前需要先進行長寬裁剪操作

def Image_PreProcessing():
	# 圖片存儲路徑	
	im = Image.open('background.jpeg')
	# Resize圖片大小,入口參數爲一個tuple,爲新的圖片大小
	imBackground = im.resize((424,300))
	imBackground.save('1111.jpg','JPEG')

	
	im = Image.open('chessboard.jpeg')
	# Resize圖片大小,入口參數爲一個tuple,爲新的圖片大小
	imBackground = im.resize((540,540))
	imBackground.save('new_chessboard.jpg','JPEG')
	
	
	im = Image.open('chessblack.png')
	# Resize圖片大小,入口參數爲一個tuple,爲新的圖片大小
	imBackground = im.resize((32,32))
	imBackground.save('new_chessblack.png','PNG')

	im = Image.open('chesswhite.png')
	# Resize圖片大小,入口參數爲一個tuple,爲新的圖片大小
	imBackground = im.resize((32,32))
	imBackground.save('new_chesswhite.png','PNG')	
	

加載素材以及pygame的初始化 : windows()

  • 在pygame中可以使用pygame.image.load()函數來加載位圖。(支持jpg,png,gif,bmp,pcx,tif,tga等多種圖片格式),具體方法是image = pygame.image.load(“image.png”).convert_alpha()
  • convert_alpha()方法會使用透明的方法繪製前景對象,這裏我使用的黑子和白子有alpha通道,如果不使用convert_alpha(),棋盤上顯示的將會是如下效果(下圖中白子素材使用了convert_alpha方法導入,但是黑子素材只是用了convert方法)

在這裏插入圖片描述

  • gloval.setval(A, B)的意思是A=B,其中A和B可以是跨文件的全局變量,具體實現方法見gloval .py。
  • gloval.setval(‘screen’, pg.display.set_mode((1024,768),0,32))設置窗口的大小爲1024*768
def windows():
	pg.init()  #初始化pygame,爲使用硬件做準備 
#	screen=pg.display.set_mode((1024,768),0,32)#分辨率,標誌位,色深
	#加載背景和光標圖片
	gloval.setval('screen', pg.display.set_mode((1024,768),0,32))
	gloval.setval('imBackground', pg.image.load('new_background.jpg').convert())
	gloval.setval('imChessboard', pg.image.load('new_chessboard.jpg').convert())
	gloval.setval('imBlackPiece', pg.image.load('new_chessblack.png').convert_alpha())
	gloval.setval('imWhitePiece', pg.image.load('new_chesswhite.png').convert_alpha())

	pg.display.set_caption('五子棋      by Ace Cheney') #設置窗口標題

global. py (全局變量管理模塊)

  • 用字典來保存工程全局變量,getval函數實現全局變量的存入,setval實現全局變量的取出
def init():
	global glodic
	glodic = {}
def setval(name,val):
	glodic[name]=val
def getval(name,defva=None):
	return glodic[name]

menu. py(菜單模塊)

顏色定義:press=(0,0,0),button=(132,71,34)
這一塊主要是實現各個遊戲狀態下的菜單界面以及功能,因爲4,5,6號遊戲狀態菜單相同,所以通過menu4來實現。

在這裏插入圖片描述
下面通過舉例menu1的三個函數,來看看具體的實現方法。

通常情況下的菜單顯示: menu1()

  • pg.draw.rect()方法畫三個菜單按鈕
  • screen.blit()顯示三個文本框
def menu1():
	screen=gloval.getval('screen')

	#畫’開始遊戲‘,‘遊戲說明’,’結束遊戲‘按鈕
	pg.draw.rect(screen,button,[670,260,200,80],5)
	pg.draw.rect(screen,button,[670,410,200,80],5)
	pg.draw.rect(screen,button,[670,560,200,80],5)

	my_font=pg.font.Font('mufont.ttf',45)
	text1=my_font.render("開始遊戲",True,button)
	text2=my_font.render("遊戲說明",True,button)
	text3=my_font.render("結束遊戲",True,button)
	screen.blit(text1,(680,270))
	screen.blit(text2,(680,420))
	screen.blit(text3,(680,570))

當鼠標移動到菜單上: movemenu1()

  • 如果鼠標移動到相應菜單顯示的區域,鼠標的光標樣式改變pg.mouse.set_cursor(),參數*pg.cursors.broken_x是準星
  • 菜單顏色發生變化,顏色從button(132,71,34)變成press(0,0,0)
def movemenu1():
	mouse_x=gloval.getval('mouse_x')
	mouse_y=gloval.getval('mouse_y')
	screen=gloval.getval('screen')
	my_font=pg.font.Font('mufont.ttf',45)

	if 670<mouse_x<870 and 260<mouse_y<340:
		pg.mouse.set_cursor(*pg.cursors.broken_x)
		pg.draw.rect(screen,press,[670,260,200,80],5)
		text1=my_font.render("開始遊戲",True,press)

		screen.blit(text1,(680,270))
	elif 670<mouse_x<870 and 410<mouse_y<490:
		pg.mouse.set_cursor(*pg.cursors.broken_x)

		pg.draw.rect(screen,press,[670,410,200,80],5)
		text2=my_font.render("遊戲說明",True,press)
		screen.blit(text2,(680,420))
	elif 670<mouse_x<870 and 560<mouse_y<640:
		pg.mouse.set_cursor(*pg.cursors.broken_x)

		pg.draw.rect(screen,press,[670,560,200,80],5)
		text3=my_font.render("結束遊戲",True,press)
		screen.blit(text3,(680,570))	
	else:
		pg.mouse.set_cursor(*pg.cursors.arrow)

當鼠標點擊時:pressmenu1()

def pressmenu1():
	screen=gloval.getval('screen')
	my_font=pg.font.Font('mufont.ttf',45)
	pressed_x=gloval.getval('pressed_x')
	pressed_y=gloval.getval('pressed_y')
	if 670<pressed_x<870 and 260<pressed_y<340:#開始遊戲,進入菜單2
		gloval.setval('gamestate', 2)
		gloval.setval('press_intro', 0)

	elif 670<pressed_x<870 and 410<pressed_y<490:#遊戲介紹
		gloval.setval('press_intro', 1)
	elif 670<pressed_x<870 and 560<pressed_y<640:#退出遊戲
		pg.quit()
		exit()
	else :
		gloval.setval('press_intro', 0)	

其他部分原理相同,不再贅述,有特殊功能的特殊解決即可,比如遊戲狀態二的“返回上級菜單”功能,只需要gloval.setval(‘gamestate’, 1)即可,比如悔棋,只需要gloval.setval(‘press_regret’, 1)即可。

easy_pc.py 人機對戰模塊

導入庫以及打分表的設計

值得注意的是:

  • 打分表是給電腦看的,是計算機尋找落子位置的依據。
  • 這裏的打分表不能針對白子或者黑子設計,而應該針對己方和對方設計,因爲不管自己先手或者後手,電腦的評判標準都應該是一樣的。有很多人設計五子棋程序發現人機對戰時自己先手比後手簡單,或者自己先手比後手難,很大可能是打分表的設計有誤。
  • 這裏的分數是一個五連位置將一個五連位置看成整體,對這個五連位置打的分數,五連包括橫五連,豎五連,以及斜五連。
#--coding:utf-8--
from PIL import Image 
import pygame as pg
from sys import exit #用exit來退出程序
import gloval 
import random
gloval.init()
##打分表
tuple_score=[None]*10
tuple_score[0]=7                #沒有子
tuple_score[1]=35				#一個己方子
tuple_score[2]=800				#兩個己方子
tuple_score[3]=15000			#三個己方子
tuple_score[4]=800000			#四個己方子
tuple_score[5]=15				#一個對方子
tuple_score[6]=400				#兩個對方子
tuple_score[7]=8000			#三個對方子
tuple_score[8]=100000			#四個對方子
tuple_score[9]=0				#又有白又有黑

計算一個空位的分數chess_score()

  • 只需要尋找空位計算分數即可,因爲分數是爲電腦落子提供評判依據提供的服務的,已有落子的位置計算機不能再次落子,自然不用計算分數
  • 一個位置的分數是所有包括該位置的五連的分數的總和,以橫五連爲例,包括以下五種情況,如下圖所示:,除了橫五連,還有豎五連,左斜,右斜,一共20種情況
    在這裏插入圖片描述
	##計算一個空位的分數(需要棋譜數組和空位位置,返回該位置的分數)
def chess_score(array,chess_pos,whiteround):
	pos_score=0	
	x=chess_pos[0]
	y=chess_pos[1]
	black_num=0
	white_num=0	
	##豎列
	for i in range(5):#1234  統計豎列所有五元組的得分總和
		for j in range(5):#01234 統計一個五元組的得分
			if [x-j+i,y] in array[::2]:#黑子判斷 #橫向
				black_num+=1
			if [x-j+i,y] in array[1::2]:#白子判斷 #橫向
				white_num+=1
		pos_score=pos_score+chess_tuple_score(black_num, white_num,whiteround)#計算一個元組
		white_num=0
		black_num=0
	##橫列
	for i in range(5):
		for j in range(5):
			if [x,y-j+i] in array[::2]:#黑子判斷 #橫向
				black_num+=1
			if [x,y-j+i] in array[1::2]:#白子判斷 #橫向
				white_num+=1
		pos_score=pos_score+chess_tuple_score(black_num, white_num,whiteround)
		white_num=0
		black_num=0
	##左斜/
	for i in range(5):
		for j in range(5):
			if [x+j-i,y-j+i] in array[::2]:#黑子判斷 #橫向
				black_num+=1
			if [x+j-i,y-j+i] in array[1::2]:#白子判斷 #橫向
				white_num+=1
		pos_score=pos_score+chess_tuple_score(black_num, white_num, whiteround)
		white_num=0
		black_num=0
	##右斜\	
	for i in range(5):
		for j in range(5):
			if [x-j+i,y-j+i] in array[::2]:#黑子判斷 #橫向
				black_num+=1
			if [x-j+i,y-j+i] in array[1::2]:#白子判斷 #橫向
				white_num+=1
		pos_score=pos_score+chess_tuple_score(black_num, white_num, whiteround)
		white_num=0
		black_num=0
	return pos_score

計算一個五連的分數:chess_tuple

字面意思,計算一個五連的分數,別忘了在計算之前先判斷自己是白子還是黑子,判斷方法很簡單,在白方回合的白子就是己方子,黑子就是敵方子,反之亦然。

def chess_tuple_score(black_num,white_num,whiteround):
	if black_num==0 and white_num==0: 	#沒有子
		pos_tuple_score=tuple_score[0]
	elif black_num>0 and white_num>0: 	#又有白又有黑
		pos_tuple_score=tuple_score[9]
	else:                             	#只有黑或者只有白
		if whiteround == -1:
			if black_num != 0:				#計算橫着一格黑子
				pos_tuple_score=tuple_score[black_num]
			if white_num != 0:				#計算橫着一格白子
				pos_tuple_score=tuple_score[white_num+4]
		if whiteround == 1:
			if black_num != 0:				#計算橫着一格黑子
				pos_tuple_score=tuple_score[black_num+4]
			if white_num != 0:				#計算橫着一格白子
				pos_tuple_score=tuple_score[white_num]		
	return pos_tuple_score

找到整個棋盤的最大分數位置find_maxscore()

  • 輸入參數是回合和整個棋盤的已有落子的全部位置,這很好理解也很符合常理。返回位置是電腦選擇的落子點,值得一提的是,這個點並不一定是得分最高的點;)
  • 對棋盤上所有可以落子位置的得分進行排序,如果得分前三高的的點分數相差不超過50分,則可以近似認爲這三個位置棋效相同,隨機選一個位置落子,如果不滿足上一條件,而前兩高分數的落子分差不超過100,則在這兩個位置種隨機選一個落子。
  • 之所以這樣做,是爲了避免走出同樣的棋局,也就避免了玩家使用“套路”來過於輕鬆的取勝電腦。
def find_maxscore(array,whiteround):
	if array==[]:
		best_pos=[8,8]
	else:
		chess_score_array=[]
		for row in range(1,15):
			for col in range(1,15):
				chess_pos = [row,col]
				if chess_pos not in array:
					pos_score=chess_score(array,chess_pos,whiteround)
					chess_score_array.append([pos_score, row, col])
		chess_score_array.sort(reverse=True)
		
		#隨機落子
		if chess_score_array[0][0]-chess_score_array[2][0]<50:	
			choose_pos=random.randint(0,2)
			
		elif chess_score_array[0][0]-chess_score_array[1][0]<100:	
			choose_pos=random.randint(0,1)
		else :	
			choose_pos=0
		pc_pressed_x=chess_score_array[choose_pos][1]
		pc_pressed_y=chess_score_array[choose_pos][2]
		best_pos=[pc_pressed_x,pc_pressed_y]
		#print(best_pos)
		print(chess_score_array)
	return best_pos

judgewin. py 勝負判斷模塊

模塊導入

#--coding:utf-8--
from PIL import Image 
import pygame as pg
#from pygame.locals import *#導入一些常用的函數和常量
from sys import exit #用exit來退出程序
import easygui 
import gloval 
gloval.init()

顯示獲勝信息 check_win()

  • 傳入參數爲棋面落子座標列表,偶數爲黑子,奇數爲白子,所以不用單獨儲存
  • 判斷五連爲真即爲一方贏,落子座標列表長度等於棋面空位總數225代表和棋
  • 一方勝利或者和棋則代表遊戲結束,顯示相關信息並且遊戲狀態跳轉到7狀態
def check_win(chess_array):#黑色白色棋子的索引值列表
	press=(0,0,0)
	black_array=chess_array[::2]#偶數
	white_array=chess_array[1::2]#奇數
	screen=gloval.getval('screen')
	if five_pieces(black_array)==1:
		# 黑方勝利
		my_font=pg.font.Font('mufont.ttf',55)
		text2=my_font.render("黑方勝利",True,press)
		screen.blit(text2,(680,100))	
		game_end()
	if five_pieces(white_array)==1:
		# 白方勝利
		my_font=pg.font.Font('mufont.ttf',55)
		text2=my_font.render("白方勝利",True,press)
		screen.blit(text2,(680,100))	
		game_end()
	if len(chess_array)== 225:
		# 平局
		my_font=pg.font.Font('mufont.ttf',55)
		text2=my_font.render("和棋",True,press)
		screen.blit(text2,(680,100))	
		game_end()#狀態置七

判斷是否五連 five_pieces()

  • 判斷技巧:不需要遍歷整個棋盤判斷是否在某個位置出現五連,只需要判斷最後一個落子位置有沒有實現五連即可。這裏傳入的參數是白子落子位置列表或者黑子落子位置列表。
def five_pieces(array):
	# 提取最後一顆棋子,負一
	if array:
		x=array[-1][0]
		y=array[-1][1]
		for j in range(1,12):
			#豎五連
			if [x,j] in array and [x,j+1] in array and [x,j+2] in array and [x,j+3] in array and [x,j+4] in array :
				return 1
			#橫五連
			if [j,y] in array and [j+1,y] in array and [j+2,y] in array and [j+3,y] in array and [j+4,y] in array :
				
				return 1
		for j in range(5):
			#\五連

			if [x-j,y-j] in array and [x-j+1,y-j+1] in array and [x-j+2,y-j+2] in array and [x-j+3,y-j+3] in array and [x-j+4,y-j+4] in array :
				return 1
			#/五連
			if [x-j,y+j] in array and [x-j+1,y+j-1] in array and [x-j+2,y+j-2] in array and [x-j+3,y+j-3] in array and [x-j+4,y+j-4] in array :

				return 1
		return 0
	

源碼及材料已上傳CSDN,全劇終,共計兩萬三千餘字。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章