今天看啥  ›  专栏  ›  阿超448

用canvas写一个象棋(一)——绘制棋盘,棋子

阿超448  · 掘金  ·  · 2021-05-30 15:03
阅读 57

用canvas写一个象棋(一)——绘制棋盘,棋子

这是我最近几天做着玩的,这篇文章仅为了记录下实现的过程,欢迎大家共同探讨

最终的效果图

image.png

预览地址(云服务配置贼差,耐心等会,用电脑看哈,没做适配)

http://119.45.20.162:8089/vue_mange_template/#/chess

描述

用的vue框架(任何框架都能做,不用框架也可以,只是这个我用的多)。 一开始想到的时候,有考虑过两种实现方式,一种是画一堆div,一种是用canvas实现,我最终选择了用canvas实现。 目前已经实现了游戏的大部分规则,game over还没做,我怕做着做着忘记前面的实现了,先记录一下

  • 目标样式

image.png

绘制棋盘和棋盘上的格子

  • 思考

整个棋盘是横着9个竖着10个,我们将canvas的宽度设置为450,高度设置为500,边距为25,每个格子的宽度为50,这样在循环后就能用一个循环画出来棋盘的格子了

<template>
 <div class="chess_test">
   <canvas
     id="chess"
     :width="450"
     :height="height"
     :style="{ background: primary }"
   ></canvas>
 </div>
</template>

<script>
export default {
 data() {
   return {
     canvas: null, // 画布
     ctx: null,
     primary: "#E4B97F", // 主题色
     height: 500,
     width: 450,
     locationScale: 50, // 一格的物理尺寸
     padding: 25, // 左右边距
   };
 },
 mounted() {
   this.initData();
 },
 methods: {
   // 初始化数据
   initData() {
     const canvas = (this.canvas = document.getElementById("chess"));
     const ctx = (this.ctx = canvas.getContext("2d"));
     this.drawChessContainer();
   },
   // 绘制棋盘
   drawChessContainer() {
     const { ctx } = this;
     // 每次绘制棋盘前,先清空画布
     ctx.rect(0, 0, this.width, this.height);
     // 设置颜色
     ctx.fillStyle = this.primary;
     ctx.strokeStyle = "#000";

     // 循环画出来会少一条横线,我们手动加上
     ctx.beginPath();
     ctx.moveTo(this.padding, 475);
     ctx.lineTo(425, 475);
     // 绘制线条
     ctx.stroke();
     for (var index = 1; index <= 9; index++) {
       // 横线
       ctx.beginPath();
       ctx.moveTo(this.padding, index * this.locationScale - this.padding);
       ctx.lineTo(425, index * this.locationScale - this.padding);
       // 绘制线条
       ctx.stroke();

       // 竖线
       ctx.beginPath();
       ctx.moveTo(this.locationScale * index - this.padding, this.padding);
       ctx.lineTo(this.locationScale * index - this.padding, 475);
       // 绘制线条
       ctx.stroke();
     }
   },
 },
};
</script>

<style lang="scss" scoped>
.chess_test {
 background: #555;
 width: 100%;
 height: 100%;
 position: relative;
 .action {
   display: flex;
   align-items: center;
   justify-content: center;
   padding: 20px;
 }
 #chess {
   background: #fff;
   position: absolute;
   left: 50%;
   top: 50%;
   transform: translate(-50%, -50%);
 }
}
</style>

复制代码
  • 效果

image.png

绘制河道和将帅区域的斜线

  • 继续思考

中间有河道,我们可以绘制一个矩形放到河道的位置覆盖,再往河道的位置画文字。双方有将帅区域,斜线也需要我们手动去绘制。下面的代码将只会出现发生变化的部分,否则太占篇幅了

