Fur Trader 的 ANSI C 移植指南:从 1978 年 BASIC 经典到 C99 的忠实转换
2026/9/23 11:31:00 网站建设 项目流程

Fur Trader 的 ANSI C 移植指南:从 1978 年 BASIC 经典到 C99 的忠实转换

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

导读Fur Trader(毛皮商人)是《Basic Computer Games》(1978)中的经典文字冒险游戏,玩家扮演 1776 年法国毛皮贸易远征队的首领,在三个要塞间选择贸易路线、买卖四种毛皮。本文以本仓库中 C 语言移植版的核心文档与完整源码为骨架,讲解该游戏从 Vintage BASIC 到 ANSI C(C99)的移植思路、源码结构、编译运行方法、定价与随机事件机制,并重点剖析翻译者在文档中披露的"狐狸皮价格未初始化"历史 Bug 及其处理策略。

游戏背景:1776 年的毛皮贸易远征

在开始阅读移植代码之前,先理解游戏本身的设定。根据 38_Fur_Trader/README.md 的介绍:

你是一支 1776 年法国毛皮贸易远征队的首领,即将离开安大略湖区去出售毛皮并为来年采购补给。你可以在三个要塞中任选其一进行交易,补给价格与毛皮售价取决于你选择的要塞,同时你需要说明手中持有哪些种类的毛皮。

游戏的原始作者是加拿大卡尔加里大学的 Dan Bachor,原版以 Microsoft 8K BASIC 编写,收录于 David Ahl 的《Basic Computer Games》一书。游戏会一直持续,直到你主动选择不再交易。

