纯原生 HTML5 / CSS / JavaScript 实现的 MS-DOS 风格命令行环境:80×25 文本模式、VGA Mode 13h
图形模式、批处理解释器、带持久化的虚拟磁盘,以及致敬 NES《超级马里奥兄弟》1-1 关卡的原创小游戏
MARIO.COM。无第三方库、无构建步骤。
免责声明:
MARIO.COM是非商业的致敬性原创实现。所有像素图与音符均为本项目原创绘制/作曲, 未使用任何任天堂 ROM 或受版权保护的素材文件,仅在风格与玩法上致敬原作;本项目与任天堂(Nintendo) 无任何关联,也未获其认可。MS-DOS 是 Microsoft 的商标,本项目同样与 Microsoft 无关联。
| 方式 | 说明 |
|---|---|
| GitHub Pages | 在仓库 Settings → Pages 选择分支根目录发布,访问 https://<user>.github.io/HTML-dos-simulator/(index.html 会跳转到 dos.html)。 |
| 本地直接打开 | 双击 dos.html(file:// 下可用,不需要服务器)。 |
| 本地服务器 | npx http-server . 或 python3 -m http.server,再打开 /dos.html。 |
启动后会显示模拟 BIOS 自检(按 Esc 跳过),随后执行 AUTOEXEC.BAT 并进入 C:\> 提示符。
输入 MARIO 回车即可开始游戏(MARIO /M 关闭背景音乐,MARIO /A 开局即开启自动通关,MARIO /? 查看按键)。
游戏中按 P 随时开关自动通关。
支持最新版 Chrome、Firefox、Safari、Edge,以及手机/平板浏览器(见下方“手机与平板”)。音频在首次按键/点按后启用(浏览器自动播放限制)。
触屏设备会自动启用触屏界面(顶栏的 Touch 开关可强制开/关):
- 命令行:点按屏幕弹出软键盘(支持输入法逐字/联想输入);屏幕下方的按键条提供 Esc、Tab、Ctrl+C、 方向键(按住连发)、退格、F3、Enter,以及 MARIO / DIR / MENU / HELP 快捷命令;在屏幕上上下滑动回看历史输出; 开机自检时点按屏幕即可跳过。
- 游戏手柄:运行 MARIO、PLASMA、BALL 等图形程序时自动出现:十字键、A(跳)、B(跑)、START、AUTO(自动通关)、EXIT。 支持多点触控(按住 ▶ 的同时按 A),手指在按键间滑动会自动切换按键,有震动反馈(设备支持时)。
- 布局:竖屏时画面在上、手柄在下;横屏时隐藏顶栏和状态栏,画面居中、手柄悬浮在两侧留白处。 小屏上图形画面允许非整数倍缩放以充分利用屏幕;文本模式 80 列在竖屏上偏小,建议横屏。
- 兼容性:脚本只使用 ES2018 语法(已用解析器检查),CSS 新特性(
min()、env()等)都有回退值, 照顾较老的手机内核和 App 内置浏览器;脚本带版本号防止新页面混用旧缓存。若浏览器仍无法启动, 页面顶部会显示红色提示和具体原因(微信/QQ 内可点“在浏览器中打开”)。 - 已在 Playwright 的 iPhone 13 / iPhone SE / Pixel 7 模拟中验证:无横向溢出,控件不遮挡画面, 触摸按住、多点触控、输入法输入、滑动回看均可用(未在真机上测试)。
文本模式:80×25、16 色 CGA/EGA 调色板、9×16 字符单元的位图字体(8×8 字形 + 程序生成的 CP437
制表/方块字形);闪烁块状光标(Insert 切换插入/覆盖,覆盖模式为下划线光标);1000 行滚动缓冲;
可选 CRT 效果(扫描线、光晕、暗角);4 种主题(Classic VGA 灰 #AAAAAA/黑、Green、Amber、Blue)。
页面 HUD:顶栏有主题色板、CRT 开关、帮助与出厂重置;显示器下方有铭牌、电源/硬盘/软驱/AUTO 指示灯 (硬盘和软驱灯随真实的虚拟磁盘读写闪烁)、“消磁”(切换 CRT)与“电源”(重启,文件保留)按钮; 最下方的状态栏显示视频模式、当前目录、INS/OVR 与 CAPS、DOS 时钟、C: 剩余空间。 运行 MARIO 时会出现游戏面板:分数、金币、生命、时间、自动通关开关与规划器实时统计。
行编辑:←/→(Ctrl 按词)、Home/End、Backspace/Delete、Insert、↑/↓ 历史(100 条)、F1/F3(逐字/整行 复制上一条命令)、Tab/Shift+Tab 补全命令与文件名并循环候选、Esc 清行、Ctrl+C 中止、F6/Ctrl+Z 输入 ^Z、 Ctrl+L 清屏、Shift+PgUp/PgDn 或鼠标滚轮回看、F12 快捷键帮助面板。命令行上限 127 字符(与 DOS 一致)。
虚拟文件系统:A:(1.44MB 空软盘)与 C:(512MB),8.3 文件名、大小写不敏感、统一大写显示;
文本按字符串、二进制按 Base64 存储;每次修改同步写入 localStorage;FORMAT 清空磁盘;
工具栏 Factory reset 恢复出厂内容。初始 C::COMMAND.COM、IO.SYS/MSDOS.SYS(隐藏)、
CONFIG.SYS、AUTOEXEC.BAT、README.TXT、GAMES\(PLASMA.COM、BALL.COM)、
UTILS\(示例批处理 DEMO、HELLO、ARGS、COUNT、FOREVER、MENU、GFX)、MARIO\(MARIO.COM、MARIO.TXT)、TEMP\。
内置命令(46 个,表驱动注册)
| 类别 | 命令 |
|---|---|
| 文件 |
DIR(/W /P /A[:属性] /O /S /B,*/?)、CD/CHDIR、MD/MKDIR、RD/RMDIR(/S /Q)、TYPE、COPY(通配符、/Y /-Y、a+b 合并、COPY CON)、DEL/ERASE(/P)、REN/RENAME(通配符模板)、MOVE、ATTRIB(±R ±H ±S ±A /S)、TREE(/F /A)、FC(/C /N) |
| 系统 |
ECHO、CLS、VER、VOL、DATE、TIME(真实时钟,可设置偏移)、SET、PATH、PROMPT($P $G $N $_ $T $D $V $L $B $Q HELP、PAUSE、REM、CALL、EXIT、MEM(/C)、CHKDSK(/V)、FORMAT(/S /Q /V)、LABEL、DOSKEY /HISTORY
|
| 批处理 |
IF(EXIST / ERRORLEVEL / 字符串比较 / NOT / /I)、GOTO、FOR %%A IN (...) DO(含通配符)、SHIFT、CHOICE(/C /N /S /T) |
| 过滤器 |
MORE、SORT(/R /+n)、FIND(/V /C /N /I),支持管道 ` |
| 图形/娱乐/界面 |
MODE 13 / MODE 3(MODE 80、CO80)、BEEP [Hz] [ms]、MARIO、COLOR、THEME、CRT
|
每个命令都支持 /?(或 HELP 命令名)。未知命令显示 Bad command or file name。
外部命令查找顺序:内部命令 → 当前目录 → PATH 中各目录,依次尝试 .COM、.EXE、.BAT。
.COM/.EXE 是带签名 HTMLDOS:<ID> 的二进制存根,执行时按签名分派到 JS 实现,因此复制到 A: 也能运行。
批处理:@ECHO OFF/ECHO ON|OFF、REM、::、%0–%9、%*、%%、%VAR%、IF、GOTO/:label、
FOR、CALL(嵌套返回)、直接调用另一个 .BAT 时链式跳转(不返回,与 DOS 一致)、SHIFT、CHOICE、
Ctrl+C 时提示 Terminate batch job (Y/N)?。
图形模式:MODE 13 进入 320×200×256(VGA 默认调色板结构),此时命令行以 40×25、8×8 字体在
图形缓冲中显示;MODE 3 返回文本模式。画面按整数倍缩放并居中(保持像素比例)。
JS API(machine.gfx):pset/pget、line(Bresenham)、rect、fillRect、circle、fillCircle、
text(8×8 位图字体)、blit(颜色 0 透明,支持翻转与颜色重映射)、setPalette/getPalette/resetPalette、
swap(双缓冲提交)、waitVBlank(≈60Hz);按键状态查询 machine.input.isDown(code)。
程序返回或按 Esc 时自动回到 80×25 文本模式并恢复原屏幕内容。示例程序:PLASMA.COM(调色板循环等离子)、
BALL.COM(弹球;方向键改变重力、空格加球、C 清空,屏幕下方实时显示方向键按下状态)。
| 按键 | 作用 |
|---|---|
| ← / →(或 A / D) | 移动 |
| ↓(或 S) | 下蹲(大体)/ 进入水管 |
| Z 或 Space | 跳跃(A 键,按住跳得更高) |
| X 或 Shift | 奔跑(B 键) |
| Enter | 开始 / 暂停(显示 PAUSE) |
| P | 自动通关开 / 关(画面右上显示 AUTO 徽标,页面 HUD 上也有开关) |
| Esc | 退出回到 DOS |
- 分辨率取舍:使用 NES 原生 256×240 的 256 色索引缓冲(
MODE 13风格的调色板与绘图 API),由界面层整数倍缩放、 上下/左右加黑边居中(letterbox)。缩放进 320×200 会破坏 16px 图块的像素网格,裁剪 40 行又会丢失 HUD 或地面, 因此选择保持原生网格(说明同样写在js/mario.js顶部注释中)。 - 关卡 1-1:长度 212 图块;坑洞位于 69–70、86–88、153–154;问号方块/砖块(含蘑菇、金币、10 连金币砖、星星砖)、
位于 64 的隐藏 1UP 方块、6 根水管(57 号可进入地下奖励房,163 号为出口)、四组阶梯、Goomba ×16、Koopa ×1、
旗杆(198)与城堡。数据在
js/mario-data.js中以数组描述,便于核对。 - 物理:固定 60Hz 逻辑步长(与渲染分离,渲染帧率不影响手感);走/跑两档加速度与最高速度、滑行转向减速、 三档起跳速度与“按住 A / 松开 A”两种重力(静止短跳约 20px、长跳约 66px,奔跑长跳约 83px)、空中惯性、 先水平后垂直的 AABB 图块碰撞。数值由 NES 原版的子像素常数换算而来。
- 玩法:顶问号块出金币/蘑菇;蘑菇变大,大体受伤变小并短暂无敌;小体顶砖只会弹动、大体可顶碎;
踩 Goomba 弹跳并按连踩递增计分;踩 Koopa 变壳,再碰/踩可踢壳,移动的壳会撞死其他敌人;侧撞死亡(跳起后落下的动画)、
掉坑死亡、TIME 归零死亡;死亡后重开本关并减命,生命为 0 显示
GAME OVER;100 金币加命; 旗杆按高度计分、沿杆滑下、走进城堡、剩余时间换分,显示COURSE CLEAR!并提示 Enter 再玩 / Esc 返回 DOS; 最高分保存在localStorage(htmldos.mario.hiscore)。 - 音频:Web Audio 方波/三角波合成:跳跃、踩踏、金币、蘑菇出现、变大、1UP、顶砖、碎砖、踢壳、受伤、死亡、 旗杆、过关、暂停、时间警告;原创的 8 小节背景音乐(主旋律 + 和声 + 三角波低音),时间少于 100 时加速。
按 P(或页面 HUD 上的 Autopilot 开关、或 MARIO /A)后由 js/mario-ai.js 中的规划器代打,
玩家仍可用 Enter 暂停、Esc 退出。它不只是“冲向旗杆”:AI 分两层,会顶问号块、吃蘑菇和隐藏 1UP、
踩敌人踢龟壳、钻隐藏水管进地下奖励房,并且可以切换“性格”。
策略层——程序化人格(Procedural Personas)。参考 Holmgård、Liapis、Togelius、Yannakakis
的 Procedural Personas(MiniDungeons / Mario 上的“效用函数即人格”):每种人格只是对各种收益的权重不同。
需要新子目标时,列出画面内可得的机会(问号块、隐藏方块、有内容的砖块、道具、可踩的敌人、静止的龟壳、
地下的金币、隐藏水管),按 人格权重 × 价值 − 路程 打分取最高(带滞后防抖),都不值得时就去旗杆;
够不着的目标会被暂时拉黑。按 C 或 HUD 上的按钮切换,MARIO /A:HUNTER 直接指定(前几个字母即可),选择会记住。
所有风格都会去吃顶出来的蘑菇变成大马里奥,并在关底瞄准旗杆最顶端拿 5000 分:
| 风格 | 偏好 | 1-1 实测(确定性测试设置) |
|---|---|---|
| ALL-ROUND 全能 | 方块、蘑菇、隐藏 1UP,顺路踩敌人;不赶时不跑 | 顶约 10 个方块、拿到隐藏 1UP |
| SPEEDRUN 速通 | 直奔旗杆,只吃路上的蘑菇 | 不走水管的风格里最快(约 2130 帧) |
| COLLECTOR 收集者 | 金币第一:问号块、金币砖、地下奖励房 | 进奖励房,约 22 枚金币 |
| HUNTER 猎手 | 踩栗子、踢龟壳、追星星 | 消灭敌人最多之一 |
| EXPLORER 探索者 | 隐藏水管、隐藏方块 | 进奖励房 |
| CAUTIOUS 稳健 | 不追敌人和秘密,走路为主,受伤惩罚 ×4 | 较慢、最稳 |
- 拦截移动的蘑菇:蘑菇会沿砖块走、掉下、撞水管折返,但这些都是确定的。规划器先只推进道具,算出它 未来 240 帧的轨迹,启发式估计“最早在哪一帧能截住它”,而不是追着它当前的位置跑。
- 旗杆顶端 5000 分:抓旗时脚高于 y=64 才是 5000 分,只有从最高一级台阶起跳才够得着。进入关底范围后, 目标换成“旗杆顶端”,A* 先把马里奥带上高台(启发式按落脚高度减去起跳高度计算),站上高台后枚举 “后退 k 帧 → 助跑 n 帧 → 按住跳跃”的宏动作,用前向模型逐个验证,取第一个能拿 5000 分的跳法; 都不行再用 8 倍预算的 A* 搜几次,最后才退回普通抓旗。
执行层——A* 前向模型规划。参考 2009 年 Mario AI Competition 冠军 Robin Baumgarten 的 A* 智能体 (源码镜像):
- 前向模型:
MarioGame.clone()复制游戏状态(区域图块写时复制),用游戏自己的物理代码推进; 游戏是确定性的,所以预测与实际完全一致。 - A* 搜索:动作集为 {左/右/不动} × {跳} × {跑}(去水管时加“下”),每个节点重复同一动作 2 帧; 优先级 = 已用帧数 × 0.9 + 到子目标的估计帧数(从当前速度加速到最高速、需要爬升的高度)+ 惩罚; 离目标还远时高度按“落地后”计算,避免半路乱跳造成启发式平台。死亡分支直接剪掉,受伤按人格加重惩罚; 相同状态只展开一次;节点惰性模拟。
- 活路检查:达成子目标的节点(比如踩完一只栗子)还要通过生存检查——几种简单后续操作里至少有一种 能安全撑过 16 帧,免得落进另一群敌人中间;没到达目标时只采纳“之后被证实还能活”的部分计划,且只取前 32 帧(滚动时域),避免带着惯性冲过头。搜不到时先从真实状态重规划,再用 3 倍预算加深一次,最后才放弃该目标。
- 计划延续:剩余计划不足 12 帧时从计划末尾的预测状态继续搜索并拼接。每次搜索限 300 个节点 / 6 ms。
- 像人一样玩:Mario AI Championship 2012 图灵测试赛道的结论是,机器人露馅在“永远全速奔跑、 总在坑边最后一个像素起跳”。因此拟人人格给按住 B 加了少量代价(不赶时间就走路,启发式同比放大以免搜索膨胀), 贴着坑边起跳会被惩罚,完成子目标后偶尔停顿片刻(先用前向模型确认停顿是安全的)。
- 路线叠加层:按 V 显示 AI 正在执行的计划路线(品红色点)和当前子目标(“行军蚁”虚线框 + 名称), 画面右上角的徽标显示当前风格;HUD 上显示当前目标、搜索节点数/耗时和顶块/踩敌/秘密计数。
- 在测试中,六种风格都能从起点无伤通关 1-1,并且各自表现出不同的行为(见
tests/tests.js)。
- 美术为原创像素画,只在风格上接近:马里奥采用红衣/蓝背带裤/白手套配色,帧数与细节与原版不同; 地面、云、山丘、城堡等图块均为自绘。
- 背景音乐与所有乐句(死亡、过关、Game Over、1UP 等)为原创作曲,不是原版旋律的转写——
这与“所有音符数据均为原创”的约束一致;
MarioData.MUSIC是普通的音符表,可自行替换。 - 未实现火焰花(大体顶出蘑菇块仍给蘑菇,仅加分)、中途检查点、烟花、城堡升旗、角色重叠时的精灵闪烁限制、 走出画面左侧后敌人回收等细节;星星已实现(无敌并撞飞敌人),但没有专用音乐。
- 视差(远景云 0.5×、山丘 0.7×、灌木 0.85×)是按要求新增的效果,原版背景与前景同速滚动。
- 奖励房与出口水管为简化布局;砖块碎片/顶起动画为近似实现。
所有模块挂在全局 DOS 命名空间下,可变状态集中在 DOS.Machine 实例中(浏览器控制台可用 machine 访问)。
| 模块 | 文件 | 职责 |
|---|---|---|
| Terminal | js/terminal.js |
字符网格、光标、滚动缓冲、文本模式渲染器、MODE 13 控制台渲染、颜色主题 |
| Input | js/input.js |
键盘事件规范化、按键队列/状态、行编辑器、历史、Tab 补全 |
| FileSystem | js/filesystem.js、js/disk-image.js |
虚拟磁盘、路径解析、通配符、持久化;出厂磁盘内容 |
| Parser | js/parser.js、js/batch.js |
命令行切分、开关、变量展开、管道/重定向、IF/FOR;批处理解释器 |
| Commands | js/commands.js |
表驱动内置命令注册表(Commands.register({...}) 即可新增命令) |
| Graphics | js/graphics.js |
索引色帧缓冲、256 色调色板、绘图原语、双缓冲 |
| Audio | js/audio.js |
BEEP、音效表、乐句与循环音乐合成 |
| Programs | js/programs.js |
外部程序注册表:COMMAND、PLASMA、BALL、MARIO |
| MarioGame | js/mario.js、js/mario-data.js、js/mario-ai.js |
MarioGame(状态与物理,可 clone())、MarioRenderer(只读渲染)、MarioProgram(宿主循环);精灵/关卡/物理/音乐数据;MarioAutoPilot(人格化效用规划 + A* 自动通关) |
| Machine | js/machine.js |
集中状态、执行管线、外壳循环、显示模式切换、BIOS 自检 |
| — | js/font.js、js/main.js |
位图字体;浏览器接线(布局、渲染循环、顶栏、状态 HUD、指示灯、帮助面板、aria-live) |
node tests/run-tests.js # 全部 66 项(无依赖,Node 18+)
node tests/run-tests.js Mario # 按名称过滤浏览器中打开 tests/test.html 运行同一套用例。覆盖 Parser、FileSystem、批处理解释器(含 Ctrl+C 终止)、
内置命令的正常/错误路径、行编辑与历史、Graphics 原语与程序进出图形模式,以及 MarioGame 物理:
短跳与长跳高度差、奔跑速度与跳跃、转向滑行、墙体碰撞、踩踏弹跳、Koopa 壳、侧撞死亡、掉坑死亡与 GAME OVER、
蘑菇变大与受伤变小(含无敌时间)、顶砖、时间耗尽、暂停、1-1 数据核对、水管往返奖励房,以及一个自动驾驶
脚本从起点跑完整个 1-1 并到达 COURSE CLEAR;自动驾驶部分验证六种 AI 风格都能无伤通关且行为各不相同
(每种都吃到蘑菇以大马里奥过关、抓到旗杆顶端 5000 分;速通在不走水管的风格里最快、收集者进奖励房拿到 15 枚以上金币、
探索者走隐藏水管、全能拿到隐藏 1UP、猎手消灭敌人最多之一、稳健跳过秘密),以及路线叠加层数据与 /A:风格 开关解析。
验证环境:Node 22(单元测试)+ Playwright 驱动的 Chromium(真实键盘事件、截图检查),2026-10-20。
| 项目 | 结果 |
|---|---|
每个内置命令的正常路径与错误路径(Bad command or file name、File not found、Invalid directory、Unable to create directory、Required parameter missing、Access denied、Invalid drive specification 等) |
✅ 通过(单元测试 + 手工执行) |
DIR *.TXT、COPY C:\UTILS\*.BAT A:(7 个文件)、DEL A?.TMP、DEL *.* 确认提示 |
✅ 通过 |
空输入、不存在的路径、超长命令(>127 字符被截断并响铃)、连续退格、根目录 CD .. |
✅ 通过 |
Tab 补全(ty+Tab → TYPE,REA+Tab → README.TXT)、↑ 历史、Insert 覆盖、Shift+PgUp 回看 |
✅ 通过(Chromium 真实按键) |
MODE 13 后 PLASMA、BALL 正常运行,Esc 返回 80×25 文本模式并恢复原屏幕 |
✅ 通过(截图确认) |
MARIO 完整走完 1-1:开始、跳坑、顶问号方块、踩 Goomba、过水管、旗杆、COURSE CLEAR |
✅ 通过(自动驾驶脚本在 Node 中完整通关;浏览器中截图确认旗杆滑落、进城堡、时间结算与 COURSE CLEAR 画面) |
死亡后扣命重开,生命为 0 显示 GAME OVER |
✅ 通过(单元测试 + 浏览器截图) |
刷新页面后文件保留、最高分保留;FORMAT 后磁盘清空(/S 写回系统文件) |
✅ 通过 |
含 IF、GOTO、FOR、CALL、CHOICE 的批处理(DEMO hello world)正确执行,FOREVER.BAT 用 Ctrl+C → Y 中止 |
✅ 通过 |
主题切换、CRT 开关、F12 帮助面板、窄屏横向滚动、无 JS 控制台错误、file:// 直接打开 |
✅ 通过 |
自动通关:MARIO /A 在浏览器中实时跑完 1-1(不丢命),P 键与 HUD 开关都能切换;硬盘/软驱指示灯随磁盘访问闪烁 |
✅ 通过(Chromium,2026-10-29) |
AI 风格:MARIO /A:COLL 在浏览器中实时(6 ms 预算)进入奖励房、21 枚金币后无伤过关;C 键 / HUD 按钮切换风格并记住选择,V 键开关路线叠加层,画面外的目标标签带方向箭头 |
✅ 通过(Chromium,2026-10-29) |
手机:iPhone 13 / SE / Pixel 7 竖横屏无溢出、控件不遮挡画面;点按跳过自检、软键盘(输入法)输入 ver、按键条 ↑ 取回历史、手柄按住移动、多点跳跃、AUTO、EXIT、滑动回看 |
✅ 通过(Playwright 设备模拟;未在真机测试) |
| 音频(BEEP、音效、音乐)的实际听感 |
- 不是 x86 模拟器:
.COM/.EXE只能运行内置 JS 程序(按签名分派),任意二进制会提示Program too big to fit in memory。 - 只支持英文键盘布局的可打印字符;手机端的触屏支持仅在浏览器设备模拟中验证过,未在真机测试。
EXIT不能退出主命令解释器;不支持XCOPY、EDIT、DELTREE、ANSI.SYS转义序列。localStorage容量约 5MB,超过时写入会静默失败(内存中的数据仍有效)。- Chrome 等浏览器可能不允许网页拦截 F12(开发者工具),此时可用工具栏上的 Help (F12) 按钮。