// 绘制棋盘
   drawChessContainer() {
     // ....循环绘制格子部分

     // 双方的将军区域
     // 上方
     ctx.beginPath();
     ctx.moveTo(175, this.padding);
     ctx.lineTo(275, 125);
     ctx.stroke();
     ctx.beginPath();
     ctx.moveTo(275, this.padding);
     ctx.lineTo(175, 125);
     ctx.stroke();
     // 下方
     ctx.beginPath();
     ctx.moveTo(175, 375);
     ctx.lineTo(275, 475);
     ctx.stroke();
     ctx.beginPath();
     ctx.moveTo(175, 475);
     ctx.lineTo(275, 375);
     ctx.stroke();
     
     // 楚河,汉界
     ctx.fillStyle = this.primary;
     ctx.lineWidth = 2;
     ctx.beginPath();
     ctx.rect(26, 225, 398, this.locationScale);
     ctx.fill();
     ctx.font = '28px "报隶-简"';
     ctx.fillStyle = "#000";
     ctx.textBaseline = "middle";
     ctx.fillText("楚河", 75, 250);
     ctx.fillText("汉界", 325, 250);
   },
复制代码
  • 效果

image.png

绘制炮和兵下面的十字线

  • 思考

我们定义一个虚拟坐标轴,把棋盘左上角定义为0的位置,往右是x轴,往下是y轴,一个格子的物理距离50等于虚拟坐标轴一格的距离。双方的炮,和兵下面都有 十字线(我自己起的名字,原谅我不知道怎么这个怎么称呼),x轴为0时没有左边部分,x轴为最大时,没有右边部分,这样我们就可以先定义好会有十字线的位置的坐标,再通过一个循环搞定这些,免去一个一个手动画的辛苦

  • 效果

image.png

data(){
    return {
        crossList: [
        // 上部分
        { x: 1, y: 2 },
        { x: 7, y: 2 },
        { x: 0, y: 3 },
        { x: 2, y: 3 },
        { x: 4, y: 3 },
        { x: 6, y: 3 },
        { x: 8, y: 3 },

        // 下部分
        { x: 0, y: 6 },
        { x: 2, y: 6 },
        { x: 4, y: 6 },
        { x: 6, y: 6 },
        { x: 8, y: 6 },
        { x: 1, y: 7 },
        { x: 7, y: 7 },
      ], // 十字数组
    }
},
methods:{
    // 绘制棋盘
drawChessContainer(){
    // ......绘制棋盘的代码
    this.drawCross()
},
// 绘制十字线
    drawCross(x, y) {
      const { ctx, crossList, locationScale } = this;
      ctx.lineWidth = 1;
      // 画 炮和兵 下面的十字
      crossList.forEach((item) => {
        if (item.x !== 0) {
          // 左上
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 10, item.y * locationScale + 20);
          ctx.lineTo(item.x * locationScale + 20, item.y * locationScale + 20);
          ctx.stroke();
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 20, item.y * locationScale + 20);
          ctx.lineTo(item.x * locationScale + 20, item.y * locationScale + 10);
          ctx.stroke();
          // 左下
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 10, item.y * locationScale + 30);
          ctx.lineTo(item.x * locationScale + 20, item.y * locationScale + 30);
          ctx.stroke();
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 20, item.y * locationScale + 30);
          ctx.lineTo(item.x * locationScale + 20, item.y * locationScale + 40);
          ctx.stroke();
        }
        if (item.x !== 8) {
          // 右上
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 30, item.y * locationScale + 20);
          ctx.lineTo(item.x * locationScale + 40, item.y * locationScale + 20);
          ctx.stroke();
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 30, item.y * locationScale + 20);
          ctx.lineTo(item.x * locationScale + 30, item.y * locationScale + 10);
          ctx.stroke();

          // 右下
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 30, item.y * locationScale + 30);
          ctx.lineTo(item.x * locationScale + 40, item.y * locationScale + 30);
          ctx.stroke();
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 30, item.y * locationScale + 30);
          ctx.lineTo(item.x * locationScale + 30, item.y * locationScale + 40);
          ctx.stroke();
        }
      });
    },
}

复制代码

绘制棋子

  • 思考

