[深度学习]实现一个博弈型的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?

小伙伴:那我还能怎样……

相关文章
相关标签/搜索