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月前
在代码的海洋中航行:我的技术感悟之旅
【10月更文挑战第19天】 在这个数字时代,编程不仅仅是一项技能,它是探索世界、解决问题和创造新事物的工具。本文通过个人的技术学习和实践经历,分享了从初学者到能够独立完成项目的心路历程。文章强调了持续学习的重要性,以及在面对挑战时如何保持好奇心和耐心。通过具体案例,展示了技术如何影响我们的工作和生活,并鼓励读者拥抱变化,勇于尝试新技术。
31 1
|
3月前
|
算法 Python
编程之道:从小白到大牛的蜕变之路
【9月更文挑战第25天】在编程的世界里,每个人都是从零开始,但并非每个人都能成为大牛。本文将通过深入浅出的方式,分享我从编程小白到大牛的蜕变之路,包括学习编程的初衷、遇到的困难、解决问题的方法和心得体会。希望我的经历能给你带来启示和鼓舞,让你在编程的道路上越走越远。
|
JavaScript Java
【游戏开发】自从遇见了口袋方舟后,我的世界变得精彩了起来
【游戏开发】自从遇见了口袋方舟后,我的世界变得精彩了起来
184 0
|
开发工具
想学做游戏到底该怎么学
嗨!大家好,我是小蚂蚁。 遇到过很多想学习做游戏却又不得章法的人,有些人可能只是有个想法,有些人真的付诸了行动。但是大部分人最终都是以失败而告终的,不是说最终没有做出来一个游戏,而是连第一步的门槛也没迈的过去。 做游戏做了这么多年,也教了不少的学员,我觉得我至少有一定的经历,可以来说一下,想学习做游戏到底该怎么学。
209 0
1024里的小温暖,用技术让生活变得更美好 | 有彩蛋!
1024里的小温暖,用技术让生活变得更美好 | 有彩蛋!
110 0
|
API 定位技术 C++
【致敬童年】Funcode实现坦克大战
【致敬童年】Funcode实现坦克大战
|
传感器 Web App开发 小程序
字节跳动的游戏野心
字节跳动的游戏野心
302 0
字节跳动的游戏野心
|
人工智能 安全 机器人
好文|奔向宇宙,揭开太空机器人的神秘面纱
空间机器人是在太空中执行空间站建造与运营、卫星组装与服务、行星表面探测与实验等任务的一类特种机器人,是世界航天大国竞相发展的热点领域。当前,空间机器人已经在国际空间站、飞船、卫星等飞行器的在轨维护、空间装配、月球探测和火星探测等任务中得到广泛的应用,相关研发试验活动高度活跃,呈现出一系列发展特点和趋势。