五子棋——UI篇

介绍内容

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

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

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

页面结构

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

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

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

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

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

画棋盘的网格

这里用到JS来控制canvas画棋盘,我们知道五子棋的棋盘是由些纵线和横线组成的,棋盘的样子如下:
blob.png
分别有15条纵线和横线,每个格子为30px的正方形,棋盘边缘有15px的补白。

在棋盘中实现画线

在JS中用画笔画一条线:

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

效果:
blob.png

开始画棋盘的网线

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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();
}

效果:
blob.png

棋盘背景

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
}
}

最终的效果:
blob.png

画棋子

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

棋子的画法

1
2
3
4
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();给圆填充颜色。
效果:
blob.png
这个效果还不像棋子,棋子中间要有些发亮才行的,我们给棋子中间加一个亮度的渐变:
我们直接看onload方法里的代码,再解释其中重要的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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的一个圆。

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

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

落子样式

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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方法里调用函数来落子:

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

效果:
blob.png

实现鼠标落子

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

1
2
3
4
5
6
7
8
9
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的值。
效果:
blob.png
这时候还有一个问题,已经下了黑子的点,重新点击还会被白子覆盖掉。那怎么解决呢?
首先我们定义一个二维数组,存放所有的落子点,如果有落子,就给其记录下来。落子的时候再判断是否已经落子,如果已经落子了就不允许重新落子。思路就是这样。
二维数组代码:

1
2
3
4
5
6
7
var chessBoard = [] ;
for (var i=0; i<15; i++) {
chessBoard[i] = [] ;
for (var j=0; j<15; j++) {
chessBoard[i][j] = 0;
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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。
效果:
blob.png

总结

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

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

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

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

Allen151 wechat
评论功能没弄好,如果有问题,欢迎在我公众号上提问!!
坚持原创,您的支持将鼓励我继续创作!