×

JS实现五子棋——UI篇

96
潘栋民
2017.08.03 21:41* 字数 1567

介绍内容

前些时间,阿尔法狗对战柯洁围棋大赛很热门,那只是人工智能中的一个方向,展示了机器能代替人做某些事情。
而围棋是很讲究智力的游戏,所以实现起来也是很难的,Google花了很多钱这在方面。
那段时间我也用JS写了一个小游戏——五子棋,五子棋相对来讲简单很多。我那时候在公众号上展示给大家,好像大家兴趣不大,可能是因为五子棋游戏太过简单,又或者是对我推的东西不感兴趣。那个公众号已经被封杀了,现在我又重新开了一个公众号。下面有二维码,如果有兴趣可以推一下。
现在我要写一下教程,你们可以点击这里体验一番。
这个实例完全是用JS实现的,有UI部分也有AI部分,我们需要有一定的canvas和JS的知识就足够了。

  • 棋盘的实现
    1.canvas绘画直线。
    2.设置画笔的颜色。

  • 棋子的实现
    1.canvas画圆。
    2.填充渐变色。

页面结构

页面上有一个正方形的棋盘,我们用画布canvas来实现棋盘。

<canvas id="chess" width="450px" height="450px"></canvas>

棋盘有一定的阴影效果,使棋盘更美观些。我们通过定义CSS样式来实现。

canvas {
    display: block;
    margin: 50px auto;
    box-shadow: -2px -2px 2px #EFEFEF, 5px 5px 5px #B9B9B9;
}

这个时候的效果如下,有一定的阴影效果:

阴影效果

画棋盘的网格

这里用到JS来控制canvas画棋盘,我们知道五子棋的棋盘是由些纵线和横线组成的,棋盘的样子如下:

网格的实现

分别有15条纵线和横线,每个格子为30px的正方形,棋盘边缘有15px的补白。

在棋盘中实现画线

在JS中用画笔画一条线:

var chess = document.getElementById("chess") ;//获取canvas
var context = chess.getContext("2d");
context.strokeStyle = "#aaa" ;//画笔的颜色
context.moveTo(15,15);
context.lineTo(435,15);
context.stroke();

效果:

一条横线的画法

开始画棋盘的网线

我们回顾了一下画线,那接下来我们用循环方式画15条纵线和15条横线:

var chess = document.getElementById("chess") ;//获取canvas
var context = chess.getContext("2d");

context.strokeStyle = "#aaa" ;//画笔的颜色

for (var i=0; i<15; i++) {//通过循环画网格
    context.moveTo(15,15+i*30);
    context.lineTo(435,15+i*30);
    context.stroke();
    context.moveTo(15+i*30,15);
    context.lineTo(15+i*30,435);
    context.stroke();
}

效果:

完成网格

棋盘背景

可能你已经发现了,白色的棋盘背景视觉非常不好,那么接下来我们就来为棋盘添加背景。我们选择一张木色的图片,如果你想为棋盘添加你特有的水印,可以通过制图软件添加。
H5添加图片的方法是通过画图的方式,画上去就会覆盖掉之前画的网格,所以我们通过对画网格的代码进行封装成一个函数,画完背景后再调用画网格的函数来达到不被覆盖的效果。总的代码如下:

var img = new Image();
img.src = "img/2.png" ; 
img.onload = function (){
    context.drawImage(img,0,0,450,450);
    drawLine();
}
function drawLine () {//把画线封装成函数
    for (var i=0; i<15; i++) {//通过循环画网格
        context.moveTo(15,15+i*30);
        context.lineTo(435,15+i*30);
        context.stroke();
        context.moveTo(15+i*30,15);
        context.lineTo(15+i*30,435);
        context.stroke();
    }
}

最终的效果:

棋盘效果

画棋子

我们是在背景上画棋子的,所以画棋子的代码应该放在onload方法里面。

棋子的画法

    context.beginPath() ;
    context.arc(200,200,100,0,2*Math.PI);
    context.closePath() ;
    context.fill();

解释一下特别的代码context.arc(200,200,100,0,2*Math.PI);四个参数分别是圆心横坐标、圆心纵坐标、半径、开始弧度、结束弧度。
context.fill();给圆填充颜色。
效果:

一个棋子

这个效果还不像棋子,棋子中间要有些发亮才行的,我们给棋子中间加一个亮度的渐变:
我们直接看onload方法里的代码,再解释其中重要的代码:

