【Canvas】使用Vue3+TS+Canvas实现五子棋

1.效果预览

动画.gif

项目地址✨

2.实现思路

  • 创建画布

  • 创建绘制对象和棋盘DOM
let ctx: CanvasRenderingContext2D;

const chess_canvas = ref();
  • 使用二维数组记录棋盘格的信息
let record: number[][] = [];
  • 记录当前要下的棋子是黑还是白
let isBlack = true;
  • 初始化画布,将画布的大小设置为600x600,边缘留出20,棋盘规模为14x14,每个格子的大小为40
  ctx = chess_canvas.value?.getContext("2d") as CanvasRenderingContext2D;
  chess_canvas.value!.width = 600;
  chess_canvas.value!.height = 600;
  chess_canvas.value!.style.background = "#e3cdb0";
  isBlack = true;
  • 绘制棋盘的横线和竖线
  for (let i = 0; i < 15; i++) {
    ctx.beginPath();
    ctx.moveTo(20, 20 + 40 * i);
    ctx.lineTo(580, 20 + 40 * i);
    ctx.stroke();
    ctx.closePath();
  }

  for (let i = 0; i < 15; i++) {
    ctx.beginPath();
    ctx.moveTo(20 + 40 * i, 20);
    ctx.lineTo(20 + 40 * i, 580);
    ctx.stroke();
    ctx.closePath();
  }
  • 初始化棋盘格数组,0代表未下,1代表黑子,2代表白子
  for (let i = 0; i < 15; i++) {
    record[i] = new Array(15).fill(0);
  }
  • 给棋盘添加点击事件,棋子只能落到格子的交叉点上,所以要对x的位置和y的位置进行取整,选取离点击点位置最近的格子,绘制棋子,之后更换棋子颜色
  chess_canvas.value!.onclick = e => {
    let x = e.offsetX - (e.offsetX % 40) + 20;
    let y = e.offsetY - (e.offsetY % 40) + 20;

    let pX = (x - 20) / 40;
    let pY = (y - 20) / 40;
    // 棋盘的X和Y和二维数组的X和Y是相反的
    if (record[pY][pX] != 0) {
      return;
    }

    ctx.beginPath();
    ctx.arc(x, y, 10, 0, 2 * Math.PI);
    ctx.fillStyle = isBlack ? "black" : "white";
    ctx.fill();
    ctx.closePath();

    record[pY][pX] = isBlack ? 1 : 2;
    isBlack = !isBlack;
  };
  • 在下过一个棋子之后,判断这颗棋子是否能和棋盘中的其他棋子连成5颗。
    我的做法是这样的(自己想的,代码量不多,有不足之处请指教):每下过一颗棋子,以该棋子为中心,读取【上4颗+该棋子+下4颗】,【左4颗+该棋子+右4颗】,【左上对角4颗+该棋子+左下对角4颗】,【右上对角4颗+该棋子+右下对角4颗】,存储为4个数组:


    image.png

    然后依次判断这4个数组中,是否有连续5个相同的数字(数字的值等于刚刚下过的棋子的值),判断算法是这样设计的:一个数组最大长度为9,若该数组中含有5个相同的数字,那么该数组的中间数字必定为这5个数字中的一个,然后以该中间数为中心,若左边的数和中间数相等,则一直向左寻找边界,若右边的数和中间的数相等,则一直向右寻找边界,如果找到左右边界,判断左右边界的差值,如果差值为4,那么已经连成了5子。

const isWin = (x: number, y: number) => {
  let res: number[][] = [];
  let xadd = [];
  let yadd = [];
  let zadd = [];
  let wadd = [];
  let flag = false;
  // 上 下 左 右 +4
  for (let i = 4; i >= -4; i--) {
    if (x - i >= 0 && x - i <= 14) {
      xadd.push(record[x - i][y]);
    }
    if (y - i >= 0 && y - i <= 14) {
      yadd.push(record[x][y - i]);
    }
    if (x - i >= 0 && x - i <= 14 && y - i >= 0 && y - i <= 14) {
      zadd.push(record[x - i][y - i]);
    }
    if (x + i >= 0 && x + i <= 14 && y - i >= 0 && y - i <= 14) {
      wadd.push(record[x + i][y - i]);
    }
  }
  res.push(xadd);
  res.push(yadd);
  res.push(zadd);
  res.push(wadd);

  let target = record[x][y];
  res.forEach(arr => {
    let mid = Math.floor(arr.length / 2);
    let left = mid;
    let right = mid;
    while (arr[left] == target) {
      left--;
    }
    while (arr[right] == target) {
      right++;
    }
    if (right - 1 - (left + 1) == 4) {
      flag = true;
    }
  });
  return flag;
};
  • 连成5子后,显示胜利弹窗,可以选择重玩
    setTimeout(() => {
      if (isWin(pY, pX)) {
        const con = confirm(`${!isBlack ? "黑棋" : "白棋"}赢了!是否重新开局?`);
        ctx.clearRect(0, 0, 600, 600);
        con && initCanvas();
      }
    }, 10);

