canvas快速入门(七)制作五子棋小游戏

注释很详细,直接上代码

新增内容:
1. 五子棋基本制作
2.五子棋判断输赢算法
3. 使用径向渐变增加棋子立体感
4. 使用阴影增加棋子立体感

项目结构:

在这里插入图片描述

源码:

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    *{
        margin: 0;
        padding: 0;
    }
    canvas{
        display: block;
        margin: 0 auto;
        background-color:#d7b495;
        margin-top: 3vh;
    }
</style>
<body>
</body>
<script src="./js/canvas.js"></script>
</html>

canvas.js

let canvasSize = 1400; //画布大小
let canvasInteriorCellSize = 50; //画布内部单元大小
let canvasPadding = canvasInteriorCellSize; //画布内边距
let canvasInterior = canvasSize - 2 * canvasPadding; //画布内部大小
let canvasInteriorCellCount = canvasInterior / canvasInteriorCellSize; //画布内部单元格数量
let canvas; //画布
const blackHolder = 0, //黑方
  whiteHolder = 1; //黑方与白方
const blackChessPieces = 1, //黑子
  whiteChessPieces = 2; //白子
let holder = blackHolder; //当前下子方
let map = []; //棋盘数组
let isWinSign = false; //是否赢

/**
 * 初始化棋盘数组
 * @returns {void} - 无返回值
 */
function initMap() {
  for (let i = 0; i <= canvasInteriorCellCount; i++) {
    map[i] = [];
    for (let j = 0; j <= canvasInteriorCellCount; j++) {
      map[i][j] = 0;
    }
  }
}

/**
 * 创建画布
 * @param {number} width - 宽度
 * @param {number} height - 高度
 * @returns {HTMLCanvasElement} -返回画布
 */
function createCanvas(width, height) {
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  document.body.append(canvas);
  return canvas;
}

/**
 * 绘制棋盘
 * @returns {void} - 无返回值
 */
function drawChessboard() {
  const ctx = canvas.getContext("2d");
  for (let i = 1; i <= canvasInteriorCellCount + 1; i++) {
    let dist = i * canvasInteriorCellSize;

    //绘制竖线
    ctx.beginPath();
    ctx.moveTo(dist, canvasPadding);
    ctx.lineTo(dist, canvasSize - canvasPadding);
    ctx.stroke();

    //绘制横线
    ctx.beginPath();
    ctx.moveTo(canvasPadding, dist);
    ctx.lineTo(canvasSize - canvasPadding, dist);
    ctx.stroke();
  }
}

/**
 * 落子点击事件
 * @returns {void} - 无返回值
 */
function dropEvent() {
  canvas.addEventListener("click", function (e) {
    let { offsetX, offsetY } = e; //获取点击位置
    let size = canvasInteriorCellSize; //棋盘单元格大小
    let padding = canvasPadding; //画布内边距

    //最近棋盘交叉点x坐标
    let x =
      canvasPadding + Math.floor((offsetX - padding + size / 2) / size) * size;
    //最近棋盘交叉点y坐标
    let y =
      canvasPadding + Math.floor((offsetY - padding + size / 2) / size) * size;

    //如果点击在棋盘内(不包含边距)
    if (
      x >= padding &&
      x <= canvasSize - padding &&
      y >= padding &&
      y <= canvasSize - padding
    ) {
      drawChess(x, y);
    }
  });
}

/**
 * 绘制棋子
 * @param {number} x - 棋子x坐标
 * @param {number} y - 棋子y坐标
 * @returns {void} - 无返回值
 */
function drawChess(x, y) {
  let r = (canvasInteriorCellSize / 2) * (6 / 7); //棋子半径
  let ctx = canvas.getContext("2d");
  let numX = x / canvasInteriorCellSize - 1; //棋子x坐标对应的棋盘交叉点序号
  let numY = y / canvasInteriorCellSize - 1; //棋子y坐标对应的棋盘交叉点序号

  //创建径向渐变
  let g = ctx.createRadialGradient(
    x - r / 2,
    y - r / 2,
    0,
    x - r / 2,
    y - r / 2,
    r
  );

  ctx.beginPath();

  ctx.arc(x, y, r, 0, 2 * Math.PI); //绘制圆

  //如果该位置已有棋子则直接返回
  if (map[numX][numY]) {
    return;
  }

  //根据下子方设置棋子颜色,标记与切换当前执子
  if (holder == blackHolder) {
    map[numX][numY] = blackChessPieces; //标记棋子

    //设置渐变色标
    g.addColorStop(0, "#ccc");
    g.addColorStop(1, "#000");

    holder = whiteHolder; //切换当前执子为白方
  } else {
    map[numX][numY] = whiteChessPieces;
    g.addColorStop(0, "#ccc");
    g.addColorStop(1, "#fff");
    holder = blackHolder;
  }

  //设置渐变填充
  ctx.fillStyle = g;

  setChessShadow();

  //判断是否赢并标记
  isWinSign = isWin(numX, numY) ? true : false;

  //填充
  ctx.fill();
  ctx.closePath();

  //判断是否赢并弹出提示
  if (isWinSign) {
    setTimeout(function () {
      alert((holder == !blackHolder ? "黑棋" : "白棋") + "赢了!");
      restart(); //重新开始
    }, 100); //延迟弹出提示给最后一个棋子绘制时间
  }
}

