加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

纯JS 智能五子棋 out版

(2012-01-09 12:30:53)
标签:

css3

javascript

五子棋

纯js

人机对战

底层函数

棋盘棋子

计分

方向检测

分类: Javascript

本来打算在元旦发布出来庆祝新年的,不过这个版本感觉越写越笨,不对劲,结构不好导致后面检测计算太难,于是就抹掉重新写过。因为是写了一半不想继续写的,所以里面会有很多bug,按钮事件也没有没绑定的,只是按了开始然后下棋而已。不过怎么说也写了几天,里面还是有可取之处,所以还是发出来,分享下思路,这也是一个学习进步中的点,因为有差的,才会有好的。

 

具体的界面如下图所示:

 

纯JS <wbr>智能五子棋 <wbr>out版

这是在firefox显示的,兼容现代浏览器及IE。棋子棋盘都是纯CSS3画出来,没有用一张图片(下面那几个按钮是系统默认的哈),IE的话棋子就用了图片,毕竟悲催的IE不支持CSS3,但是这里我又做了点小优化,IE其它版本用png24,IE6用png8,虽然显示还是会起锯齿白边,但是大小就从2K缩小到700B,有些细节还是要注意的。

如果你想用户体验好点的话,建议还是用FF,CHROME,SAFARI玩哈。

 

你可以通过这个链接下载五子棋的源文件:

纯JS 智能五子棋 out版

 

为方便下载,提供本人网盘帐号密码,请不要弄乱里面的页面,以方便其它人下载,谢谢。

帐号:287019674@qq.com

密码:123456

 

下面我分析下里面的关键点:

1、css3棋盘棋子

虽然文章主要是JS,但是我用CSS3研究了大半天,不能浪费哈,相信你也想知道它们的画法,如果你不感兴趣可以跳过...

1)棋子

html结构很简单,如下:

<div class="piece black"><i></i></div>

一个piece写基本的属性,如宽高、圆角等,black控制棋子颜色,white就是白色。而I标签是高光,我比较喜欢用I标签来挂钩。

以黑棋为例,在写好black的基本属性后,写i高光,重点代码如下:

-moz-linear-gradient(-90deg,rgba(255,255,255,0.32),rgba(255,255,255,0.01) 65%,rgba(255,255,255,0.01));

拉一条由左上到右下的渐变,由白色透明度0.32到基本不透明,实现一个整体的提亮效果。

 

.black:before写局部的高光,放再左上角,这个是最靠近光源的区域,所以最亮;

.black:after写局部的高光,放再右下角,这个是棋子对周边区域的反光的表现区域,所以亮度会比较弱,但是加上了之后立体感会好些。

 

白棋与黑棋类似,不过在细节颜色透明度等做了调节,以增强立体感,具体的代码请参考源代码,这里就不作分析了。

 

2)棋盘

棋盘结构就比较复杂了,里面还有程序生成的html,这样原始的HTML文件看起来就不会太多太杂。

棋盘以chessboardBox为主体,边框我是这样写的:

 

 border:4px double #5a3405;

 

我定义了一个4像素宽的双边框,这样显示出来就是两条1px的黑线夹着2px的底色线,咋一看感觉棋盘就有了厚度感,再用伪类跟主体写两个box-shadow,一个外阴影一个内阴影,这样立体感就出来了。

 

里面的chessboard_bg用一个table做的,其实它不是一个普通单线的table,它是用于显示棋格的单线table(不说句废话我手痒哈),里面还有个细节,不知道你发现了没,没有的话看下图你就知道了:

 

纯JS <wbr>智能五子棋 <wbr>out版

在每个黑色单线格子里面还有一个td的伪类after实现的半透明内格,用于内部提亮,这样看起来立体感就加强了。

这样棋盘的布局基本就结束了?还没有。里面还有一个你没看到的iBox,一般的下棋的话下的位子是线的交界,而不是下在对应的棋格内,所以上面画的只是纯粹的显示,真正用于绑定下棋事件的是iBox里面对应每个交界线的224个I标签。如果没有这一步,那么当用户点击td的时候,到底应该下到四个顶点中的哪一个,后面写程序就麻烦了。到此基本的布局才算告一段落。

 

