从零开发一个网页版 1024 小游戏

举报
yd_292659020 发表于 2026/10/12 23:05:01 2026/10/12
【摘要】 从零开发一个网页版 1024 小游戏一篇完整的开发手记:从需求分析、架构设计、核心算法到 UI 打磨,最后发布到华为云开发者作品展览馆的全流程。 一、项目背景1024 是一款经典的数字合并益智游戏,玩法与 2048 一脉相承——只是目标更加亲民:合成 1024 即可获胜。本项目面向学生群体,希望提供一个课间即可打开、无需安装、跨平台运行的轻量级网页游戏。技术选型:纯 HTML + CSS ...

从零开发一个网页版 1024 小游戏

一篇完整的开发手记:从需求分析、架构设计、核心算法到 UI 打磨,最后发布到华为云开发者作品展览馆的全流程。

一、项目背景

1024 是一款经典的数字合并益智游戏,玩法与 2048 一脉相承——只是目标更加亲民:合成 1024 即可获胜。本项目面向学生群体,希望提供一个课间即可打开、无需安装、跨平台运行的轻量级网页游戏。

技术选型:纯 HTML + CSS + JavaScript,零框架、零依赖。一个 python3 -m http.server 就能跑起来。

最终效果预览:

![1024 小游戏封面]


二、游戏规则

  • 棋盘:4×4 网格,共 16 个格子
  • 初始状态:随机生成 2 个方块(值为 2 或 4)
  • 操作方式:键盘方向键(↑↓←→)或触摸滑动
  • 合并规则:相同数字的方块碰撞时合并,值翻倍
  • 生成规则:每次有效移动后,随机空位生成一个新方块(90% 概率为 2,10% 概率为 4)
  • 胜利条件:合成 1024
  • 失败条件:所有格子被填满且无法再合并

三、架构设计

项目采用经典的三层分离结构,没有使用任何前端框架:

game1024/
├── index.html    # 页面结构层(55 行)
├── style.css     # 视觉表现层(273 行)
├── game.js       # 游戏逻辑层(285 行)
└── README.md     # 项目说明

设计分层

┌─────────────────────────────────────────┐
│         表现层 (UI Layer)                │
│   index.html + style.css                │
│   · 页面结构 · 渐变样式 · 动画效果       │
├─────────────────────────────────────────┤
│         逻辑层 (Game Logic)              │
│   game.js                               │
│   · 网格管理 · 合并算法 · 输入处理       │
├─────────────────────────────────────────┤
│         数据层 (Data Layer)              │
│   localStorage                          │
│   · 最高分持久化 · 游戏状态              │
└─────────────────────────────────────────┘

为什么不用框架?因为这是一个 4×4 网格的数字游戏,交互逻辑简单,DOM 操作量可控。引入 React/Vue 反而增加了包体积和构建复杂度。纯 JS 直接操作 DOM,代码量 285 行,清晰易读,对学生来说也是很好的学习材料。


四、核心实现

4.1 数据结构:方块对象

每个方块不是一个简单的数字,而是一个带有唯一 ID 的对象:

const t = {
  id: nextId++,      // 唯一标识,用于 DOM 追踪
  value: 2,           // 当前数值
  row: 0,             // 行坐标
  col: 1,             // 列坐标
  isNew: true,        // 是否新生成(触发淡入动画)
  merged: false,      // 是否刚合并(触发缩放动画)
  pendingValue: null  // 合并后的待更新值
};

为什么需要 ID? 因为合并动画需要两个方块先滑到同一位置,再"吃掉"其中一个。如果没有 ID,渲染时无法区分"哪个方块要消失"和"哪个方块要留下"。ID 让每个方块在整个生命周期中可追踪。

4.2 网格与棋盘

棋盘用一个 4×4 的二维数组表示,每个位置存放方块对象或 null:

function emptyGrid() {
  return Array.from({ length: SIZE }, () => Array(SIZE).fill(null));
}

// grid 示例:
// [ [tile, null, tile, null],
//   [null, tile, null, null],
//   [tile, null, null, tile],
//   [null, null, tile, null] ]

4.3 随机生成方块

每次有效移动后,在随机空位生成一个新方块:

function addRandom() {
  const empty = [];
  for (let r = 0; r < SIZE; r++)
    for (let c = 0; c < SIZE; c++)
      if (!grid[r][c]) empty.push([r, c]);
  if (!empty.length) return null;

  const [r, c] = empty[Math.floor(Math.random() * empty.length)];
  const value = Math.random() < 0.9 ? 2 : 4;  // 90% 生成 2,10% 生成 4
  const t = { id: nextId++, value, row: r, col: c, isNew: true };
  grid[r][c] = t;
  return t;
}

4.4 核心算法:移动与合并

