[深度學習]實現一個博弈型的AI,從五子棋開始(2)

嗯,今天接着來搞五子棋,從五子棋開始給小夥伴們聊AI。html

 

昨天晚上咱們已經實現了一個五子棋的邏輯部分,其實講道理,有個規則在,能夠開始搞AI了,可是考慮到不夠直觀,咱們仍是順帶先把五子棋的UI也先搞出來。因此今天我們搞UI。數組

 

邏輯部分在這裏:[深度學習]實現一個博弈型的AI,從五子棋開始(1)框架

 

小夥伴:啥?再次省去吐槽一萬字,說好的講深度學習在哪兒,說好的強化學習在哪兒,今天又是五子棋……函數

我:是五子棋,AI不能缺場景啊,沒有場景談AI就是空談,是得先有個棋啊。再說了,雖然說以前搞了個邏輯,至少搞個界面出來測一下嘛,萬一場景的邏輯都沒對,還AI個錘子!學習

老羅:又關我什麼事?ui

 

好了,不扯了,回正題,咱們一開始設計就是邏輯和UI分離,上一篇咱們實現了邏輯部分,今天來實現UI部分,給咱的五子棋搞個UI。spa

 

(2)五子棋下棋UI的實現設計

Python作五子棋UI的話,我們這裏就用 PyGame 來搞,固然也有別的庫,說老實話Python作UI我真沒搞過多少,PyGame 的基礎用法和各類知識我就不展開了,畢竟這不是重點,有興趣的小夥伴能夠自行Google,我也是邊學邊用呢,哈哈!code

 

既然是作UI,得有素材,我在網上找了一個棋盤:orm

 

 

以及黑白兩顆棋子:

 

 

PS:爲了UI上面好看,棋子由於是圓形的,最好是處理成PNG格式,帶Alpha通道,外面透明。另外這幾張圖不知道上傳了會不會被壓縮成別的格式,我打了個包放在文章末尾了。

 

在我們以前的工程裏建個目錄「UI」,棋盤取名 chessboard.jpg 放在目錄下,兩顆棋子分別取名 piece_black.png、piece_white.png 也放到目錄下。

 

看看屬性,棋盤是540*540像素的,棋子是32*32像素,數字記下來,而後我們找的這個棋盤是有邊緣的,量一下,邊緣離第一根線大約是22像素。要作render,得用到這些數字。

 

橫豎各15根線這個不用說,15根線中間有14個格子,因此線和線的距離是總寬度減去兩個邊緣再除以格子數: (540 - 22 * 2) / 14 貌似除不盡,那就先這樣子。

 

好了,建一個文件 render.py ,我們先把剛剛那些數字放進去,順便該import的也import了,好比pygame、好比我們昨天的定義和昨天的五棋子邏輯:

 

#coding:utf-8

import pygame
from pygame.locals import *
from consts import *
from gobang import GoBang

#IMAGE_PATH = '/Users/phantom/Projects/AI/gobang/UI/'
IMAGE_PATH = 'UI/'

WIDTH = 540
HEIGHT = 540
MARGIN = 22
GRID = (WIDTH - 2 * MARGIN) / (N - 1)
PIECE = 32

 

而後咱們定義一個新的類,GameRender,render初始化的時候咱們綁定一個邏輯類,而後初始化pygame,把窗體大小設置一下,該加載的資源先加載了,代碼比較簡單,沒有什麼爲何,pygame就是這麼用的,pygame有興趣的小夥伴本身Google。

 

render咱們仍然考慮定義了一個current表示當前步,黑棋先下,因此current定義成黑色。

 

class GameRender(object):
    def __init__(self, gobang):
        # 綁定邏輯類
        self.__gobang = gobang
        # 黑棋開局
        self.__currentPieceState = ChessboardState.BLACK

        # 初始化 pygame
        pygame.init()
        # pygame.display.set_mode((width, height), flags, depth) 
        self.__screen = pygame.display.set_mode((WIDTH, HEIGHT), 0, 32)
        pygame.display.set_caption('五子棋AI')

        # UI 資源
        self.__ui_chessboard = pygame.image.load(IMAGE_PATH + 'chessboard.jpg').convert()
        self.__ui_piece_black = pygame.image.load(IMAGE_PATH + 'piece_black.png').convert_alpha()
        self.__ui_piece_white = pygame.image.load(IMAGE_PATH + 'piece_white.png').convert_alpha()

 

