canvas深入浅出(四)| 小册免费学

简介: 之前三节我们铺垫了canvas的相关知识,这一节我们来“落地”,实现一个五子棋的小游戏

ed40fadc543e63c8e8f7c8cddcd1c78.png

之前三节我们铺垫了canvas的相关知识,这一节我们来“落地”,实现一个五子棋的小游戏

开始之前,我们先想一下要做什么:

  1. 首先我们要绘制一个棋盘
  2. 然后要有黑子和白子
  3. 每次落子时判断当前位置是否已经落子,“米”字型方向上是否有五个子,如果满足条件,当前落子一方获胜

下面马上开始,先做好准备工作,准备一个canvas容器和一个刷新按钮用来重新开盘,然后准备一个结果区显示提示信息

<canvas id="canvas" width="200px" height="200px"></canvas>
<p class="result"></p>
<button onclick="loadPanel(400, 400,30,13)">刷新</button>
复制代码


棋盘


首先我们来绘制棋盘,这个很简单,循环画直线即可,我们将所有的操作放置在一个函数中,以便重新开局

/**
 * @param w 棋盘宽度
 * @param h 棋盘高度
 * @param cs 格子尺寸
 * @param ps 棋子半径
 */
function loadPanel(w, h, cs, ps) {
  let i, j, k;
  //1)绘制棋盘,边缘应隔开棋子半径的距离
  cs = cs || 16;//默认格子宽高
  ps = ps || 4;//棋子半径
  h = h || w;//高度默认等于宽度
  let el = document.getElementById('canvas');
  el.setAttribute('width', w + 'px');
  el.setAttribute('height', h + 'px');
  let context = el.getContext("2d");
  //计算棋盘分割,向下取整
  let splitX = ~~((w - 2 * ps) / cs), 
      splitY = ~~((h - 2 * ps) / cs);
  //循环划线
  context.translate(ps, ps);
  context.beginPath();
  context.strokeStyle = '#000';
  //垂直线
  for (i = 0; i < splitX + 1; i++) {
    context.moveTo(cs * i, 0);
    context.lineTo(cs * i, splitY * cs);
    context.stroke();
  }
  //水平线
  for (j = 0; j < splitY + 1; j++) {
    context.moveTo(0, cs * j);
    context.lineTo(splitX * cs, cs * j);
    context.stroke();
  }
  context.closePath();
}
// 绘制棋盘
loadPanel(400, 400, 30, 13);
复制代码

~~作用是向下取整,可以将浮点类型转为整数,字符串类型的数字也可以,但是无法转为数字的结果为0

现在打开浏览器,看一下效果

1682515089(1).png


棋子

棋子的绘制也不难,就是画圆,黑子填充黑色,白子填充白色,然后描个边,相关API之前也有过介绍,我们先在棋盘上绘制两个静态的棋子观察效果

context.beginPath()
context.arc(cs * 0, cs * 0, ps, 2 * Math.PI, false);
context.fillStyle = '#fefefe'
context.fill()
context.stroke();
context.closePath()
context.beginPath()
context.arc(cs * 1, cs * 0, ps, 2 * Math.PI, false);
context.fillStyle = '#000'
context.fill()
context.stroke();
context.closePath()
复制代码

1682515111(1).png

效果还不错,但是在下棋时需要鼠标点击,这里的棋子渲染是需要鼠标点击事件来触发的,下面来给canvas添加鼠标点击事件,我们需要获取鼠标的点击坐标,然后计算出他应该落在哪个点,还要循环落子,

let user = 0, colors = ['#000', '#fefefe'];
el.addEventListener('click', function (e) {
  let x = e.offsetX,
    y = e.offsetY,
    //计算落子范围
    rx = ~~((x - ps) / cs) + (((x - ps) % cs <= cs / 2) ? 0 : 1),
    ry = ~~((y - ps) / cs) + (((y - ps) % cs <= cs / 2) ? 0 : 1);
  context.beginPath();
  context.arc(cs * rx, cs * ry, ps, 2 * Math.PI, false);
  context.fillStyle = colors[user];
  context.strokeStyle = '#000';
  user ? user = 0 : user = 1;//切换执子者
  context.fill();
  context.stroke();
  context.closePath();
})
复制代码

现在我们已经完成了50%了

1682515156(1).png


胜方判定


现在进行做事后的一步,判定“米”字方向上是否存在5个同色棋子,我们先来考虑一下答题思路,你先不要往下看,先考虑一下自己的想法

1682515187(1).png


好了,现在不管你想没想出来,我来说一下大概思路,首先定义一个对象来存储落子情况,大概的形式是这样的

{
  '1-1': 0,
  '1-2': 1
}
// key是棋盘位置,value是身份
复制代码

然后使用分别使用[0,1]、[1, 0]、[1, 1]、[1, -1]表示上下、左右、斜向上、斜向下四个方位,待会会分别拿这几个方位来进行遍历,原理大概是这样的

