嗯,今天接着来搞五子棋,从五子棋开始给小伙伴们聊AI。

昨天晚上我们已经实现了一个五子棋的逻辑部分,其实讲道理,有个规则在,可以开始搞AI了,但是考虑到不够直观,我们还是顺带先把五子棋的UI也先搞出来。所以今天咱们搞UI。

逻辑部分在这里:[深度学习]实现一个博弈型的AI,从五子棋开始(1)

小伙伴:啥?再次省去吐槽一万字,说好的讲深度学习在哪儿,说好的强化学习在哪儿,今天又是五子棋……

我:是五子棋,AI不能缺场景啊,没有场景谈AI就是空谈,是得先有个棋啊。再说了,虽说之前搞了个逻辑,至少搞个界面出来测一下嘛,万一场景的逻辑都没对,还AI个锤子!

老罗:又关我什么事?

好了,不扯了,回正题,我们一开始设计就是逻辑和UI分离,上一篇我们实现了逻辑部分,今天来实现UI部分,给咱的五子棋搞个UI。

(2)五子棋下棋UI的实现

Python做五子棋UI的话,咱们这里就用 PyGame 来搞,当然也有别的库,说老实话Python做UI我真没搞过多少,PyGame 的基础用法和各种知识我就不展开了,毕竟这不是重点,有兴趣的小伙伴可以自行Google,我也是边学边用呢,哈哈!

既然是做UI,得有素材,我在网上找了一个棋盘:

以及黑白两颗棋子:

 

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?

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

[深度学习]实现一个博弈型的AI,从五子棋开始(2)的更多相关文章

  1. [深度学习]实现一个博弈型的AI,从五子棋开始(1)

    好久没有写过博客了,多久,大概8年???最近重新把写作这事儿捡起来……最近在折腾AI,写个AI相关的给团队的小伙伴们看吧. 搞了这么多年的机器学习,从分类到聚类,从朴素贝叶斯到SVM,从神经网络到深度 ...

  2. [深度学习]实现一个博弈型的AI,从五子棋开始

    好久没有写过博客了,多久,大概8年???最近重新把写作这事儿捡起来……最近在折腾AI,写个AI相关的给团队的小伙伴们看吧. 搞了这么多年的机器学习,从分类到聚类,从朴素贝叶斯到SVM,从神经网络到深度 ...

  3. PDNN: 深度学习的一个Python工具箱

    PDNN: 深度学习的一个Python工具箱 PDNN是一个在Theano环境下开发出来的一个Python深度学习工具箱.它由苗亚杰(Yajie Miao)原创.现在仍然在不断努力去丰富它的功能和扩展 ...

  4. 深度学习:又一次推动AI梦想(Marr理论、语义鸿沟、视觉神经网络、神经形态学)

    几乎每一次神经网络的再流行,都会出现:推进人工智能的梦想之说. 前言: Marr视觉分层理论 Marr视觉分层理论(百度百科):理论框架主要由视觉所建立.保持.并予以解释的三级表象结构组成,这就是: ...

  5. 图像识别 | AI在医学上的应用 | 深度学习 | 迁移学习

    参考:登上<Cell>封面的AI医疗影像诊断系统:机器之心专访UCSD张康教授 Identifying Medical Diagnoses and Treatable Diseases b ...

  6. AI理论学习笔记(一):深度学习的前世今生

    AI理论学习笔记(一):深度学习的前世今生 大家还记得以深度学习技术为基础的电脑程序AlphaGo吗?这是人类历史中在某种意义的第一次机器打败人类的例子,其最大的魅力就是深度学习(Deep Learn ...

  7. 转:【AI每日播报】从TensorFlow到Theano:横向对比七大深度学习框架

    http://geek.csdn.net/news/detail/139235 说到近期的深度学习框架,TensorFlow火的不得了,虽说有专家在朋友圈大声呼吁,不能让TensorFlow形成垄断地 ...

  8. AI安全初探——利用深度学习检测DNS隐蔽通道

    AI安全初探——利用深度学习检测DNS隐蔽通道 目录 AI安全初探——利用深度学习检测DNS隐蔽通道 1.DNS 隐蔽通道简介 2. 算法前的准备工作——数据采集 3. 利用深度学习进行DNS隐蔽通道 ...

  9. AI - 深度学习之美十四章-概念摘要(8~14)

    原文链接:https://yq.aliyun.com/topic/111 本文是对原文内容中部分概念的摘取记录,可能有轻微改动,但不影响原文表达. 08 - BP算法双向传,链式求导最缠绵 反向传播( ...

随机推荐

  1. Redis “瘦身”指南

    code[class*="language-"], pre[class*="language-"] { background-color: #fdfdfd; - ...

  2. 移动端效果之Picker

    写在前面 接着前面的移动端效果的研究,这次来看看picker选择器的实现原理 移动端效果之Swiper 代码看这里:github 1. 核心解析 1.1 基本HTML结构 <!-- 说明: 1. ...

  3. Centos7安装Percona5.7

    OS: Centos7.0 DB: Percona5.7 1. 通过yum安装 ## 删除之前的mysql数据库, 我用的是centos7.再安装虚拟机的时候,预装了很多软件.所以mysql和mari ...

  4. Kindeditor JS 取值问题以及上传图片后回调等

    KindEditor.ready(function (K) { var editor = K.create('#editor_id', { //上传管理 uploadJson: '/js/kinded ...

  5. 邮件实现详解(四)------JavaMail 发送(带图片和附件)和接收邮件

    好了,进入这个系列教程最主要的步骤了,前面邮件的理论知识我们都了解了,那么这篇博客我们将用代码完成邮件的发送.这在实际项目中应用的非常广泛,比如注册需要发送邮件进行账号激活,再比如OA项目中利用邮件进 ...

  6. 热门开源项目:Guns-后台管理系统

    Guns基于SpringBoot,致力于做更简洁的后台管理系统,完美整合springmvc + shiro + mybatis-plus + beetl!Guns项目代码简洁,注释丰富,上手容易,同时 ...

  7. Database 2 Day DBA guide_Chapter2

    website:http://www.oracle.com/webfolder/technetwork/tutorials/obe/db/11g/r2/2day_dba/install/install ...

  8. VUE长按事件

    PS:在开发中常常会有长按事件的需求,这里我简单的介绍几种长按事件的需求 需求一:长按数字累加或者累减 HTML: <div class="mui-numbox" data- ...

  9. Why does eclipse automatically add appcompat v7 library support whenever I create a new project?

    Best ways to solve these: Firstly in project,Right click->properties->Android.There you can se ...

  10. Servlet实现后台分页查询

    相信大家在搭建后台的时候,经常会使用到分页功能,当然,目前有不少框架(如esayUI)都自带分页的实现,为了更好的理解分页原理,近期本人自己摸索了关于分页查询的一些心得. 归根结底,分页的核心还是在封 ...