render類嘛,各類draw了,對不對,確實是。不過這裏有一個問題。

 

以前的邏輯類裏咱們定義了一個二維數組chessMap還記得嗎?看看邏輯類GoBang的定義:

class GoBang(object):
    def __init__(self):
        self.__chessMap = [[ChessboardState.EMPTY for j in range(N)] for i in range(N)]
        self.__currentI = -1
        self.__currentJ = -1
        self.__currentState = ChessboardState.EMPTY

 

咱們先思考一個問題,chessMap裏的座標,和我們棋盤的座標怎麼對應呢,chessMap裏 i,j 就是0到14,0到14;我們棋盤上,render的時候,那但是按像素來的啊,棋盤但是0到540像素呢,嚴格的說,是540減去兩個邊緣,22到518像素,得先對應吧。好,作個座標變換,把棋子下標 i,j 變成像素 x,y。從邊緣開始計算,每相鄰一個棋子,加一個格子的大小GRID,那若是咱們的棋子要擺上去的話,要擺到棋子中間,因此 x,y 分別再減去半個棋子的大小,代碼就2行,比較清晰了:

 

    def coordinate_transform_map2pixel(self, i, j):    
        # 從 chessMap 裏的邏輯座標到 UI 上的繪製座標的轉換
        return MARGIN + j * GRID - PIECE / 2, MARGIN + i * GRID - PIECE / 2

 

好,這下咱們能夠從邏輯類裏讀狀態出來繪製了,再考慮一下,座標變換嘛,還需不須要反過來變。是,確實須要,下棋落子的時候,實際是在UI上給獲得 x,y 對吧,咱們得去set一下邏輯類裏的狀態吧,因此這時候又須要把 x,y 座標變換成 i,j 的,怎麼算就不詳細展開了,類似的邏輯。

這裏我們偷個懶的話,前一個映射函數不是有式子了麼:

x = MARGIN + j * GRID - PIECE / 2

y = MARGIN + i * GRID - PIECE / 2

作個位移,推導一下等式的兩邊, 把 j 用 x 來表達一下, i 用 y 來表達一下,就能夠了:

i = (y - MARGIN + PIECE / 2) / GRID

j = (x - MARGIN + PIECE / 2) / GRID

這裏細心的小夥伴們發現了,i 和 j 可能不是整數哦,首先得到的座標固然是經過鼠標來,這個原本就有誤差,不會那麼剛恰好,而且GRID好像也不是整數,除一下,都不知道是多少了,OK,那我們Round一下咯。

又有小夥伴說了,不是棋盤有邊緣麼,那個MARGIN就時刻提醒咱們,有個邊緣,要是我在邊緣上點擊,會不會出現負值,或者大於N的值。對,考慮得很好,得判斷一下邊界,這下應該差很少了,能夠寫代碼了:

 

    def coordinate_transform_pixel2map(self, x, y):    
        # 從 UI 上的繪製座標到 chessMap 裏的邏輯座標的轉換
        i , j = int(round((y - MARGIN + PIECE / 2) / GRID)), int(round((x - MARGIN + PIECE / 2) / GRID))
        # 有MAGIN, 排除邊緣位置致使 i,j 越界
        if i < 0 or i >= N or j < 0 or j >= N:
            return None, None
        else:
            return i, j

 

好了,到如今,座標的映射也搞定了,終於能夠draw、draw、draw了,好吧,那就draw,先畫棋盤再畫棋子,棋子是啥顏色就畫啥顏色,空白的就跳過:

 

    def draw_chess(self):
        # 棋盤
        self.__screen.blit(self.__ui_chessboard, (0,0))
        # 棋子
        for i in range(0, N):
            for j in range(0, N):
                x,y = self.coordinate_transform_map2pixel(i,j)
                state = self.__gobang.get_chessboard_state(i,j)
                if state == ChessboardState.BLACK:
                    self.__screen.blit(self.__ui_piece_black, (x,y))
                elif state == ChessboardState.WHITE:
                    self.__screen.blit(self.__ui_piece_white, (x,y))
                else: # ChessboardState.EMPTY
                    pass

 

