现代风格中国跳棋 Web 游戏

举报
yd_292659020 发表于 2026/11/01 01:02:03 2026/11/01
【摘要】 从零开发一款现代风格中国跳棋 Web 游戏一篇文章讲透:六角星棋盘的坐标建模、Canvas 渲染引擎、连跳逻辑、以及从本地开发到上架华为云作品展览馆的完整流程。 一、为什么做这个跳棋(Chinese Checkers / Halma)是一款规则简单但策略深度极高的经典棋类游戏。市面上多数在线版本界面陈旧、交互笨拙,棋盘用图片拼接、棋子用 <div> 模拟,既不流畅也不美观。本项目的目标很明...

从零开发一款现代风格中国跳棋 Web 游戏

一篇文章讲透:六角星棋盘的坐标建模、Canvas 渲染引擎、连跳逻辑、以及从本地开发到上架华为云作品展览馆的完整流程。

一、为什么做这个

跳棋(Chinese Checkers / Halma)是一款规则简单但策略深度极高的经典棋类游戏。市面上多数在线版本界面陈旧、交互笨拙,棋盘用图片拼接、棋子用 <div> 模拟,既不流畅也不美观。

本项目的目标很明确:

  • 纯前端单文件实现,打开浏览器即玩,零安装零依赖
  • 现代深色主题 UI,立体光泽棋子、发光动画、毛玻璃信息栏
  • 完整游戏逻辑,单步移动、连续跳跃、胜负判定、回合管理

最终产物是一个 13.8KB 的 index.html,却实现了完整的双人对战体验。

二、游戏规则速览

规则 说明
棋盘 标准六角星形,121 个格子
对战 2 人轮流,红方 vs 蓝方
棋子 每方 10 颗,起始于对角三角形区域
移动 单步走到相邻空位,或跳过相邻棋子落到对面空位
连跳 跳跃后若仍可跳跃,可继续跳或主动结束回合
胜利 先将全部 10 颗棋子移至对面三角形区域者获胜

三、技术选型

为什么不用框架?

一个棋盘游戏的核心逻辑不到 300 行 JavaScript。引入 React/Vue 的开销远大于收益——虚拟 DOM diffing 对 Canvas 渲染毫无帮助,状态管理用一个全局对象就够了。

最终选型:

  • HTML5 Canvas — 棋盘和棋子的绘制,60fps 动画
  • 原生 JavaScript — 游戏逻辑,无框架、无构建步骤
  • CSS3 — 界面样式、动画、毛玻璃效果

为什么用 Canvas 而不是 SVG?

六角星形棋盘有 121 个格子 + 20 颗棋子 = 141 个图形元素。SVG 需要维护 141 个 DOM 节点,每次重绘都触发 DOM 操作。Canvas 只需要一个 <canvas> 元素,所有绘制在内存中完成,性能更好,也更容易实现渐变、阴影等视觉效果。

四、棋盘建模:立方坐标系

这是整个项目最核心的技术决策。

问题

六角网格不能用普通的 (row, col) 笛卡尔坐标表示——因为六边形的邻接关系和正方形不同。每个六边形有 6 个邻居(而非 4 个),且行间有偏移。

方案:Cube Coordinates

采用立方坐标系(Cube Coordinates),用三个轴 (x, y, z) 表示六边形位置,约束条件:

x + y + z = 0

六个方向的向量:

const DIRS = [
  [1, -1, 0], [1, 0, -1], [0, 1, -1],
  [-1, 1, 0], [-1, 0, 1], [0, -1, 1]
];

这个坐标系的美妙之处在于:

  1. 邻居计算极简 — 当前格子 (x, y, z) 加上方向向量即为邻居
  2. 跳跃计算极简 — 加两倍方向向量即为跳跃落点
  3. 距离计算极简 — max(|dx|, |dy|, |dz|) 即为六角距离
  4. 无行间偏移问题 — 不需要处理 odd-row / even-row 的偏移逻辑

棋盘生成

中国跳棋的星形棋盘 = 中心大六边形(半径 4)+ 6 个三角形臂(每个延伸 4 层):

function buildBoard() {
  const cells = new Map();

  // 1. 中心六边形:|x| ≤ 4, |y| ≤ 4, |z| ≤ 4
  for (let x = -4; x <= 4; x++) {
    for (let y = -4; y <= 4; y++) {
      const z = -x - y;
      if (Math.abs(z) <= 4) addCell(cells, x, y, z);
    }
  }

  // 2. 六个三角形臂
  const tips = [
    [1,-1,0], [1,0,-1], [0,1,-1],
    [-1,1,0], [-1,0,1], [0,-1,1]
  ];
  for (const [dx, dy, dz] of tips) {
    for (let layer = 1; layer <= 4; layer++) {
      for (let w = 0; w < layer; w++) {
        // 垂直方向(旋转 60°)
        const px = -dz, py = -dx, pz = -dy;
        addCell(cells,
          4*dx + layer*dx + w*px,
          4*dy + layer*dy + w*py,
          4*dz + layer*dz + w*pz
        );
      }
    }
  }
  return cells;
}

