Honinbo
Honinbo是一款开源的围棋ai程序,名称来源于日本古棋圣【本因坊秀策】。
技术架构
前端: Electron Vue Node.js
引擎: Python torch sqlite
开发流程
您如果想要加入本项目,请添加QQ群790148267。
可能需要的软件:Visual Studio Code,Pycharm,fork(一种免费的git管理工具)
开发新功能时从product分支签出新分支,在staging分支进行开发,最后测试完毕由管理员合并至product分支。
分支名字考虑用中文名,可以用xxx/xxxx的方式创建在文件夹内的分支,比如feature/xxx,则该分支会出现在feature文件夹下。
数据集地址: git@gitee.com:Jifashi_619/go-data-set.git
神经网络权重文件地址:https://gitee.com/Jifashi_619/saved_model
多写注释。
🥂安装
前端:
cd /honinbo; # 安装 npm i; # 如果网路卡顿 尝试以下命令进行安装 npm i cnpm -g; cnpm i #运行 npm run dev;
引擎:
cd /honinbo/engine pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/ git clone https://gitee.com/Jifashi_619/saved_model.git python3 main.py
前端部分
围棋规则浅谈
- 吃子
一个棋子的上下左右四个格子若为空,则称为棋子的气,当四个气都被其他棋子占据之后,这个子就被称为死棋。
多个相邻(指相邻的上下左右格子)的同色棋子组成一个棋串,棋串会按整体去计算气,若气为0,则应该从棋盘上提去。
- 打劫
围棋不允许出现重复(指完全一样)的棋形,所以当一方被吃一子的时候,不能马上吃回,必须先去其他地方落子,这就是打劫。打劫只会发生在被吃单个棋子的情况,吃多个马上吃回并不会导致棋盘重复。
- 胜负
去除棋盘上所有的死棋后,黑白计算棋子的个数 棋盘围空的数量,黑需要贴6目半(目即棋子围起来的空,1空为1目),多者获胜。
围棋逻辑实现
棋盘数据结构
棋盘是一个19*19的二维数组,0代表空,-1为白子,1为黑子,每次落子后程序对数组进行处理
程序中棋盘边界为0-18,用户层为1-19
落子
-
Honinbo会先判断落子是否合法,即x,y坐标是否在(1,19)的范围内。再判断该点是否处于打劫状态
-
combine(x,y)
会尝试将当前落子点与上下左右四个方向匹配,若找到同色棋子,则会合并到棋串当中。
//棋串数据结构: { black:{ 1:{ 2: { num:19 } }, 19:[{x:1,y:2}] } }
即棋串按黑白二色区分,次级键是x,y坐标,最后则是这个棋串的编号。19开始则为棋串数组(因为棋盘只会是0-18的点),管理了该棋串共有哪些元素。
-
getStringInfo(x,y)
会根据传入的坐标获取棋串,或者返回该点不存在棋串 -
getSrcString(su,sd,sl,sr)
会对四个方向的棋串进行选举,决定基准棋串。 -
combineCurrentString(x,y,su,sd,sl,sr,src)
对这些棋串进行合并操作。
if (sd > 0 && sd !== src) { console.log("合并sd :>> ", sd); subString[src] = subString[src].concat(subString[sd]); for (let key in subString) { if (key < 19) { for (let subKey in subString[key]) { if (subString[key][subKey] === sd) { subString[key][subKey] = src; } } } } delete subString[sd]; }
吃子、杀棋
每次落子的时候,若出现杀棋也只会是上下左右四个方向的相邻格,因此:
-
kill(x,y)
会获取上下左右四个点的坐标,若是异色棋子则进行处理,交给checkKill
判断 -
checkKill(x,y,flag)
从棋串中按编号查询到该棋串数组,进行遍历:
-
若四个方向没有为空的点,则判断为死棋,继续递归。
-
若有空格则直接判断为活棋,
return
-
棋盘边界与同色棋子也是死棋,因为它们都算是占了一个气眼
- 若出现杀棋则交给
cleanString(num)
,从棋串中将棋子信息清除
自身死棋
围棋不允许落子导致自身死棋,因此未杀棋就需要交给kill(x,y)
判断,只是这里的flag颜色标识是判断的自身
若自身死棋则交给suicide(x,y)
将刚才的落子从棋串中清除。
总结整体流程
-
落子基本判断
-
combine()
-
kill()
-
suicide()
⏳ 引擎部分
通信
前后端通信有两种方式:Tcp和命令行(暂不支持),Tcp传输Json数据。
引擎分析
{ "operator":"run", "board": [], "color": "black" }
run命令代表前端要求引擎进行分析。
保存前端的棋谱
{ "operator":"saveGoban", "goban": "sgf..." }
引擎会将这些数据入库保存。
神经网络部分
架构
提取器(feature.py)->评价器(value.py)、策略器(policy.py)->结果
损失计算:
-
评价器:均方差损失
-
策略器:交叉熵损失
def forward(winner, self_play_winner, probas, self_play_probas): value_error = (self_play_winner - winner) ** 2 policy_error = torch.sum((-self_play_probas * (1e-6 probas).log()), 1) total_error = (value_error.view(-1) policy_error).mean() return total_error
神经网络设计
输入
由于棋盘状态是:0->空,1->落黑子,-1->落白子,与图像识别类似,所以考虑用卷积神经网络。
输入给提取器的共23通道,如下:
1.全部是自己的棋 2-8. 最近7步 棋盘上是否有自己的棋 9,10,11自己的棋子分别还剩1 2 3 口气的棋盘
12全部是对手的棋 13-19.最近7步 棋盘上是否有对手的棋 20,21,22对手的棋子分别还剩1 2 3 口气的棋盘 23. 全1或全-1 表示目前是谁在落子
输出
提取器输出10通道
评价器输出单通道的胜率值
策略器输出[1,361]
的矩阵,对应每个点的落子概率
分析
引擎从策略器的输出中取出5个概率最大的点,并且是合法的落子点,返回给前端
后续会接入蒙特卡洛树,未完待续。