- 示例工程
【免费下载链接】basic-computer-games
An updated version of the classic "Basic Computer Games" book, with well-written examples in a variety of common MEMORY SAFE, SCRIPTING programming languages. See https://coding-horror.github.io/basic-computer-games/
本指南以《Basic Computer Games》(1978)中的经典益智游戏 Flip Flop 为核心,围绕其在 00_Alternate_Languages/36_Flip_Flop 目录中的备选语言移植展开。你将了解该游戏的完整规则与操作指令、BASIC 原版的伪随机翻转算法,以及 Python、Rust、MiniScript 等移植实现如何忠实还原"同一局内固定随机序列"这一关键机制,并掌握各实现的运行方式与移植要点。
游戏概述:把十个 X 翻成十个 O
Flip Flop 是一个单人益智游戏,目标非常直观:把一行十个X
X X X X X X X X X X变成一行十个O
O O O O O O O O O O操作方式是输入一个数字,对应这行中某个位置的下标。每次输入后,该位置的符号会发生翻转(X变O,或O变X)。游戏的关键设定在于:某些数字输入后只有 1 个位置改变,而另一些数字会连锁改变第 2 个位置——例如输入 3 可能只翻转位置 3,但也可能顺带翻转其他某个位置。官方提示玩家应能在12 步或更少步数内解决,鼓励玩家摸索取胜策略。
游戏还内置了两个特殊指令:
- 输入0:将整行重置回全部
X,用于重新尝试同一局; - 输入11:在游戏任意时刻开始一局全新的游戏。
该游戏最初由纽约新海德公园(New Hyde Park, New York)的 Michael Kass 编写,收录于 1978 年出版的《Basic Computer Games》一书,后经 Vintage BASIC 站点以flipflop.bas形式存档,成为本仓库移植工作的原始依据。完整的游戏说明见 36_Flip_Flop/README.md。
备选语言目录的定位:三条标准的例外集合
00_Alternate_Languages/36_Flip_Flop/README.md 这份关联文档本身即是对整个00_Alternate_Languages组织方式的说明:该目录下每个子文件夹代表一个程序向非约定 10 种语言之一的移植版本,而这些"约定语言"需要同时满足三个条件:
- 流行度:按 TIOBE 指数衡量足够流行;
- 内存安全(Memory safe);
- 通常被视为"脚本"语言(Scripting language)。
而备选语言移植不受这三条标准约束——这正是 00_Alternate_Languages/README.md 中明确的定位:例如在这里(仅限本目录)C 或 PASCAL 也是允许的,但仍需保持原始程序的观感与交互风格(控制台应用),并尽量保持代码可移植性;若无法移植,则必须在 README 与目录命名中明确说明限制。
也就是说,主目录 36_Flip_Flop 下收录的是 C#、Java、JavaScript、Python、Ruby、Rust、VB.NET 等"约定语言"移植,而00_Alternate_Languages/36_Flip_Flop专门存放不属于约定 10 种语言的移植——例如当前仓库中该目录仅收录了MiniScript一个移植(00_Alternate_Languages/36_Flip_Flop/MiniScript/flipflop.ms)。仓库欢迎任何语言的额外移植,但这些移植仅作教育用途,不计入项目最终的移植数量统计。
BASIC 原版源码解析:伪随机翻转的核心机制
原版逻辑集中在 36_Flip_Flop/flipflop.bas 中,整个程序只有约 80 行,却包含了两个值得注意的算法点。
状态表与输入校验
游戏用A$字符串数组保存 10 个位置的当前符号(第 27-31 行初始化全部为"X"),并维护两个关键变量:
M:上一次的输入值;N:本次输入值;C:累计猜测次数(第 220 行置 0,第 630 行每次翻转后C=C+1)。
输入校验(第 340-370 行)保证了:
N必须为整数,否则跳到第 310 行输出ILLEGAL ENTRY--TRY AGAIN.;N=11表示重新开始(跳回第 180 行重新初始化随机种子与版面);N>11视为非法输入;N=0表示只重置版面为全X(跳回第 230 行,保留计数器与随机数)。
两条不同的伪随机公式
游戏的核心趣味来自"第二次翻转位置"的不可预测性。BASIC 原版根据本次输入是否与上一次相同选择两条公式:
当
N与M不同(第 420 行):R = TAN(Q+N/Q-N) - SIN(Q/N) + 336*SIN(8*N) N = INT(10 * (R - INT(R)))当
N与M相同(第 530 行,连续输入同一个数字):R = .592*(1/TAN(Q/N+Q))/SIN(N*2+Q) - COS(N) N = INT(10 * (R - INT(R)))
其中Q是开局时Q=RND(1)生成的随机种子(第 190 行)。两条公式都用三角函数把输入位置N映射到[0, 10)区间的小数,再取整数部分作为"附带翻转"的第二位置。由于Q在一局内固定,同一局中相同输入会得到相同的附带位置——这是保证谜题可复现、可推敲的关键设计;而每开一局Q重新生成,谜面就完全不同。
附带翻转还有一个细节:若计算出的第二位置N恰好也是O(已翻转),则逻辑上会回落到把N位置设回X(第 480、590 行),形成一种"连锁但收敛"的行为。
胜负判定
每步翻转后重新打印整行(第 610-640 行),随后检查 10 个位置是否全部为O(第 650-670 行)。全部为O时,根据步数输出两种结语(第 680-710 行):
- 步数
C<=12:VERY GOOD. YOU GUESSED IT IN ONLY C GUESSES. - 步数
C>12:TRY HARDER NEXT TIME. IT TOOK YOU C GUESSES.
最后询问是否再来一局(第 720-770 行),回答N开头则结束程序。
多语言移植实践对比
备选语言移植的价值在于:用不同语言的语法忠实还原同一套规则。以仓库中的三个实现为例,可以看到"同样的算法,不同的落地方式"。
Python:显式封装翻转逻辑
36_Flip_Flop/python/flipflop.py 用flip_dict = {"X": "O", "O": "X"}表达翻转(第 28 行),并把"附带位置计算 + 翻转"抽成独立的flip_bits函数(第 31-46 行),接收一个r_function: Callable[[int], float]回调来注入两条不同的公式——当m == n(连续输入同一数字)时使用第二条公式,否则使用第一条(第 97-111 行)。同时它明确注释了第 0 个元素的存在理由:"We add an extra 0-th item because this sometimes is set to something but we never check what it is for completion of the puzzle"(第 68-70 行)——即保留 1-based 索引以对齐 BASIC 的A$(N)语义,而最终胜负判定只检查row[1:]。
Rust:结构体封装游戏状态
36_Flip_Flop/rust/src/game.rs 把游戏建模为Game结构体,字段包括board: [char; 10]、last_move、entropy(即 BASIC 中的随机种子Q)与tries。两条公式被合并进get_other方法(第 83-94 行),通过equals_last_move布尔参数选择分支;flip方法(第 70-81 行)处理X/0互转,reset_board与reset_game(第 115-124 行)分别对应 BASIC 中"输入 0 重置版面"与"输入 11 开新局"两种语义——后者还会重新生成entropy = rand::random()。入口 36_Flip_Flop/rust/src/main.rs 通过morristown公共工具库打印标题与交互提示,主循环反复调用game.play()直至玩家不再继续。
MiniScript:备选语言的代表实现
作为00_Alternate_Languages/36_Flip_Flop目录下唯一的备选语言移植,00_Alternate_Languages/36_Flip_Flop/MiniScript/flipflop.ms 由 Joe Strout 于 2023 年移植,并保留了原始创作署名。它同样采用[""] + ["X"] * 10的技巧维持 1-based 索引(第 43 行),getInput函数循环校验输入必须为整数且落在0..11(第 32-39 行),并且把两条公式完整保留:tan(q + n/q - n) - sin(q/n) + 336*sin(8*n)(第 60 行)与0.592 * (1 / tan(q/n + q)) / sin(n*2 + q) - cos(n)(第 72 行),与 BASIC 原版逐项对应。
移植要点与注意事项
综合上述实现,可以把 Flip Flop 移植中容易踩坑的点归纳如下:
| 要点 | BASIC 原版行为 | 移植建议 |
|---|---|---|
| 索引体系 | A$数组 1-based 访问(A$(N)) | Python/MiniScript 用占位第 0 元素保持 1-based,Rust 用(i - 1) as usize转 0-based |
| 随机种子 | Q=RND(1),一局内固定 | 每局重新随机,同局内复用;输入 0 重置版面不重置种子 |
| 两条公式 | 按N==M与否切换 | 将"上一步输入"作为状态显式保存,供分支判断 |
| 输入校验 | 非整数、>11判非法 | 循环读取直到合法,非法时输出ILLEGAL ENTRY--TRY AGAIN. |
| 特殊输入 | 0重置版面,11开新局 | 在分支前先处理,注意11会重置计数器与种子 |
| 胜负判定 | 全部为O且步数<=12 | 输出VERY GOOD与TRY HARDER NEXT TIME两种结语,注意区分 |
如何运行这些实现
仓库是只读的,你可以通过查看与本地运行来验证上述行为:
- BASIC 原版:使用兼容 Vintage BASIC 的解释器运行 36_Flip_Flop/flipflop.bas;
- Python:
python flipflop.py(位于 36_Flip_Flop/python/flipflop.py); - Rust:进入 36_Flip_Flop/rust 后
cargo run(依赖见Cargo.toml); - MiniScript:在 MiniScript 环境(如 Mini Micro)中加载 00_Alternate_Languages/36_Flip_Flop/MiniScript/flipflop.ms 执行。
如果你想为 Flip Flop 贡献一个新的备选语言移植,请参照00_Alternate_Languages的目录约定:将代码放入对应子目录并附上 README,保持控制台应用的原始观感,同时像上述实现一样在注释中说明任何与原版行为不一致的地方——这类移植仅作教育用途,但这恰恰是理解"同一算法在不同语言中的表达差异"的最佳练习。
- 示例工程
【免费下载链接】basic-computer-games
An updated version of the classic "Basic Computer Games" book, with well-written examples in a variety of common MEMORY SAFE, SCRIPTING programming languages. See https://coding-horror.github.io/basic-computer-games/
相关推荐
Archon Monorepo 上下文预热指南:用 Prime 命令为 AI Agent 建立完整的代码库认知
Archon Monorepo 上下文预热指南:用 Prime 命令为 AI Agent 建立完整的代码库认知 本文围绕 Archon 仓库中面向编码 Agen
示例工程Bagels 游戏的多语言移植实践:从 BASIC 到 Go、Nim 与 MiniScript 的替代语言实现解析
Bagels 游戏的多语言移植实践:从 BASIC 到 Go、Nim 与 MiniScript 的替代语言实现解析 导读 :本文以仓库 00_Alternate
示例工程Chemist 游戏的附加语言移植指南:从 BASIC 到 Python、Rust 与 Perl 的多语言实现解析
Chemist 游戏的附加语言移植指南:从 BASIC 到 Python、Rust 与 Perl 的多语言实现解析 导读 本文以开源仓库 basic compu
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考