最终生成 正好 121 个格子,与标准中国跳棋棋盘完全一致。

坐标转像素

Cube 坐标到屏幕像素的转换(flat-top hex 布局):

const px = SIZE/2 + x * HEX_R * 1.5;
const py = SIZE/2 + (z + y * 0.5) * HEX_R * Math.sqrt(3);

五、游戏逻辑

合法移动计算

function calcMoves(cell, jumpsOnly) {
  const moves = new Map();
  for (const dir of DIRS) {
    // 单步移动
    if (!jumpsOnly) {
      const n = getNeighbor(cell, dir);
      if (n && !n.piece) moves.set(n.key, { cell: n, type: 'step' });
    }
    // 跳跃:相邻格有棋子,再相邻格为空
    const n1 = getNeighbor(cell, dir);
    const n2 = getJumpTarget(cell, dir);  // cell + 2*dir
    if (n1 && n2 && n1.piece && !n2.piece)
      moves.set(n2.key, { cell: n2, type: 'jump' });
  }
  return moves;
}

逻辑很直观:遍历 6 个方向,检查单步移动(邻居为空)和跳跃(邻居有棋子、跳过邻居后为空)。

连续跳跃

跳跃完成后,检查落点是否还能继续跳跃:

if (move.type === 'jump') {
  const moreJumps = calcMoves(target, true);  // jumpsOnly = true
  if (moreJumps.size > 0) {
    selectedCell = target;
    validMoves = moreJumps;
    isJumping = true;  // 进入连跳状态
    return;  // 不切换玩家,等待继续操作
  }
}

连跳状态下,玩家可以继续跳跃,或点击「结束回合」主动结束。这给了玩家策略选择——有时连续跳跃反而会把棋子送到不利位置。

胜负判定

每步移动后检查:对方起始区域是否被己方棋子填满。

let redScore = 0, blueScore = 0;
for (const key of redZone) {
  if (board.get(key).piece === 'blue') blueScore++;
}
for (const key of blueZone) {
  if (board.get(key).piece === 'red') redScore++;
}
if (redScore === 10) showWin('red');
if (blueScore === 10) showWin('blue');

六、渲染引擎

棋子的立体光泽

每颗棋子由四层绘制叠加而成:

// 1. 阴影层
ctx.arc(c.px + 1, c.py + 3, r, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(0,0,0,0.3)';

// 2. 主体渐变层(径向渐变模拟球体)
const grad = ctx.createRadialGradient(
  c.px - r*0.3, c.py - r*0.3, 1,  // 高光偏移
  c.px, c.py, r                    // 球心到边缘
);
grad.addColorStop(0, '#fca5a5');   // 高光色
grad.addColorStop(0.5, '#f87171'); // 中间色
grad.addColorStop(1, '#dc2626');   // 阴影色

// 3. 高光斑点
ctx.arc(c.px - r*0.35, c.py - r*0.35, r*0.3, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255,255,255,0.4)';

// 4. 边缘描边
ctx.strokeStyle = 'rgba(0,0,0,0.2)';

高光偏移到左上方 (-0.3r, -0.3r),模拟光源从左上照射的效果,让平面圆形看起来像立体球体。

选中发光动画

选中的棋子有呼吸式发光效果:

if (isSelected) {
  const glowR = r + 8 + 3 * Math.sin(animTime * 0.1);
  const glow = ctx.createRadialGradient(
    c.px, c.py, r, c.px, c.py, glowR
  );
  glow.addColorStop(0, 'rgba(248,113,113,0.5)');
  glow.addColorStop(1, 'rgba(248,113,113,0)');
  // 发光半径随 sin 函数呼吸变化
}

animTime 在 requestAnimationFrame 循环中递增,驱动所有动画。

合法落点脉冲提示

const pulse = 0.5 + 0.3 * Math.sin(animTime * 0.08);
ctx.arc(c.px, c.py, 6, 0, Math.PI * 2);
ctx.fillStyle = `rgba(168,85,247,${pulse})`;

紫色圆点的透明度随时间脉冲变化,引导玩家注意合法落点。

七、界面设计

现代深色主题

body {
  background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
}

深蓝渐变背景,搭配毛玻璃信息栏:

.turn-indicator {
  background: rgba(255,255,255,0.08);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255,255,255,0.1);
}

配色方案

元素 颜色 用途
红方棋子 #f87171 → #dc2626 渐变球体
蓝方棋子 #60a5fa → #2563eb 渐变球体
提示点 #a855f7 紫色脉冲
按钮 #6366f1 → #8b5cf6 渐变按钮
背景 #0f172a → #1e293b 深蓝渐变