爲了下棋的時候體驗稍微好一點呢,咱們在鼠標上是否是最好也畫一個棋子,這樣感受點上去就能落子,好像會好一點:

 

    def draw_mouse(self):
        # 鼠標的座標
        x, y = pygame.mouse.get_pos()
        # 棋子跟隨鼠標移動
        if self.__currentPieceState == ChessboardState.BLACK:
            self.__screen.blit(self.__ui_piece_black, (x - PIECE / 2, y - PIECE / 2))
        else:
            self.__screen.blit(self.__ui_piece_white, (x - PIECE / 2, y - PIECE / 2))

 

若是出現連續的5顆同色棋子,要顯示贏棋的結果,那就再來個draw:

 

    def draw_result(self, result):
        font = pygame.font.Font('/Library/Fonts/Songti.ttc', 50)
        tips = u"本局結束:"
        if result == ChessboardState.BLACK :
            tips = tips + u"黑棋勝利"
        elif result == ChessboardState.WHITE:
            tips = tips + u"白棋勝利"
        else:
            tips = tips + u"平局"
        text = font.render(tips, True, (255, 0, 0))
        self.__screen.blit(text, (WIDTH / 2 - 200, HEIGHT / 2 - 50))

 

想一想還差啥?

對,下棋的邏輯還沒作吧,鼠標點擊,在棋盤上放顆棋子,咱們剛剛draw棋子的時候實際上是讀取的邏輯類裏的chessMap,那下棋的時候,去set對應的狀態:

 

    def one_step(self):
        i, j = None, None
        # 鼠標點擊
        mouse_button = pygame.mouse.get_pressed()
        # 左鍵
        if mouse_button[0]:
            x, y = pygame.mouse.get_pos()
            i, j = self.coordinate_transform_pixel2map(x, y)

        if not i is None and not j is None:
            # 格子上已經有棋子
            if self.__gobang.get_chessboard_state(i, j) != ChessboardState.EMPTY:
                return False
            else:
                self.__gobang.set_chessboard_state(i, j, self.__currentPieceState)
                return True

        return False

 

 

如今不是還沒AI嘛,咱們一不作二不休,先搞一我的人對弈,那就再加一個切換顏色的函數:

 

    def change_state(self):
        if self.__currentPieceState == ChessboardState.BLACK:
            self.__currentPieceState = ChessboardState.WHITE
        else:
            self.__currentPieceState = ChessboardState.BLACK

 

好了,還差啥?好像做爲render的話,感受差很少,那就來個main函數溜一溜代碼試試,新建一個 game.py,這裏咱們 main 函數裏先給AI留個接口,至少留個框架咯 :

 

import pygame
from pygame.locals import *
from sys import exit
from consts import *
from gobang import GoBang
from render import GameRender
#from gobang_ai import GobangAI

if __name__ == '__main__': 
    gobang = GoBang()
    render = GameRender(gobang)
    #先給AI留個接口
    #ai = GobangAI(gobang, ChessboardState.WHITE)
    result = ChessboardState.EMPTY
    enable_ai = False

    while True:
        # 捕捉pygame事件
        for event in pygame.event.get():
            # 退出程序
            if event.type == QUIT:
                exit()
            elif event.type ==  MOUSEBUTTONDOWN:
                # 成功着棋
                if render.one_step():
                    result = gobang.get_chess_result()
                else:
                    continue
                if result != ChessboardState.EMPTY:
                    break
                if enable_ai:
                    #ai.one_step()
                    result = gobang.get_chess_result()
                else:
                    render.change_state()
        
        # 繪製
        render.draw_chess()
        render.draw_mouse()

        if result != ChessboardState.EMPTY:
            render.draw_result(result)

        # 刷新
        pygame.display.update()

 

好了,跑一下試試,沒有AI就拉兩個小夥伴來對弈,實在不行先左手和右手來一把,好像還行,邏輯沒問題:

 

 

整理一下完整版的 render.py :

 

#coding:utf-8

import pygame
from pygame.locals import *
from consts import *
from gobang import GoBang

#IMAGE_PATH = '/Users/phantom/Projects/AI/gobang/UI/'
IMAGE_PATH = 'UI/'

WIDTH = 540
HEIGHT = 540
MARGIN = 22
GRID = (WIDTH - 2 * MARGIN) / (N - 1)
PIECE = 32

