本文代碼基於 python3.6 和 pygame1.9.4。python
五子棋比起我以前寫的幾款遊戲來講,難度提升了很多。若是是人與人對戰,那麼,電腦只須要判斷是否贏了就能夠。若是是人機對戰,那你還得讓電腦知道怎麼下。數組
咱們先從簡單的問題來看。bash
首先確定是要畫出棋盤來,用 pygame 畫出一個 19 × 19 或 15 × 15 的棋盤並非什麼難事,這在以前的文章中已經屢次用到,就不贅述了。markdown
須要說一下的是畫棋子,由於沒找到什麼合適的棋子圖片,因此只要本身來畫棋子。 咱們用 pygame.draw.circle
畫出來的圓形是這樣的: 函數
pygame.draw
中有畫抗鋸齒直線的函數
aaline
,可是並無
aacircle
這樣的函數來畫一個抗鋸齒的圓。
這裏就須要用到 pygame.gfxdraw
啦。pygame.gfxdraw
目前還僅是實驗版本,這意味着這個 API 可能會在之後的 pygame 版本中發生變化或消失。post
要繪製抗鋸齒和填充形狀,請首先使用函數的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)
複製代碼
咱們用這個方法在棋盤上畫一個棋子試試看。 spa
落子須要判斷鼠標事件,當鼠標左鍵點擊,獲取鼠標點擊的位置,而後根據棋盤的位置,計算出棋子落在棋盤的位置。code
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]: # 鼠標左鍵點擊 mouse_pos = pygame.mouse.get_pos() click_point = _get_clickpoint(mouse_pos) 複製代碼
當一子落下,如何斷定是否勝利?orm
能夠確定的是,當某一子落下的時候,若是出現了 5 連,那麼落下的這顆子一定在這條 5 連線上。那麼這個問題就能夠簡化了,咱們無需全盤掃描,只須要在落子位置上橫豎撇捺掃描一下,判斷是否出現 5 連便可。
咱們定義一個棋盤類,類中實例化一個 19 × 19 的二維數組,初始值皆爲 0,表示空,用 1 表示黑子,2 表示白子。這個類對外提供一個落子方法 drop
,接收參數落子方和落子座標,若是落子後勝利,則返回勝利者,不然返回 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 複製代碼
這裏我定義了一個偏移量,咱們一共要計算橫豎撇捺 4 條線,任意一條線出現 5 連就算獲勝。計算方法其實是同樣的,只是方向不一樣,因此定義一個偏移量數組,不一樣的偏移量表示不一樣的方向,這樣就能夠利用循環來實現了,節省了不少代碼。
這就是全篇的重頭戲了,要怎麼教電腦下五子棋。首先聲明,我用的是相對傳統的方式,不是真正意義上的深度學習。
五子棋就是要實現 5 連,因此,一開始,個人想法是:將全部連線保存在一個數組中,落子的時候選擇最長的連線落子。但這樣有個問題解決不掉,如何讓電腦識別「三三」呢?
後來網上看到篇文章,使用的方法是:遍歷棋盤上的空位,計算每個位置其橫豎撇捺 8 個方向上是否有己方的子,有一個就加 10 分,最後選得分最高的位置落子。
這樣不太嚴謹,寫出來的電腦估計水平很菜,可是這個思路倒是對的,落子就是要找到最值得的地方,那麼咱們乾脆對每個可落子的地方來作一個評估,選出最優解。
這裏咱們須要瞭解一下五子棋的幾種基本棋形:連五,活四,衝四,活三,眠三,活二,眠二。
顧名思義,五顆同色棋子連在一塊兒,贏了。
四顆同色棋子連在一塊兒,而且左右兩邊都沒有對方棋子阻擋,有兩個連五點。
四顆同色棋子連在一塊兒,而且一邊有對方棋子阻擋,或者四顆棋子不是連的,當中有個空擋,這時只有一個連五點。
活三:三顆同色棋子連在一塊兒。
只可以造成衝四的三,無外乎兩種狀況,一是一邊被擋住了,一是當中有 2 個空格。(其實我在代碼中僅考慮了第一種狀況,即使造成衝四,也不是什麼危險局面。)
活二,可以造成活三的二;眠二,可以造成眠三的二。這裏就不放圖了,參考活三眠三。
理解了這些棋形,那麼按咱們以前的思路,就是如何打分了。
以此類推下去。咱們能夠總結一點規律:
基本邏輯就是這樣,這一塊的代碼我寫得也很差,整個判斷寫了100多行,就不貼代碼了,你們能夠直接下源碼看。
五子棋執黑是必贏的,代碼中,玩家就是執黑先手,電腦執白後手,因此,下的好是徹底能夠贏電腦的,不過一個小小失誤也極可能被電腦翻盤。
相關博文推薦
掃碼關注個人公衆號,後臺回覆 「五子棋」,獲取源碼。