我们需要一个数组,里面记着双方棋子的位置的配置项,包含位置,类型,分组等等 这个数组需要自己去定义,下面是我定义的某一项

  {
   y: 0, // 位置
   x: 0, // 位置
   text: "車", // 显示的文字
   show: true, // 表示棋子是否显示
   type: "car", // 是什么棋子 car既代表車
   camp: "black", // 属于红方
   active: false, // 是否激活,后边做逻辑的时候用到
 },
复制代码

双反各有16个棋子,各个棋子的位置不同。怎么确定棋子的位置呢,我们之前设置了一个虚拟坐标就起作用了,例如 将 在x:4 y:0 位置,我们将 4*50(每一格的物理大小)+25(边距大小)=arcx arcx就是圆心的x轴,y也这样计算就能得到 圆心的x,y的实际位置,可以用于canvas绘制

绘制棋子的代码直接放下面吧,免得占篇幅。第一篇就先写到这里,有空再写怎么让棋子动起来,让游戏跑起来的逻辑

为自己带盐

广州有没有好的坑位,求介绍

绘制棋盘棋子的完整代码

     <template>
  <div class="chess_test">
    <canvas
      id="chess"
      :width="450"
      :height="height"
      :style="{ background: primary }"
    ></canvas>
  </div>
</template>

