小游戏管理平台-五子棋

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第六天,点击查看活动详情

线上地址:http://codeape.site

源码:https://gitee.com/wooden-joint/my-game/blob/master/src/components/GoBang.vue

小游戏管理平台:https://juejin.cn/post/7149169293714784293

开发技术

vue2 + js + canvas

需求

五子棋大家都懂,五个子连起来就赢,可以是横竖或左斜右斜四个方向;由于技术和精力有限,这里只能左手和右手下棋了,大家见谅。

代码讲解

初始化数据

由于采用 canvas,老规矩肯定要先获取 canvas 上下文;然后就可以开始画棋盘,然后再初始化棋盘每个点的位置,将其保存到一个二维数组中。

init() {
  setTimeout(() => {
    this.canvas = this.$refs.canvas;
    this.ctx = this.canvas.getContext("2d");
    // 画棋盘
    this.drawCheckerboard();
    // 保存棋子的坐标
    this.saveLocation();
  }, 100);
  setTimeout(() => {
    this.getCanvasMargin();
  }, 1050);
},
  • 注意:由于后续需要鼠标点击 canvas 中的对应坐标,所以这里需要获取 canvas 距离屏幕左上边界的距离,由于我是每个小游戏单独一个弹出层,所以每次移动弹出层都需要重新获取该位置信息。
// 获取canvas与浏览器 左边 / 顶部 的距离
getCanvasMargin() {
  // 获取dom元素对象与浏览器 左边/顶部 的距离      5是border
  this.canvasLeft = this.$refs.canvas.getBoundingClientRect().left + 5;
  this.canvasTop = this.$refs.canvas.getBoundingClientRect().top + 5;
},

画棋盘

一个标准的围棋棋盘是 14 * 14 个格子的。所以我也按照该比例来画棋盘了。由于有14个格子,所以对应的需要行和列都需要有15根线。

我这里定义每个格子是 30px,所以棋盘的第一根线的起始位置应在 15px 处。

计算好横和竖线的起始位置和每根线的间距,然后就可以进行绘制了。

beginPath 创建一个路径

moveTo 起始位置

lineTo 终点位置

strokeStyle 绘制需要的颜色

stroke 根据已定义好的路径来进行绘制

然后再绘制5个点,找到点对应的行和列信息,然后在该位置上绘画出5个小圆即可。

beginPath 创建一个路径

arc 创建一个圆弧

fillStyle 填充需要的颜色

fill 往路径中填充

这样一个五子棋的棋盘就绘制完成了。

drawCheckerboard() {
  let begin = 15; // 开始的位置
  let lineLength = this.canvasWidth - 30; // 每条线的总长度
  let gridWidth = this.gridWidth + 1; // 每个格子加右边/下边线的宽度
  for (let i = 0; i < this.lineNum; i++) {
    // 横线
    this.ctx.beginPath();
    this.ctx.moveTo(begin, begin + gridWidth * i);
    this.ctx.lineTo(begin + lineLength, begin + gridWidth * i);
    this.ctx.strokeStyle = "#763a0d";
    this.ctx.stroke();
    // 竖线
    this.ctx.beginPath();
    this.ctx.moveTo(begin + gridWidth * i, begin);
    this.ctx.lineTo(begin + gridWidth * i, begin + lineLength);
    this.ctx.strokeStyle = "#763a0d";
    this.ctx.stroke();
  }
  // 画中间的五个点
  let arr = [
    { x: 3, y: 3 },
    { x: 3, y: 11 },
    { x: 7, y: 7 },
    { x: 11, y: 3 },
    { x: 11, y: 11 },
  ];
  for (let item of arr) {
    this.ctx.beginPath();
    this.ctx.arc(
      begin + gridWidth * item.x, begin + gridWidth * item.y, 
      5, 0, 2 * Math.PI
    );
    this.ctx.fillStyle = "#763a0d";
    this.ctx.fill();
  }
},

初始化棋盘每个点的坐标

定义两个个二维数组,行和列作为二维数组的索引,并保存下对应的x和y坐标,并把该位置标志为未下棋。另一个数组记录每个格子的棋子信息(0:没棋 1:黑 2:白)。

  • 注:一年多前的代码了,当时写得太捞了,分别保存了两个数组,现在回看,将第二个数组取代第一个数组的 isPlay 就好了。
saveLocation() {
  let gridWidth = this.gridWidth + 1;
  for (let i = 0; i < this.lineNum; i++) {
    this.allChessArr.push([]);
    this.chessArr.push([]);
    for (let j = 0; j < this.lineNum; j++) {
      // 保存所有的棋子位置信息
      this.allChessArr[i][j] = {
        x: 15 + gridWidth * j,
        y: 15 + gridWidth * i,
        isPlay: false,
      };
      // 将所有的棋子置为 0
      this.chessArr[i][j] = 0;
    }
  }
},

点击下棋

