您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)python實(shí)現(xiàn)五子棋人機(jī)對(duì)戰(zhàn)游戲的示例分析的內(nèi)容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧。
開(kāi)端
畫(huà)棋盤(pán)
首先肯定是要畫(huà)出棋盤(pán)來(lái),用 pygame 畫(huà)出一個(gè) 19 × 19 或 15 × 15 的棋盤(pán)并不是什么難事,這在之前的文章中已經(jīng)多次用到,就不贅述了。
畫(huà)棋子
需要說(shuō)一下的是畫(huà)棋子,因?yàn)闆](méi)找到什么合適的棋子圖片,所以只要自己來(lái)畫(huà)棋子。
我們用 pygame.draw.circle 畫(huà)出來(lái)的圓形是這樣的:
鋸齒狀十分明顯,pygame.draw 中有畫(huà)抗鋸齒直線的函數(shù) aaline,但是并沒(méi)有 aacircle 這樣的函數(shù)來(lái)畫(huà)一個(gè)抗鋸齒的圓。
這里就需要用到 pygame.gfxdraw 啦。pygame.gfxdraw 目前還僅是實(shí)驗(yàn)版本,這意味著這個(gè) API 可能會(huì)在以后的 pygame 版本中發(fā)生變化或消失。
要繪制抗鋸齒和填充形狀,請(qǐng)首先使用函數(shù)的aa *版本,然后使用填充版本。例如:
col = (255, 0, 0) surf.fill((255, 255, 255)) pygame.gfxdraw.aacircle(surf, x, y, 30, col) pygame.gfxdraw.filled_circle(surf, x, y, 30, col)
我們用這個(gè)方法在棋盤(pán)上畫(huà)一個(gè)棋子試試看。
可以看到效果已明顯改善。
落子
落子需要判斷鼠標(biāo)事件,當(dāng)鼠標(biāo)左鍵點(diǎn)擊,獲取鼠標(biāo)點(diǎn)擊的位置,然后根據(jù)棋盤(pán)的位置,計(jì)算出棋子落在棋盤(pán)的位置。
while True: for event in pygame.event.get(): if event.type == QUIT: sys.exit() elif event.type == MOUSEBUTTONDOWN: pressed_array = pygame.mouse.get_pressed() if pressed_array[0]: # 鼠標(biāo)左鍵點(diǎn)擊 mouse_pos = pygame.mouse.get_pos() click_point = _get_clickpoint(mouse_pos)
勝利判定
當(dāng)一子落下,如何判定是否勝利?
可以肯定的是,當(dāng)某一子落下的時(shí)候,如果出現(xiàn)了 5 連,那么落下的這顆子必定在這條 5 連線上。那么這個(gè)問(wèn)題就可以簡(jiǎn)化了,我們無(wú)需全盤(pán)掃描,只需要在落子位置上橫豎撇捺掃描一下,判斷是否出現(xiàn) 5 連即可。
我們定義一個(gè)棋盤(pán)類,類中實(shí)例化一個(gè) 19 × 19 的二維數(shù)組,初始值皆為 0,表示空,用 1 表示黑子,2 表示白子。這個(gè)類對(duì)外提供一個(gè)落子方法 drop,接收參數(shù)落子方和落子坐標(biāo),如果落子后勝利,則返回勝利者,否則返回 None。
Chessman = namedtuple('Chessman', 'Name Value Color') Point = namedtuple('Point', 'X Y') BLACK_CHESSMAN = Chessman('黑子', 1, (45, 45, 45)) WHITE_CHESSMAN = Chessman('白子', 2, (219, 219, 219)) offset = [(1, 0), (0, 1), (1, 1), (1, -1)] class Checkerboard: def __init__(self, line_points): self._line_points = line_points self._checkerboard = [[0] * line_points for _ in range(line_points)] def _get_checkerboard(self): return self._checkerboard checkerboard = property(_get_checkerboard) # 判斷是否可落子 def can_drop(self, point): return self._checkerboard[point.Y][point.X] == 0 def drop(self, chessman, point): """ 落子 :param chessman: 黑子/白子 :param point:落子位置 :return:若該子落下之后即可獲勝,則返回獲勝方,否則返回 None """ print(f'{chessman.Name} ({point.X}, {point.Y})') self._checkerboard[point.Y][point.X] = chessman.Value if self._win(point): print(f'{chessman.Name}獲勝') return chessman # 判斷是否贏了 def _win(self, point): cur_value = self._checkerboard[point.Y][point.X] for os in offset: if self._get_count_on_direction(point, cur_value, os[0], os[1]): return True def _get_count_on_direction(self, point, value, x_offset, y_offset): count = 1 for step in range(1, 5): x = point.X + step * x_offset y = point.Y + step * y_offset if 0 <= x < self._line_points and 0 <= y < self._line_points and self._checkerboard[y][x] == value: count += 1 else: break for step in range(1, 5): x = point.X - step * x_offset y = point.Y - step * y_offset if 0 <= x < self._line_points and 0 <= y < self._line_points and self._checkerboard[y][x] == value: count += 1 else: break return count >= 5
這里我定義了一個(gè)偏移量,我們一共要計(jì)算橫豎撇捺 4 條線,任意一條線出現(xiàn) 5 連就算獲勝。計(jì)算方法實(shí)際上是一樣的,只是方向不同,所以定義一個(gè)偏移量數(shù)組,不同的偏移量表示不同的方向,這樣就可以利用循環(huán)來(lái)實(shí)現(xiàn)了,節(jié)省了很多代碼。
電腦落子
這就是全篇的重頭戲了,要怎么教電腦下五子棋。
首先聲明,我用的是相對(duì)傳統(tǒng)的方式,不是深度學(xué)習(xí)。
五子棋就是要實(shí)現(xiàn) 5 連,所以,一開(kāi)始,我的想法是:將所有連線保存在一個(gè)數(shù)組中,落子的時(shí)候選擇最長(zhǎng)的連線落子。但這樣有個(gè)問(wèn)題解決不掉,如何讓電腦識(shí)別“三三”呢?
后來(lái)網(wǎng)上看到篇文章,使用的方法是:遍歷棋盤(pán)上的空位,計(jì)算每一個(gè)位置其橫豎撇捺 8 個(gè)方向上是否有己方的子,有一個(gè)就加 10 分,最后選得分最高的位置落子。
這樣不太嚴(yán)謹(jǐn),寫(xiě)出來(lái)的電腦估計(jì)水平很菜,但是這個(gè)思路卻是對(duì)的,落子就是要找到最值得的地方,那么我們干脆對(duì)每一個(gè)可落子的地方來(lái)做一個(gè)評(píng)估,選出最優(yōu)解。
這里我們需要了解一下五子棋的幾種基本棋形:連五,活四,沖四,活三,眠三,活二,眠二。
連五
顧名思義,五顆同色棋子連在一起,贏了。
活四
四顆同色棋子連在一起,并且左右兩邊都沒(méi)有對(duì)方棋子阻擋,有兩個(gè)連五點(diǎn)。
沖四
四顆同色棋子連在一起,并且一邊有對(duì)方棋子阻擋,或者四顆棋子不是連的,當(dāng)中有個(gè)空擋,這時(shí)只有一個(gè)連五點(diǎn)。
活三、跳活三
活三:三顆同色棋子連在一起。
跳活三:中間隔了一個(gè)空格的活三。
眠三
只能夠形成沖四的三,無(wú)外乎兩種情況,一是一邊被擋住了,一是當(dāng)中有 2 個(gè)空格。(其實(shí)我在代碼中僅考慮了第一種情況,即便形成沖四,也不是什么危險(xiǎn)局面。)
活二和眠二
活二,能夠形成活三的二;眠二,能夠形成眠三的二。這里就不放圖了,參考活三眠三。
打分機(jī)制
理解了這些棋形,那么按我們之前的思路,就是如何打分了。
首先,連五肯定是不存在的,出現(xiàn)連五勝負(fù)已分,所以只要棋局還在進(jìn)行中,就不會(huì)出現(xiàn)連五。那么,什么優(yōu)先級(jí)最高?自然就是活四了。
其次是對(duì)方的“四”,對(duì)方活四,你防不防都一樣輸了,對(duì)方?jīng)_四,你就必須防守。
再次是我方的活三或沖四,活三跟沖四其實(shí)是一個(gè)級(jí)別的,對(duì)方必須防守。
再次是對(duì)方的活三或沖四。
以此類推下去。我們可以總結(jié)一點(diǎn)規(guī)律:
相同的棋形,我方優(yōu)于對(duì)方。
沖四跟活三一個(gè)級(jí)別,眠三跟活二一個(gè)級(jí)別。
如果中間有空格的話,肯定是要比沒(méi)空格的略微低級(jí)一點(diǎn),但不至于降級(jí)。
基本邏輯就是這樣,這一塊的代碼我寫(xiě)得也不好,整個(gè)判斷寫(xiě)了100多行,就不貼代碼了,大家可以直接下源碼看。
五子棋執(zhí)黑是必贏的,代碼中,玩家就是執(zhí)黑先手,電腦執(zhí)白后手,所以,下的好是完全可以贏電腦的,不過(guò)一個(gè)小小失誤也很可能被電腦翻盤(pán)。
感謝各位的閱讀!關(guān)于“python實(shí)現(xiàn)五子棋人機(jī)對(duì)戰(zhàn)游戲的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。