2、提示

当鼠标滑过每个下子点的时候,会给出一个提示,告诉用户,你将在该处下子,这个提示的框如下:

 

 纯JS <wbr>智能五子棋 <wbr>out版

 

基本结构如下:

 

 <div class="mouseBox">
    <i class="mouseP mouseLT"></i>
    <i class="mouseP mouseRT"></i>
    <i class="mouseP mouseLB"></i>
    <i class="mouseP mouseRB"></i>
</div>

 

一个 mouseBox里面挂四个mouseP的钩,再用mouseLT分别定义个体特征,如定位、边框显示等。比如mouseLT就是左上角的两条小线,显示了border-top跟border-left,其它原理一样。当鼠标滑过某个下子点的时候就获取该点的定位属性,然后将mouseBox相对该点定位,因为mouseBox是一个容器,没有实际存在,所以鼠标可以穿过它点击对应的I标签。至此关于CSS布局的告一段落。

 

3、基本底层类

在JS文件头部有几个基本的底层类,用于获取元素,修改类名等,具体我有一篇文章对它们进行分析,你可以通过这个链接看看《JS底层接口函数》,这里不做详细解说。

 

4、基本设定函数

createMap()是用于生成table棋格,同时生成对应的iBox以及里面用于绑定点击事件的I标签。

mouseOverTips(obj)用于定位鼠标下子提示,如果页面上没有mouseBox,那么就生成一个再定位,如果有那么就显示它,再定位到相应的位置上。

clearTips()隐藏掉鼠标提示。

 

5、生成棋子

createPiece(obj,num)用于生成棋子,传来一个I标签对应的对象obj和棋子的类型num,0为黑子,1为白子。

用map二维数组记录当前棋盘状况,如果下黑子那么对应map[][]=0、白子map[][]=1、默认空位为-1。

 

6、计分

在鼠标事件里面我是这么定义的:

iArray[i].onclick=function(){
   createPiece(iArray[this.index],0);
   setScore();
   playChess();
  }

下了一个棋子之后,先计算棋盘上面的分值,然后再下棋。这里主要说的是setScore()函数。

进入函数后初始化基本的元素,然后做一个整体循环,即:

 

for(var i=0;i<15;i++)
  for(var j=0;j<15;j++){}

 

循环每个格子,如果格子是空格,那么就循环当前格子的8个方向,检测连子情况,这里会比较抽象,还是看看图先:

 

纯JS <wbr>智能五子棋 <wbr>out版

假如当前点如图所示,那么应该对它进行这样的检测,-4到0一趟检测,-3到1一趟检测,进行5趟,看看里面最大的黑子数是多少,然后把它赋值给对应的score[][]。因为这样走过一遍检测后,其实就已经检测了两个方向,所以方向检测那里我只写了for(var l=0;l<4;l++)这样一句方向循环,for(var m=-4;m<=0;m++)是控制循环起始点, for(var k=m;k<m+5;k++)表示的是一趟检测里面的5个格位,如果检测到黑子,那么defenseCount++,如果检测到白子attackCount++,然后跟对应的score[][]比较,如果大与目前的值那么就把得到的子数加分到对应的score[][]里面,保证对应的score是8个方向上最大的值。

 

但是这样会出现一个状况,如下:

 

纯JS <wbr>智能五子棋 <wbr>out版

上面检测如果无误的话,就会出现这样的状况,那么有6个权重一样都是2的格位可以下,一般来说都是下挨着黑子的格子,那么在原来的基础上就应该有个离黑子越近,分数设定越高的判断。所以我写了个sideCheck()函数。

 

sideCheck()函数会扫描当前棋盘不为空格的格子,当检测到合适的格子后,就对其八个方向进行检测(这回不用对每行进行5次逐格检测,而是一行两次就够了),如果是空格,那么就对应的加上分数,让它越近越高分。

 