以[0, 1]为例,在for(let i = 1; i<= 4 ; i++){}中,分别用0和1去乘以i,然后加上当前点击未知的x或者y坐标,就能够遍历,因为i无论是多少乘0都是0,所以x坐标是不会变的,那么y坐标会遍历上边的四个,如果这四个的都是当前落子选手的即判定为赢,如果找不到就向反方向去找,即循环for(let i = -1; i>= -4 ; i--){},如果当前循环结束没有判定胜利再去其他方向重读此步骤

1682515217(1).png

计算出坐标来之后在存储落子的对象中寻找落子者,如果是当前落子者就累加,如果遍历结束满足获胜条件游戏结束

let user = 0, colors = ['#000', '#fefefe'];
let chks = [[1, 0], [0, 1], [1, 1], [1, -1]]; // 四个方向
let pieces = {}; // 记录游戏者落子位置
let successNum = 5;//赢棋标准
let resultEl = document.querySelector('.result');
el.addEventListener('click', function (e) {
  let x = e.offsetX,
    y = e.offsetY,
    //计算落子范围
    rx = ~~((x - ps) / cs) + (((x - ps) % cs <= cs / 2) ? 0 : 1),
    ry = ~~((y - ps) / cs) + (((y - ps) % cs <= cs / 2) ? 0 : 1);
  context.beginPath();
  context.arc(cs * rx, cs * ry, ps, 2 * Math.PI, false);
  context.fillStyle = colors[user];
  context.strokeStyle = '#000';
  user ? user = 0 : user = 1;//切换执子者
  context.fill();
  context.stroke();
  context.closePath();
  piece = pieces[rx + '-' + ry] = user;
  for (j = 0; j < chks.length; j++) {
    let num = 1, chk = chks[j];
    for (i = 1; i <= 4; i++) {
      if (pieces[(rx + chk[0] * i) + '-' + (ry + chk[1] * i)] == piece) {
        num++
      } else {
        for (i = -1; i >= -4; i--) {
          if (pieces[(rx + chk[0] * i) + '-' + (ry + chk[1] * i)] == piece) {
            num++
          }
        }
        break
      }
    }
    if (num == successNum) {
      status = false
      resultEl.innerHTML = ['白', '黑'][user] + '方赢';
      break;
    }
  }
})
复制代码

然后就完事了

1682515242(1).png


但是现在还有很多逻辑问题没有解决,游戏结束之后限制不可继续落子,点击位置如果已经有过落子则不可继续落子等这些就当作业留给你吧,其实不难,好好考虑考虑吧

我最终的实现效果如下

相关文章
|
前端开发 API
canvas深入浅出(二)| 小册免费学
上一节最后我们说了一个绘制曲线的API——arc(那个API并不是专门用来绘制圆形,只是使用曲线可以绘制圆形),他还有一个兄弟——arcTo(x1, y1, x2, y2, r),根据当前描点与给定的控制点1连接的直线,和控制点1与控制点2连接的直线,作为使用指定半径的圆的切线,画出两条切线之间的弧线路径
88 0
canvas深入浅出(二)| 小册免费学
|
前端开发 API
canvas深入浅出(三)| 小册免费学
所有的内容都是手动绘制还是有点麻烦,好在canvas支持导入图片,createPattern(image, type),该方法接受两个参数。Image 可以是一个 Image 对象的引用,或者另一个 canvas 对象。Type 必须是下面的字符串值之一:repeat,repeat-x,repeat-y 和 no-repeat。
91 0
canvas深入浅出(三)| 小册免费学
从零开始手把手教你使用javascript+canvas开发一个塔防游戏04右侧信息展板
从零开始手把手教你使用javascript+canvas开发一个塔防游戏04右侧信息展板
110 0
|
XML 前端开发 JavaScript
canvas深入浅出(一)| 小册免费学
canvas是为了解决页面只能显示静态图片而出现的一种可以使用JavaScript绘制的HTML标签,它可以接受两个参数width和height(原来有三个,还有一个moz-opaque控制透明度,已经废弃了)
85 0
|
存储 Web App开发 缓存
前端性能优化(三)| 小册免费学
上一讲我们说到了HTTP缓存,这一讲的缓存并不单单指HTTP缓存
99 0
|
域名解析 缓存 网络协议
前端性能优化(一)| 小册免费学
移动互联网时代,用户对于网页的打开速度要求越来越高。首屏作为直面用户的第一屏,其重要性不言而喻。优化用户体验更是我们前端开发非常需要 focus 的东西之一。
84 0
|
缓存 前端开发 网络协议
前端性能优化(二)| 小册免费学
上一节介绍了网页加载过程中可优化的点,这一节我来说一下网络部分的优化。
82 0
|
JavaScript 前端开发 Go
前端性能优化(四)| 小册免费学
前面介绍多个前端性能优化的点,现在我们来说一下性能优化的最后一关——页面渲染 浏览器渲染的过程我们之前也稍微提过,这里再说一下解析 HTML 文件,构建 DOM 树,同时浏览器主进程负责下载 CSS 文件
102 0
|
存储 缓存 移动开发
第四课(三)|学习笔记
快速学习第四课(三)
第四课(三)|学习笔记
|
搜索推荐 网络协议 Java
第四课(二)|学习笔记
快速学习第四课(二)
第四课(二)|学习笔记