纯JS 智能五子棋 初级版

标签:
css3javascript五子棋纯js人机对战底层函数棋盘棋子计分方向检测 |
分类: Javascript |
《纯JS 智能五子棋 out版》都写了出来,这个版本怎么可能不写,我感觉它还是不错的,一不小心会输给它,或者跟它纠结了至少半个盘面才赢,不过觊觎里面还有很多细节没完善,所以命名为《纯JS 智能五子棋初级版》,做人还是低调的好,不过目前还是引以为傲的一个作品哈,初级版的界面如下:
这不是跟原来的一样吗,坑爹啊!!! 不一样的,其实它下面多了两个按钮哈...
正如前面out版所说,我除了方向循环,界面设计采用了前面那个之外,其它的基本都是重新写的,智商从“智障”提升到“一般大众”水平,界面可见功能都绑定上去了,兼容firefox、chrome、safari、ie各版本,你可以通过这个链接下载程序源代码,小心别输给它哦 q(^0^)p :《纯JS 智能五子棋 初级版》
为方便下载,提供本人网盘帐号密码,请不要弄乱里面的页面,以方便其它人下载,谢谢。
密码:123456
关于棋盘,棋子,鼠标滑过提示下棋位置这些前面已经提过了,我也就不说了,大家如果没看又感兴趣的话,可以查看《纯JS 智能五子棋 out版》,里面有详细的描述,下面我对修改的部分关键点进行分析:
1、基本操作
进入游戏界面后点击开始按钮就开始游戏,没有计时功能,所以你点开始后去吃饭饭或者上个厕所回来也不会判你输(这截然是废话);点击暂停就出现一个半透明黑屏(这个是那扫雷界面的,其实暂停不暂停没什么感觉);重开按钮会清空界面所有棋子;保存按钮会把当前棋盘状况写进cookie,保存时间一个钟头;恢复按钮是当你有保存cookie并且cookie没过期的话就恢复到你保存的棋局。
2、基本函数
增加类名,获取元素等基本底层函数;
生成棋格createMap();
生成棋子createPiece(obj,num)
开始游戏startGame():初始化作用,别且生成一个白色棋子,电脑先走(这样电脑的胜率就高些哈)。
暂停游戏stopGame();
重开游戏resetGame():清空盘面,解除事件绑定;
支持cookie与否canCookie();
保存游戏saveGame():
恢复游戏restoreGame():获取cookie里面的值,调用createPiece对应的生成棋子;
绑定事件bindEvent():为I标签绑定事件,如onclick,
解除绑定unbindEvent();
游戏胜负winOrLose(obj):这里就用到了前面版本的方向检测,需要对当前棋子分4个行,每行5趟检测看看是否有5子同色相连出现,如果有那么胜负已分,停止游戏。
3、计分
计分依然是setScore()函数,不过这个版本是这样子一个结构:
score[i][j]={black:0,white:0};
用一个一次性对象记录black跟white,放在score[i][j]里面,而map也重新定义了结构:
map[i][j][k]={black:0,white:0};
i跟j表示当前的格位置,k表示方向,每个方向上临近的连续棋子有多少个,map[i][j][4]就表示当前格放的是什么棋子,-1为空,0为黑棋,1为白棋。可以参考下面这张图,这样比较容易理解:
如图当前八个方向,初始化为0,map[][][1].black=2,map[][][2].white=1,map[][][5].black=2,只获取“紧密连着”的棋子数。
首先正循环一遍棋盘,全部格子都要做一次赋值,如果当前格子的周边是黑子,那么就把它周边该方向的黑子数目加1后赋给当前格子,即
map[i][j][k].black=map[i+k2][j+k3][k].black+1;
以上面那张图为例,当前格子的1方向有黑子,那么就那个黑子格子的1方向黑子加1后赋值给它,而那个格子本身1方向是1那么加1后就成了2再赋值。不过这样在检测的时候是从左向右、由上到下的,所以如果右下有棋子的话,它会检测不到,顶多就是1。所以我后面才加了一个反方向的从右到左,由下到上的检测,这样获取的数据才是完整正确的,当然同时也进行了白子的检测。
接着就是为每个格子进行基础的打分,八个方向逐个检测,取得其中字数最大的方向赋值给maxCount.black,然后再与同行两个方向相加后的比较,如果比它们的和小,那么就取它们的值,如果它们的和大于4的话,那么maxCount.black还是等于4。如图所示:
格子1的maxCount.black取1方向的3,格子2的maxCount.black取3方向+5方向这一行的3,而不是2。
然后一个子100分打分,最多400分,这样就完成了基本的打分操作。
4、下棋
下棋是playChess()函数;依旧用到ScoreList跟ScoreList2两个数组存放分数,不过这里用到的只是第一个元素而已,其实只需要一个存放最大值的变量即可,但是这里用了数组是为了能使用out版本的递归获取元素的方法做扩展,不过我懒,没深入去做哈。然后用maxArray存放当前拥有黑子最大分数的对应I标签,白子用maxArray2。然后只要分数小于400就进入下一步checkArray()检测,如果等于400那就表示已经不用检测了,不下这个位子别无他路。
其中checkArray最为复杂,我扩充了好几次,所以函数比较长,里面黑子白子处理方式都是类似的,所以我只讲黑子,省点口舌;传入的参数如下
checkArray(maxArray,ScoreList,num)
maxArray表示目前拥有ScoreList[0]的元素,num表示黑子还是白子;
对
1)判断如果flag是否为空,因为可能是2个方向加起来而不是一个方向连着的,如果里面有值,就对其进行最边缘元素的检测,如果是空格,那么就赋值ScoreList[0]给maxArray[i].score(五子棋里面叫做“活”),而如果是num2,即相反的棋子的话,那么就将ScoreList[0]-25赋值给maxArray[i].score(五子棋里面叫“眠”),以进行下面第二轮的筛选。同时如果检测到一个空的那么zeroCount++,检测到相反棋子的就num2Count++,如果zeroCount大于1,那么就说明碰到了这种情况,如图:
可以形成禁手的,表示下一个子可以形成多个“活”,这样攻击性强,所以maxArray[i].score+=5*(zeroCount-1);在原来基础上加分,连得越多,加得越多,下这个位子的可能性就更高。
如果zeroCount跟num2Count同时都大于1的话,那么加分更高,因为它在形成多个“活”的同时,还形成多个“眠”。
2)如果flag为空,则说明在把个方向里面没有连子数目等于ScoreList[0]/100的,比如ScoreList[0]/100=3,则说明当前没有3子相连的情况,而是某个行以1+2形或者2+1成的,那么以相同原理,判断当前行是“活”还是“眠”,有多少“活”多少“眠”,分别赋值第二阶段的分数。然而到了这里,还没有用到checkMore这个数组,它是用于第三阶段的打分。
3)checkMore
既然做了一个自身棋子的检测,那就顺便做一个在自身棋子“多活多眠多通路”基础上,尽量堵住相反棋子的检测,所以otherCheckMore就应运而生了,
4)otherCheckMore
这个在检测checkMore的时候可以同时的记录,只是做多一次加分操作,检测目前方向是否“活”,“活几”,是否“眠”,“眠几”,这里所谓的“眠”就是在该方向上末端位置被己方颜色堵住,所以检测的时候是“==num”,而不是“==num2”,然后对应的赋值,活三3分、活二2分、活一1分,眠三2分、眠二1分、眠一不鸟它。
经过3次检测过滤后,获取当前最大的分数,得到分数对应的对象数组后返回该数组,这个时候是黑棋一个最高分数组,白棋一个最高分数组,然后判断谁的最高分更高,谁高取谁,相等的话就去白子的,因为攻击高于防守,最后得到数组maxArray,如果maxArray只有一个元素那就直接下,如果多于一个的话就随机一个下,然后解除该位置的事件绑定,以免鼠标不小心又点上了。
5、其它
经过改动后,只要一个大范围检测,就可以获取到3趟过滤的数据,所以算法效率提高了许多;但是里面还有很多细节没有去做,比如下面这两种情况:
它还是会傻B傻B的下在这个提示区域位置上,
这个位置它优先级其实很高的,因为再下一子就可以连成2个“活三”,优先级等价于一个现成的“活三”,但是我那种写法它是比死三还低级的,所以还得改进。而且一些边缘检测,比如下到边缘的时候还会去下它,即使下了之后毫无攻击意义。
6)改进
上面所说的二比三优先的个例,我其实想到了一个解决的方案,因为首次检测的时候已经获取一个ScoreList分数集,取它前三个高分的分数,获取对应的对象,保证足够的对象数据后(或者全部都进行三个阶段检测打分)再进行3个阶段的整体打分,最后再提取最高的分数对象来下子,把分阶划分明确,这样就可以提高下子的准确性,提高胜率。不过我懒,就不写了,测试了一个多礼拜的五子棋,我快顶不顺了,目前有点五子棋恐惧症,还是休息休息,目前这个就凑合着,以后来激情了再写哈(这摆明就是藉口哈)...