一个JavaScript写的黑白棋AI
赖勇浩(http://laiyonghao.com)
首先,这个代码不是我写的,但注释是我加上去的。作者是shaofei cheng,他的网站:http://shaofei.name
第二,目前这个代码只是使用了 alpha-beta 剪枝,棋力还弱,有很大的优化空间。但是代码写得非常清晰,如果有朋友对人机弈棋方面的课题有兴趣又还没有入门,这份代码作为一个例子是很棒的。
第三,目前计算机只能搜索 3 层,我觉得加上迭代深化和历史启发算法之后,搜索到 5 层是不成问题的。现代 JavaScript 的性能不错。
第四,作者在代码里展示了不少技巧,值得学习和借鉴,哪怕不懂 JavaScript 也很容易看懂代码(我也不懂)。
第五,试试这个 AI 的棋力:http://shaofei.name/OthelloAI/othello.html
以下是代码:
var AI = {};new function(){AI.Pattern= pattern;// 定义了 8 个偏移量// 可以简单通过加法得到任一点周围 8 个点的坐标// -11 -10 -9// -1 x 1// 9 10 11// 如左上角的坐标为 x + (-11)var directions=[-11,-10,-9,-1,1,9,10,11];function pattern(){ // 把整个棋盘填满 0 for(var i=0;i<100;i++)this[i]=0; // 中间的 4 个格子,先放上两黑两白的棋子 this[54]=this[45]=1;this[55]=this[44]=2; // 黑净胜外围子数目(黑减去白),估值时用。 this.divergence=0; // 当前可走棋方为黑棋 this.color=1; // 已经走了几步棋 this.moves=0; // 稳定原型 // 0 是空白,1 是黑棋,2 是白棋,3 是边界 // 把 8 * 8 的棋盘扩展成 10 * 10,是一种技巧 // 可以简化坐标有效性的判断 var stableProto = [ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 ] // 从一个 8 * 8 的棋盘载入状态 this.load=function(arr) { for(var y=1;y<=8;y++) { for(var x=1;x<=8;x++) { this[y*10+x]=arr[y-1][x-1]; } } } // 判断能不能 pass // 如果能,则当前可走棋方变更 this.pass=function() { for(var y=1;y<=8;y++) { for(var x=1;x<=8;x++) { if(this[y*10+x]==0) { // 有任何一步棋可走,都不可以 Pass if(this.move(x,y,this.color)) { return false; } } } } //alert("pass"); // 这是一个技巧,因为 this.color 的值域是 {1, 2} // 所以当 color 是 1 时,执行完下一语句后就是 2 // 当 color 是 2 时,执行完下一语句后就是 1 this.color = 3 - this.color; return true; } this.clone=function() { function pattern(){} pattern.prototype=this; return new pattern(); } this.toString=function() { var icon=[" ","*","o"] var r=""; for(var y=1;y<=8;y++) { for(var x=1;x<=8;x++) { r+=icon[this[y*10+x]]+" "; //r+=stableDiscs[y*10+x]+" "; } r+="/n"; } return r+this.exact(); } // 净胜子数 this.exact=function() { // 这里是一个技巧, r[0] 是不使用的,r[1] r[2] 对应黑白棋子的个数 var r=[0,0,0]; for(var y=1;y<=8;y++) { for(var x=1;x<=8;x++) { r[this[y*10+x]]++; // 数目加一 } } // 当前颜色的数量为 0,输了,返回负极值 if(r[this.color]==0) return -64; // 敌对颜色的数量为 0,赢了,返回极值 if(r[3-this.color]==0) return 64; // 返回当前走棋方比对方多的数量 return r[this.color]-r[3-this.color]; } // 对棋盘的估值 this.calculate=function() { // 基本估值方法: // 1、能占棋盘四角是很有价值的 // 2、邻近棋盘四角的位子是很差的 // 3、稳定子 // 4、外围子净胜数 var r=[0,0,0]; var r=this.divergence; // 如果左上角有棋子,自己的,就+30分,敌方的,-30 分 if(this[11]) r+=((this[11]==this.color)?1:-1)*30; // 次左上角,分值是 -15 else if(this[22]==this.color)r-=15; // 右上角,分值 30 if(this[18])r+=((this[18]==this.color)?1:-1)*30; // 次右上角,分值 -15 else if(this[27]==this.color)r-=15; // 左下角,分值 30 if(this[81])r+=((this[81]==this.color)?1:-1)*30; // 次左下角,分值 -15 else if(this[72]==this.color)r-=15; // 右下角,分值 30 if(this[88]){r+=((this[88]==this.color)?1:-1)*30;} // 次右下角,分值 -15 else if(this[77]==this.color)r-=15; // 查找稳定子, // 稳定子就是挨着 4 个角点并且周边的棋子要么是同色,要么是边界 //var color = this.color; var stableDiscs=stableProto.slice(); var queue = []; if(this[11]!=0) queue.push([11,this[11]]); if(this[18]!=0) queue.push([18,this[18]]); if(this[81]!=0) queue.push([81,this[81]]); if(this[88]!=0) queue.push([88,this[88]]); while(queue.length) { var position = queue[0][0]; var c = queue[0][1]; // 不懂 JS 的数组的内存管理算法,不过感觉从头上删除肯定是比较慢的, // 我感觉从后面删除会更好,或者使用标记不删除的方法性能会更好 queue.shift(); //if(stableDiscs[position]==0 || stableDiscs[position]==3) continue; stableDiscs[position] = c; if( (stableDiscs[position-10]==3 || stableDiscs[position+10]==3 || stableDiscs[position-10] == c || stableDiscs[position+10] == c) && (stableDiscs[position-1]==3 || stableDiscs[position+1]==3 || stableDiscs[position-1] == c || stableDiscs[position+1] == c) && (stableDiscs[position-11]==3 || stableDiscs[position+11]==3 || stableDiscs[position-11] == c || stableDiscs[position+11] == c) && (stableDiscs[position-9]==3 || stableDiscs[position+9]==3 || stableDiscs[position-9] == c || stableDiscs[position+9] == c) ) { stableDiscs[position]=c; // 稳定子的分值为 7 r += ((c==this.color)?1:-1)*7; // 进一步扩展,查找稳定子 for(var i = 0;i <directions.length ; i++) if(stableDiscs[directions[i]+position]==0 && this[directions[i]+position]==c) queue.push([directions[i]+position,c]); } } // 返回估值 return r; } this.toLocalString=function(depth) { var r=""; if(!depth)depth=0; for(var y=1;y<=8;y++) { for(var x=1;x<=8;x++) { if(this[y*10+x]!=0)r+=(this[y*10+x]==1?"*":"o")+" "; else { var tmp=this.move(x,y,this.color); if(tmp) { var tmp2=-tmp.search(-Infinity,Infinity,depth); if(tmp2<0||tmp2>9)r+=tmp2; else r+=" "+tmp2; } else r+="X "; } } r+="/n"; } return r+this.exact(); } // 计算机去找一步可走的棋步 // 这里 AI 部分的入口 this.computer=function(depth,exactDepth){ if(!depth)depth=0; if(!exactDepth)exactDepth=depth; var r=[]; var max=-Infinity; for(var y=1;y<=8;y++) { for(var x=1;x<=8;x++) { if(this[y*10+x])continue; // 找到一个空白格子 else { // 尝试走这个格子 var tmp=this.move(x,y,this.color); // 不成功,非法 if(!tmp)continue; // 已走步数+已搜索深度 >= 有 60 步 // 这时使用精确搜索得到更精确的结果 if(this.moves+exactDepth>=60) { var v=-tmp.exactSearch(-Infinity,Infinity); //alert([x,y]+":"+v); } // 离四个角最近的那 3 * 4 个格子,则多搜索一层 // 因为对手可能在下一手下在角上,会出现大翻盘。 else if( (x==2||x==7) && (y==2||y==7) ) var v=-tmp.search(-Infinity,Infinity,depth+1); else var v=-tmp.search(-Infinity,Infinity,depth); // 还不如之前的棋步 if(v<max)continue; // 比之前的棋步好 if(v>max){ // 保存起来 r=[[x,y]];max=v; } // 另一个可选的棋步 else r.push([x,y]); } } } // 在所有可选的棋步中,随机选择一个,让玩家觉得比较多变化,不那么单调。 var tmp=Math.floor(Math.random()*r.length); return r[tmp]; } // 搜索算法 // 使用负极大值形式的 Alpha-Beta 剪枝搜索算法 this.search=function(alpha,beta,depth,pass){ // 叶子节点,返回估值 if(depth==0)return this.calculate(); var canmove=false; for(var y=1;y<=8;y++) { for(var x=1;x<=8;x++) { if(this[y*10+x]!=0)r+=(this[y*10+x]==1?"*":"o")+" "; else { var tmp=this.move(x,y,this.color); if(!tmp)continue; canmove=true; // 往更深搜索 var r=-tmp.search(-beta,-alpha,depth-1); //if(depth==4)WScript.echo(r); // 收窗窗口 if(r>=alpha)alpha=r; // 胜着 if(alpha>beta)return Infinity; } } } // 返回当前局面的最佳着法估值 if(canmove)return alpha; // 双方都没有可下子之处,返回净胜子数 if(pass) return this.exact(); // pass 一次,往深搜索 this.color=3-this.color; return -this.search(-beta,-alpha,depth-1,true); } // 精确搜索,这段的算法原理跟 search 是一样的 this.exactSearch=function(alpha,beta,pass){ // 已经走了 60 步了,返回净胜子数 if(this.moves==60)return this.exact(); var canmove=false; for(var y=1;y<=8;y++) { for(var x=1;x<=8;x++) { if(this[y*10+x]!=0);//r+=(this[y*10+x]==1?"*":"o")+" "; else { var tmp=this.move(x,y,this.color); if(!tmp)continue; canmove=true; var r=-tmp.exactSearch(-beta,-alpha); if(r>=alpha)alpha=r; if(alpha>beta)return Infinity; } } } if(canmove)return alpha; if(pass)return this.exact(); this.color=3-this.color; return -this.exactSearch(-beta,-alpha,true); } // 尝试在 x, y 放下 this.color 颜色的棋子,成功返回下一棋盘状态,否则返回 null this.move=function(x,y) { // 复制当前状态 var pattern=this.clone(); pattern.color=3-this.color; // 注意这个负号 pattern.divergence=-pattern.divergence; // move 数++ pattern.moves++; var canmove; canmove=false; // 放在函数入口处,可以优化性能 // 把 10*y+x 放入临时变量可优化性能 if(pattern[10*y+x]!=0)return null; // 8 方向判断 for(var i=0;i<8;i++) { // 转换为一维索引 var p=10*y+x+directions[i]; // 邻近的格子上棋子不同色 if(pattern[p]==3-this.color) while(pattern[p]!=0) { // 往同方向搜索 p+=directions[i]; // 另一端还有一个自己的棋子,则是一个可走的点。 if(pattern[p]==this.color) { canmove=true; // 把中间的棋子翻过来 while((p+=-directions[i])!=10*y+x) { pattern[p]=this.color; //alert(p); for(var d=0;d<8;d++) { // 非空 if(!pattern[p+directions[d]] // 非边界 &&p+directions[d]>10 &&p+directions[d]<89 &&(p+directions[d])%10!=0 &&(p+directions[d])!=9) // 外围净胜子数增加 pattern.divergence++; } } break; } } } // 返回新的棋盘状态 if(canmove){pattern[10*y+x]=this.color;return pattern;} else return null; }}//pattern.prototype = emptyboard;//WScript.echo(new pattern().move(5,6).move(6,4).move(4,3).move(3,4).toLocalString(3));//WScript.echo(new pattern().move(5,6).search(-Infinity,Infinity,2));}()
一个JavaScript写的黑白棋AI相关推荐
- Python蒙特卡洛树搜索算法实现的黑白棋AI系统
资源下载地址:https://download.csdn.net/download/sheziqiong/85836329 资源下载地址:https://download.csdn.net/downl ...
- 深度解析黑白棋AI代码原理(蒙特卡洛搜索树MCTS+Roxanne策略)
深度解析黑白棋AI代码原理(蒙特卡洛搜索树MCTS+Roxanne策略) 文章目录 深度解析黑白棋AI代码原理(蒙特卡洛搜索树MCTS+Roxanne策略) 黑白棋规则 传统黑白棋策略 蒙特卡洛搜索树 ...
- 吴昊品游戏核心算法 Round 9 —— 正统黑白棋AI(博弈树)
黑白棋程式简史 在1980年代,电脑并不普及,在黑白棋界里,最强的仍然是棋手(人类). 到了1990年代初,电脑的速度以几何级数增长,写出来的黑白棋程式虽然仍然有点笨拙,但由于计算深度(电脑的速度快) ...
- 吴昊品游戏核心算法 Round 9 —— 黑白棋AI系列之西洋跳棋(第二弹)(双向BFS+STL)(POJ 1198)...
接上回,如图所示,这是黑白棋的一个变种,Solitaire也是一种在智能手机上普遍存在的一种游戏.和翻转棋(Flip Game)一样,西洋跳棋(Solitaire)也没有正统的黑白棋(奥赛罗,又称Ot ...
- 发布我的下棋作品--Monkey黑白棋(AI还可以,战胜了不少其它的黑白棋程序)
采用了AlaphBeta剪枝法,深度4至8 . 本软件由C#2005开发,以下是界面: 持白子的话,能够战胜不少黑白棋的软件(当然,像"伤心黑白棋"这样的高手,我的程序是差一截). ...
- c语言写的黑白棋游戏代码,C语言编写的黑白棋游戏源代码..doc
C语言编写的黑白棋游戏源代码. C语言编写的黑白棋游戏/*3.3.4 源程序*/ #include "graphics.h" /*图形系统头文件*/ #define LEFT 0x ...
- java drawboard_学习一个月JAVA写的黑白棋程序(欢迎高手来指点)
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 public void clean(Graphics g) //清屏 { g.setColor(0xffffff); g.fillRect(0,0,180 ...
- c语言黑白棋ai游戏源码
#include <graphics.h> // EasyX_2011惊蛰版 #include <strstream> #include <ctime> #prag ...
- c语言翻转棋ai算法,黑白棋游戏(也叫翻转棋)(AI 版)
黑白棋(也叫翻转棋)的棋盘是一个有8*8方格的棋盘.下棋时将棋下在空格中间,而不是像围棋一样下在交叉点上.开始时在棋盘正中有两白两黑四个棋子交叉放置,黑棋总是先下子. 下子的方法:把自己颜色的棋子放在 ...
最新文章
- 【IOC 控制反转】Android 事件依赖注入 ( 事件三要素 | 修饰注解的注解 | 事件依赖注入步骤 )
- 谷歌浏览器中文版_GitHub上最励志的计算机自学教程:8个月,从中年Web前端到亚马逊百万年薪软件工程师 | 中文版...
- CVPR 2021 | 自适应激活函数ACON:统一ReLU和Swish的新范式
- 【数据结构与算法】之深入解析“三数之和”的求解思路与算法示例
- angular-file-upload+springMVC的使用
- oracle恢复drop建的表首次,案例:Oracle dul数据挖掘 没有备份情况下非常规恢复drop删除的数据表...
- VS.NET2005中的WEBPART初步(二)
- get请求,参数值为json字符串如何传值
- 单人存档_电子发票归档怎么保存稳当?电子发票存档你都会了吗?
- 1.8 Linux用户与用户组文件权限
- input输入框计算总和
- selenium+python自动化106 - 滑动 iframe 上的滚动条
- UPnP 体系架构和基本原理 —— UPnP 网络组件
- 你不是痘痘肌,你只是管不住嘴
- android 北斗测试,安卓手机查看是否支持北斗导航系统的详细检测方法
- 优秀IT项目经理的基本要求
- 现役大学生必看!干货满满!
- 苹果 CMS 搭建视频网站,定时采集视频
- 前端基础知识学习 XML JSON RESTFUL SOAP WEBSERVICE
- OkHttp相关知识(三)
热门文章
- 文法和语言的基本知识
- Vue父组件传子组件数据中,Vue监听不到数据改变
- 回归分析结果表格怎么填_spss回归分析结果图,帮忙看一下,麻烦详细地解释解释...
- Nightcafe Creator:AI绘画艺术图片生成器
- Mysql.cnf配置详解
- 佟年计算机天才不会打游戏,亲爱的热爱的:Gun神带佟年开黑,网友:甜蜜游戏时间...
- scala-第七章-打印9*9乘法口诀表
- BERT原理和结构详解
- Jira - JIRA-Bootstrap ERROR
- 论坛议程|COSCon'22 女性论坛(L)