入场动画

所有 UI 元素都有入场动画,通过 CSS @keyframes 实现:

@keyframes fadeInDown {
  from { opacity: 0; transform: translateY(-20px); }
  to   { opacity: 1; transform: translateY(0); }
}

标题从上方滑入,棋盘从下方淡入,控制栏最后出现——形成视觉节奏。

八、交互设计

点击逻辑

点击棋子 → 选中(高亮)→ 计算合法移动 → 显示提示点
点击提示点 → 执行移动 → 检查连跳 → 切换回合或继续
点击空白 → 取消选中

连跳的玩家选择

连跳是一个策略决策点。实现上:

  • 跳跃后如果还能跳,不自动切换回合
  • 显示「结束回合」按钮,玩家可主动结束
  • 也可继续点击新的提示点进行连跳

这比"自动连跳到无法跳为止"更符合实际棋类对弈体验——玩家应该有权决定每一步。

九、从本地到上架:完整发布流程

游戏开发完成后,通过华为云作品展览馆发布,流程如下:

1. Git 仓库初始化

git init
git add -A
git commit -m "feat: 中国跳棋 Web 游戏 - 初始版本"

2. GitCode OAuth 授权

通过 OAuth 扫码登录获取 GitCode access token,创建远程仓库并推送代码。

3. IAM 域信息解析

通过 hcloud CLI 获取华为云账号的 Domain ID,并生成 STS 临时凭证(900 秒有效),用于后续 API 调用鉴权。

4. DevBridge 隧道

创建开发隧道,将本地 8080 端口暴露为公网可访问 URL:

https://<tunnelId>-8080.devbridge-s2.hwtunnel.com

这样平台可以访问到正在运行的游戏,用于封面截图。

5. 封面自动生成

使用 Playwright 对运行中的游戏截图,自动合成带标题和简介的封面图(1280×720,16:9)。

6. 详情包打包

生成系统架构图 + 详解文章,打包为 zip 上传。

7. 发布上架

调用平台 API 上传封面和详情包,关联活动,完成发布。

POST /v1/gallery/works
→ 201 Created
→ workId=007611a48b400000
→ reward=+200 积分

十、项目结构

整个项目极其精简:

checkers-game/
├── index.html    # 单文件实现(HTML + CSS + JS,13.8KB)
└── README.md     # 项目说明

index.html 内部分区:

区域 行数 职责
<style> ~80 行 全部 CSS 样式和动画
HTML 结构 ~30 行 页面骨架
棋盘建模 ~40 行 Cube 坐标、星形棋盘生成
游戏逻辑 ~60 行 移动计算、连跳、胜负判定
渲染引擎 ~70 行 Canvas 绘制、动画
交互处理 ~40 行 点击事件、回合管理

十一、踩坑与经验

1. 棋盘生成:三角形臂的方向

最初用轴坐标系 (q, r) 生成三角形臂,方向向量算错导致格子重叠或缺失。改用立方坐标系后,通过旋转 60° 得到垂直方向,问题消失。

教训:六角网格问题优先考虑 Cube Coordinates,它的对称性让方向计算变得平凡。

2. 连跳的回合管理

最初实现中,跳跃后直接切换回合,玩家无法选择是否继续连跳。后来引入 isJumping 状态标志,连跳时不切换回合,由玩家决定。

教训:游戏逻辑要尊重玩家的策略选择权,不要替玩家做决定。

3. Canvas 坐标的居中

棋盘生成后格子可能偏出画布。解决方案是先计算所有格子的边界,再整体偏移居中:

const ox = (SIZE - minX - maxX) / 2;
const oy = (SIZE - minY - maxY) / 2;
cellList.forEach(c => { c.px += ox; c.py += oy; });

4. 封面截图的字体问题

在 Linux 环境下截图,中文字体可能缺失导致豆腐块。通过 preflight.sh 预装 Noto Sans CJK 字体解决,并用 verify-glyphs.py 做像素级校验。

十二、总结

这个项目证明了:

  1. 简单技术栈也能做出高质量产品 — 原生 HTML/CSS/JS,无框架、无构建,13.8KB 实现完整游戏
  2. 坐标系选择决定代码复杂度 — Cube Coordinates 让六角网格的所有计算变得简洁
  3. 视觉细节决定体验品质 — 渐变、阴影、动画这些"小事"累积起来就是"现代感"
  4. 发布流程可以高度自动化 — 从截图到封面到上架,全流程脚本化

游戏已发布至华为云开发者作品展览馆,欢迎体验:

🔗 展览馆页面

🔗 代码仓库


本文记录了 2026 年 11 月开发中国跳棋 Web 游戏的完整过程。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。