@yaohaixiao/tetris.js
v1.17.0
Published
tetris.js - 一款 JavaScript 开发的仿 FC 经典俄罗斯方块游戏
Maintainers
Readme
tetris.js
简体中文 | English

tetris.js 是一款零依赖的原生 JavaScript 俄罗斯方块游戏,基于 Canvas 实现,支持多端输入与 AI 控制。项目采用固定帧流程驱动架构,结合 Scheduler、Command Queue 与 Replay 系统,实现清晰的游戏更新管线,是一个轻量级前端游戏引擎设计与架构实践示例。
功能特性

游戏完整实现了经典俄罗斯方块的核心功能,包含方块生成、移动、旋转、下落、碰撞检测、消行、升级、分数统计等能力,同时搭配丰富的界面渲染、动画特效与交互反馈。
游戏操控
- 电脑键盘:方向键控制移动与旋转,空格键一键落底,P 键暂停,M 键切换背景音乐,R 键重新开始,Q 键退出游戏,S 键切换 AI 模式;
- 游戏手柄:全面适配,支持左摇杆与十字方向键操作;
- 移动端触控:复刻 GameBoy 风格虚拟按键,完整支持触屏操作;
等级与难度
- 等级选择:支持 1-10 级(键盘按 1-9 键 / T 键,手柄、触屏使用上下键调节);
- 难度选择:简单 / 普通 / 困难 / 专家(键盘按 E/N/H/X 键,手柄、触屏按 A/B/Y/X 键);
- 总计 256 个关卡:致敬经典 FC 游戏机设计,达到 256 关后关卡自动循环;
游戏规则
- 下落速度:1 级初始下落间隔为 1000 毫秒,在前 60% 关卡区间内平滑加速,最终达到 100 毫秒的极限速度
- 计分规则:消除得分 = 基础分值 × 当前关卡等级(消除1行得100分,消除4行得800分)
- 升级规则:采用动态升级条件,初始需消除10行即可升级,升级所需行数逐步递增,最高单级需消除60行
视听体验
- 16 首背景音乐:每16个关卡自动切换曲目,涵盖经典、电子、民谣、合成波等多种风格;
- 16 套消行音效:和弦与配器参数会随关卡变化,听觉体验层层递进;
- 8 套方块配色:每32个关卡切换一套配色,从经典亮色逐步过渡到霓虹、宝石等特色主题;
- 动画特效:包含倒计时、消行闪烁、分数浮动、落地高亮、升级庆祝、暂停计时等多种动态效果;
系统能力
- 操作回放:游戏结束后,可完整回看整局游戏的操作过程(视频:https://www.bilibili.com/video/BV1oRVA6uEXG/?vd_source=8d9b68dd3ed316bb9b3a13e3f3f778eb)
- AI 操控:具备多步预判能力,通过棋盘评估算法选择最优落子方案,并区分不同难度;(HARD 级别 AI 视频:https://www.bilibili.com/video/BV16CVd6tEEY/?vd_source=8d9b68dd3ed316bb9b3a13e3f3f778eb)
- 本地存储:自动持久化保存游戏最高分;
- 自适应布局:完美适配桌面端、平板、手机等各类设备屏幕;
技术亮点
- 原生 JavaScript:基于纯原生 JavaScript 开发,零第三方依赖;
- 模块化设计:基于 ES Module (ESM) 规范,各模块(音频、渲染、逻辑)高度解耦,易于维护和替换;
- 分层架构:采用了分层架构(Layered Architecture)与组件化设计;
- 状态集中管理:基于 GameStore 的集中式状态管理,通过纯函数更新数据,实现游戏逻辑与画面渲染完全分离。配合 Command Pattern,原生支持录像回放与 AI 训练;
- 独立调度器:使用调度器驱动所有动画与音效,不受浏览器帧率影响;
- 完善的测试体系:使用 Jest 编写单元测试,Cypress 实现端到端测试;
架构说明
本项目采用分层架构设计,结构清晰、模块化程度高、可维护性强。不仅适用于俄罗斯方块这类游戏开发,也可作为小型前端2D画布游戏的通用架构参考,稍加改造即可拓展至其他类型游戏。

架构优势
- 模块化划分清晰:各层级职责明确,模块间低耦合。基础工具、游戏规则、服务模块、运行核心各司其职,维护与扩展十分便捷。
- 集中式状态管理:所有核心游戏状态统一存放于
GameStore,通过纯函数stateHandler完成状态更新,避免数据散乱。该设计原生支持操作回放,也为时间旅行调试等高级功能预留扩展空间。 - 命令模式驱动主循环:玩家操作、AI 决策、方块自动下落均封装为标准命令对象,实现操作的记录、回放与管控,是回放系统和 AI 玩法的底层支撑。
- 事件总线实现模块解耦:基于发布订阅模式通信,消行、升级、游戏结束等事件统一广播,渲染、音频、动画模块独立响应,互不依赖。
- 统一任务调度系统:专属调度器
Scheduler统筹所有定时任务,保障方块下落、动画、音效、AI 运算的时序精准,不受帧率波动干扰,游戏逻辑可稳定复现。 - 多输入通道统一抽象:键盘、游戏手柄、触屏操作均映射为标准游戏指令,上层逻辑无需感知输入设备类型,降低新设备的接入成本。
- 确定性游戏逻辑:状态变更仅由命令与时间决定,无随机副作用和隐式依赖,相同输入必然产生一致结果,适配回放、问题复现与 AI 模拟场景。
- 插件化扩展设计:音频、动画、AI、回放等功能均为可插拔独立模块,不会侵入核心逻辑,新增功能与版本迭代更加灵活。
- AI 与核心逻辑隔离:AI 仅通过游戏状态快照推演最优策略,不会直接修改运行数据,架构健壮,便于迭代不同算法与难度模式。
- 运行层与表现层分离:核心运行层负责规则与状态管理,渲染层专注画面绘制。目前是 Canvas 渲染,也可扩展至 WebGL 渲染层,也能把核心逻辑移植到服务端、小程序等多端环境。
AI 操控
tetris.js 内置了具有多步预判能力的 AI 系统,通过棋盘评估算法选择最优落子方案,并区分不同难度。
AI 决策架构

核心能力
- 多步前瞻:AI 能预判未来 2-4 步(依难度而定),配合 7-bag 确定性方块序列,精确规划落子策略
- Beam Search 剪枝:在搜索树中智能剪枝,depth=4 也能在毫秒级完成决策
- 沙箱隔离推演:AI 在独立副本中模拟,不会污染真实游戏状态
- 涌现式 Well 井策略:AI 未显式编程井行为,却通过评估体系自然学会了留井等 I 块消 Tetris 的经典战术
评估体系
AI 综合以下指标评估每个候选落点:
- 高度控制:总高度背景压力 + 最高列危险区指数惩罚
- 空洞惩罚:强权重惩罚空洞,一个洞毁全局
- 表面平整:相邻列高度差惩罚,保持棋盘平整
- 消行奖励:表驱动分级奖励,Tetris(消4行)高分引导
- 计分感知:T-Spin、Back-to-Back、All Clear、Combo 等高级计分规则全部纳入评估
难度梯度
| 难度 | 预判深度 | 失误率 | 延迟 | 说明 | | ---- | -------- | ------ | ----- | ------------------ | | 简单 | 2 步 | 25% | 580ms | 偶尔犯错,反应较慢 | | 普通 | 3 步 | 15% | 480ms | 偶尔失误,中等速度 | | 困难 | 4 步 | 5% | 280ms | 很少犯错,较快响应 | | 专家 | 4 步 | 0% | 150ms | 从不犯错,极限反应 |
所有难度共享同一套价值观(评估权重),差异仅在于预判深度、随机失误率和响应速度。
二次开发指引
- 游戏基础配置:
lib/configuration.js; - 修改方块样式/配色:
- 配色配置:
lib/game/contants/color-paletters.js; - 方块样式:
lib/game/contants/shapes.js;
- 配色配置:
- 新增背景音乐/音效:在音频模块追加资源与关卡映射:
- 背景音乐:
- 添加背景音乐;
lib/services/audio/constants/bgm; - 注册背景音乐:
lib/services/audio/constants/musics.js;
- 添加背景音乐;
- 游戏音效:
lib/services/audio/sounds.js;
- 背景音乐:
- 游戏动画配置:
- 动画管理系统:
lib/runtime/animation-system.js; - 新增动画:
lib/services/animations,动画实现参考现有动画设代码注释; - 注册动画:
- 订阅动画消息:
lib/game/index.js中监听动画触发消息; - 执行动画:
this.Animations.register(new CountdownAnimation({ Scheduler, Game: this })); - 依赖注入:
{ Scheduler, Game: this }配置信息既是需要注入的依赖,根据需要注入依赖;
- 订阅动画消息:
- 动画管理系统:
- EventBus 事件管理:
- 消息注册:
lib/events/event-catalog.js; - 事件路由:
lib/events/router(模块订阅消息超过6条即可)添加路由模块;
- 消息注册:
- 自定义游戏规则(速度、计分、升级):修改规则计算函数:
- 速度配置:
lib/game/rules/get-speed.js; - 消除行数得分:
lib/game/constants/game.js; - 记分/升级:
lib/game/actions/apply-clear-lines.js;
- 速度配置:
- 扩展新输入设备:
- 新增:
lib/services/input层新增适配器即可(继承 Base 基类,实现依赖注入和消息订阅发布; - 注册:
lib/game/index.js在游戏核心模块注册(参考现有的 Keyboard, Gamepad 和 Touch);
- 新增:
- 输入/命令映射:
- 输入映射:
lib/engine/dispatch-input.js; - 命令映射:
lib/engine/dispatch-command.js; - 添加指令集:
lib/game/actions/difficulty-actions.js;
- 输入映射:
- AI 配置:
- 难易度配置:
lib/ai/core/ai-difficulty.js; - 决策规划配置:
lib/ai/planner/self-play.js;
- 难易度配置:
浏览器兼容
| Edge | Firefox | Chrome | Safari | Opera | | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | 128 – 131 | 130 – 132 | 109 – 131 | 17.5 – 18.1 | 113 – 114 |
备注:项目使用标准 ES6+、Canvas、Gamepad API,不兼容 IE 系列浏览器。
游戏按键说明
tetris.js 有多种按键控制方式:键盘按键、Gamepad 游戏手柄按键和针对移动设备的模拟 GAME BOY 按键;
键盘操作
- Enter:开始游戏
- ↑:转动方块
- ← / →:左右移动方块
- ↓:加速下落
- Space:直接落底
- M:开启/关闭背景音乐
- P:暂停/继续游戏
- R:重新开始游戏
- Q:强制结束游戏
- B:从难度选择页面返回等级选择页面
- S:切换 AI / 人工控制
- C: 缓存方块
等级选择
- 1–9:选择 1 至 9 级
- T:选择 10 级
难度选择
- E:简单(开局预设 0 行方块)
- N:普通(开局预设 3 行方块)
- H:困难(开局预设 6 行方块)
- X:专家(开局预设 9 行方块)
游戏手柄操作
- START:开始游戏
- BACK:
- 游戏中强制结束游戏
- 从难度选择页面返回等级选择页面
- RB:切换 AI / 人工控制
- RT:缓存方块
- 左摇杆 / 十字方向键:
- ↑:转动方块
- ← / →:左右移动方块
- ↓:加速下落
- X:重新开始游戏
- Y:暂停/继续游戏
- A:开启/关闭背景音乐
- B:直接落底
等级选择
- 十字方向键 ↑:提升等级
- 十字方向键 ↓:降低等级
难度选择
- A:简单
- B:普通
- Y:困难
- X:专家
移动端触控(GameBoy 布局)
- ↑:转动方块
- ↓:加速下落
- ←:向左移动
- →:向右移动
- BACK:强制结束游戏
- HOLD:缓存方块
- A:开启/关闭背景音乐
- B:加速下落
- X:暂停游戏
- Y:重新开始游戏
等级选择
- ↑ / ↓:调整等级(最低 1 级,最高 10 级)
- START:进入难度选择界面
难度选择
- A:简单
- B:普通
- Y:困难
- X:专家
- BACK:返回等级选择界面
游戏规则
下落速度
方块的下落间隔由 getSpeed()
函数计算。游戏从第 1 级(1000 毫秒/格)开始运行,采用公式:
step = ceil(1000 / floor(MAX_LEVEL × 0.6))
在最大等级
MAX_LEVEL(256 级)的前 60% 区间内,下落速度平滑线性递增直至极限。剩余 40% 的关卡将保持 100 毫秒/格的极限速度,让玩家专注于生存挑战。
计分规则
最终得分 = 单次消除基础分值 × 当前关卡等级
| 消除行数 | 基础分值 | | :----------------- | :------- | | 1 行 | 100 | | 2 行 | 300 | | 3 行 | 500 | | 4 行(俄罗斯方块) | 800 | | 5 行 | 1200 |
举例:在第 1 级消除 4 行,可得 800 × 1 = 800 分;在第 50 级消除 4 行,可得 800 × 50 = 40000 分。
升级规则
游戏通过 levelUpSteps
实现动态升级条件。首次升级仅需消除 10 行,之后每升一级所需消除行数增加 2 行(10
→ 12 → 14……),单级最高要求消除 60 行。
游戏总共设置 256 个关卡,达到最大关卡后等级数值将循环重置,致敬经典 FC 游戏设计。
方块配色规则
游戏内置 8 套风格各异的方块配色方案,每 32 个关卡自动切换一套,让高等级游戏过程也能保持丰富的视觉体验。
| 关卡区间 | 配色方案 | 风格说明 | | :--------- | :------- | :----------- | | 1-32 关 | 经典 | 默认鲜艳配色 | | 33-64 关 | 暖色 | 活力暖色系 | | 65-96 关 | 冷色 | 清爽冷色系 | | 97-128 关 | 糖果 | 甜美糖果色 | | 129-160 关 | 森林 | 自然森林色调 | | 161-192 关 | 日落 | 温暖落日色调 | | 193-224 关 | 霓虹 | 高亮霓虹色彩 | | 225-256 关 | 宝石 | 璀璨宝石色调 |
背景音乐规则
游戏内置 16 首不同风格的背景音乐,随着关卡提升自动切换,每 16 个关卡更换一首曲目。
| 关卡区间 | 曲目名称 | 风格 | | :--------- | :--------------- | :----------- | | 1-16 关 | TetrisTheme | 经典主旋律 | | 17-32 关 | SpringFestival | 喜庆节日风 | | 33-48 关 | FirstDivision | 经典民谣曲风 | | 49-64 关 | GongXiFaCai | 节日祝福曲风 | | 65-80 关 | Loginska | 电子律动曲风 | | 81-96 关 | BeyondTheWall | 悠远神秘曲风 | | 97-112 关 | Technotris | 科技电子曲风 | | 113-128 关 | GoldenSnakeDance | 东方韵味曲风 | | 129-144 关 | Korobeiniki | 经典民谣 | | 145-160 关 | Ascension | 空灵飞升曲风 | | 161-176 关 | NeonNights | 霓虹合成波 | | 177-192 关 | FrozenPeaks | 清冷孤高曲风 | | 193-208 关 | CyberRush | 赛博高速曲风 | | 209-224 关 | Starlight | 星河梦幻曲风 | | 225-240 关 | FinalPush | 终极挑战曲风 | | 241-256 关 | JourneyToWest | 史诗压轴曲风 |
对战模式
对战模式支持 HUMAN VS AI(人机对战)和 HUMAN VS HUMAN(双人对战)两种模式。
赛制规则
- 单局(Round):一次游戏结束(某玩家方块堆满),胜者 +1 分
- 整场(Match):先达到目标分数(默认 20 分,可在
Configuration.victoryScore配置)的玩家获胜 - 整场结束后展示对战结果覆盖层,按 Enter 键可重新开始
攻击系统
玩家消行时可向对手发送垃圾行,垃圾行从对方棋盘底部推入,增加对方游戏难度。
攻击力计算
| 消行数 | 攻击力(垃圾行数) | 说明 | | :----- | :----------------- | :----------------- | | 1 行 | 0 | 单消无攻击力 | | 2 行 | 1 | Double | | 3 行 | 2 | Triple | | 4 行 | 3 | Tetris(最高收益) | | 5+ 行 | 4 | 超级消除 |
垃圾行抵消机制
- 玩家消行产生的攻击力优先抵消自己即将受到的垃圾行(防御)
- 抵消后若有剩余攻击力,才会发送给对手(进攻)
- 鼓励玩家在受攻击时积极消行来自保
垃圾行空洞
垃圾行会随机生成空洞(空格),难度越高空洞越多:
| 难度 | 每行空洞数 | 说明 | | :----- | :--------- | :------- | | Easy | 1 | 容易填补 | | Normal | 2 | 需要规划 | | Hard | 3 | 较难处理 | | Expert | 4 | 极难填补 |
双人对战输入设备分配
HUMAN VS HUMAN 模式下,输入设备根据已连接的手柄数量动态分配:
| 手柄数量 | P1 (index=0) | P2 (index=1) | | :------- | :------------ | :---------------- | | 1 个 | 键盘 | 手柄-0 + 键盘禁用 | | 2+ 个 | 键盘 + 手柄-0 | 手柄-1 + 键盘禁用 |
- P1 在菜单界面负责选择等级和难度
- P2 在游戏进行中键盘自动禁用,仅手柄可用
对战界面
- 实时记分牌:双方胜场数实时显示
- 对战结果覆盖层:整场结束后展示胜者名称,按 Enter 重赛
PS:祝玩得开心,喜欢记得 STAR 支持!
贡献者
- ChatGTP: 提供游戏架构优化和升级的各种建议,并为项目创建精美的架构图;
- DeepSeek:提供项目中完善的测试代码和代码注释编写支持,并与之对称完成项目中重要的功能模块(对战模块、游戏手柄控制模块、AI 决策模块)技术细节的优化完善;
