现代风格中国跳棋 Web 游戏
从零开发一款现代风格中国跳棋 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]
];
这个坐标系的美妙之处在于:
- 邻居计算极简 — 当前格子
(x, y, z)加上方向向量即为邻居 - 跳跃计算极简 — 加两倍方向向量即为跳跃落点
- 距离计算极简 —
max(|dx|, |dy|, |dz|)即为六角距离 - 无行间偏移问题 — 不需要处理 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 做像素级校验。
十二、总结
这个项目证明了:
- 简单技术栈也能做出高质量产品 — 原生 HTML/CSS/JS,无框架、无构建,13.8KB 实现完整游戏
- 坐标系选择决定代码复杂度 — Cube Coordinates 让六角网格的所有计算变得简洁
- 视觉细节决定体验品质 — 渐变、阴影、动画这些"小事"累积起来就是"现代感"
- 发布流程可以高度自动化 — 从截图到封面到上架,全流程脚本化
游戏已发布至华为云开发者作品展览馆,欢迎体验:
🔗 展览馆页面
🔗 代码仓库
本文记录了 2026 年 11 月开发中国跳棋 Web 游戏的完整过程。
- 点赞
- 收藏
- 关注作者
评论(0)