class GameRender(object):
    def __init__(self, gobang):
        # 綁定邏輯類
        self.__gobang = gobang
        # 黑棋開局
        self.__currentPieceState = ChessboardState.BLACK

        # 初始化 pygame
        pygame.init()
        # pygame.display.set_mode((width, height), flags, depth) 
        self.__screen = pygame.display.set_mode((WIDTH, HEIGHT), 0, 32)
        pygame.display.set_caption('五子棋AI')

        # UI 資源
        self.__ui_chessboard = pygame.image.load(IMAGE_PATH + 'chessboard.jpg').convert()
        self.__ui_piece_black = pygame.image.load(IMAGE_PATH + 'piece_black.png').convert_alpha()
        self.__ui_piece_white = pygame.image.load(IMAGE_PATH + 'piece_white.png').convert_alpha()

    def coordinate_transform_map2pixel(self, i, j):    
        # 從 chessMap 裏的邏輯座標到 UI 上的繪製座標的轉換
        return MARGIN + j * GRID - PIECE / 2, MARGIN + i * GRID - PIECE / 2

    def coordinate_transform_pixel2map(self, x, y):    
        # 從 UI 上的繪製座標到 chessMap 裏的邏輯座標的轉換
        i , j = int(round((y - MARGIN + PIECE / 2) / GRID)), int(round((x - MARGIN + PIECE / 2) / GRID))
        # 有MAGIN, 排除邊緣位置致使 i,j 越界
        if i < 0 or i >= N or j < 0 or j >= N:
            return None, None
        else:
            return i, j

    def draw_chess(self):
        # 棋盤
        self.__screen.blit(self.__ui_chessboard, (0,0))
        # 棋子
        for i in range(0, N):
            for j in range(0, N):
                x,y = self.coordinate_transform_map2pixel(i,j)
                state = self.__gobang.get_chessboard_state(i,j)
                if state == ChessboardState.BLACK:
                    self.__screen.blit(self.__ui_piece_black, (x,y))
                elif state == ChessboardState.WHITE:
                    self.__screen.blit(self.__ui_piece_white, (x,y))
                else: # ChessboardState.EMPTY
                    pass
                
    def draw_mouse(self):
        # 鼠標的座標
        x, y = pygame.mouse.get_pos()
        # 棋子跟隨鼠標移動
        if self.__currentPieceState == ChessboardState.BLACK:
            self.__screen.blit(self.__ui_piece_black, (x - PIECE / 2, y - PIECE / 2))
        else:
            self.__screen.blit(self.__ui_piece_white, (x - PIECE / 2, y - PIECE / 2))

    def draw_result(self, result):
        font = pygame.font.Font('/Library/Fonts/Songti.ttc', 50)
        tips = u"本局結束:"
        if result == ChessboardState.BLACK :
            tips = tips + u"黑棋勝利"
        elif result == ChessboardState.WHITE:
            tips = tips + u"白棋勝利"
        else:
            tips = tips + u"平局"
        text = font.render(tips, True, (255, 0, 0))
        self.__screen.blit(text, (WIDTH / 2 - 200, HEIGHT / 2 - 50))

    def one_step(self):
        i, j = None, None
        # 鼠標點擊
        mouse_button = pygame.mouse.get_pressed()
        # 左鍵
        if mouse_button[0]:
            x, y = pygame.mouse.get_pos()
            i, j = self.coordinate_transform_pixel2map(x, y)

        if not i is None and not j is None:
            # 格子上已經有棋子
            if self.__gobang.get_chessboard_state(i, j) != ChessboardState.EMPTY:
                return False
            else:
                self.__gobang.set_chessboard_state(i, j, self.__currentPieceState)
                return True

        return False
            
    def change_state(self):
        if self.__currentPieceState == ChessboardState.BLACK:
            self.__currentPieceState = ChessboardState.WHITE
        else:
            self.__currentPieceState = ChessboardState.BLACK

 

 

好了,就這樣~  

UI素材我打個包放這兒了:

點擊這裏下載UI素材

 

…………後記…………

我:小夥伴們別吐槽了,明天必定開始搞AI了,由於五子棋我們有啦~

小夥伴:好吧,終於。

我:等一下,明天?NO,我口誤,下一篇必定開始搞AI了,明天不必定有時間來寫博客呢 - -

小夥伴:再再次省去吐槽一萬字!

我:反正每週至少寫兩篇嘛,OK?

小夥伴:那我還能怎樣……

相關文章
相關標籤/搜索