本仓库中该游戏拥有多种语言移植(C#、Java、JavaScript、Python 等),而C 语言版本被归类在00_Alternate_Languages(备用语言)目录下——依据 README.md 的说明,该目录收录不属于项目约定 10 种官方语言(C#、Java、JavaScript、Kotlin、Lua、Perl、Python、Ruby、Rust、VB.NET)的移植,仅用于教学目的。

移植文档核心内容:从 Vintage Basic 到 ANSI C

本次关联文档 00_Alternate_Languages/38_Fur_Trader/c/README.md 虽然篇幅简短,却完整交代了移植的出处、目标与关键技术决策:

  • 原始出处:源码下载自 Vintage Basic 网站(游戏仓库中所有 BASIC 源码均来自 vintage-basic.net/games.html)。
  • 目标语言:转换为 ANSI-C,源码注释中进一步明确为 C99 标准(见 furtrader.c)。
  • 翻译者署名:krt@krt.com.au,2020-10-10。

翻译者注释的四大设计原则

原文档中翻译者(krt)明确记录了自己的移植策略,这直接决定了整个源码的风格:

  1. 尽可能保留原始布局与流程:不重构游戏逻辑,让 C 代码的执行顺序与原始 BASIC 的GOTO跳转结构一一对应;
  2. 使用枚举类型:为 Fort(要塞)编号和 Fur(毛皮)类型引入枚举/符号常量,这是对原代码的"明显改进",使代码更易读;
  3. 尽量减少指针的使用:坚持老式 C 格式化风格——原因是"你永远不知道编译器有多老",强调兼容性与可移植性;
  4. 保留并修复 Fox Fur 价格 Bug:这是文档中最有价值的技术细节,下文专节剖析。

源码结构总览:常量、辅助函数与状态机

C 版本实现于 furtrader.c(共 474 行),整体结构清晰,按职责分为三大部分。

常量与"枚举"

虽然文档提到使用枚举类型,实际源码采用了宏常量 + 常量数组的组合方式(见 furtrader.c),以兼容最老式的 C 编译器:

#define FUR_TYPE_COUNT 4 #define FUR_MINK 0 #define FUR_BEAVER 1 #define FUR_ERMINE 2 #define FUR_FOX 3 #define MAX_FURS 190 const char *FUR_NAMES[FUR_TYPE_COUNT] = { "MINK", "BEAVER", "ERMINE", "FOX" }; #define FORT_TYPE_COUNT 3 #define FORT_MONTREAL 1 #define FORT_QUEBEC 2 #define FORT_NEWYORK 3 const char *FORT_NAMES[FORT_TYPE_COUNT] = { "HOCHELAGA (MONTREAL)", "STADACONA (QUEBEC)", "NEW YORK" };

关键约束一目了然:玩家最多持有190 张毛皮,只有 4 种毛皮类型、3 个要塞可选。

辅助函数

源码按单一职责拆分了 12 个辅助函数,替代了原 BASIC 中大量GOSUB子程序:

函数职责
printAtColumn(int, const char*)在指定列打印文本(对应 BASIC 的TAB()
print(const char*)带换行输出一行文本
getNumericInput()循环读取用户输入并转换为整数,非法输入重新提示(strtol+ 指针校验)
getYesOrNo()循环读取 YES/NO 输入,只校验首字母Y/N,兼容大小写
getFortChoice()展示三要塞选择提示,循环直到输入 1/2/3
showFortComment(int)打印所选路线的难度说明
getFursPurchase(int*)依次询问四种毛皮的持有数量
zeroInventory(int*)/sumInventory(int*)清零 / 求和毛皮库存
randomAB(float, float)/randFloat()生成[a,b]区间与[0,1]区间的随机浮点数

其中getNumericInput通过strtolendptr判断转换是否成功(见 furtrader.c),getYesOrNo则通过首字母判断,允许yYnN等任意变体——这正是项目指南要求的"支持大小写输入"。

状态机驱动的主循环

与原始 BASIC 依靠GOTO/GOSUB线形流转不同,C 版本用一个显式的状态机组织游戏流程(见 furtrader.c):

#define STATE_STARTING 1 #define STATE_CHOOSING_FORT 2 #define STATE_TRAVELLING 3 #define STATE_TRADING 4

main()while(1)无限循环根据game_state分发,四个状态依次构成完整游戏周期:

  1. STATE_STARTING:打印引言,重置资金为 600 美元、清空库存,询问是否交易;
  2. STATE_TRADING:显示当前资金与 190 张毛皮,让玩家输入四种毛皮数量;若总数超过 190,输出 "YOU MAY NOT HAVE THAT MANY FURS. DO NOT TRY TO CHEAT. I CAN ADD." 并回到 STARTING 重来;
  3. STATE_CHOOSING_FORT:选择要塞并显示路线评价,询问是否更换要塞;
  4. STATE_TRAVELLING:按所选要塞生成价格、扣减补给与旅行费用、触发随机事件、计算销售收入,然后询问是否来年继续交易。

编译与运行

源码头部注释直接给出了编译命令(见 furtrader.c):

gcc -g -Wall -Werror furtrader.c -o furtrader
  • -g保留调试符号,便于在 gdb 中跟踪游戏状态;
  • -Wall -Werror将警告升级为错误,强制代码零警告——这既是移植者自律的体现,也保证了代码在老旧编译器上也能顺利通过。

编译后直接运行./furtrader即可开始游戏,全程为命令行交互,输入均为键盘应答。

核心机制一:要塞选择、价格生成与成本

三条路线的风险-收益设计

选择要塞后,showFortComment会打印该路线的难度说明(见 furtrader.c):

  • Fort 1 Hochelaga(蒙特利尔):最容易的路线,但离海港远,毛皮售价低、补给价高;
  • Fort 2 Stadacona(魁北克):中等难度路线,毛皮价值与补给价格均居中,但需要搬运货物并穿越 Lachine 急流;
  • Fort 3 New York(纽约):最难路线,穿越易洛魁人领地,毛皮售价最高、补给最低。

价格生成公式

每种毛皮的价格由"随机区间 + 取整"公式生成,与原始 BASIC 中的INT((.2*RND(1)+.7)*10^2+.5)/10^2完全对应,即:( (幅度 * rand + 基数) * 100 + 0.5 ) / 100,得到两位小数的价格。各要塞、各毛皮的价格区间整理如下(数据取自 furtrader.c 的实际公式):

毛皮HochelagaStadaconaNew York
MINK 貂0.70 – 0.900.85 – 1.151.05 – 1.20
BEAVER 河狸0.75 – 0.950.90 – 1.101.00 – 1.25
ERMINE 白鼬0.65 – 0.850.80 – 0.950.95 – 1.10
FOX 狐狸0.80 – 1.001.10 – 1.35见下节 Bug 分析

可见"越危险收益越高"的平衡:纽约路线整体价格水平最高,而蒙特利尔最低。

补给与旅行成本

前往每个要塞都会一次性扣减补给费与旅行费(源码中与 BASIC 一致,注意 C 版纽约补给价为 $85.00):

要塞补给费旅行费合计扣减
Hochelaga$150.00$10.00$160
Stadacona$125.00$15.00$140
New York$85.00$25.00$105

核心机制二:随机事件表

魁北克路线(Stadacona)

event_picker = (10 * randFloat()) + 1,按 1~10 整数分布触发事件(见 furtrader.c):

取值概率事件效果
1–220%河狸太重,搬运中丢失并被偷走player_furs[BEAVER] = 0
3–640%安全到达 Stadacona无损失
7–820%独木舟在 Lachine 急流倾覆全部毛皮清零
9–1020%狐狸皮未妥善腌制,无人购买player_furs[FOX] = 0

纽约路线(New York)

取值概率事件效果
1–220%遭易洛魁人袭击,全员遇难exit(0)游戏直接结束
3–640%幸运安全抵达无损失
7–820%侥幸逃脱袭击,但丢下全部毛皮全部毛皮清零
9–1020%貂皮与河狸皮途中受损相关毛皮售价减半

其中"受损减半"分支值得注意:屏幕提示文本写的是 "YOUR MINK AND BEAVER WERE DAMAGED",但 C 源码实际执行的是mink_price /= 2; fox_price /= 2;(见 furtrader.c),而原始 BASIC(furtrader.bas)减半的是B1(河狸)与M1(貂)。也就是说,提示文本与实际减半的毛皮种类在 C 版中并不完全一致——这是阅读源码时可留意的一处细节。

销售结算

到达要塞后统一结算(见 furtrader.c):

beaver_value = beaver_price * player_furs[ FUR_BEAVER ]; fox_value = fox_price * player_furs[ FUR_FOX ]; ermine_value = ermine_price * player_furs[ FUR_ERMINE ]; mink_value = mink_price * player_furs[ FUR_MINK ]; player_funds += beaver_value + fox_value + ermine_value + mink_value;

随后打印四种毛皮的销售收入与累计资金,询问 "DO YOU WANT TO TRADE FURS NEXT YEAR?",回答 NO 即结束游戏。

核心机制三:Fox Fur 价格 Bug 深度剖析

这是翻译者文档中着墨最多、也是移植最有价值的部分。翻译者写道:

有意思的是,代码似乎在 Fox Furs(狐狸皮)的价格上存在一个 Bug。该商品的价格存储在变量D1中,但某些代码路径不会设置这个价格,因此存在使用未初始化值上一轮循环残留值的可能。我认为这不是原作者的本意。所以我保留了原始代码流程(沿用之前的D1值),同时捕获未初始化的路径,为其赋一个"最佳猜测"值。

原始 BASIC 中的问题

对照 furtrader.bas 可以看到:

  • 蒙特利尔分支(第 1177 行)与魁北克分支都设置了D1(狐狸皮价格);
  • 纽约分支(第 1250 行起)只设置了M1E1B1,唯独没有设置D1
  • 如果玩家第一年就直接选择纽约,销售结算时(第 1414 行PRINT "YOUR FOX SOLD FOR $";D1*F(4))使用的D1将是未初始化变量;如果此前去过其他要塞,则会沿用上一轮的价格。

这直接导致了 38_Fur_Trader/README.md 中"Known Bugs"记录的玩家可见现象:某些毛皮的价格不会随要塞变化——所有毛皮价格都在访问 Fort 1 时被设置,当你选择 Fort 2 或 Fort 3 时,部分价格仍沿用 Fort 1 的值,导致同样的毛皮在去没去过其他要塞时得到不同售价。

C 移植的修复策略

C 版本在保留原始行为的同时修复了未初始化路径(见 furtrader.c 与 furtrader.c):

float fox_price = -1; /* sometimes this takes the "last" price (probably this was a bug) */ ... if ( fox_price < 0 ) { /* Original Bug? There is no Fox price generated for New York, it will use any previous "D1" price. So if there was no previous value, make one up */ fox_price = ( ( 0.25 * randFloat() + 1.05 ) * 100 + 0.5 ) / 100; /* not in orginal code */ }

具体策略分两步:

  1. 初始化防御fox_price初始化为-1(一个不可能是合法价格的哨兵值),避免真正的未定义行为;
  2. 保留原始语义 + 兜底:如果玩家此前去过其他要塞,fox_price已有值,则沿用上一轮价格(忠实保留原版行为);只有从未设置过(< 0)时,才用与蒙特利尔狐狸价格类似的公式(0.25*rand + 1.05,区间 1.05–1.30)生成一个"最佳猜测"值,并明确注释/* not in orginal code */标注这是移植新增代码。

这种"既忠实又安全"的处理方式值得学习:它没有擅自改变原版游戏逻辑,而是用注释与哨兵值把历史 Bug 的边界条件显式化。

跨语言一致性验证

这一 Bug 修复策略并非 C 版独有——对照 38_Fur_Trader/python/furtrader.py 可以看到,Python 版同样声明fox_price = None,并在纽约分支中判断if fox_price is None:才生成兜底价格,注释几乎一字不差。这说明多个移植版本对同一历史 Bug 采取了相同的"保留 + 兜底"共识,可交叉印证该分析的准确性。

移植风格观察与源码细节

结合翻译者文档与源码,可以总结 C 版移植的几个风格特征:

  • 指针最小化:指针仅出现在必须修改调用方变量的地方(如getFursPurchase(int *furs)zeroInventory(int*)),其余数据传递全部使用值语义;
  • 老式 C 风格:使用printffgetsstrtol等经典 API,无任何平台相关扩展,符合"不知道编译器有多老"的兼容性考量;
  • 明确的错误哨兵:源码内置Internal error #1/#2/#3/#4防御分支,分别覆盖非法要塞编号与越界事件值(见 furtrader.c 等),在输入不可能触达的路径上仍做了兜底;
  • 随机数种子srand(time(NULL))以当前时间初始化随机序列,保证每局价格与事件不同(见 furtrader.c)。

另有一处可留意的源码细节:Internal Error #4分支的printf调用缺少对应的event_picker实参(见 furtrader.c),由于该分支逻辑上不可达,并未造成实际影响,但若以-Wformat严格编译可能产生警告。

总结

Fur Trader的 ANSI C 移植是"忠实转换 + 现代工程化"的典型案例:它用符号常量、辅助函数和状态机把 1978 年 BASIC 的GOTO迷宫重构成可读的 C99 代码,却完整保留了原版的定价公式、事件概率、文本输出与游戏节奏;面对原始代码中的狐狸皮价格 Bug,翻译者没有武断地"修掉",而是通过文档披露、哨兵值初始化、保留历史语义、注释标注新增代码的方式,把历史真相与修复意图同时留给了后来者。对想学习经典游戏移植、或研究"如何在兼容性与正确性之间取舍"的开发者,这份文档与源码是一份小而完整的范本。

如需进一步探索,可继续阅读:C 移植说明、C 源码、游戏原始说明与已知 Bug、原始 BASIC 源码、Python 移植对照。

【免费下载链接】basic-computer-gamesAn 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),仅供参考

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

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

立即咨询