根据 canvas 离浏览器左和上的距离,和鼠标点击的坐标,计算出在 canvas 中的坐标,然后遍历数组,只要有一个坐标是在点的上下左右15px的范围内,则表示该位置就是下棋位。

  • 注:回看代码发现这里太蠢了,这里根本用不着两层for循环,只要用x, y坐标除以线和格子的宽度,再通过30px来取整就可以计算出行和列的索引,然后直接根据这个索引就知道下棋位了。

如果该位置没有棋的话,就可以进行下棋操作,给数组添加1或2(黑或白)。绘画一个棋子,然后通过 checkChess 判断游戏是否已经结束了,没结束就颜色取反。

getMouse() {
  if (this.gameOver) return;
  // canvas 中棋子的位置
  let chessX = window.event.pageX - this.canvasLeft;
  let chessY = window.event.pageY - this.canvasTop;
  let x, y;
  // 获取鼠标点击 下棋的位置
  for (let i = 0; i < this.lineNum; i++) {
    for (let j = 0; j < this.lineNum; j++) {
      if (this.chessArr[i][j] == 0) {
        x = this.allChessArr[i][j].x;
        y = this.allChessArr[i][j].y;
        if (
          chessX >= x - 15 &&
          chessX <= x + 15 &&
          chessY >= y - 15 &&
          chessY <= y + 15
        ) {
          let isBlack = this.isBlack;
          let chess = isBlack ? 1 : 2;
          // 1:黑  2:白
          this.chessArr[i][j] = chess;
          this.drawChess(x, y, isBlack);
          // 判断游戏是否结束
          if (this.checkChess(i, j, chess)) {
            this.gameOver = true;
            return;
          }
          this.isBlack = !this.isBlack; // 下棋颜色取反
          return;
        }
      }
    }
  }
},

绘画棋子

创建一个 Image 实例,然后当图片 onload 后,使用 drawImage 绘画棋子。

  • 注:这里很捞,当时写的每次都加载一个图片,这里可以初始化的时候先加载好图片,然后保存下来。
drawChess(x, y, isBlack) {
  let img = new Image();
  isBlack
    ? (img.src = this.chessImg.black)
    : (img.src = this.chessImg.white);
  img.onload = () => {
    // (image, x, y, 切片width, 切片height, 切片x, y, w, h)
    this.ctx.drawImage(img, x - 15, y - 15, 28, 28);
  };
},

checkChess 检查是否已经游戏结束

这里一大坨看上去很多,其实都是相似的逻辑的,就是分别对待 x,y和两条斜边的情况。

我这里的思想是先根据 x,y和两条斜边,定义好四个对象,然后根据当前的棋子,判断该对象的左右两边是否还能延伸,如果可以延伸,则该方向的总和加1,当该值大于等于5,则游戏结束。如果遍历完成,四个对象的总和都小于5,则游戏继续。

checkChess(i, j, chess) {
  let x = { sum: 1, isL: true, isR: true },
    y = { sum: 1, isL: true, isR: true },
    z = { sum: 1, isL: true, isR: true },
    z2 = { sum: 1, isL: true, isR: true };
  for (let a = 1; a < 5; a++) {
    // 处理行
    x.isL && j + a < 15 && this.chessArr[i][j + a] == chess
      ? x.sum++
      : (x.isL = false);
    x.isR && j - a >= 0 && this.chessArr[i][j - a] == chess
      ? x.sum++
      : (x.isR = false);
    // 处理列
    y.isL && i + a < 15 && this.chessArr[i + a][j] == chess
      ? y.sum++
      : (y.isL = false);
    y.isR && i - a >= 0 && this.chessArr[i - a][j] == chess
      ? y.sum++
      : (y.isR = false);
    // 处理斜边1 '\'
    z.isL && i + a < 15 && j + a < 15 && this.chessArr[i + a][j + a] == chess
      ? z.sum++
      : (z.isL = false);
    z.isR && i - a >= 0 && j - a >= 0 && this.chessArr[i - a][j - a] == chess
      ? z.sum++
      : (z.isR = false);
    // 处理斜边2 '/'
    z2.isL && i + a < 15 && j - a >= 0 && this.chessArr[i + a][j - a] == chess
      ? z2.sum++
      : (z2.isL = false);
    z2.isR && i - a >= 0 && j + a < 15 && this.chessArr[i - a][j + a] == chess
      ? z2.sum++
      : (z2.isR = false);
  }
  // console.log("x: ", x.sum, "y: ", y.sum, "z: ", z.sum, "z2: ", z2.sum);
  if (x.sum >= 5 || y.sum >= 5 || z.sum >= 5 || z2.sum >= 5) return true;
  return false;
},

感悟

整个五子棋,小游戏下来对思维和js基本功还是有一定的锻炼的,整个里面最复杂的就是判断游戏是否已经结束的处理了。

现在回顾当时的代码,感觉很不堪回首,有很多地方的代码都是写得很差的,存在一定的优化空间,现在细看也能看到了自己的成长。

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容