上期,我們做好了游戲的棋盤,本期我們處理如何落子,及設(shè)計(jì)我們的AI
廢話少說,先看看如何落子
落子
落子的過程大概為
- 獲取鼠標(biāo)的位置
- 計(jì)算位置所對(duì)應(yīng)的落子點(diǎn)
- 畫出棋子
看一下主函數(shù)中的流程
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
# 1.有鼠標(biāo)點(diǎn)擊事件發(fā)生了,我們獲取鼠標(biāo)的位置
# 2.計(jì)算鼠標(biāo)點(diǎn)擊所在網(wǎng)格點(diǎn)的位置
# 3.添加棋子
- 獲取鼠標(biāo)位置
獲取鼠標(biāo)的位置很簡(jiǎn)單,pygame為我們做好了各種事件的檢測(cè)及記錄,我們只需要看有沒有鼠標(biāo)落下事件的發(fā)生,然后獲取位置就好了
好了,我們看如何獲取鼠標(biāo)的位置,只有一個(gè)語句就好了
pos = event.pos
- 計(jì)算網(wǎng)格點(diǎn)的位置
計(jì)算網(wǎng)格點(diǎn)的位置也很簡(jiǎn)單,只要用坐標(biāo)值除以網(wǎng)格的寬度就好了,這里有一點(diǎn)需要注意,就是我們不可能每次都點(diǎn)擊到網(wǎng)格點(diǎn)上,因此需要有一個(gè)四舍五入的過程,就是我們點(diǎn)擊的位置距離哪個(gè)點(diǎn)近,我們就默認(rèn)用戶點(diǎn)擊了哪一個(gè)點(diǎn)。
grid = (int(round(event.pos[0] / (GRID_WIDTH + .0))),
int(round(event.pos[1] / (GRID_WIDTH + .0))))
- 添加棋子
要畫出棋子,我們必須記錄下我們走的每一步棋,并且在刷新屏幕的時(shí)候?qū)⑦@些棋子全部畫出來。我們定義一個(gè)全局變量movements用于我們的每一步棋。然后,每次落子之后,我們就將落子信息存儲(chǔ)在里面。
movements = []
def add_coin(screen, pos, color):
movements.append(((pos[0] * GRID_WIDTH, pos[1] * GRID_WIDTH), color))
pygame.draw.circle(screen, color,
(pos[0] * GRID_WIDTH, pos[1] * GRID_WIDTH), 16)
再定義一個(gè)畫出每一步棋的函數(shù)
def draw_movements(screen):
for m ini movements:
# m[0] 存的是位置,m[1]存的是顏色
pygame.draw.circle(screen, m[1], m[0], 16)
好了,我們?cè)谔砑悠遄拥淖⑨屜旅嫣砑右痪?/p>
add_coin(screen, pos, BLACK)
在刷新屏幕之前調(diào)用畫出棋子的函數(shù)
draw_movements(screen)
好了,我們運(yùn)行一下,在屏幕中畫一個(gè)五字~~
OK,落子順利完成,
下一步就是我們游戲的核心了,我們要寫我們自己的AI~~
五子棋 AI
在我們開發(fā)我們的AI之前我們還有一個(gè)事情要做
判斷游戲結(jié)束
如何判斷游戲結(jié)束?只要有五個(gè)子連成線就好了!對(duì)的,每次落子的時(shí)候我們只要判斷所落的子周圍有沒有統(tǒng)一顏色的棋子可以連成五子的,因此我們需要一個(gè)矩陣記錄每個(gè)位置子的顏色~
color_metrix = [[None] * 20 for i in range(20)]
此時(shí),我們就可以定義我們的判斷游戲結(jié)束的函數(shù)了,函數(shù)的邏輯很簡(jiǎn)單,只要判斷所落子的周圍是否有五子連成線一共有四個(gè)方向
def game_is_over(pos, color):
hori = 1
verti = 1
slash = 1
backslash = 1
left = pos[0] - 1
while left > 0 and color_metrix[left][pos[1]] == color:
left -= 1
hori += 1
right = pos[0] + 1
while right < 20 and color_metrix[right][pos[1]] == color:
right += 1
hori += 1
up = pos[1] - 1
while up > 0 and color_metrix[pos[0]][up] == color:
up -= 1
verti += 1
down = pos[1] + 1
while down < 20 and color_metrix[pos[0]][down] == color:
down += 1
verti += 1
left = pos[0] - 1
up = pos[1] - 1
while left > 0 and up > 0 and color_metrix[left][up] == color:
left -= 1
up -= 1
backslash += 1
right = pos[0] + 1
down = pos[1] + 1
while right < 20 and down < 20 and color_metrix[right][down] == color:
right += 1
down += 1
backslash += 1
right = pos[0] + 1
up = pos[1] - 1
while right < 20 and up > 0 and color_metrix[right][up] == color:
right += 1
up -= 1
slash += 1
left = pos[0] - 1
down = pos[1] + 1
while left > 0 and down < 20 and color_metrix[left][down] == color:
left -= 1
down += 1
slash += 1
if max([hori, verti, backslash, slash]) == 5:
return True
好的在我們畫出棋子之后加入游戲結(jié)束的判斷
if game_is_over(grid, BLACK):
running = False
對(duì)了記得在添加棋子的函數(shù)里將改變我們color_metrix的語句加進(jìn)去。這樣只要我們有五個(gè)同色子連成線游戲就結(jié)束了!
先前我們是直接在主循環(huán)中處理事件的這樣不好,我們提取出一個(gè)函數(shù)來用于處理用戶走子,及AI的響應(yīng)函數(shù)接口像這樣:
def move(surf, pos):
'''
Args:
surf: 我們的屏幕
pos: 用戶落子的位置
Returns a tuple or None:
None: if move is invalid else return a
tuple (bool, player):
bool: True is game is not over else False
player: winner (USER or AI)
'''
這個(gè)函數(shù)首先判斷落子的位置是否已近有子,有的話返回None, 否則落子為合法的,我們調(diào)用add_coin, 最后我們調(diào)用respond函數(shù)
這個(gè)函數(shù)是我們用來運(yùn)行我們的AI,并決定下一步動(dòng)作的,返回值和我們的move一樣,這樣,我們的move就變成了下面這樣
def move(surf, pos):
'''
Args:
surf: 我們的屏幕
pos: 用戶落子的位置
Returns a tuple or None:
None: if move is invalid else return a
tuple (bool, player):
bool: True is game is not over else False
player: winner (USER or AI)
'''
grid = (int(round(pos[0] / (GRID_WIDTH + .0))),
int(round(pos[1] / (GRID_WIDTH + .0))))
if grid[0] <= 0 or grid[0] > 19:
return
if grid[1] <= 0 or grid[1] > 19:
return
pos = (grid[0] * GRID_WIDTH, grid[1] * GRID_WIDTH)
# num_pos = gridpos_2_num(grid)
# if num_pos not in remain:
# return None
if color_metrix[grid[0]][grid[1]] is not None:
return None
curr_move = (pos, BLACK)
add_coin(surf, BLACK, grid, USER)
if game_is_over(grid, BLACK):
return (False, USER)
return respond(surf, movements, curr_move)
這里我們給add_coin 添加了一個(gè)參數(shù),就是當(dāng)前落子的角色,其中
USER, AI = 1, 0
我們用隨機(jī)落子代替我們的AI
def respond(surf, movements, curr_move):
# 測(cè)試用,隨機(jī)落子
grid_pos = (random.randint(1, 19), random.randint(1, 19))
# print(grid_pos)
add_coin(surf, WHITE, grid_pos, 16)
if game_is_over(grid_pos, WHITE):
return (False, AI)
return None
看一下效果~
好了~ 我們的五子棋完成了!
什么?? 不滿意?
好吧,AI確實(shí)比較麻煩,這里我給大家介紹一下思路,完整的代碼大家可以到我的github上去下載,下載鏈接在文章的最后~
AI思路
五子棋的規(guī)則大家都懂,沒下一個(gè)棋子都對(duì)周圍的區(qū)域會(huì)產(chǎn)生影響,我們的AI可以記錄每個(gè)位置的價(jià)值,每次下棋的時(shí)候可以選擇得分最高的那個(gè)位置,具體操作我們可以這樣,連成子的數(shù)目越多,價(jià)值越高,如果可以在多個(gè)方向上連成子,我們可以將價(jià)值相應(yīng)的增加倍數(shù),同時(shí)在對(duì)方有三個(gè)子連成線(獨(dú)立的,即兩邊沒有其他子)的時(shí)候必須堵住對(duì)方。大概就是這樣的。
具體實(shí)現(xiàn)我們就不講解了,看一下最終效果
我試了一下,和AI下我基本上有輸?shù)模灿汹A的。可能是我的水平比較差,大家可以寫自己的AI或者在我的AI基礎(chǔ)上再改進(jìn)!
完整的AI代碼可以去我的 github 看,點(diǎn)擊這里進(jìn)入GitHub。
如果這篇文章對(duì)您有幫助,贊賞一下吧~
您的支持是我繼續(xù)創(chuàng)作的動(dòng)力~~~