3.全部代码

  
import { onBeforeUnmount, onMounted, ref } from "vue";
/** 绘制对象 */
let ctx: CanvasRenderingContext2D;

/** 棋盘DOM */
const chess_canvas = ref();

/** 记录棋盘格信息的数组 */
let record: number[][] = [];

/** 当前是否要下黑棋 */
let isBlack = true;

/** 初始化 */
const initCanvas = () => {
  ctx = chess_canvas.value?.getContext("2d") as CanvasRenderingContext2D;
  chess_canvas.value!.width = 600;
  chess_canvas.value!.height = 600;
  chess_canvas.value!.style.background = "#e3cdb0";

  isBlack = true;

  drawCheckerboard();
};

/** 绘制棋盘:每个棋格大小40*40 */
const drawCheckerboard = () => {
  for (let i = 0; i < 15; i++) {
    ctx.beginPath();
    ctx.moveTo(20, 20 + 40 * i);
    ctx.lineTo(580, 20 + 40 * i);
    ctx.stroke();
    ctx.closePath();
  }

  for (let i = 0; i < 15; i++) {
    ctx.beginPath();
    ctx.moveTo(20 + 40 * i, 20);
    ctx.lineTo(20 + 40 * i, 580);
    ctx.stroke();
    ctx.closePath();
  }

  /** 初始化棋盘格数组,0代表未下,1代表黑子,2代表白子 */
  for (let i = 0; i < 15; i++) {
    record[i] = new Array(15).fill(0);
  }

  /** 给棋盘添加点击事件 */
  chess_canvas.value!.onclick = e => {
    // 进行取整,确保棋子落在最近的棋盘格交叉点上
    let x = e.offsetX - (e.offsetX % 40) + 20;
    let y = e.offsetY - (e.offsetY % 40) + 20;

    let pX = (x - 20) / 40;
    let pY = (y - 20) / 40;

    if (record[pY][pX] != 0) {
      return;
    }

    ctx.beginPath();
    ctx.arc(x, y, 10, 0, 2 * Math.PI);
    ctx.fillStyle = isBlack ? "black" : "white";
    ctx.fill();
    ctx.closePath();

    record[pY][pX] = isBlack ? 1 : 2;
    isBlack = !isBlack;

    setTimeout(() => {
      if (isWin(pY, pX)) {
        const con = confirm(`${!isBlack ? "黑棋" : "白棋"}赢了!是否重新开局?`);
        ctx.clearRect(0, 0, 600, 600);
        con && initCanvas();
      }
    }, 10);
  };
};

const isWin = (x: number, y: number) => {
  let res: number[][] = [];
  let xadd = [];
  let yadd = [];
  let zadd = [];
  let wadd = [];
  let flag = false;
  // 上 下 左 右 +4
  for (let i = 4; i >= -4; i--) {
    if (x - i >= 0 && x - i <= 14) {
      xadd.push(record[x - i][y]);
    }
    if (y - i >= 0 && y - i <= 14) {
      yadd.push(record[x][y - i]);
    }
    if (x - i >= 0 && x - i <= 14 && y - i >= 0 && y - i <= 14) {
      zadd.push(record[x - i][y - i]);
    }
    if (x + i >= 0 && x + i <= 14 && y - i >= 0 && y - i <= 14) {
      wadd.push(record[x + i][y - i]);
    }
  }
  res.push(xadd);
  res.push(yadd);
  res.push(zadd);
  res.push(wadd);

  let target = record[x][y];
  res.forEach(arr => {
    let mid = Math.floor(arr.length / 2);
    let left = mid;
    let right = mid;
    while (arr[left] == target) {
      left--;
    }
    while (arr[right] == target) {
      right++;
    }
    if (right - 1 - (left + 1) == 4) {
      flag = true;
    }
  });
  return flag;
};

onMounted(() => {
  initCanvas();
});
onBeforeUnmount(() => {
  ctx.clearRect(0, 0, 600, 600);
});

你可能感兴趣的:(【Canvas】使用Vue3+TS+Canvas实现五子棋)