前端人工智能:canvas 绘制棋盘及落子逻辑

2,568 阅读1分钟

快捷目录

前端人工智能:来谈谈前端人工智能

前端人工智能:前端AI研发之算法

前端人工智能:canvas 绘制棋盘线条

前端人工智能:canvas 绘制黑白棋子


前两节中,我们利用 canvas 绘制出了线条和棋子。那么在这一节当中,我们将利用前两节所学的知识把棋盘构建出来。把布局全部搭建完毕后,用于去承载我们后续用于人工智能的逻辑性代码。

使用 canvas 绘制棋盘

我们在前两节中知道应该怎么去画线了,而一个棋盘是由 15 * 15 的盘面组成的。那按照正常逻辑来说,我们肯定是通过循环去绘制这个盘面,而不是一根根去画对吧。

那接下来我们就开始正式绘制棋盘,而为了跟棋子区分开来,我就不使用黑色去绘制了,改用灰色(context.strokeStyle),并且要跟它说一声,我们要开始画了(context.beginPath()):

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title></title>
</head>

<body>
  <canvas id="chess" width=800 height=800></canvas>
</body>
<script type="text/javascript">
  var chess = document.getElementById("chess");
  var context = canvas.getContext("2d");
  context.strokeStyle = "#ccc";
  context.beginPath()
</script>
</html>

我们需要绘制 15 条线,也就是说我们需要去循环 15 次:

for (var i = 0; i < 15; i++) {
  // 起点
  context.moveTo(15, 15 + i * 30)
  // 终点
  context.lineTo(435, 15 + i * 30)
  // 结束绘画
  context.stroke()
}

然后我们再看看效果:

我们可以清楚地看到横线都出来了,那接下来我们来解释一下代码为什么要这么去写。我们需要绘制15条横线,那我们就从15开始( context.moveTo(15, 15 + i * 30)),这里的15是x轴横坐标。那么纵坐标呢,我们就希望它慢慢地往下涨,每次涨 30,所以是15 + i * 30

那么我们终点的context.lineTo(435, 15 + i * 30)为什么是 435 呢?我们每一行往下涨 30,然后一共涨 14 行,也就是 30 乘以 14 ,再加上我们第一行原本的 15,是不是就得到 435 了。不管是起点还是终点,我们绘制的都是横线,所以y轴都是不变的,就保持15 + i * 30

那我们的横线就解决了,接下来我们就得画竖线。那竖线其实跟我们的横线是差不多的,横线有了,那竖线是不是把x轴跟y轴反过来就可以了:

var chess = document.getElementById("chess");
var context = chess.getContext("2d");
context.strokeStyle = "#ccc";
context.beginPath()
for (var i = 0; i < 15; i++) {
  // 横线起点
  context.moveTo(15, 15 + i * 30)
  // 横线终点
  context.lineTo(435, 15 + i * 30)
  // 竖线起点
  context.moveTo(15 + i * 30, 15)
  // 竖线终点
  context.lineTo(15 + i * 30, 435)
  // 结束绘画
  context.stroke()
}

我们来看看效果:

那我们棋盘就完成了,颜色有点淡的我们再调整一下。那我们棋盘做好了,接下来是不是就得落子了呢?哎,这个就稍微有点难度了。为什么麻烦呢?因为这个就需要涉及到计算了。在我们落子的时候,你肯定没办法点得那么准对吧,很难去做到刚好就落到我们期望的位置。

基本落子逻辑处理

我们可以弄一个 3030 的范围,只要你落在这个范围上,我都会认为你就是落到这个点上了。也就是说每个点都会有自己 3030 的范围,即中心点上下左右 15 的范围,这就是我们解决落子点的一个判断方法。

那接下来我们就开始写落子点了,首先这个肯定会有一个 onclick 点击事件,这个没问题吧。但是我们使用的是canvas,它不像 div,没办法单独给每个点去添加一个点击事件。所以我们只能给 canvas 去添加:

chess.onclick = function (e) {
    console.log(e.offsetX, e.offsetY);
}