<script>
export default {
  data() {
    return {
      canvas: null, // 画布
      ctx: null,
      primary: "#E4B97F", // 主题色
      height: 500,
      width: 450,
      locationScale: 50, // 一格的物理尺寸
      padding: 25, // 左右边距
      crossList: [
        // 上部分
        { x: 1, y: 2 },
        { x: 7, y: 2 },
        { x: 0, y: 3 },
        { x: 2, y: 3 },
        { x: 4, y: 3 },
        { x: 6, y: 3 },
        { x: 8, y: 3 },

        // 下部分
        { x: 0, y: 6 },
        { x: 2, y: 6 },
        { x: 4, y: 6 },
        { x: 6, y: 6 },
        { x: 8, y: 6 },
        { x: 1, y: 7 },
        { x: 7, y: 7 },
      ], // 十字数组
      chessFontSize: 28, // 棋子中字的大小
      chessWidth: 15, // 棋子半径
      chessList: [
        // 黑方
        {
          y: 0,
          x: 0,
          text: "車",
          show: true, // 表示棋子是否显示
          type: "car",
          camp: "black",
          active: false,
        },
        {
          y: 0,
          x: 1,
          text: "馬",
          show: true,
          type: "horse",
          camp: "black",
          active: false,
        },
        {
          y: 0,
          x: 2,
          text: "象",
          show: true,
          type: "like",
          camp: "black",
          active: false,
        },
        {
          y: 0,
          x: 3,
          text: "士",
          show: true,
          type: "shi",
          camp: "black",
          active: false,
        },
        {
          y: 0,
          x: 4,
          text: "帅",
          show: true,
          type: "will",
          camp: "black",
          active: false,
        },
        {
          y: 0,
          x: 5,
          text: "士",
          show: true,
          type: "shi",
          camp: "black",
          active: false,
        },
        {
          y: 0,
          x: 6,
          text: "象",
          show: true,
          type: "like",
          camp: "black",
          active: false,
        },
        {
          y: 0,
          x: 7,
          text: "馬",
          show: true,
          type: "horse",
          camp: "black",
          active: false,
        },
        {
          y: 0,
          x: 8,
          text: "車",
          show: true,
          type: "car",
          camp: "black",
          active: false,
        },
        {
          y: 2,
          x: 1,
          text: "炮",
          show: true,
          type: "gun",
          camp: "black",
          active: false,
        },
        {
          y: 2,
          x: 7,
          text: "炮",
          show: true,
          type: "gun",
          camp: "black",
          active: false,
        },
        {
          y: 3,
          x: 0,
          text: "卒",
          show: true,
          type: "pawn",
          camp: "black",
          active: false,
        },
        {
          y: 3,
          x: 2,
          text: "卒",
          show: true,
          type: "pawn",
          camp: "black",
          active: false,
        },
        {
          y: 3,
          x: 4,
          text: "卒",
          show: true,
          type: "pawn",
          camp: "black",
          active: false,
        },
        {
          y: 3,
          x: 6,
          text: "卒",
          show: true,
          type: "pawn",
          camp: "black",
          active: false,
        },
        {
          y: 3,
          x: 8,
          text: "卒",
          show: true,
          type: "pawn",
          camp: "black",
          active: false,
        },

        // 红方
        {
          y: 9,
          x: 0,
          text: "車",
          show: true,
          type: "car",
          camp: "red",
          active: false,
        },
        {
          y: 9,
          x: 1,
          text: "馬",
          show: true,
          type: "horse",
          camp: "red",
          active: false,
        },
        {
          y: 9,
          x: 2,
          text: "象",
          show: true,
          type: "like",
          camp: "red",
          active: false,
        },
        {
          y: 9,
          x: 3,
          text: "士",
          show: true,
          type: "shi",
          camp: "red",
          active: false,
        },
        {
          y: 9,
          x: 4,
          text: "将",
          show: true,
          type: "will",
          camp: "red",
          active: false,
        },
        {
          y: 9,
          x: 5,
          text: "士",
          show: true,
          type: "shi",
          camp: "red",
          active: false,
        },
        {
          y: 9,
          x: 6,
          text: "象",
          show: true,
          type: "like",
          camp: "red",
          active: false,
        },
        {
          y: 9,
          x: 7,
          text: "馬",
          show: true,
          type: "horse",
          camp: "red",
          active: false,
        },
        {
          y: 9,
          x: 8,
          text: "車",
          show: true,
          type: "car",
          camp: "red",
          active: false,
        },
        {
          y: 7,
          x: 1,
          text: "炮",
          show: true,
          type: "gun",
          camp: "red",
          active: false,
        },
        {
          y: 7,
          x: 7,
          text: "炮",
          show: true,
          type: "gun",
          camp: "red",
          active: false,
        },
        {
          y: 6,
          x: 0,
          text: "兵",
          show: true,
          type: "pawn",
          camp: "red",
          active: false,
        },
        {
          y: 6,
          x: 2,
          text: "兵",
          show: true,
          type: "pawn",
          camp: "red",
          active: false,
        },
        {
          y: 6,
          x: 4,
          text: "兵",
          show: true,
          type: "pawn",
          camp: "red",
          active: false,
        },
        {
          y: 6,
          x: 6,
          text: "兵",
          show: true,
          type: "pawn",
          camp: "red",
          active: false,
        },
        {
          y: 6,
          x: 8,
          text: "兵",
          show: true,
          type: "pawn",
          camp: "red",
          active: false,
        },
      ],
    };
  },
  mounted() {
    this.initData();
  },
  methods: {
    // 初始化数据
    initData() {
      const canvas = (this.canvas = document.getElementById("chess"));
      const ctx = (this.ctx = canvas.getContext("2d"));
      this.drawChessContainer();
      this.drawChess();
    },
    // 绘制棋盘
    drawChessContainer() {
      const { ctx } = this;
      // 每次绘制棋盘前,先清空画布
      ctx.rect(0, 0, this.width, this.height);
      // 设置颜色
      ctx.fillStyle = this.primary;
      ctx.strokeStyle = "#000";

      // 循环画出来会少一条横线,我们手动加上
      ctx.beginPath();
      ctx.moveTo(this.padding, 475);
      ctx.lineTo(425, 475);
      // 绘制线条
      ctx.stroke();
      for (var index = 1; index <= 9; index++) {
        // 横线
        ctx.beginPath();
        ctx.moveTo(this.padding, index * this.locationScale - this.padding);
        ctx.lineTo(425, index * this.locationScale - this.padding);
        // 绘制线条
        ctx.stroke();

        // 竖线
        ctx.beginPath();
        ctx.moveTo(this.locationScale * index - this.padding, this.padding);
        ctx.lineTo(this.locationScale * index - this.padding, 475);
        // 绘制线条
        ctx.stroke();
      }

      // 双方的将军区域
      // 上方
      ctx.beginPath();
      ctx.moveTo(175, this.padding);
      ctx.lineTo(275, 125);
      ctx.stroke();
      ctx.beginPath();
      ctx.moveTo(275, this.padding);
      ctx.lineTo(175, 125);
      ctx.stroke();
      // 下方
      ctx.beginPath();
      ctx.moveTo(175, 375);
      ctx.lineTo(275, 475);
      ctx.stroke();
      ctx.beginPath();
      ctx.moveTo(175, 475);
      ctx.lineTo(275, 375);
      ctx.stroke();

      // 楚河,汉界
      ctx.fillStyle = this.primary;
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.rect(26, 225, 398, this.locationScale);
      ctx.fill();
      ctx.font = '28px "报隶-简"';
      ctx.fillStyle = "#000";
      ctx.textBaseline = "middle";
      ctx.fillText("楚河", 75, 250);
      ctx.fillText("汉界", 325, 250);

      this.drawCross();
    },
    // 绘制单个棋子
    drawChessSingle(chess, { chessWidth } = {}) {
      if (!chess.show) return;
      const { ctx, chessFontSize, locationScale, padding } = this;
      chessWidth = chessWidth || this.chessWidth;
      let x = chess.x * locationScale + padding;
      let y = chess.y * locationScale + padding;
      // 画棋
      ctx.beginPath();
      ctx.lineWidth = "5";
      ctx.strokeStyle = "#898341";
      ctx.arc(x, y, chessWidth, 0, 2 * Math.PI);
      ctx.stroke();
      ctx.closePath();
      ctx.fillStyle = chess.camp;
      ctx.fill();
      ctx.lineWidth = "1";

      // 画字
      ctx.font = chessFontSize + 'px "报隶-简"';
      ctx.fillStyle = "#fff";
      ctx.textBaseline = "middle";
      ctx.fillText(chess.text, x - chessFontSize / 2, y);
    },
    // 绘制棋子
    drawChess() {
      this.chessList.forEach((item) => {
        this.drawChessSingle(item);
      });
    },
    // 绘制十字线
    drawCross(x, y) {
      const { ctx, crossList, locationScale } = this;
      ctx.lineWidth = 1;
      // 画 炮和兵 下面的十字
      crossList.forEach((item) => {
        if (item.x !== 0) {
          // 左上
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 10, item.y * locationScale + 20);
          ctx.lineTo(item.x * locationScale + 20, item.y * locationScale + 20);
          ctx.stroke();
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 20, item.y * locationScale + 20);
          ctx.lineTo(item.x * locationScale + 20, item.y * locationScale + 10);
          ctx.stroke();
          // 左下
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 10, item.y * locationScale + 30);
          ctx.lineTo(item.x * locationScale + 20, item.y * locationScale + 30);
          ctx.stroke();
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 20, item.y * locationScale + 30);
          ctx.lineTo(item.x * locationScale + 20, item.y * locationScale + 40);
          ctx.stroke();
        }
        if (item.x !== 8) {
          // 右上
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 30, item.y * locationScale + 20);
          ctx.lineTo(item.x * locationScale + 40, item.y * locationScale + 20);
          ctx.stroke();
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 30, item.y * locationScale + 20);
          ctx.lineTo(item.x * locationScale + 30, item.y * locationScale + 10);
          ctx.stroke();

          // 右下
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 30, item.y * locationScale + 30);
          ctx.lineTo(item.x * locationScale + 40, item.y * locationScale + 30);
          ctx.stroke();
          ctx.beginPath();
          ctx.moveTo(item.x * locationScale + 30, item.y * locationScale + 30);
          ctx.lineTo(item.x * locationScale + 30, item.y * locationScale + 40);
          ctx.stroke();
        }
      });
    },
  },
};
</script>

<style lang="scss" scoped>
.chess_test {
  background: #555;
  width: 100%;
  height: 100%;
  position: relative;
  .action {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
  }
  #chess {
    background: #fff;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
  }
}
</style>

复制代码



原文地址:访问原文地址
快照地址: 访问文章快照