[深度学习]实现一个博弈型的AI,从五子棋开始(1)
好久没有写过博客了,多久,大概8年???最近重新把写作这事儿捡起来……最近在折腾AI,写个AI相关的给团队的小伙伴们看吧。
搞了这么多年的机器学习,从分类到聚类,从朴素贝叶斯到SVM,从神经网络到深度学习,各种神秘的项目里用了无数次,但是感觉干的各种事情离我们生活还是太远了。最近AlphaGo Zero的发布,深度学习又火了一把,小伙伴们按捺不住内心的躁动,要搞一个游戏AI,好吧,那就从规则简单、老少皆宜的五子棋开始讲起。
好了,废话就说这么多,下面进入第一讲,实现一个五子棋。
小伙伴:此处省去吐槽一万字,说好的讲深度学习,怎么开始扯实现一个五子棋程序了,大哥你不按套路出牌啊……
我:工欲善其事必先利其器,要实现五子棋的AI,连棋都没有,AI个锤子!
老罗:什么事?
……
五子棋分为有禁手和无禁手,我们先实现一个普通版本的无禁手版本作为例子,因为这个不影响我们实现一个AI。补充说明一下,无禁手黑棋必胜,经过比赛和各种研究,人们逐渐知道了这个事实就开始想办法来限制黑棋先手优势。于是出现了有禁手规则,规定黑棋不能下三三,四四和长连。但随着比赛的结果的研究的继续进行,发现其实即使是对黑棋有禁手限制,还是不能阻止黑棋开局必胜的事实,像直指开局中花月,山月,云月,溪月,寒星等,斜指开局中的名月,浦月,恒星,峡月,岚月都是黑棋必胜。于是日本人继续提出了交换和换打的思想,到了后来发展成了国际比赛中三手交换和五手二打规则,防止执黑者下出必胜开局或者在第五手下出必胜打。所以结论是,在不正规的比赛规则或者无禁手情况下,黑棋必胜是存在的。
(1)五子棋下棋逻辑实现
这里用Python来实现,因为之后的机器学习库也是Python的,方便一点。
界面和逻辑要分开,解耦合,这个是毋庸置疑的,并且之后还要训练AI,分离这是必须的。所以我们先来实现一个五子棋的逻辑。
我们先来考虑五子棋是一个15*15的棋盘,棋盘上的每一个交叉点(或格子)上一共会有3种状态:空白、黑棋、白棋,所以先建个文件 consts.py
做如下定义:
from enum import Enum N = 15 class ChessboardState(Enum):
EMPTY = 0
BLACK = 1
WHITE = 2
棋盘的状态,我们先用一个15*15的二维数组chessMap来表示,建一个类 gobang.py
currentI、currentJ、currentState 分别表示当前这步着棋的坐标和颜色,再定义一个get和set函数,最基本的框架就出来了,代码如下:
from enum import Enum
from consts import * 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 def get_chessMap(self):
return self.__chessMap def get_chessboard_state(self, i, j):
return self.__chessMap[i][j] def set_chessboard_state(self, i, j, state):
self.__chessMap[i][j] = state
self.__currentI = i
self.__currentJ = j
self.__currentState = state
这样界面端可以调用get函数来获取各个格子的状态来决定是否绘制棋子,以及绘制什么样的棋子;每次下棋的时候呢,在对应的格子上,通过坐标来设置棋盘Map的状态。
所以最基本的展示和下棋,上面的逻辑就够了,接下来干什么呢,得考虑每次下棋之后,set了对应格子的状态,是不是需要判断当前有没有获胜。所以还需要再加两个函数来干这个事情,思路就是从当前位置从东、南、西、北、东南、西南、西北、东北8个方向,4根轴,看是否有连续的大于5颗相同颜色的棋子出现。假设我们目前落子在棋盘正中,需要判断的位置如下图所示的米字形。