上面的代码中,我们打印了一个offsetXoffsetYoffsetXoffsetY是相对于点击元素的左上坐标。然后我们定义变量把点击的坐标值除以我们格子的大小 30,再通过向下取整(Math.floor):

chess.onclick = function (e) {
  // console.log(e.offsetX, e.offsetY);
  var i = Math.floor(e.offsetX / 30)
  var j = Math.floor(e.offsetY / 30)
  console.log(i, j);
}

接下来我们来看看这个得到的坐标值是不是我们想要的效果:

可以看到当我们点击左上角的时候,我们得到的坐标值为(0,0);点击第二行第二列时,我们可以得到坐标值(2,2)。那我们得到坐标值x和y后,我们现在就可以去进行绘画了。

那我们画这个圆的时候,首先得留意一下,我们这个棋盘距离左边是不是有 15 的距离。因为每个格子的中心点上下左右都是 15,那么我们画圆的时候就得把这个 15 给算进去。结合下图看一下:

比如我们点击红框的范围,此时我们要框选出这个盒子,是不是就得拿i乘以盒子的大小 30。但是我们还得加上棋盘距离左边那 15 的距离,所以我们这个圆的起始点x就得等于15+i*30,同理起始点y就得等于15+j*30

半径的话我们使用 13,因为 15 刚好就是盒子的半径,我们尽量取比半径小点的值。

// 复习一下
context.arc(圆心x轴的位置,圆心y轴的位置,半径,起始角度,终点角度,是否逆时针)

context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI);

然后我们再按照黑棋的做法给它加反光的效果:

chess.onclick = function (e) {
 
  var i = Math.floor(e.offsetX / 30)
  var j = Math.floor(e.offsetY / 30)

  var context = chess.getContext("2d");
  context.beginPath();
  // 画圆
  context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI);
  // 增加反光效果
  var grd = context.createRadialGradient(15 + i * 30, 15 + j * 30, 13, 15 + i * 30 + 2, 15 + j * 30 - 2, 0)
  // 给反光效果加颜色
  grd.addColorStop(0, 'black')
  grd.addColorStop(1, 'white')
  context.fillStyle = grd
  context.fill()
  context.stroke()
}

保存代码运行一下,我们来看看有没有落子成功。

我们可以看到棋子已经完美落地,而截至目前为止呢,我们把基本的布局基本都做完了。基本的落子逻辑也处理完了,但是这个逻辑目前还是存在问题的,我们只是简单地把落子的位置处理好了。

在本节中,我们完善了整个布局,并且已经开始运用简单的算法去处理落子的位置。那么在下一节中,我们将完善我们的落子逻辑,并且要对游戏的胜利规则进行算法的计算。


本节代码

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title></title>
</head>

<body>
  <canvas id="chess" width=800 height=800></canvas>
</body>
<script type="text/javascript">
  var chess = document.getElementById("chess");
  var context = chess.getContext("2d");
  context.strokeStyle = "#666";
  context.beginPath()

  // 绘制棋盘
  for (var i = 0; i < 15; i++) {
    // 横线起点
    context.moveTo(15, 15 + i * 30)
    // 横线终点
    context.lineTo(435, 15 + i * 30)
    // 竖线起点
    context.moveTo(15 + i * 30, 15)
    // 竖线终点
    context.lineTo(15 + i * 30, 435)
    // 结束绘画
    context.stroke()
  }


  // 落子
  chess.onclick = function (e) {
    // 横坐标
    var i = Math.floor(e.offsetX / 30)
    // 纵坐标
    var j = Math.floor(e.offsetY / 30)
    var context = chess.getContext("2d");
    context.beginPath();
    context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI);
    var grd = context.createRadialGradient(15 + i * 30, 15 + j * 30, 13, 15 + i * 30 + 2, 15 + j * 30 - 2, 0)
    grd.addColorStop(0, 'black')
    grd.addColorStop(1, 'white')
    context.fillStyle = grd
    context.fill()
    context.stroke()
  }

  

</script>

</html>