深入解析 Hi-Lo 游戏:从 1978 年 BASIC 到多种内存安全脚本语言的移植实践
2026/9/23 13:50:13 网站建设 项目流程
  • 示例工程

【免费下载链接】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/

项目地址:https://gitcode.com/gh_mirrors/ba/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 条规则,所有移植版都必须忠实还原:

  1. "HI-LO" 奖池(jackpot)中的金额在 1 到 100 美元之间。
  2. 玩家有6 次机会猜测奖池金额。
  3. 每次猜测后,计算机会提示猜测过高(TOO HIGH)还是过低(TOO LOW)。
  4. 若 6 次机会用完仍未猜中,计算机公布奖池金额。
  5. 若在 6 次机会内猜中,计算机登记该次赢得的金额。
  6. 每轮猜测结束后,玩家可选择继续游戏或结束程序;开始新一局时,奖池会生成一个新的随机金额。
  7. 若玩家多次获胜,赢得的金额会累计相加

一个关键行为细节出现在各移植版中:如果某一轮 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 = 6QUESTION_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 = 1HIGH_NUMBER_RANGE = 100MAX_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 将逻辑拆分为intromake_guesscheck_guessblew_itoutro五个具名方法,可读性极强:

  • 常量MAX_TRIES = 6RANGE = (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 原版PythonC#JavaRustRubyJavaScript
随机金额范围0~99(INT(100*RND(1))1~1001~1001~1001~1001~1000~99(忠实还原)
猜测上限6666666
非数字输入处理无防护抛异常循环重试无(parseInt循环重试gets.to_i静默转 0parseInt得 NaN
重玩判定精确YESYES(大写归一)以 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 时有四点值得注意:

  1. 随机数的区间语义:BASIC 的INT(100*RND(1))产生 0~99,而多数现代版本选择 1~100 的直觉区间。无论采用哪种,必须保持与提示逻辑的自洽(比较只依赖大小关系,两种区间对玩法无影响)。
  2. 累计奖金的生命周期:猜中累加(R=R+Y),6 次失败清零(R=0)——这一"惩罚性清零"是游戏张力所在,任何移植都不可遗漏。
  3. 输入与重玩判定的宽容度:BASIC 精确匹配YES且大小写敏感;现代版本普遍归一化大小写,甚至允许Y(C#、Java)或以 N 退出(Ruby)。若需严格还原历史体验,请参照 BASIC 原版。
  4. 排版还原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/

项目地址:https://gitcode.com/gh_mirrors/ba/basic-computer-games
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询