那代码怎么写呢,最最笨的办法,按照字面意思来翻译咯,比如横轴,先看当前位置左边有多少颗连续同色的,再看右边有多少颗连续同色的,左边加右边,就是当前横轴上的连续数,如果大于5,则胜利。
def have_five(self, current_i, current_j):
#四个方向计数 竖 横 左斜 右斜
hcount = 1 temp = ChessboardState.EMPTY #H-左
for j in range(current_j - 1, -1, -1): #横向往左 from (current_j - 1) to 0
temp = self.__chessMap[current_i][j]
if temp == ChessboardState.EMPTY or temp != self.__currentState:
break
hcount = hcount + 1
#H-右
for j in range(current_j + 1, N): #横向往右 from (current_j + 1) to N
temp = self.__chessMap[current_i][j]
if temp == ChessboardState.EMPTY or temp != self.__currentState:
break
hcount = hcount + 1
#H-结果
if hcount >= 5:
return True
以此类推,再看竖轴、再看左斜、再看右斜。于是,have_five函数变成这样了:
def have_five(self, current_i, current_j):
#四个方向计数 横 竖 左斜 右斜
hcount = 1
vcount = 1
lbhcount = 1
rbhcount = 1 temp = ChessboardState.EMPTY #H-左
for j in range(current_j - 1, -1, -1): #横向往左 from (current_j - 1) to 0
temp = self.__chessMap[current_i][j]
if temp == ChessboardState.EMPTY or temp != self.__currentState:
break
hcount = hcount + 1
#H-右
for j in range(current_j + 1, N): #横向往右 from (current_j + 1) to N
temp = self.__chessMap[current_i][j]
if temp == ChessboardState.EMPTY or temp != self.__currentState:
break
hcount = hcount + 1
#H-结果
if hcount >= 5:
return True
#V-上
for i in range(current_i - 1, -1, -1): # from (current_i - 1) to 0
temp = self.__chessMap[i][current_j]
if temp == ChessboardState.EMPTY or temp != self.__currentState:
break
vcount = vcount + 1
#V-下
for i in range(current_i + 1, N): # from (current_i + 1) to N
temp = self.__chessMap[i][current_j]
if temp == ChessboardState.EMPTY or temp != self.__currentState:
break
vcount = vcount + 1
#V-结果
if vcount >= 5:
return True
#LB-上
for i, j in zip(range(current_i - 1, -1, -1), range(current_j - 1, -1, -1)):
temp = self.__chessMap[i][j]
if temp == ChessboardState.EMPTY or temp != self.__currentState:
break
lbhcount = lbhcount + 1
#LB-下
for i, j in zip(range(current_i + 1, N), range(current_j + 1, N)):
temp = self.__chessMap[i][j]
if temp == ChessboardState.EMPTY or temp != self.__currentState:
break
lbhcount = lbhcount + 1
#LB-结果
if lbhcount >= 5:
return True
#RB-上
for i, j in zip(range(current_i - 1, -1, -1), range(current_j + 1, N)):
temp = self.__chessMap[i][j]
if temp == ChessboardState.EMPTY or temp != self.__currentState:
break
rbhcount = rbhcount + 1
#RB-下
for i, j in zip(range(current_i + 1, N), range(current_j - 1, -1, -1)):
temp = self.__chessMap[i][j]
if temp == ChessboardState.EMPTY or temp != self.__currentState:
break
rbhcount = rbhcount + 1
#LB-结果
if rbhcount >= 5:
return True
这样是不是就写完了,五子棋的逻辑全部实现~
NO,别高兴得太早,我想说,我好恶心,上面那个代码,简直丑爆了,再看一眼,重复的写了这么多for,这么多if,这么多重复的代码块,让我先去吐会儿……
好了,想想办法怎么改,至少分了4根轴,是重复的对不对,然后每根轴分别从正负两个方向去统计,最后加起来,两个方向,也是重复的对不对。
于是我们能不能只写一个方向的代码,分别调2次,然后4根轴,分别再调4次,2*4=8,一共8行代码搞定试试。
因为有45°和135°这两根斜轴的存在,所以方向上应该分别从x和y两个轴来控制正负,于是可以这样,先写一个函数,按照方向来统计:
xdirection=0,ydirection=1 表示从y轴正向数;
xdirection=0,ydirection=-1 表示从y轴负向数;
xdirection=1,ydirection=1 表示从45°斜轴正向数;
……
不一一列举了,再加上边界条件的判断,于是有了以下函数:
def count_on_direction(self, i, j, xdirection, ydirection, color):
count = 0
for step in range(1, 5): #除当前位置外,朝对应方向再看4步
if xdirection != 0 and (j + xdirection * step < 0 or j + xdirection * step >= N):
break
if ydirection != 0 and (i + ydirection * step < 0 or i + ydirection * step >= N):
break
if self.__chessMap[i + ydirection * step][j + xdirection * step] == color:
count += 1
else:
break
return count
于是乎,前面的have_five稍微长的好看了一点,可以变成这样:
def have_five(self, i, j, color):
#四个方向计数 横 竖 左斜 右斜
hcount = 1
vcount = 1
lbhcount = 1
rbhcount = 1 hcount += self.count_on_direction(i, j, -1, 0, color)
hcount += self.count_on_direction(i, j, 1, 0, color)
if hcount >= 5:
return True vcount += self.count_on_direction(i, j, 0, -1, color)
vcount += self.count_on_direction(i, j, 0, 1, color)
if vcount >= 5:
return True lbhcount += self.count_on_direction(i, j, -1, 1, color)
lbhcount += self.count_on_direction(i, j, 1, -1, color)
if lbhcount >= 5:
return True rbhcount += self.count_on_direction(i, j, -1, -1, color)
rbhcount += self.count_on_direction(i, j, 1, 1, color)
if rbhcount >= 5:
return True
还是一大排重复的代码呀,我还是觉得它丑啊,我真的不是处女座,但是这个函数是真丑啊,能不能让它再帅一点,当然可以,4个重复块再收成一个函数,循环调4次,是不是可以,好,就这么干,于是have_five就又漂亮了一点点:
def have_five(self, i, j, color):
#四个方向计数 横 竖 左斜 右斜
directions = [[(-1, 0), (1, 0)], \
[(0, -1), (0, 1)], \
[(-1, 1), (1, -1)], \
[(-1, -1), (1, 1)]] for axis in directions:
axis_count = 1
for (xdirection, ydirection) in axis:
axis_count += self.count_on_direction(i, j, xdirection, ydirection, color)
if axis_count >= 5:
return True return False
嗯,感觉好多了,这下判断是否有5颗相同颜色棋子的逻辑也有了,再加一个函数来给界面层返回结果,逻辑部分的代码就差不多了:
def get_chess_result(self):
if self.have_five(self.__currentI, self.__currentJ, self.__currentState):
return self.__currentState
else:
return ChessboardState.EMPTY
于是,五子棋逻辑代码就写完了,完整代码 gobang.py 如下:
#coding:utf-8 from enum import Enum
from consts import * 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 def get_chessMap(self):
return self.__chessMap def get_chessboard_state(self, i, j):
return self.__chessMap[i][j] def set_chessboard_state(self, i, j, state):
self.__chessMap[i][j] = state
self.__currentI = i
self.__currentJ = j
self.__currentState = state def get_chess_result(self):
if self.have_five(self.__currentI, self.__currentJ, self.__currentState):
return self.__currentState
else:
return ChessboardState.EMPTY def count_on_direction(self, i, j, xdirection, ydirection, color):
count = 0
for step in range(1, 5): #除当前位置外,朝对应方向再看4步
if xdirection != 0 and (j + xdirection * step < 0 or j + xdirection * step >= N):
break
if ydirection != 0 and (i + ydirection * step < 0 or i + ydirection * step >= N):
break
if self.__chessMap[i + ydirection * step][j + xdirection * step] == color:
count += 1
else:
break
return count def have_five(self, i, j, color):
#四个方向计数 横 竖 左斜 右斜
directions = [[(-1, 0), (1, 0)], \
[(0, -1), (0, 1)], \
[(-1, 1), (1, -1)], \
[(-1, -1), (1, 1)]] for axis in directions:
axis_count = 1
for (xdirection, ydirection) in axis:
axis_count += self.count_on_direction(i, j, xdirection, ydirection, color)
if axis_count >= 5:
return True return False
小伙伴:大哥,憋了半天,就憋出这么不到60行代码?
我:代码不在多,实现则灵……
明天来给它加个render,前端界面就有了,就是一个简单的完整游戏了,至于AI,别急嘛。
好吧,就这样…
UI部分在这里:
[深度学习]实现一个博弈型的AI,从五子棋开始(1)的更多相关文章
- [深度学习]实现一个博弈型的AI,从五子棋开始(2)
嗯,今天接着来搞五子棋,从五子棋开始给小伙伴们聊AI. 昨天晚上我们已经实现了一个五子棋的逻辑部分,其实讲道理,有个规则在,可以开始搞AI了,但是考虑到不够直观,我们还是顺带先把五子棋的UI也先搞出来 ...
- [深度学习]实现一个博弈型的AI,从五子棋开始
好久没有写过博客了,多久,大概8年???最近重新把写作这事儿捡起来……最近在折腾AI,写个AI相关的给团队的小伙伴们看吧. 搞了这么多年的机器学习,从分类到聚类,从朴素贝叶斯到SVM,从神经网络到深度 ...
- PDNN: 深度学习的一个Python工具箱
PDNN: 深度学习的一个Python工具箱 PDNN是一个在Theano环境下开发出来的一个Python深度学习工具箱.它由苗亚杰(Yajie Miao)原创.现在仍然在不断努力去丰富它的功能和扩展 ...
- 深度学习:又一次推动AI梦想(Marr理论、语义鸿沟、视觉神经网络、神经形态学)
几乎每一次神经网络的再流行,都会出现:推进人工智能的梦想之说. 前言: Marr视觉分层理论 Marr视觉分层理论(百度百科):理论框架主要由视觉所建立.保持.并予以解释的三级表象结构组成,这就是: ...
- 图像识别 | AI在医学上的应用 | 深度学习 | 迁移学习
参考:登上<Cell>封面的AI医疗影像诊断系统:机器之心专访UCSD张康教授 Identifying Medical Diagnoses and Treatable Diseases b ...
- AI理论学习笔记(一):深度学习的前世今生
AI理论学习笔记(一):深度学习的前世今生 大家还记得以深度学习技术为基础的电脑程序AlphaGo吗?这是人类历史中在某种意义的第一次机器打败人类的例子,其最大的魅力就是深度学习(Deep Learn ...
- 转:【AI每日播报】从TensorFlow到Theano:横向对比七大深度学习框架
http://geek.csdn.net/news/detail/139235 说到近期的深度学习框架,TensorFlow火的不得了,虽说有专家在朋友圈大声呼吁,不能让TensorFlow形成垄断地 ...
- AI安全初探——利用深度学习检测DNS隐蔽通道
AI安全初探——利用深度学习检测DNS隐蔽通道 目录 AI安全初探——利用深度学习检测DNS隐蔽通道 1.DNS 隐蔽通道简介 2. 算法前的准备工作——数据采集 3. 利用深度学习进行DNS隐蔽通道 ...
- AI - 深度学习之美十四章-概念摘要(8~14)
原文链接:https://yq.aliyun.com/topic/111 本文是对原文内容中部分概念的摘取记录,可能有轻微改动,但不影响原文表达. 08 - BP算法双向传,链式求导最缠绵 反向传播( ...
随机推荐
- Python调用C# Com dll组件实战
之前公司有套C# AES加解密方案,但是方案加密用的是Rijndael类,而非AES的四种模式(ECB.CBC.CFB.OFB,这四种用的是RijndaelManaged类),Python下Crypt ...
- (转)C#中各种集合类比较
数组(Array)的不足(即:集合与数组的区别) 1. 数组是固定大小的,不能伸缩.虽然System.Array.Resize这个泛型方法可以重置数组大小,但是该方法是重新创建新设置大小的数组,用的是 ...
- JavaScript性能优化之函数节流(throttle)与函数去抖(debounce)
函数节流,简单地讲,就是让一个函数无法在很短的时间间隔内连续调用,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用. 函数节流的原理挺简单的,估计大家都想到了,那就是定时器.当我 ...
- WPF DataGrid自定义样式
微软的WPF DataGrid中有很多的属性和样式,你可以调整,以寻找合适的(如果你是一名设计师).下面,找到我的小抄造型的网格.它不是100%全面,但它可以让你走得很远,有一些非常有用的技巧和陷阱. ...
- ELK日志收集分析系统配置
ELK是日志收益与分析的利器. 1.elasticsearch集群搭建 略 2.logstash日志收集 我这里的实现分如下2步,中间用redis队列做缓冲,可以有效的避免es压力过大: 1.n个ag ...
- 运用 finereport 和 oracle 结合开发报表思路大总结
近排自己学习了一款软件finereport开发报表模块,自己总结了如何了解需求,分析需求,再进行实践应用开发,最后进行测试数据的准确性,部署报表到项目对应的模块中显示. 一.需求(根据需求文档分析) ...
- ASP.NET没有魔法——ASP.NET MVC是如何运行的?它的生命周期是什么?
前面的文章我们使用ASP.NET MVC创建了个博客应用,那么它是如何工作的呢?我们都知道ASP.NET的程序需要部署到IIS上才能够通过浏览器来访问,那么IIS与ASP.NET MVC程序之间又是如 ...
- 浅谈script标签中的async和defer
script标签用于加载脚本与执行脚本,在前端开发中可以说是非常重要的标签了.直接使用script脚本的话,html会按照顺序来加载并执行脚本,在脚本加载&执行的过程中,会阻塞后续的DOM渲染 ...
- Appium python自动化测试系列之appium环境搭建(二)
2.1 基础环境搭建 当我们学习新的一项技术开始基本都是从环境搭建开始,本书除了第一章节也是的,如果你连最基础的环境都没有那么我们也没必要去说太多,大概介绍一下: 1.因为appium是支持andr ...
- 一天工作所用到的Git命令
一天工作所用到的Git命令 像大多数新手一样,我一开始是在网上搜索 Git 命令,然后把答案复制粘贴,并没有真正理解它们究竟做了什么. 后来我曾经想过:"如果有一个最常见的 Git 命令的列 ...