/**
 * 设置棋子阴影
 * @returns {void} - 无返回值
 */
function setChessShadow() {
  let ctx = canvas.getContext("2d");
  ctx.shadowColor = "#333";
  ctx.shadowBlur = 5;
  ctx.shadowOffsetX = 3;
  ctx.shadowOffsetY = 3;
}

/**
 * 重新开始
 * @returns {void} - 无返回值
 */
function restart() {
  //移除画布
  document.getElementsByTagName("canvas")[0].remove();
  start(); //开始
  holder = blackHolder; //重新设置当前执子为黑方
  isWinSign = false; //重新设置赢标记
}

/**
 * 判断是否赢
 * @param {number} x - 棋子的x下标
 * @param {number} y - 棋子的y下标
 * @returns {boolean} - 返回是否赢
 */
function isWin(x, y) {
  //方向数组
  const directions = [
    [-1, 0], // 垂直方向
    [0, 1], // 水平方向
    [-1, 1], // 右上到左下斜向
    [1, 1], // 左上到右下斜向
  ];

  const sign = map[x][y]; //当前棋子类型标记

  for (let dir of directions) {
    let count = 1;
    // 分别向两个方向延伸
    for (let i = 1; i < 5; i++) {
      //对应方向下一个子的下标
      const nx = x + dir[0] * i;
      const ny = y + dir[1] * i;

      // 如果超出棋盘边界或者棋子类型不同,则跳出循环
      if (
        nx < 0 ||
        nx > canvasInteriorCellCount ||
        ny < 0 ||
        ny > canvasInteriorCellCount ||
        map[nx][ny] !== sign
      )
        break;
      count++;
    }
    // 反方向延伸
    for (let i = 1; i < 5; i++) {
      const nx = x - dir[0] * i;
      const ny = y - dir[1] * i;
      if (
        nx < 0 ||
        nx > canvasInteriorCellCount ||
        ny < 0 ||
        ny > canvasInteriorCellCount ||
        map[nx][ny] !== sign
      )
        break;
      count++;
    }

    // 如果任何一个方向的连续相同棋子数量达到了5个,则返回 true
    if (count >= 5) return true;
  }

  return false;
}

/**
 * 开始
 * @returns {void} - 无返回值
 */
function start() {
  initMap();
  canvas = createCanvas(canvasSize, canvasSize);
  drawChessboard();
  dropEvent();
}

start();

效果演示:

在这里插入图片描述

本系列到这暂且告一段落了,以后有啥有趣的再续更

相关推荐

  1. Python用Pygame实现一个五子棋游戏

    2024-07-19 13:58:04       50 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-19 13:58:04       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-19 13:58:04       71 阅读
  3. 在Django里面运行非项目文件

    2024-07-19 13:58:04       58 阅读
  4. Python语言-面向对象

    2024-07-19 13:58:04       69 阅读

热门阅读

  1. C语言指针的理解

    2024-07-19 13:58:04       18 阅读
  2. Centos---命令详解 vi 系统服务 网络

    2024-07-19 13:58:04       21 阅读
  3. 基于深度学习的数据增强

    2024-07-19 13:58:04       21 阅读
  4. 【题解】StarryCoding P259 好奇怪好奇怪

    2024-07-19 13:58:04       21 阅读
  5. PHP 调用 JD 详情 API 接口:数据获取新途径

    2024-07-19 13:58:04       22 阅读
  6. 使用git提交代码时候出现403怎么解决

    2024-07-19 13:58:04       18 阅读
  7. tensorrt-llm知识

    2024-07-19 13:58:04       18 阅读
  8. 芯片基础 | `wire`类型引发的学习

    2024-07-19 13:58:04       19 阅读
  9. oracle extract的使用

    2024-07-19 13:58:04       23 阅读
  10. mysql、oracle、db2数据库连接参数

    2024-07-19 13:58:04       19 阅读
  11. 什么是TCP/IP协议

    2024-07-19 13:58:04       23 阅读
  12. 初识synchronized

    2024-07-19 13:58:04       23 阅读
  13. 【QT】001第一个程序

    2024-07-19 13:58:04       19 阅读