10分钟-带你走进H5-五子棋

简介: 10分钟-带你走进H5-五子棋


用到的技术

  1. es6
  2. canvas

canvas

Canvas API 提供了一个通过JavaScriptHTML的元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。

初识canvas

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0,maximum-scale=1,minimum-scale=1,user-scalable=no"
    />
    <title>初识canvas.html</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      canvas {
        background-color: aqua;
      }
    </style>
  </head>
  <body>
    <canvas></canvas>
  </body>
</html>



CF}$[0BTBVNFSOW2X()WKC0.png

可以看到 canvas是一个 默认宽高为 300*150、内联的一个元素。

画直线 lineTo

以下代码画出一个对角线

// 1 获取dom元素
      const canvas = document.querySelector('canvas');
      // 2 获取画布对象
      const ctx = canvas.getContext('2d');
      // 3 设定起点
      ctx.moveTo(0, 1);
      // 4 设定终点
      ctx.lineTo(300, 150);
      // 5 描边
      ctx.stroke();


LHP9GK[@9(V2)GNSW`4X2}X.png

改画布大小

需要注意的是,想要修改 canvas标签的宽度和高度,不能直接在css中改,否则是拉伸canvas。只能在标签的属性上通过 widthheight来改

错误示例

canvas {
    width: 600px;
    height: 300px;
  }

)R6YOX(89FP46P)}~90OE@V.png


正确示例

<canvas width="600" height="300"></canvas>

BW5{BJZ~J[0Z8~%JT4C07YL.png

修改颜色 strokeStyle

当我们不想要默认的黑色线条时,可以通过 strokeStyle来修改颜色

ctx.strokeStyle = 'pink';
  ctx.stroke();

{R@5DBQ9}OY773}`8$~(Y3Q.png

画圆弧

// arc(圆心坐标x,圆心坐标y,半径长度,开始弧度,结束弧度)
  ctx.arc(300, 150, 100, 0, Math.PI * 2);
  ctx.stroke();


SHMQP4EPM1P3AM($C2%XML4.png

也可以修改为 填充的圆弧 fill

ctx.arc(300, 150, 100, 0, Math.PI * 2);
ctx.fill();


E}LYU5BBAE~ZO9%UZXT3G`I.png

如果想要修改填充的颜色的话 就要使用 fillStyle

ctx.fillStyle = 'yellow';
ctx.fill();


Z%}V]G6WOEZMH]W1~S{(F$M.png

show time

下列的代码示例,有注释的表示是新增的代码

搭建好静态结构

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0,maximum-scale=1,minimum-scale=1,user-scalable=no"
    />
    <title>五子棋</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      body {
        text-align: center;
        height: 100vh;
      }
      canvas {
        /* 可以自己写一个背景颜色来代替以下四行代码 */
        background-image: url(./images/20211227092309212.jpg);
        background-repeat: no-repeat;
        background-size: cover;
        background-position: center;
      }
    </style>
  </head>
  <body>
    <canvas></canvas>
  </body>
</html>



新建一个类,声明相关方法

下列的代码示例,有注释的表示是新增的代码

主要有四个功能

  1. 初始化相关属性 如 棋盘宽高等
  2. 声明描绘棋盘的方法
  3. 声明点击棋盘,进行下棋的方法
  4. 声明下棋时,判断是否胜利的方法
class Gobang {
        /** 构造函数 */
        constructor() {}
        /** 初始化画布 */
        init() {}
        /** 描绘棋盘 */
        drawCheckerBroad() {}
        /** canvas绑定点击事件,实现下棋功能 */
        checkerBroadEvent() {}
        /** 判断是否胜利 * */
        victory() {}
      }

初始化画布

下列的代码示例,有注释的表示是新增的代码

这里面需要初始化的属性有

需求是想要的棋盘 一共 18*18个格子,那么就需要用到19条线,每一个格子大小为20*20

  1. this.canvas 用来在后面绑定点击事件使用
  2. this.checkerBoradWidth 用来声明一个格子多大
  3. this.lineNums 用来声明棋盘一个要画多少条线段
  4. this.blaorwh 用来声明当前下棋的选手是黄色还是白色
  5. this.chessMartrixs 棋盘位置矩阵,用来记录下棋的棋子位置和判断是否胜利
init() {
      // 格子的宽度宽度
      this.checkerBoradWidth = 20;
      this.lineNums = 19;
      // 白色还是黄色  为黄色
      this.blaorwh = '黄色';
      // 存放棋子的位置 二维数组 19*19
      this.chessMartrixs = [];
      for (let i = 0; i <= this.lineNums; i++) {
        this.chessMartrixs.push(
          new Array(this.lineNums).fill(0, 0, this.lineNums)
        );
      }
      this.canvas = document.querySelector('canvas');
      // 设置 画布的大小 刚好等于20个格子的大小
      this.canvas.width = this.checkerBoradWidth * (this.lineNums - 1);
      this.canvas.height = this.checkerBoradWidth * (this.lineNums - 1);
      this.ctx = this.canvas.getContext('2d');
    }

描绘棋盘

下列的代码示例,有注释的表示是新增的代码

需求是想要的棋盘 一共 18*18个格子,那么就需要用到19条线,每一个格子大小为20*20

// 开辟新的路径
      this.ctx.beginPath();
      // 设定线条颜色
      this.ctx.strokeStyle = '#ccc';
      // 遍历描绘 棋盘上的每一条线 19*19条线
      for (let i = 0; i < this.lineNums; i++) {
        // 描绘横线  checkerBoradWidth 为 棋盘 格子的宽度
        this.ctx.moveTo(0, i * this.checkerBoradWidth);
        this.ctx.lineTo(
          // lineNums 为 线条的个数
          this.lineNums * this.checkerBoradWidth,
          i * this.checkerBoradWidth
        );
        // 描绘竖线
        this.ctx.moveTo(i * this.checkerBoradWidth, 0);
        this.ctx.lineTo(
          i * this.checkerBoradWidth,
          this.lineNums * this.checkerBoradWidth
        );
      }
      this.ctx.stroke();
 

然后在 构造函数中分别调用 初始化画布和描绘棋盘的方法,然后实例化

constructor() {
  this.init();
  this.drawCheckerBroad();
}

实例化

const gobang = new Gobang();

出现效果


点击棋盘,进行下棋

下列的代码示例,有注释的表示是新增的代码

通过给canvas标签绑定点击事件,获取鼠标坐标,然后就地画圆即可

checkerBroadEvent() {
      // 注意要使用箭头函数,否则容易导致 this指向问题
      this.canvas.addEventListener('click', (e) => {
        // 获取鼠标在画布中的坐标
        const x = e.offsetX;
        const y = e.offsetY;
        // 每次点击 重新开辟路径
        this.ctx.beginPath();
        // 根据点击的坐标绘制 棋子
        this.ctx.arc(x, y, this.checkerBoradWidth / 2, 0, 2 * Math.PI);
        // 判断白棋还是黑棋
        if (this.blaorwh === '黄色') {
          // 黄色
          this.ctx.fillStyle = 'yellow';
        } else {
          // 白色 描边 否则容易和背景混淆
          this.ctx.fillStyle = '#fff';
          this.ctx.stroke();
        }
        this.ctx.fill();
        this.blaorwh = this.blaorwh === '黄色' ? '白色' : '黄色';
      });
    }
  

在构造函数中也调用它

constructor() {
      this.init();
      this.drawCheckerBroad();
      this.checkerBroadEvent(); // 鼠标点击下棋功能
    }

这时候会出现两个问题

  1. 下棋的位置不是在线段的中心,有可能歪了
    XBEB)I]OX@`U2N@GQ_4(CT3.png
  2. 同一个位置,可以放无数个棋子(需要判断是否已经有棋子)
    ()OOT`@4MPP93(GRBUXLG~W.png

我们需要解决它

修正下棋的标准位置

下列的代码示例,有注释的表示是新增的代码

主要通过判断当前下棋的坐标位置和水平或竖直方向上的哪一条线段的位置近,那么就设置下棋的位置等于距离最近的线段的坐标。

/** canvas绑定点击事件,实现下棋功能 */
        checkerBroadEvent() {
          // 注意要使用箭头函数,否则容易导致 this指向问题
          this.canvas.addEventListener('click', (e) => {
            const x = e.offsetX;
            const y = e.offsetY;
            // 设定画圆弧的圆心坐标 需要加上每一个格子20px大小的
            let arcX, arcY;
            // 用来记录 棋子的坐标点  如 (0,1)  (1,3) 等
            for (let i = 0; i <= this.lineNums; i++) {
              // 判断鼠标的x坐标靠近哪一条线  水平方向
              if (
                Math.abs(x - i * this.checkerBoradWidth) <=
                this.checkerBoradWidth / 2
              ) {
                arcX = i * this.checkerBoradWidth;
              }
              // 判断鼠标的y坐标靠近哪一条边线  垂直方向
              if (
                Math.abs(y - i * this.checkerBoradWidth) <=
                this.checkerBoradWidth / 2
              ) {
                arcY = i * this.checkerBoradWidth;
              }
            }
            // 如果点出了其他位置,直接返回
            if (arcX === undefined || arcY === undefined) return;
            this.ctx.beginPath();
            // 。。。。。
          });
        }
   

判断当前位置是否已经有棋子了

下列的代码示例,有注释的表示是新增的代码

使用一个二维数组 this.checkerBoradWidth ,记录下棋的坐标位置,当再次下棋时,判断当前位置是否有棋子来实现功能

checkerBroadEvent() {
          this.canvas.addEventListener('click', (e) => {
            const x = e.offsetX;
            const y = e.offsetY;
            let arcX, arcY;
            // 用来记录 棋子的坐标点  把当前棋子的位置记录在 棋子矩阵中
            let chessX, chessY;
            for (let i = 0; i <= this.lineNums; i++) {
              if (
                Math.abs(x - i * this.checkerBoradWidth) <=
                this.checkerBoradWidth / 2
              ) {
                arcX = i * this.checkerBoradWidth;
                // 记录当前棋子的位置 用来做下棋时的位置重复判断
                chessX = i;
              }
              if (
                Math.abs(y - i * this.checkerBoradWidth) <=
                this.checkerBoradWidth / 2
              ) {
                arcY = i * this.checkerBoradWidth;
                // 记录当前棋子的位置 用来做下棋时的位置重复判断
                chessY = i;
              }
            }
            // 如果点出了其他位置,直接返回
            if (arcX === undefined || arcY === undefined) return;
            // 如果有人下过了,那么就返回
            if (this.chessMartrixs[chessY][chessX]) return;
            // 把当前棋子位置存一份到 矩阵中
            this.chessMartrixs[chessY][chessX] = this.blaorwh;
            this.ctx.beginPath();
            this.ctx.arc(
              arcX,
              arcY,
              this.checkerBoradWidth / 2,
              0,
              2 * Math.PI
            );
         
            if (this.blaorwh === '黄色') {
              this.ctx.fillStyle = 'yellow';
            } else {
              this.ctx.fillStyle = '#fff';
              this.ctx.stroke();
            }
            this.ctx.fill();
            this.blaorwh = this.blaorwh === '黄色' ? '白色' : '黄色';
          });
        }

判断选手胜利-重点

我们实现判断选手是否胜利的思路如下

以下四条轴中,任意一条轴(由两条短轴连接而成)上有5个同色的棋子就表示胜利

A{8X37NREVS77CSG4E{[RVA.png

通过观察以上图表,得出一个规律:我们需要设置一个判断棋子是否相连的关键数组

[[-1, -1], [-1, 0], [1, -1], [0, 1]]

  1. 比如我们对上面的数组进行遍历 比如 可以获取到 第一个元素 [-1,-1]
  2. 同时我们对 [-1,-1] 进行取反 ,便也得到 [1,1],那么便可以确定一条长轴
    @4I)96J_KMA~XIK]G2WJXAO.png
  3. 此时,我们拿 [-1,-1] 单独开启遍历,计算 这边方向的棋子个数
    HW~CXZOGVYOS7$MC[JJ]INQ.png
  4. 同时,我们拿 [1,1] 也开始遍历,计算这边方向的棋子个数
    JUSD[V@GN`%8V0U2HE8{H}U.png
  5. 如果两者加起来大于等于 5 ,便表示胜利

代码

/** * 判断选手是否胜利 */
        victory(matrix, x, y, borw) {
          // 判断胜利条件的4个轴方向
          let victoryUnits = [
            [-1, -1],
            [-1, 0],
            [1, -1],
            [0, 1],
          ];
          const getLineNums = (matrix, direcsx, x, y, borw) => {
            // 用来统计相连的同色的棋子的数量
            let results = 0;
            for (let i = 0; i < 5; i++) {
              const vicx = x + i * direcsx[0];
              const vicy = y + i * direcsx[1];
              // 判断位置没有超出
              if (
                matrix[vicy] !== undefined &&
                matrix[vicy][vicx] !== undefined &&
                borw === matrix[vicy][vicx]
              ) {
                // 判断是否和当前的颜色相等
                results++;
              } else {
                break;
              }
            }
            return results;
          };
          // 判断是否胜利
          const result = victoryUnits.some((item) => {
            // 统计一侧短轴的同色棋子的个数
            const n1 = getLineNums(matrix, item, x, y, borw);
            // 统计另一侧短轴的同色棋子的个数
            const n2 = getLineNums(
              matrix,
              item.map((v) => -v),
              x,
              y,
              borw
            );
            // 因为自己也算一个了
            return n1 + n2 - 1 >= 5;
          });
          return result;
        }

完整代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0,maximum-scale=1,minimum-scale=1,user-scalable=no"
    />
    <title>H5-五子棋</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      body {
        text-align: center;
        height: 100vh;
      }
      canvas {
        background-image: url(./images/20211227092309212.jpg);
        background-repeat: no-repeat;
        background-size: cover;
        background-position: center;
      }
    </style>
  </head>
  <body>
    <canvas></canvas>
    <script>
      class Gobang {
        constructor() {
          this.init();
          this.drawCheckerBroad();
          this.checkerBroadEvent();
        }
        init() {
          this.checkerBoradWidth = 20;
          this.lineNums = 19;
          this.blaorwh = '黄色';
          this.chessMartrixs = [];
          for (let i = 0; i <= this.lineNums; i++) {
            this.chessMartrixs.push(
              new Array(this.lineNums).fill(0, 0, this.lineNums)
            );
          }
          this.canvas = document.querySelector('canvas');
          this.canvas.width = this.checkerBoradWidth * (this.lineNums - 1);
          this.canvas.height = this.checkerBoradWidth * (this.lineNums - 1);
          this.ctx = this.canvas.getContext('2d');
        }
        drawCheckerBroad() {
          this.ctx.beginPath();
          this.ctx.strokeStyle = '#ccc';
          for (let i = 0; i < this.lineNums; i++) {
            this.ctx.moveTo(i * this.checkerBoradWidth, 0);
            this.ctx.lineTo(
              i * this.checkerBoradWidth,
              this.lineNums * this.checkerBoradWidth
            );
            this.ctx.moveTo(0, i * this.checkerBoradWidth);
            this.ctx.lineTo(
              this.lineNums * this.checkerBoradWidth,
              i * this.checkerBoradWidth
            );
          }
          this.ctx.stroke();
        }
        checkerBroadEvent() {
          this.canvas.addEventListener('click', (e) => {
            const x = e.offsetX;
            const y = e.offsetY;
            let arcX, arcY;
            let chessX, chessY;
            for (let i = 0; i <= this.lineNums; i++) {
              if (
                Math.abs(x - i * this.checkerBoradWidth) <=
                this.checkerBoradWidth / 2
              ) {
                arcX = i * this.checkerBoradWidth;
                chessX = i;
              }
              if (
                Math.abs(y - i * this.checkerBoradWidth) <=
                this.checkerBoradWidth / 2
              ) {
                arcY = i * this.checkerBoradWidth;
                chessY = i;
              }
            }
            if (arcX === undefined || arcY === undefined) return;
            if (this.chessMartrixs[chessY][chessX]) return;
            this.chessMartrixs[chessY][chessX] = this.blaorwh;
            this.ctx.beginPath();
            this.ctx.arc(
              arcX,
              arcY,
              this.checkerBoradWidth / 2,
              0,
              2 * Math.PI
            );
            if (this.blaorwh === '黄色') {
              this.ctx.fillStyle = 'yellow';
            } else {
              this.ctx.fillStyle = '#fff';
              this.ctx.stroke();
            }
            this.ctx.fill();
            if (
              this.victory(this.chessMartrixs, chessX, chessY, this.blaorwh)
            ) {
              console.log(this.blaorwh + '胜利');
              return;
            }
            this.blaorwh = this.blaorwh === '黄色' ? '白色' : '黄色';
          });
        }
        victory(matrix, x, y, borw) {
          let victoryUnits = [
            [-1, -1],
            [-1, 0],
            [1, -1],
            [0, 1],
          ];
          const getLineNums = (matrix, direcsx, x, y, borw) => {
            let results = 0;
            for (let i = 0; i < 5; i++) {
              const vicx = x + i * direcsx[0];
              const vicy = y + i * direcsx[1];
              if (
                matrix[vicy] !== undefined &&
                matrix[vicy][vicx] !== undefined &&
                borw === matrix[vicy][vicx]
              ) {
                results++;
              } else {
                break;
              }
            }
            return results;
          };
          const result = victoryUnits.some((item) => {
            const n1 = getLineNums(matrix, item, x, y, borw);
            const n2 = getLineNums(
              matrix,
              item.map((v) => -v),
              x,
              y,
              borw
            );
            return n1 + n2 - 1 >= 5;
          });
          return result;
        }
      }
      const gobang = new Gobang();
    </script>
  </body>
</html>


目录
相关文章
|
2月前
|
算法 程序员 定位技术
探索代码世界的奇幻旅程
【8月更文挑战第30天】 在编程的世界里,每一个代码块都像是一块拼图,它们共同构建起一个庞大而复杂的数字宇宙。本文将通过一系列生动的比喻和实例,带领读者走进编程的奇幻世界,揭示那些隐藏在键盘敲击声背后的奥秘与乐趣。从初学者的迷茫到资深开发者的洞见,我们将一起经历技术成长的心路历程,解锁编程带来的无限可能。
30 1
|
4月前
|
机器学习/深度学习 人工智能 自然语言处理
探索编程世界的奇幻旅程
【6月更文挑战第18天】在数字时代的浪潮中,编程不仅是技术操作的体现,更是一种思维的展现。本文将通过一系列生动的故事和实例,揭示编程背后的逻辑美学与创造力的火花,带领读者走进编程世界的奇幻之旅。
|
3月前
|
开发者
【植物大战僵尸杂交版】致敬传奇游戏玩家——一个普通人的六年坚持
【植物大战僵尸杂交版】致敬传奇游戏玩家——一个普通人的六年坚持
|
JavaScript Java
【游戏开发】自从遇见了口袋方舟后,我的世界变得精彩了起来
【游戏开发】自从遇见了口袋方舟后,我的世界变得精彩了起来
146 0
|
开发工具
想学做游戏到底该怎么学
嗨!大家好,我是小蚂蚁。 遇到过很多想学习做游戏却又不得章法的人,有些人可能只是有个想法,有些人真的付诸了行动。但是大部分人最终都是以失败而告终的,不是说最终没有做出来一个游戏,而是连第一步的门槛也没迈的过去。 做游戏做了这么多年,也教了不少的学员,我觉得我至少有一定的经历,可以来说一下,想学习做游戏到底该怎么学。
202 0
三子棋——年轻人的第一款小游戏
主函数部分(test.c) 头文件部分(game.h) 1.初始化函数 2.打印棋盘函数 3.玩家移动函数 4.电脑移动函数 5.判断输赢函数 6.重回game函数 效果展示 总结
108 0
三子棋——年轻人的第一款小游戏
Pyhton实践项目之(一)五子棋人机对战
Pyhton实践项目之(一)五子棋人机对战
113 0
【“玩物立志”-scratch少儿编程】亲手实现小猫走迷宫小游戏:其实挺简单
【“玩物立志”-scratch少儿编程】亲手实现小猫走迷宫小游戏:其实挺简单
753 0
【“玩物立志”-scratch少儿编程】亲手实现小猫走迷宫小游戏:其实挺简单
【“玩物立志”-scratch少儿编程】迷宫游戏-图片素材
【“玩物立志”-scratch少儿编程】迷宫游戏-图片素材
232 0
【“玩物立志”-scratch少儿编程】迷宫游戏-图片素材
|
API 定位技术 C++
【致敬童年】Funcode实现坦克大战
【致敬童年】Funcode实现坦克大战