从零手写经典井字棋(Tic-Tac-Toe):MiniAI实现 + 华为云临时沙箱部署与持续迭代指南
适合人群:前端初学者、算法入门者、想体验免服务器秒级发布上线的云原生开发者
最终成品:一个 13KB 的纯原生单文件index.html,支持人机对战(极小化极大算法)、同屏双人、获胜线高亮与战绩统计
部署交付:借助华为云开发者空间临时沙箱 + DevBridge 隧道实现零公网 IP 全网分发
写在前面:为什么选择井字棋?
井字棋(Tic-Tac-Toe / 三连棋)规则极其直观:在 3×3 九宫格中,双方轮流落子,率先将三子连成一线者获胜。
虽然规则简单,但它涵盖了现代独立小游戏的全部经典架构:
- 状态机与交互闭环:轮流落子、禁用重复点击、和棋满格判定;
- 博弈算法启蒙:九宫格总状态数仅 种(去除对称后仅数百种),非常适合用来教学经典的极小化极大算法(Minimax),打造绝不犯错的"无敌 AI";
- 极简交付范式:全套 HTML + CSS + JS 纯原生内联,完全不依赖任何 npm、Node.js、Vite 或构建工具,随时双击即玩。
本文将带你一步步手写实现,并在文末通过华为云 DevKit 临时沙箱免运维发布到公网,最后给出一套清晰的可持续迭代架构。
一、开发前准备
你只需准备:
- 一个现代浏览器(Chrome、Edge 等);
- 任意文本编辑器(VS Code、文本编辑器均可)。
新建 index.html,搭建基础单文件骨架:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>经典井字棋</title>
<style>/* CSS 内联 */</style>
</head>
<body>
<div class="container">
<!-- UI 元素 -->
</div>
<script>/* JS 游戏逻辑内联 */</script>
</body>
</html>
二、手把手分步实现
Step 1|数据模型与获胜线常量
井字棋棋盘一共 9 个格子,可以用一个长度为 9 的扁平数组 board 来存储状态(值为 'X'、'O' 或 null)。
// 8条预设获胜线(3条横线、3条竖线、2条对角线)
const WINNING_COMBOS = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], // 横向
[0, 3, 6], [1, 4, 7], [2, 5, 8], // 纵向
[0, 4, 8], [2, 4, 6] // 对角线
];
let board = Array(9).fill(null);
let currentPlayer = 'X'; // X 先手,O 后手
let isGameOver = false;
let gameMode = 'PVE'; // PVE (人机) | PVP (双人)
自测要点:在控制台中打印 WINNING_COMBOS.length,确认刚好是 8 种胜利排列。
Step 2|响应式九宫格与拟物发光样式
使用现代 CSS Grid 布局,仅需几行代码就能搞定自适应九宫格:
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
gap: 8px;
width: 100%;
aspect-ratio: 1 / 1;
}
.cell {
background: rgba(15, 23, 42, 0.85);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
font-weight: 900;
cursor: pointer;
transition: all 0.15s ease;
}
.cell.x {
color: #38bdf8;
text-shadow: 0 0 18px rgba(56, 189, 248, 0.6);
}
.cell.o {
color: #f43f5e;
text-shadow: 0 0 18px rgba(244, 63, 94, 0.6);
}
为了让游戏在触屏设备上体验更佳,我们在 head 中设置了 user-scalable=no 避免双击缩放。
Step 3|胜负判定逻辑
每次落子后,只需在 WINNING_COMBOS 中检索是否有任意一条线上的三格符号全部相同:
function checkWin(currentBoard) {
for (let combo of WINNING_COMBOS) {
const [a, b, c] = combo;
if (currentBoard[a] && currentBoard[a] === currentBoard[b] && currentBoard[a] === currentBoard[c]) {
return { winner: currentBoard[a], line: combo };
}
}
// 如果没有胜者,但所有格子均已落子,判定为平局
if (currentBoard.every(cell => cell !== null)) {
return { winner: 'tie', line: null };
}
return null;
}
优化技巧:返回包含获胜线的索引数组
line,获胜时直接给这 3 个格子赋予.win样式,加上呼吸灯动画,终局反馈极其醒目!
Step 4|实现不败的 AI:极小化极大算法 (Minimax)
如果 AI 只是随机落子,玩家很容易失去兴趣。利用 Minimax 算法,我们可以让 AI 洞察全局所有未来的可能分支:
- AI(O)希望得分最大化(+10 分);
- 玩家(X)希望得分最小化(-10 分);
- 越早获胜得分越高,因此引入搜索深度
depth衰减。
function minimax(currentBoard, depth, isMaximizing) {
const result = checkWin(currentBoard);
if (result) {
if (result.winner === 'O') return 10 - depth; // AI获胜
if (result.winner === 'X') return depth - 10; // 玩家获胜
if (result.winner === 'tie') return 0; // 平局
}
if (isMaximizing) {
let maxScore = -Infinity;
for (let i = 0; i < 9; i++) {
if (currentBoard[i] === null) {
currentBoard[i] = 'O';
let score = minimax(currentBoard, depth + 1, false);
currentBoard[i] = null; // 回溯撤销
maxScore = Math.max(score, maxScore);
}
}
return maxScore;
} else {
let minScore = Infinity;
for (let i = 0; i < 9; i++) {
if (currentBoard[i] === null) {
currentBoard[i] = 'X';
let score = minimax(currentBoard, depth + 1, true);
currentBoard[i] = null; // 回溯撤销
minScore = Math.min(score, minScore);
}
}
return minScore;
}
}
遍历所有空位,寻找调用 minimax 分数最高的一个位置作为 AI 落子点。由于井字棋搜索树深度最多为 9,浏览器在 2 毫秒内即可推演完毕,毫无卡顿!
Step 5|平滑对局与模式切换
为了避免 AI 落子太快像机器故障,我们在玩家落子后设置了 setTimeout(makeAiMove, 220) 拟真延迟;同时提供一键在「人机对战」和「双人同屏」间切换的模式开关,满足不同聚会娱乐场景。
三、调试技巧与常见避坑指南
| 现象 / 缺陷 | 核心原因 | 解决方案 |
|---|---|---|
| 点击已落子区域仍被重写 | 未对 board[index] !== null 做拦截 |
落子前严格阻断已占用索引 |
| 终局后点击依然可以落子 | 未绑定 isGameOver 状态锁 |
胜负产生后立即设置 isGameOver = true |
| AI 预测深度得分失真 | 评估返回值未加入 depth 偏移 |
增加 10 - depth 优先选择最快获胜分支 |
| 移动端双击页面放大变形 | 缺少移动端视口防护 | 设置 user-scalable=no, maximum-scale=1.0 |
| 连续对战比分丢失 | 未对比分对象做统一管理 | 声明全局 scores 对象,支持清空局数 |
四、基于华为云临时沙箱的免备案公网部署实录
单文件游戏写好了,怎么发给朋友或在社交平台上试玩?传统做法买云主机、配 Nginx、买域名、提备案,链路长达数天。
这里介绍利用华为云开发者空间(AI DevSpace)内置的云原生临时沙箱 + DevBridge 开发隧道,4 个指令跑完发布,零服务器购置成本、零公网 IP。
1. 为什么临时沙箱特别适合轻量作品分发?
+---------------------+ +-------------------------+ +---------------------+
| 本地开发工作区 | | 华为云 DevStation | | 全球移动/PC终端 |
| (单文件 index.html)| --------> | 云原生沙箱 | --------> | (扫码即可直接体验)|
| | 单文件秒传| Nginx 静态服务:8087 | 隧道穿透 | 即时访问 HTTPS 域名 |
+---------------------+ +-------------------------+ +---------------------+
|
v
DevBridge 安全中继网关
- 免公网 IP:宿主端主动与华为云 DevBridge 边缘节点握手,两端均为出站长连接,彻底摆脱防火墙和家庭宽带没有独立公网 IP 的限制;
- 免环境搭建:沙箱系统自带高可用 Nginx、Node.js 运行时及开发工具集;
- 随用随走:具有租期保活与超时回收机制,无需担心忘记关机造成资费扣除。
2. 标准部署四步法
第 1 步:连接沙箱环境
在支持华为云 DevKit 的工具链中建立安全连接并注入会话鉴权:
// huaweicloud_sandbox_connect
{ "source": "WEB" }
实战提示:
- 若遇到
HDKIT_RESOURCE_BUSY:活动高峰���服务器容器池排队,使用退避策略间隔 10~15 秒重试即可自动拉起;- 若遇到
HDKIT_SANDBOX_MAINTENANCE:代表平台底层正处于升级优化窗口,待维护窗口结束后即可顺畅重连。
第 2 步:单文件传输至工作空间
无需上传庞大的 node_modules 文件夹,仅需上传单个 index.html:
sandbox_upload_file(
local_path = ".../tic-tac-toe/index.html",
remote_path = "/workspace/tictactoe/index.html"
)
chmod -R o+rX /workspace/tictactoe
第 3 步:Nginx 独立端口配置
在 /etc/nginx/conf.d/tictactoe.conf 中为应用挂载独立的 8087 端口:
server {
listen 8087;
root /workspace/tictactoe;
index index.html;
location / {
try_files $uri $uri.html $uri/ =404;
autoindex off;
}
}
运行 sudo nginx -t 语法检测,并通过 sudo nginx -s reload 热生效,沙箱内部自测 curl -s http://localhost:8087 返回 200。
第 4 步:通过 DevBridge 隧道暴露公网
devbridge host -p 8087 -e 72
命令执行完毕即可输出专属于你的安全 HTTPS 域名(例如:https://<tunnel-id>-8087.devbridge-s2.hwtunnel.com)。
多项目聚合技巧:如果你在同一台沙箱中部署了多个小游戏(如 8084 俄罗斯方块、8085 消消乐、8087 井字棋),务必在同一条命令中传参:
devbridge host -p 8084 -p 8085 -p 8087 -e 72,一条隧道即可把所有端口全部带出公网,避免多进程相互覆盖。
最后使用终端命令执行探活验证:
curl -sI https://<tunnel-id>-8087.devbridge-s2.hwtunnel.com
# 验证收到 HTTP/1.1 200 OK 即可放心分享二维码给他人试玩!
五、可持续迭代架构与进阶方向
井字棋虽然短小精悍,但在工程和算法层面上拥有巨大的可持续扩展空间。如果你想将它演进为一个完整的商业化或高阶全栈作品,推荐以下四个阶段递进:
[阶段一: 算法难度分级] ──> [阶段二: 棋盘规模升级] ──> [阶段三: WebSocket联机] ──> [阶段四: 云端对战与留存]
(简单/中等/地狱) (4×4/五子棋/可变网格) (双人异地实时对决) (华为云GaussDB/积分)
1. 算法分级(概率抖动)
目前的 Minimax 属于完全理性的"无敌 AI",玩家只能打成平局或输掉,容易产生挫败感。
- 可迭代点:引入"难度选择"(简单 / 进阶 / 地狱)。在简单模式下,AI 有 40% 的概率随机走子;中等模式下采用只搜索 1~2 层的浅层贪心算法;地狱模式才开启完整 Minimax。
2. 棋盘规格跃迁(五子棋 / 拓展井字棋)
3×3 棋盘过小导致局面必然死板。
- 可迭代点:将数据结构升级为支持动态 网格(如 4×4 四子棋、8×8 翻转棋或 15×15 五子棋)。此时因为状态空间爆炸(无法全树搜索),你可以引入经典的 Alpha-Beta 剪枝(- Pruning)与启发式评估函数,大幅提升进阶算法思维。
3. 基于 WebSocket / 华为云 FunctionGraph 的跨端联机对决
目前双人模式仅支持同一屏幕轮流点击。
- 可迭代点:结合华为云函数工作流(FunctionGraph)或沙箱内起一个极其微型的 Node.js WebSocket 服务,支持玩家生成"房间号"邀请异地好友远程联机,实现真正的多人轻对战。
4. 战绩持久化与积分排行榜
- 可迭代点:本地使用
localStorage缓存个人连胜记录;远端可对接华为云 RDS 或 GaussDB 数据库,建立全服积分排行榜并加入个人中心,把单机 Demo 演变为社区互动作品。
六、总结
回顾整个开发与交付过程,我们体验到了现代极简 Web 原型开发的核心价值:
- 轻量纯粹:摆脱打包脚手架的厚重枷锁,纯单文件 HTML/CSS/JS 即可承载高质感交互与递归博弈算法;
- 云原生敏捷:华为云临时沙箱与 DevBridge 隧道打通了从"本地代码"到"公网访问"的最短链路,让每一位开发者都能随手将想法变成人人可用的在线服务。
- 点赞
- 收藏
- 关注作者
评论(0)