img.onload = function (){
    context.drawImage(img,0,0,450,450);
    drawLine();
    //画棋子  
    context.beginPath() ;
    context.arc(200,200,100,0,2*Math.PI);
    context.closePath() ;
    var gradient = context.createRadialGradient(200, 200, 50, 200, 200, 20);
    gradient.addColorStop(0, "#0a0a0a");
    gradient.addColorStop(1, "#636766");
    context.fillStyle = gradient ;
    context.fill();
}

这些代码的操作非常简单,首先画一个圆,然后填充渐变色,var gradient = context.createRadialGradient(200, 200, 50, 200, 200, 20);这是定义一个有渐变的颜色变量,前三个参数是圆心在(200,200)处,半径为50的一个圆,同理,后三个参数是圆心在(200,200)处,半径为20的一个圆。

gradient.addColorStop(0, "#0a0a0a");
gradient.addColorStop(1, "#636766");
context.fillStyle = gradient ;

这三行代码分别设置上面的第一个圆的颜色,第二个圆的颜色,和把渐变色填充给棋子。最终的填充效果是在圆心为(200,200)内径为20,外径为50的一个圆环上产生渐变。
棋子最后的效果:

中间发亮

落子样式

那我们通过上面的学习就会画一个棋子啦,接下来我们改变棋子的半径大小和颜色就能得到我们想要的棋子了。
代码放在onload里面会显得很杂乱,这是我们不想看到的,所以我们必须封装成函数再使用。
封装成以下的函数:

var oneStep = function (i, j, me){//i,j分别是在棋盘中的定位,me代表白棋还是黑棋
    context.beginPath() ;
    context.arc(15+i*30, 15+j*30, 13, 0, 2*Math.PI);//圆心会变的,半径改为13
    context.closePath() ;
    var gradient = context.createRadialGradient(15+i*30+2, 15+j*30-2, 15, 15+i*30, 15+j*30, 0);
    if(me){
        gradient.addColorStop(0, "#0a0a0a");
        gradient.addColorStop(1, "#636766");
    }else{
        gradient.addColorStop(0, "#D1D1D1");
        gradient.addColorStop(1, "#F9F9F9");
    }
    context.fillStyle = gradient ;
    context.fill();
}

主要改变了三部分,改变圆心和半径,根据接收到的参数确定圆心,判断是黑子还是白子。
然后通过在onload方法里调用函数来落子:

oneStep(0,0,true) ;
oneStep(1,1,false) ;

效果:

改变棋子大小

实现鼠标落子

实现用鼠标点击棋盘就落下一颗棋子,我们用在画布上绑定单击事件来实现,代码如下:

var me = true ;
chess.onclick = function (e){
    var x = e.offsetX ;
    var y = e.offsetY ;
    var i = Math.floor(x/30) ;
    var j = Math.floor(y/30) ;
    oneStep(i,j,me);
    me = !me ;
}

通过e.offsetXe.offsetY两个属性得到坐标,后转化成i和j,再调用oneStep()方法,定义一个变量me来决定是黑子还是白子,每点击一次就改变一次me的值。
效果:

鼠标落子

vzsf
这时候还有一个问题,已经下了黑子的点,重新点击还会被白子覆盖掉。那怎么解决呢?
首先我们定义一个二维数组,存放所有的落子点,如果有落子,就给其记录下来。落子的时候再判断是否已经落子,如果已经落子了就不允许重新落子。思路就是这样。
二维数组代码:

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

二维数组的初始值都是0,然后在单击事件的方法里添加一个判断:

chess.onclick = function (e){
    var x = e.offsetX ;
    var y = e.offsetY ;
    var i = Math.floor(x/30) ;
    var j = Math.floor(y/30) ;
    if(chessBoard[i][j] == 0){
        oneStep(i,j,me);
        if(me){
            chessBoard[i][j] = 1 ;
        }else{
            chessBoard[i][j] = 2 ;
        }
        me = !me ;  
    }
}

落子位置等于0才可以落子,落完子后给相应的点附非0值,黑子就附1,白子附2。
效果:

落子不能覆盖

总结

这是UI篇,你继续翻翻我的主页,肯定能找到AI篇的。

  • 棋盘的实现
    通过循环画直线

  • 棋子的实现
    画出你想要的棋子,渐变填充颜色,封装成一个函数供调用。

  • 落子的实现
    用数组存放每一个落子点,满足条件就落下对应的子。

UI篇到此就告一段落了,这里用到的知识并不多,相应的方法想了解更多可以到W3上看。AI篇将会在后期推出,到时候就可以实现人机交互了。跟自己打的代码比试五子棋不再只是一种想法,只要你动手,一定能实现。另外,想跟我的代码比试一下可以点击这里

javascript笔记
Web note ad 1