7、下棋

playChess()为下棋函数,根据上面的计分,我们可以得到2个二维数组,表示当前黑子的分数情况跟白子的分数情况,所以我用两个数组来记录分值ScoreList(黑子)和ScoreList2(白子),并且初始化首个元素为0;之后循环获取所有score的分值,从小到大unshift进ScoreList里面,使得ScoreList[0]保持当前最大。得到两个分值数组后就调用getMaxArray()来检测那个列表更适合下棋。

在这里应该明确一点,在同等分值情况下,进攻的权重比防守高,比如:

 

纯JS <wbr>智能五子棋 <wbr>out版


你说白棋应该下哪个位置?当然是攻击的95/100,而不是110/115。所以进入getMaxArray后就判断ScoreList2[0]和ScoreList[0]哪个大,如果ScoreList2[0]>=ScoreList[0],那么就用ScoreList2作为当前的检测数组,获取score[][]中与元素对应的格子,否则就用ScoreList[0]来获取对应元素。把获取到的元素放在一个叫maxArray的数组里面,进入下一步检测checkArray(),因为现在获取到的是目前分数最大的,但可能不是最优的,所以要进行下一步检测,而不是直接下棋。

进入checkArray()后,再对其进行一个八方向的检测,获取每个方向对应的黑子白子数目存放于blackCount和whiteCount中,再获得blackCount和whiteCount里面对应最大的那个方向,检测如果里面没有白子,那么就存进temArray里面,待会儿返回用,如果里面有一个白子,白子是在哪个位置,如果是在该方向的最边缘,那么就把它加到temArray2里面,如果白子位子不在最边缘,那么就是夹在中间,就不要它了,如图所示:

 

纯JS <wbr>智能五子棋 <wbr>out版

优先顺序为 1 > 2 > 3 ;

 

这样以来temArray的自然就比temArray2里面的要优先,但是有可能没有符合temArray要求的,那么如果temArray有值就直接返回它,没有值的话就返回temArray2。但是检测的时候也有可能temArray2也是空的,那么返回到getMaxArray()后就要检测一下是否为空,如果不为空,就返回给playChess(),让它下棋,否则就删了刚检测的那个数组的头个元素(刚就是用这个元素去获取maxArray的,但是获取不到,所以要删了它避免死循环),丢进getMaxArray再获取一次,产生递归,直到获取到的数组不为空为止,判断函数如下:

 

if(maxArray.length>0){
 return maxArray;}
 else {
  if(ScoreList2[0]>=ScoreList[0]){
   ScoreList2.shift();
   return getMaxArray(ScoreList,ScoreList2);
  }
  else if(ScoreList2[0]<ScoreList[0]){
   ScoreList.shift();
   return getMaxArray(ScoreList,ScoreList2);
  }
 }

 

8、问题

看到这里,你也许已经看出问题了,这样的计分太过笼统,而且还有不确定性,每个棋格的分数可以说是随机的,周围的黑子多了,分数就高,这样可能导致同个级别的格给挤下去了,但是下它那个位子比较好;其次,里面的循环检测太多,很多重复了的,导致这样的重复是结构定义的不好导致的;再次,想进一步更细致的判断的话,恐怕会循环判断更多次,所以还是果断舍弃它的好。上面的还只是黑棋的进一步判定就这么多冗余,加上白棋会冗余更多,所以后面的一些button绑定我也就不去做了,改了数据的存放结构重新写过,不想浪费时间在它上面了...所以说它是out版...

 

综上所述,它出了一些方向循环,界面设计可取外,计分方式跟下棋判断都是“仅供娱乐”,因为算法不好所以AI处于“智障”级别,棋子越多走得越烂哈..见笑见笑..现在我已经做了一个还比较满意的版本《纯JS 智能五子棋 初级版》并且提供源代码下载,欢迎点进去看哈... 

 

 

 

 

 

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有