- 示例工程
【免费下载链接】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/
导读
本文围绕 classic BASIC 游戏 Hi-Lo(00_Alternate_Languages/47_Hi-Lo/README.md 中收录的移植作品)展开,完整还原其游戏规则与作者设定,并以仓库内 hi-lo.bas 原始 BASIC 源码为主线,逐语言剖析 Python、C#、Java、Rust、Ruby、JavaScript 等移植版的实现思路。读完本文,你将掌握该游戏的核心算法(随机奖金生成、六次机会的高低提示、累计赢钱与重玩循环),并了解本仓库中"忠实还原 70 年代控制台观感、同时保持代码可移植性"的移植规范。
一、游戏背景与定位
Hi-Lo 是经典电子游戏合集Basic Computer Games(1978 年)中的猜数字游戏,作者为美国德克萨斯州沃斯堡(Fort Worth, Texas)的 Dean Altman。它本质上是同一合集中 GUESS 游戏的变体:不再只是猜测 1 到 100 之间的数字,而是猜中金额即可赢得美元奖金。
在仓库结构上,Hi-Lo 有两条实现分支:
- 标准目录 47_Hi-Lo:包含 10 种约定语言(Python、C#、Java、JavaScript、Ruby、Rust、VB.NET、Kotlin、Perl、Lua 等)的移植,遵循主仓库对"受欢迎(TIOBE 指数)、内存安全、一般被视为脚本语言"的三大语言选择标准;
- 备用语言目录 00_Alternate_Languages/47_Hi-Lo/:收录了不在约定 10 种之内的其他语言版本(如 Pascal、D、Elm、Nim、Go 等),按 00_Alternate_Languages/README.md 的说明,这些实现不受三大标准约束(例如允许 C 或 PASCAL),仅用于教学目的,不参与项目捐赠统计,但同样要求保持忠实于原始控制台观感并尽量可移植。
二、核心游戏规则(作者原始设定)
根据 47_Hi-Lo/README.md 记录的作者规则说明,游戏共 7 条规则,所有移植版都必须忠实还原:
- "HI-LO" 奖池(jackpot)中的金额在 1 到 100 美元之间。
- 玩家有6 次机会猜测奖池金额。
- 每次猜测后,计算机会提示猜测过高(TOO HIGH)还是过低(TOO LOW)。
- 若 6 次机会用完仍未猜中,计算机公布奖池金额。
- 若在 6 次机会内猜中,计算机登记该次赢得的金额。
- 每轮猜测结束后,玩家可选择继续游戏或结束程序;开始新一局时,奖池会生成一个新的随机金额。
- 若玩家多次获胜,赢得的金额会累计相加。
一个关键行为细节出现在各移植版中:如果某一轮 6 次机会内未猜中,累计赢钱会清零重置(R=0),而不是保留之前的奖金。这正是该游戏与普通猜数字游戏的最大差异——高风险、高回报的"连赢"机制。
三、原始 BASIC 源码逐行剖析
hi-lo.bas 全程序仅 29 行,是典型的小型 1970 年代 BASIC 程序,逻辑结构非常清晰:
10 PRINT TAB(34);"HI LO" 20 PRINT TAB(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY" 30 PRINT:PRINT:PRINT 100 PRINT "THIS IS THE GAME OF HI LO.":PRINT ... 160 R=0 ' 累计赢钱清零 170 B=0:PRINT 180 Y=INT(100*RND(1)) ' 生成 0~99 随机数作为奖池金额 200 PRINT "YOUR GUESS"; 210 INPUT A 220 B=B+1 ' 猜测次数加一 230 IF A=Y THEN 300 ' 猜中跳转到获奖输出 240 IF A>Y THEN 270 250 PRINT "YOUR GUESS IS TOO LOW.":GOTO 280 270 PRINT "YOUR GUESS IS TOO HIGH." 280 PRINT:IF B<6 THEN 200 ' 未满 6 次继续猜 290 PRINT "YOU BLEW IT...TOO BAD...THE NUMBER WAS";Y 295 R=0:GOTO 350 300 PRINT "GOT IT!!!!!!!!!! YOU WIN";Y;"DOLLARS." 310 R=R+Y 320 PRINT "YOUR TOTAL WINNINGS ARE NOW";R;"DOLLARS." 350 PRINT:PRINT "PLAY AGAIN (YES OR NO)"; 360 INPUT A$:IF A$="YES" THEN 170 380 PRINT:PRINT "SO LONG. HOPE YOU ENJOYED YOURSELF!!!" 390 END从源码结构可以看出核心算法骨架:
- 随机金额:
Y=INT(100*RND(1))生成 0~99 的整数(即 1~100 的等效表示,因为比较逻辑只依赖大小关系,对实际游玩无影响); - 猜测循环:
INPUT A读取猜测,B=B+1计数,通过与Y的比较输出 TOO HIGH / TOO LOW 提示,IF B<6 THEN 200控制最多 6 次; - 猜中分支:跳转到行 300,输出
GOT IT!!!!!!!!!! YOU WIN Y DOLLARS.,累计R=R+Y; - 失败分支:6 次未中则输出
YOU BLEW IT...TOO BAD...THE NUMBER WAS Y,并把累计奖金清零R=0; - 重玩判断:
INPUT A$读取字符串,只有输入"YES"(精确匹配、未做大小写归一化)才回到行 170 开始新一局,否则输出结束语SO LONG. HOPE YOU ENJOYED YOURSELF!!!。
值得注意的是,BASIC 版本对输入大小写敏感(必须输入YES),且没有对非数字输入做防护——这是现代移植版普遍改进的两个点。
四、现代语言移植的忠实还原与工程化改进
仓库为每种语言提供了完整可运行的移植。以下结合源码分析各实现的异同。
4.1 Python 版:最贴近 BASIC 的直译
47_Hi-Lo/python/hilo.py 是最能体现原始逻辑的版本。它用常量MAX_ATTEMPTS = 6与QUESTION_PROMPT = "? "抽象出关键参数,主循环结构与 BASIC 完全对应:
secret = random.randint(1, 100)生成 1~100 的奖池金额;for _attempt in range(MAX_ATTEMPTS)实现六次机会;- 命中时
total_winnings += secret累计奖金并打印当前总额,未命中则打印YOU BLEW IT...TOO BAD...THE NUMBER WAS {secret}; - 重玩判定通过
input().upper()做大小写归一化,只有"YES"才继续。
Python 版还保留了原版的横幅输出(CREATIVE COMPUTING MORRISTOWN, NEW JERSEY)和完全一致的提示语,体现了"忠实于原始 look-and-feel"的移植要求。
4.2 C# 版:Top-level Statements 与健壮输入
47_Hi-Lo/csharp/Program.cs 使用现代 C# 的 top-level statements 编写,在还原逻辑的同时加入了输入校验:
int jackpot = rnd.Next(100) + 1; // [0..99] + 1 -> [1..100]关键工程化细节:
- 用
Random rnd = new()作为统一随机源,rnd.Next(100) + 1明确注释了[0..99] + 1 -> [1..100]的区间换算; - 自定义
ReadInt辅助函数:int.TryParse解析失败时输出!Invalid Number Entered.并循环重试,这是对 BASIC 版无输入防护的改进; Tab(n)辅助函数生成 n 个空格,用于还原TAB(34)的居中排版;- 重玩判定
Console.ReadLine().ToUpper().StartsWith("Y")比 BASIC 的精确匹配更宽容(YES/Y均可); - 失败时累计奖金归零的语义在
guess > 6分支中体现。
4.3 Java 版:状态机驱动的面向对象实现
47_Hi-Lo/java/src/HiLo.java 是仓库中结构最"工程化"的移植,作者在注释中明确说明"不引入新特性、不加多余文本与错误检查,忠实还原 70 年代 BASIC"。它用GAME_STATE枚举实现了一个有限状态机:
private enum GAME_STATE { STARTING, START_GAME, GUESSING, PLAY_AGAIN, GAME_OVER }STARTING:首次进入打印介绍文本;START_GAME:调用init()重置猜测计数并生成随机数,打印O.K. I HAVE A NUMBER IN MIND.;GUESSING:读取猜测,validateGuess()比较大小并输出提示;猜中则累计奖金并跳转PLAY_AGAIN,6 次未中则打印奖金金额、清零奖金并跳转PLAY_AGAIN;PLAY_AGAIN:通过yesEntered()(接受Y/YES,不区分大小写)决定回到START_GAME还是进入GAME_OVER。
同时,47_Hi-Lo/java/src/HiLoGame.java 作为独立入口类调用hiLo.play(),将启动逻辑与游戏逻辑分离。Java 版还用常量LOW_NUMBER_RANGE = 1、HIGH_NUMBER_RANGE = 100、MAX_GUESSES = 6显式声明了游戏参数,随机数通过Math.random()换算到[1, 100]闭区间。
4.4 Rust 版:类型安全与输入重试
47_Hi-Lo/rust/src/main.rs 体现了 Rust 的强类型风格:
let jackpot_amount = rand::thread_rng().gen_range(1..101);- 使用
randcrate 的gen_range(1..101)生成 1~100 随机数; - 猜测输入经
guess.trim().parse::<u32>()解析,Err(_)时打印PLEASE ENTER A NUMBER VALUE.并continue,实现"非数字则重新提示"的重试逻辑(且重试不消耗猜测次数,因为continue前没有递增计数)——这是 Rust 版的一个细节行为,与 Python 版的int(input())直接抛异常不同; - 累计奖金类型为
u32,第 6 次(i == 5)仍未猜中时清零奖金; - 重玩判定用
tocontinue.trim().to_ascii_uppercase()与"YES"比较。
4.5 Ruby 版:面向对象的直白表达
47_Hi-Lo/ruby/hi_lo.rb 将逻辑拆分为intro、make_guess、check_guess、blew_it、outro五个具名方法,可读性极强:
- 常量
MAX_TRIES = 6与RANGE = (1..100)集中声明游戏参数,介绍文本通过RANGE.min/RANGE.max动态插值,避免硬编码; check_guess用三元表达式@guess > @number ? 'HIGH' : 'LOW'合并两条分支;- 失败判定
blew_it unless @guessed_correctly语义清晰; - 重玩判定
break if gets.start_with?(/n/i)与 BASIC 相反——以 N 开头即退出,其余输入一律继续,是各版本中对重玩输入最宽松的实现(不要求精确输入 YES)。
4.6 JavaScript 版:浏览器化的异步交互
47_Hi-Lo/javascript/hi-lo.js 由著名开发者 Oscar Toledo G.(nanochess)从 BASIC 转换,运行于浏览器环境:
- 通过
document.getElementById("output")追加文本节点模拟控制台输出; input()返回Promise,动态创建<INPUT>元素、监听回车键(event.keyCode == 13)后移除输入框并resolve输入值,配合async function main()的await实现顺序交互;- 随机数用
Math.floor(100 * Math.random())还原 BASIC 的INT(100*RND(1))写法,连原始版本"0~99"的语义都保持一致; - 配套的 47_Hi-Lo/javascript/hi-lo.html 提供页面容器,打开即玩,无需命令行环境。
五、各版本行为差异对照
| 行为点 | BASIC 原版 | Python | C# | Java | Rust | Ruby | JavaScript |
|---|---|---|---|---|---|---|---|
| 随机金额范围 | 0~99(INT(100*RND(1))) | 1~100 | 1~100 | 1~100 | 1~100 | 1~100 | 0~99(忠实还原) |
| 猜测上限 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
| 非数字输入处理 | 无防护 | 抛异常 | 循环重试 | 无(parseInt) | 循环重试 | gets.to_i静默转 0 | parseInt得 NaN |
| 重玩判定 | 精确YES | YES(大写归一) | 以 Y 开头 | Y/YES(忽略大小写) | YES(大写归一) | 以 N 开头即退出 | 精确YES(大写归一) |
| 失败后奖金 | 清零 | 清零 | 清零 | 清零 | 清零 | 清零 | 清零 |
这张对照表说明:各移植版在忠实还原原始规则(六次机会、过高/过低提示、猜中累计、失败清零)上高度一致,差异集中在输入健壮性与重玩判定的宽容度上——这正是"忠实原版观感"与"现代工程实践"之间最常见的平衡点。
六、如何运行与体验
各版本均可在本地直接运行(仓库只读,仅需查看与运行):
- Python:在 47_Hi-Lo/python/ 目录执行
python3 hilo.py; - C#:进入 47_Hi-Lo/csharp/,使用
dotnet run(项目文件为HiLo.csproj); - Java:编译并运行 47_Hi-Lo/java/src/HiLoGame.java(入口类,内部委托给
HiLo); - Rust:在 47_Hi-Lo/rust/ 执行
cargo run(依赖randcrate,见Cargo.toml); - Ruby:在 47_Hi-Lo/ruby/ 执行
ruby hi_lo.rb; - JavaScript:直接用浏览器打开 47_Hi-Lo/javascript/hi-lo.html;
- VB.NET / Kotlin / Perl / Lua:分别见 47_Hi-Lo/vbnet/、47_Hi-Lo/kotlin/HiLo.kt、47_Hi-Lo/perl/hi-lo.pl、47_Hi-Lo/lua/hilo.lua。
七、移植要点总结
结合 47_Hi-Lo/README.md 的"Porting Notes"留白与各实现的实际代码,移植 Hi-Lo 时有四点值得注意:
- 随机数的区间语义:BASIC 的
INT(100*RND(1))产生 0~99,而多数现代版本选择 1~100 的直觉区间。无论采用哪种,必须保持与提示逻辑的自洽(比较只依赖大小关系,两种区间对玩法无影响)。 - 累计奖金的生命周期:猜中累加(
R=R+Y),6 次失败清零(R=0)——这一"惩罚性清零"是游戏张力所在,任何移植都不可遗漏。 - 输入与重玩判定的宽容度:BASIC 精确匹配
YES且大小写敏感;现代版本普遍归一化大小写,甚至允许Y(C#、Java)或以 N 退出(Ruby)。若需严格还原历史体验,请参照 BASIC 原版。 - 排版还原:
TAB(34)居中标题等格式细节在 C#(Tab函数)、JavaScript(tab函数)、Rust({: >39}格式化)中均有对应实现,体现了 00_Alternate_Languages/README.md 强调的"忠实于原始 look-and-feel(控制台应用)"原则。
结语
Hi-Lo 是一个体量虽小、但移植价值极高的经典案例:它同时涉及随机数生成、循环控制、输入解析、累计状态与游戏循环状态机五种基础编程要素。通过对比 hi-lo.bas 与仓库中 Python、C#、Java、Rust、Ruby、JavaScript 等版本的实现,开发者可以清晰看到"忠实还原与工程改进"之间的权衡艺术——这正是本仓库作为教学资源的核心价值所在。
- 示例工程
【免费下载链接】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/
相关推荐
Appium 共享 Oxlint/Oxfmt 配置包 @appium/oxc-config:从版本日志看集中式代码规范体系的落地
Appium 共享 Oxlint/Oxfmt 配置包 @appium/oxc config:从版本日志看集中式代码规范体系的落地 本文基于 Appium mon
示例工程basic-computer-games 的 Bombardment 替代语言移植:从 1978 年 BASIC 到 Go 与 MiniScript 的实战剖析
basic computer games 的 Bombardment 替代语言移植:从 1978 年 BASIC 到 Go 与 MiniScript 的实战剖析
示例工程如何快速上手PilotGo-plugin-redis?5分钟搭建Redis集群监控系统
如何快速上手PilotGo plugin redis?5分钟搭建Redis集群监控系统 前往项目官网免费下载: https://ar.openeuler.org
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考