这是整个游戏最关键的部分。思路是:将 4×4 网格按方向拆成 4 条线,每条线独立做"压缩+合并"。

第一步:按方向提取线条

function getLines(direction) {
  const lines = [];
  for (let i = 0; i < SIZE; i++) {
    const line = [];
    for (let j = 0; j < SIZE; j++) {
      let r, c;
      switch (direction) {
        case 'left':  r = i; c = j; break;
        case 'right': r = i; c = SIZE - 1 - j; break;
        case 'up':    r = j; c = i; break;
        case 'down':  r = SIZE - 1 - j; c = i; break;
      }
      line.push([r, c]);
    }
    lines.push(line);
  }
  return lines;
}

无论哪个方向,都统一转换成"从左到右"的处理:向左移动直接取行,向右移动反向取行,向上/向下则按列处理。

第二步:压缩 + 合并

对每条线,先取出非空方块,然后从左到右扫描:如果相邻两个值相同,就合并——左边的方块值翻倍,右边的方块标记为"待移除":

for (let k = 0; k < tiles.length; k++) {
  if (k + 1 < tiles.length && tiles[k].value === tiles[k + 1].value) {
    // 合并:tiles[k] 留下并翻倍,tiles[k+1] 移向 tiles[k] 的位置后消失
    stay.pendingValue = stay.value * 2;
    scoreGain += stay.pendingValue;
    go.targetId = stay.id;     // 记录要移向哪个方块
    removed.push(go);          // 加入待移除列表
    result.push(stay);
    k++;                       // 跳过下一个(已被合并)
  } else {
    result.push(tiles[k]);
  }
}

第三步:动画时序

合并不是瞬间完成的——需要先让两个方块滑到一起(CSS transition 0.16s),然后才更新数值并移除多余方块:

// 1. 先渲染滑动(被合并的方块滑向目标位置)
renderTiles = removed.concat(allGridTiles());
render();

// 2. 等待动画完成后,更新数值、移除方块、生成新方块
setTimeout(() => {
  // 移除被合并的方块 DOM
  for (const go of removed) {
    const el = document.getElementById('tile-' + go.id);
    if (el) el.remove();
  }
  // 更新合并后方块的数值
  forEachTile((t) => {
    if (t.pendingValue) {
      t.value = t.pendingValue;
      t.merged = true;
      delete t.pendingValue;
    }
  });
  // 生成新方块
  addRandom();
  render();
}, ANIM_MS);  // ANIM_MS = 170ms

4.5 游戏结束判定

function canMove() {
  for (let r = 0; r < SIZE; r++)
    for (let c = 0; c < SIZE; c++) {
      if (!grid[r][c]) return true;                    // 有空位 → 可移动
      const v = grid[r][c].value;
      if (c + 1 < SIZE && grid[r][c + 1]?.value === v) return true;  // 右边相同
      if (r + 1 < SIZE && grid[r + 1][c]?.value === v) return true;  // 下面相同
    }
  return false;  // 没空位且没有可合并的相邻方块 → 游戏结束
}

只检查右方和下方相邻方块即可——如果右和下都不相同,左和上自然也不会相同(对称性)。

4.6 输入处理

同时支持键盘和触摸:

// 键盘:方向键 + WASD
const keyMap = {
  ArrowLeft: 'left', ArrowRight: 'right', ArrowUp: 'up', ArrowDown: 'down',
  a: 'left', d: 'right', w: 'up', s: 'down',
};
window.addEventListener('keydown', (e) => {
  const dir = keyMap[e.key];
  if (dir) { e.preventDefault(); move(dir); }
});

// 触摸:记录起点,在 touchend 时判断方向
gameEl.addEventListener('touchend', (e) => {
  const dx = e.changedTouches[0].clientX - touchStart.x;
  const dy = e.changedTouches[0].clientY - touchStart.y;
  if (Math.max(Math.abs(dx), Math.abs(dy)) < 24) return;  // 防误触
  if (Math.abs(dx) > Math.abs(dy)) move(dx > 0 ? 'right' : 'left');
  else move(dy > 0 ? 'down' : 'up');
}, { passive: true });

五、UI 设计

5.1 配色方案

采用温暖的渐变色系,从浅黄到粉橙的背景渐变,方块颜色从奶白到深红再到金色,形成清晰的视觉层次:

body {
  background: linear-gradient(135deg, #f6d365 0%, #fda085 45%, #f8a5c2 100%);
}

每个数字方块有独立的颜色,数值越大颜色越深越鲜艳:

数值 背景色 说明
2 #fff7ed 奶白色
4 #fde68a 浅黄
8 #fbbf24 琥珀
16 #f59e0b 橙黄
32 #f97316 橙色
64 #ea580c 深橙
128 #ef4444 红色
256 #e11d48 玫红
512 #be185d 品红
1024 linear-gradient(135deg, #f59e0b, #d4af37) 金色渐变 + 发光

1024 方块特别使用了金色渐变 + 发光阴影,让达成目标时有强烈的视觉成就感。

5.2 毛玻璃效果

分数栏和游戏网格都使用了 backdrop-filter: blur() 实现毛玻璃效果,配合半透明背景,在渐变背景上显得通透精致:

.grid {
  background: rgba(255, 247, 237, 0.6);
  backdrop-filter: blur(8px);
  border-radius: 14px;
  box-shadow: 0 10px 30px rgba(180, 83, 9, 0.18);
}

5.3 动画

两个核心动画:

新方块淡入——从 0.2 倍缩放到 1.1 倍再回到 1 倍,模拟"弹出"效果:

@keyframes appear {
  0%   { opacity: 0; transform: scale(0.2); }
  60%  { opacity: 1; transform: scale(1.1); }
  100% { opacity: 1; transform: scale(1); }
}

合并缩放——合并瞬间放大到 1.22 倍再回弹,给玩家正反馈:

@keyframes pop {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.22); }
  100% { transform: scale(1); }
}

5.4 响应式

使用 clamp() 和 vw 单位实现自适应:

--gap: clamp(8px, 2.4vw, 14px);          /* 间距随屏宽变化 */
.title { font-size: clamp(40px, 11vw, 56px); }  /* 标题大小自适应 */
.tile { font-size: clamp(20px, 6vw, 34px); }    /* 方块文字自适应 */
.grid { width: min(92vw, 460px); }               /* 网格最大 460px */

桌面端和移动端无需写两套样式,一个 clamp 搞定。


六、数据持久化

最高分使用 localStorage 存储,简单可靠:

// 读取
bestScore = parseInt(localStorage.getItem('best1024') || '0', 10) || 0;

// 更新
if (score > bestScore) {
  bestScore = score;
  localStorage.setItem('best1024', String(bestScore));
}

七、部署与发布

7.1 本地运行

python3 -m http.server 8080
# 浏览器打开 http://localhost:8080

7.2 代码托管

代码已推送到 GitCode 仓库:https://gitcode.com/gcw_nfhkuMAb/game1024

7.3 发布到华为云开发者作品展览馆

通过华为云高校运营平台发布作品,流程包括:

  1. 获取 IAM 凭证:创建自委托生成 STS 临时凭证
  2. 打开 DevBridge 隧道:将本地 8080 端口暴露为公网可访问地址
  3. 推送代码到 GitCode:OAuth 授权 → 创建仓库 → push
  4. 生成封面截图:Playwright 截图 + 封面合成
  5. 生成作品详情:架构图 + 详解文章打包为 zip
  6. 选择投稿活动:选择「布道师通用产品体验」
  7. 发布:门禁复核 → 提交发布请求 → 获得 +200 积分

作品地址:https://gallery.developer.huaweicloud.com/gallery/005d7ea41f800000


八、开发心得

为什么选 1024 而不是 2048?

1024 的目标更低,新手更容易达成,作为学生课间休闲游戏更合适。2048 对很多人来说太难了——玩了很多次都到不了 1024,挫败感太强。1024 让人能在几分钟内体验到胜利的成就感。

方块 ID 的必要性

最初尝试用坐标作为方块标识,但合并动画期间两个方块会短暂出现在同一位置,坐标无法区分它们。引入唯一 ID 后,每个方块在整个生命周期(生成 → 移动 → 合并 → 消失)中都可追踪,DOM 渲染逻辑变得清晰。

动画时序的控制

合并动画的关键是时序:先滑动(0.16s CSS transition),等滑动结束后再更新数值和移除方块。用 setTimeout(ANIM_MS) 等待,ANIM_MS = 170 略大于 transition 的 160ms,留 10ms 余量确保动画完成。期间设置 animating = true 锁定输入,防止玩家快速连按导致状态混乱。

零依赖的优势

整个项目 613 行代码(HTML 55 + CSS 273 + JS 285),没有任何 node_modules,没有构建步骤,没有打包工具。git clone 后直接打开 index.html 就能玩。这种简洁性对教学场景特别有价值——学生可以完整理解每一行代码。


九、项目信息

项目 内容
作品名称 1024 小游戏
技术栈 纯 HTML / CSS / JavaScript
代码行数 613 行(HTML 55 + CSS 273 + JS 285)
外部依赖 无
GitCode 仓库 https://gitcode.com/gcw_nfhkuMAb/game1024
展览馆链接 https://gallery.developer.huaweicloud.com/gallery/005d7ea41f800000
投稿活动 布道师通用产品体验

本文记录了 1024 小游戏从需求到发布的完整开发过程。如果你也是学生,想快速做一个能运行、能分享的小项目,纯前端 + 零依赖是最省心的选择。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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