古风华容道小游戏
一个古风华容道小游戏
一篇完整的开发手记:从华容道规则研究、关卡设计、古风 UI 打磨到核心移动算法实现,最后发布到华为云开发者作品展览馆的全流程。
一、项目背景
华容道(Klotski)是中国传统益智游戏,源自三国典故"曹操败走华容道"。玩家在 4×5 的棋盘上滑动大小不一的方块,目标是将最大的"曹操"方块移出底部出口。本项目以三国人物为棋子,采用古风视觉设计,包含三关经典阵法,面向青少年及益智游戏爱好者。
技术选型:纯 HTML + CSS + JavaScript,零框架、零依赖。一个 python3 -m http.server 就能跑起来。
最终效果预览:

二、游戏规则
- 棋盘:4 列 × 5 行网格,共 20 个格子
- 棋子类型:
| 棋子 | 尺寸 | 数量 | 颜色 | 说明 |
|---|---|---|---|---|
| 曹操 | 2×2 | 1 | 朱红色 | 最大方块,突围目标 |
| 关羽 | 1×2(横) | 1 | 青绿色 | 横向长方块 |
| 张飞 | 1×2(竖) | 1 | 墨青色 | 竖向长方块 |
| 赵云 | 1×2(竖) | 1 | 湖蓝色 | 竖向长方块 |
| 马超 | 1×2(竖) | 1 | 靛蓝色 | 竖向长方块 |
| 黄忠 | 1×2(竖) | 1 | 墨绿色 | 竖向长方块 |
| 卒 | 1×1 | 4 | 灰褐色 | 小方块 |
- 移动规则:棋子只能滑入相邻的空格,不可重叠、不可越界
- 操作方式:点击棋子选中后按方向键(↑↓←→)移动,或直接拖拽
- 胜利条件:曹操(2×2 大块)左上角到达棋盘底部中央位置(row=3, col=1)
- 计分方式:每移动一格记一步,步数越少越精妙
三、三关阵法设计
华容道的精髓在于初始布局。不同的布局产生不同的难度和趣味性。本项目设计了三关经典阵法:
第一阵 · 横刀立马(入门,最少 81 步)
最经典的华容道开局,曹操位于棋盘顶部中央,关羽横置其下,四员竖将分列两侧,四卒散布底部:
张飞 曹操 曹操 赵云
张飞 曹操 曹操 赵云
马超 关羽 关羽 黄忠
马超 卒 卒 黄忠
卒 卒 · ·
第二阵 · 指挥若定(中等,最少 70 步)
曹操仍在顶部,但武将位置调整——张飞、赵云下移一格,马超、黄忠移至底部两侧,四卒分散在四角和中部:
卒 曹操 曹操 卒
张飞 曹操 曹操 赵云
张飞 关羽 关羽 赵云
马超 卒 卒 黄忠
马超 · · 黄忠
第三阵 · 将拥曹营(较高,最少 72 步)
曹操不再在顶部,而是被围在中间(row=1),四卒封顶,关羽横置底部,形成"将拥曹营"之势:
卒 卒 卒 卒
张飞 曹操 曹操 赵云
张飞 曹操 曹操 赵云
马超 关羽 关羽 黄忠
马超 · · 黄忠
三关布局均经过合法性校验:棋子不重叠、不越界、恰好留 2 格空位,且均为经典可解阵法。
四、架构设计
项目采用三层分离结构,无任何前端框架:
klotski-game/
├── index.html # 页面结构层(103 行)
├── style.css # 视觉表现层(369 行)
├── game.js # 游戏逻辑层(345 行)
└── README.md # 项目说明
设计分层
┌─────────────────────────────────────────┐
│ 表现层 (UI Layer) │
│ index.html + style.css │
│ · 卷轴框架 · 古风配色 · 棋子样式 │
├─────────────────────────────────────────┤
│ 逻辑层 (Game Logic) │
│ game.js │
│ · 占位检测 · 移动判定 · 胜负检查 │
├─────────────────────────────────────────┤
│ 数据层 (Data Layer) │
│ LEVELS 常量 │
│ · 三关布局 · 棋子定义 · 胜利条件 │
└─────────────────────────────────────────┘
为什么不用框架?华容道的交互逻辑比数字合并游戏更直观——点击选中、方向键移动,DOM 操作量可控。纯 JS 直接操作 DOM,代码量 345 行,清晰易读,对青少年来说也是很好的学习材料。
五、核心实现
5.1 数据结构:棋子对象
每个棋子是一个带有位置和尺寸信息的对象:
{
id: "caocao", // 唯一标识
name: "曹操", // 显示名称
type: "caocao", // 类型(用于 CSS 样式)
w: 2, // 宽(占几列)
h: 2, // 高(占几行)
row: 0, // 当前行坐标(0 = 顶部)
col: 1 // 当前列坐标(0 = 左侧)
}
为什么需要 w/h? 华容道有三种尺寸的棋子:1×1(卒)、1×2(武将)、2×2(曹操)。用宽高统一描述,移动检测和渲染都能用同一套逻辑处理,无需为每种棋子写特殊代码。
5.2 关卡数据
三关布局定义为常量数组,每关包含名称、最少步数和棋子初始位置:
const LEVELS = [
{
name: "横刀立马",
minMoves: 81,
pieces: [
{ id: "caocao", name: "曹操", type: "caocao", w: 2, h: 2, row: 0, col: 1 },
{ id: "guanyu", name: "关羽", type: "guanyu", w: 2, h: 1, row: 2, col: 1 },
{ id: "zhangfei", name: "张飞", type: "zhangfei", w: 1, h: 2, row: 0, col: 0 },
{ id: "zhaoyun", name: "赵云", type: "zhaoyun", w: 1, h: 2, row: 0, col: 3 },
{ id: "machao", name: "马超", type: "machao", w: 1, h: 2, row: 2, col: 0 },
{ id: "huangzhong", name: "黄忠", type: "huangzhong", w: 1, h: 2, row: 2, col: 3 },
{ id: "p1", name: "卒", type: "pawn", w: 1, h: 1, row: 3, col: 1 },
{ id: "p2", name: "卒", type: "pawn", w: 1, h: 1, row: 3, col: 2 },
{ id: "p3", name: "卒", type: "pawn", w: 1, h: 1, row: 4, col: 0 },
{ id: "p4", name: "卒", type: "pawn", w: 1, h: 1, row: 4, col: 1 },
],
},
// ... 第二阵、第三阵
];
5.3 占位图与碰撞检测
华容道的核心约束是"棋子不能重叠"。每次移动前,先构建当前棋盘的占位图,再检查目标位置是否可用:
function buildOccupancy() {
const grid = Array.from({ length: ROWS }, () => Array(COLS).fill(null));
for (const p of pieces) {
for (let r = 0; r < p.h; r++) {
for (let c = 0; c < p.w; c++) {
grid[p.row + r][p.col + c] = p.id;
}
}
}
return grid;
}
移动检测分两步:边界检查 + 重叠检查:
function canPlace(piece, newRow, newCol) {
// 1. 边界检查:不能超出棋盘
if (newRow < 0 || newCol < 0) return false;
if (newRow + piece.h > ROWS || newCol + piece.w > COLS) return false;
// 2. 重叠检查:不能与其他棋子重叠
for (const other of pieces) {
if (other.id === piece.id) continue;
if (rectsOverlap(newRow, newCol, piece.w, piece.h,
other.row, other.col, other.w, other.h)) {
return false;
}
}
return true;
}
矩形重叠检测用经典的分离轴定理——两个矩形不重叠,当且仅当在某个轴上分离:
function rectsOverlap(r1, c1, w1, h1, r2, c2, w2, h2) {
return !(r1 + h1 <= r2 || r2 + h2 <= r1 ||
c1 + w1 <= c2 || c2 + w2 <= c1);
}
5.4 移动执行
每次移动一格,更新坐标、步数,并检查胜利条件:
function tryMove(piece, dr, dc) {
const nr = piece.row + dr;
const nc = piece.col + dc;
if (canPlace(piece, nr, nc)) {
piece.row = nr;
piece.col = nc;
moves++;
moveCountEl.textContent = moves;
renderPositions();
if (!won && checkWin()) {
won = true;
setTimeout(showWin, 250);
}
return true;
}
return false;
}
5.5 胜利判定
胜利条件非常简洁——曹操的左上角到达指定位置:
const WIN_ROW = 3;
const WIN_COL = 1;
function checkWin() {
const cao = pieces.find((p) => p.id === "caocao");
return cao && cao.row === WIN_ROW && cao.col === WIN_COL;
}
为什么是 (row=3, col=1)?因为曹操是 2×2 方块,左上角在 (3, 1) 时,它占据第 3-4 行、第 1-2 列——正好是棋盘底部中央的出口位置。
5.6 交互处理:选中 + 方向键 + 拖拽
游戏支持两种交互方式:
方式一:点击选中 + 方向键移动
// 点击棋子选中
el.addEventListener("click", () => {
selectedId = p.id;
renderPositions(); // 更新高亮状态
});
// 方向键移动选中棋子
window.addEventListener("keydown", (e) => {
if (!selectedId) return;
const p = pieces.find((x) => x.id === selectedId);
const map = {
ArrowUp: [-1, 0], ArrowDown: [1, 0],
ArrowLeft: [0, -1], ArrowRight: [0, 1],
};
const dir = map[e.key];
if (dir) { e.preventDefault(); tryMove(p, dir[0], dir[1]); }
});
方式二:鼠标拖拽
拖拽时计算鼠标位移方向,尝试沿该方向移动一格。拖拽过程中持续检测,实现"滑入空格"的手感:
function onPointerMove(e) {
if (!dragging) return;
const dx = e.clientX - dragStart.x;
const dy = e.clientY - dragStart.y;
const cell = getCellSize();
// 位移超过半格时触发移动
if (Math.abs(dx) > cell / 2) {
tryMove(dragPiece, 0, dx > 0 ? 1 : -1);
dragStart.x = e.clientX;
} else if (Math.abs(dy) > cell / 2) {
tryMove(dragPiece, dy > 0 ? 1 : -1, 0);
dragStart.y = e.clientY;
}
}
5.7 渲染:绝对定位 + CSS 变量
棋子用绝对定位放置在棋盘上,位置通过 CSS 变量 --cell(格子大小)和 --gap(间距)计算:
function posOf(row, col) {
const cell = getCellSize();
const gap = getGap();
return { x: gap + col * (cell + gap), y: gap + row * (cell + gap) };
}
function sizeOf(w, h) {
const cell = getCellSize();
const gap = getGap();
return { w: w * cell + (w - 1) * gap, h: h * cell + (h - 1) * gap };
}
移动时只更新 left/top 属性,配合 CSS transition 实现平滑滑动动画。
六、古风 UI 设计
6.1 配色方案
整体配色以"宣纸 + 木纹 + 朱墨"为基调,营造三国古战场的氛围:
:root {
--paper: #f5e6c8; /* 宣纸底色 */
--paper-2: #e8d5a3; /* 宣纸深色 */
--ink: #2b2118; /* 墨色文字 */
--ink-soft: #5a4a38; /* 淡墨 */
--wood: #8b6914; /* 木纹色 */
--wood-dark: #6e5210; /* 深木纹 */
--caocao: #c0392b; /* 曹操朱红 */
--general: #2e6e50; /* 武将青绿 */
--pawn: #8a7355; /* 卒灰褐 */
--gold: #b8860b; /* 金色装饰 */
}
| 色彩 | 色值 | 用途 |
|---|---|---|
| 宣纸色 | #f5e6c8 |
页面背景,模拟泛黄宣纸 |
| 墨色 | #2b2118 |
文字、边框 |
| 木纹色 | #8b6914 |
棋盘框架、卷轴轴杆 |
| 朱红色 | #c0392b |
曹操棋子,突出突围目标 |
| 青绿色 | #2e6e50 |
武将棋子(关羽) |
| 灰褐色 | #8a7355 |
卒棋子,低调朴素 |
| 金色 | #b8860b |
装饰边角、选中高亮 |
6.2 卷轴框架
整个游戏界面被包裹在一个"卷轴"中——上下各有一根木纹轴杆,两端带铜钉装饰:
.scroll-rod {
height: 18px;
border-radius: 9px;
background: linear-gradient(180deg, #c9a14a, #8b6914 40%, #6e5210 70%, #4a3608);
box-shadow: 0 3px 8px rgba(0,0,0,.35), inset 0 1px 2px rgba(255,220,150,.5);
}
.scroll-rod::before, .scroll-rod::after {
content: "";
width: 26px; height: 26px;
border-radius: 50%;
background: radial-gradient(circle at 35% 35%, #e6c36a, #8b6914 60%, #4a3608);
}
轴杆用渐变模拟圆柱体质感,两端用径向渐变圆形模拟铜钉帽,配合 box-shadow 营造立体感。
6.3 棋盘与棋子
棋盘用木纹色边框包裹,四角有金色装饰花纹。棋子根据类型有不同的背景色和边框:
.piece.caocao {
background: linear-gradient(135deg, var(--caocao), var(--caocao-dark));
border: 2px solid #e74c3c;
box-shadow: 0 3px 8px rgba(192,57,43,.4);
}
.piece.guanyu {
background: linear-gradient(135deg, var(--general), #1a5e3e);
border: 2px solid #3a8e6e;
}
.piece.pawn {
background: linear-gradient(135deg, var(--pawn), var(--pawn-dark));
border: 2px solid #a08866;
}
选中状态用金色边框高亮,给玩家明确的视觉反馈:
.piece.selected {
border-color: var(--gold);
box-shadow: 0 0 12px rgba(184,134,11,.6), 0 3px 8px rgba(0,0,0,.3);
}
6.4 书法字体
使用 Google Fonts 的三款中文字体,营造古风氛围:
<link href="https://fonts.googleapis.com/css2?family=Ma+Shan+Zheng&family=ZCOOL+KuaiLe&family=Noto+Serif+SC:wght@400;700&display=swap" rel="stylesheet">
| 字体 | 用途 |
|---|---|
| Ma Shan Zheng(马善政体) | 棋子上的武将名,手写书法风格 |
| ZCOOL KuaiLe(站酷快乐体) | 标题"华容道·三国古战阵" |
| Noto Serif SC(思源宋体) | 正文、按钮、提示文字 |
6.5 背景纹理
页面背景用多层渐变叠加,模拟宣纸的质感和泛黄效果:
body {
background:
radial-gradient(circle at 20% 10%, rgba(184,134,11,.10), transparent 40%),
radial-gradient(circle at 80% 90%, rgba(139,105,20,.12), transparent 45%),
repeating-linear-gradient(45deg, #efe0bf 0 2px, #ead9b0 2px 4px);
}
三层叠加:左上角金色光晕 + 右下角木色光晕 + 45° 斜纹纸纹,形成温润的宣纸底色。
6.6 响应式
使用 clamp() 实现自适应,桌面和移动端共用一套样式:
--cell: clamp(58px, 16vw, 78px); /* 格子大小随屏宽变化 */
--gap: clamp(3px, 1vw, 4px); /* 间距自适应 */
6.7 胜利动画
胜利时弹出半透明遮罩 + 居中卡片,展示步数和最少步数对比,配合淡入动画:
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.8); }
to { opacity: 1; transform: scale(1); }
}
.win-card { animation: fadeIn 0.4s ease-out; }
七、部署与发布
7.1 本地运行
python3 -m http.server 8080
# 浏览器打开 http://localhost:8080
7.2 代码托管
代码已推送到 GitCode 仓库:https://gitcode.com/gcw_nfhkuMAb/klotski-game
7.3 发布到华为云开发者作品展览馆
通过华为云高校运营平台发布作品,完整流程包括:
- 域名解析:获取高校运营平台域名 ID
- STS 凭证生成:创建自委托生成临时安全凭证
- 启动服务 + DevBridge 隧道:本地 8082 端口 → 公网可访问地址
- 推送代码到 GitCode:OAuth 授权 → 创建仓库 → git init/commit/push
- 生成封面截图:Playwright Chromium 自动截图
- 生成作品详情:架构图 + 详解文章打包为 zip
- 选择投稿活动:选择「布道师通用产品体验」
- 发布:门禁复核 → 提交发布请求 → 获得 +200 积分
作品地址:https://gallery.developer.huaweicloud.com/gallery/005d9c238f400000
八、开发心得
为什么选华容道?
华容道是中国传统益智游戏的经典代表,文化底蕴深厚。相比数字合并类游戏(如 1024/2048),华容道更考验空间推理和规划能力——玩家需要在脑海中模拟多步移动,找到腾挪空间。以三国人物为棋子,配合古风视觉设计,让传统文化与编程技术自然融合。
棋子尺寸的统一描述
最初考虑为每种棋子类型写单独的移动和渲染逻辑,但很快发现这会导致大量重复代码。引入 w(宽)和 h(高)两个属性后,所有棋子——1×1 的卒、1×2 的武将、2×2 的曹操——都能用同一套 canPlace()、posOf()、sizeOf() 函数处理。这是"数据驱动设计"的一个小实践:用数据描述差异,用统一逻辑处理共性。
碰撞检测的简洁实现
华容道的碰撞检测本质上是矩形重叠判断。最初用逐格扫描(遍历目标位置的每个格子检查是否被占),后来改用分离轴定理的矩形重叠判断——只需 4 次比较,无需嵌套循环,代码更简洁,逻辑更清晰:
// 两个矩形不重叠 ⟺ 在某条轴上分离
return !(r1 + h1 <= r2 || r2 + h2 <= r1 ||
c1 + w1 <= c2 || c2 + w2 <= c1);
拖拽与键盘的互补
最初只实现了点击选中 + 方向键移动,但实际试玩发现频繁切换"鼠标点击 → 键盘按键"不够流畅。于是增加了拖拽模式——直接用鼠标拖动棋子滑向空格。两种方式互补:键盘适合精确的单步移动,拖拽适合连续滑动。拖拽时以"半格"为触发阈值,避免误触。
古风设计的克制
古风 UI 容易过度装饰——过多的花纹、阴影、动画反而影响游戏体验。本项目的原则是"氛围到位,不喧宾夺主":卷轴框架只出现在页面边缘,棋子用渐变色块而非贴图,背景纹理保持低对比度。核心交互区域(棋盘)保持简洁,让玩家专注于解题。
零依赖的优势
整个项目 817 行代码(HTML 103 + CSS 369 + JS 345),没有任何 node_modules,没有构建步骤,没有打包工具。git clone 后直接打开 index.html 就能玩。这种简洁性对教学场景特别有价值——学生可以完整理解每一行代码。
九、项目信息
| 项目 | 内容 |
|---|---|
| 作品名称 | 华容道·三国古战阵 |
| 技术栈 | 纯 HTML / CSS / JavaScript |
| 代码行数 | 817 行(HTML 103 + CSS 369 + JS 345) |
| 外部依赖 | 无(仅引用 Google Fonts CDN) |
| 关卡数量 | 3 关(横刀立马 / 指挥若定 / 将拥曹营) |
| GitCode 仓库 | https://gitcode.com/gcw_nfhkuMAb/klotski-game |
| 展览馆链接 | https://gallery.developer.huaweicloud.com/gallery/005d9c238f400000 |
| 投稿活动 | 布道师通用产品体验 |
本文记录了华容道小游戏从规则研究到发布展览馆的完整开发过程。华容道看似简单,实则蕴含深刻的空间推理智慧——正如三国古战场,方寸之间,运筹帷幄。
- 点赞
- 收藏
- 关注作者
评论(0)