这些强烈的喜悦

2022-02-25 17:51:15

我';在过去的几天里,我一直在开发一个Inform 6编译器补丁,以收紧生成的代码。在我工作的时候,我在推特上发布了一个童车游戏的截图:

道路尽头的暴力不是';暴力不是';欢迎来到冒险!暴力不是';答案不是这个。暴力不是';答案不是这个。版本5/序列号961209/通知v6。37.图书馆暴力和#39;回答这个问题。SAt道路终点暴力事件不是';答案不是这个>;东内大厦暴力事件不是';答案不是这个。暴力不是';答案不是这个。暴力不是';答案不是这个。暴力不是';答案不是这个。暴力不是';答案不是这个>;

我发推特是因为它很有趣;我的追随者们一致同意。(谢谢大家!)但这是怎么发生的?嘿,我没有';我一段时间没写过代码帖子。让';让我们深入研究一下。

我的补丁旨在去除死代码。这是什么意思?好吧,假设你写了这样一个Inform 6例程:

你可能不知道I6,但你可能知道这个函数只是打印";你好" if语句总是错误的,永远不会发生。但是Inform编译器还是在编译第一个print和return语句。我想避免浪费空间。

在这个简单的例子中,死代码剥离很容易。事实上,I6编译器已经在检测死if语句;它只需要轻轻一推就可以跳过为它生成代码的过程。然而,更复杂的代码样本,嗯,更复杂,我花了几天时间修改算法以使其正确。

这些变化的关键是一个名为execution_never_Reach_的标志,它的意思正是它所说的,因为Graham Nelson喜欢自我记录代码。如果设置了该标志,则可以跳过生成输出。有趣的部分是确保在编译函数时在正确的时间打开和关闭标志。

(好吧,这里的“执行”永远不会到达,它的意思并不完全是这样的,因为我在它里面塞进了一些小旗。我不会解释为什么。与这个故事无关。)

现在,有了';这是通向这个补丁的又一步。在Z-code(和Glulx,但我们在这里重新测试Z-code)中,字符串和代码分别存储在内存中。我';d让编译器跳过生成print和return语句,但是字符串";再见残酷的世界" 仍存储在字符串内存中。

于是我走进了编译器';s compile_string()函数,并添加了两行代码,大致如下所示:

这种变化的意义在于,如果你的打印声明不是';t编译成代码,谁在乎';正在打印什么?compile_string()必须返回某些内容,因为它的类型是int。但返回的值将被丢弃在地板上。所以我们不妨返回零。

因此';这很容易,对吧?三行(好的,八行有评论,另一行是现在不重要的检查)。编译编译器,编译。中导,测试。

欢迎来到冒险!暴力不是';答案不是这个。暴力不是';答案不是这个。版本5/序列号961209/通知v6。37.图书馆暴力和#39;回答这个问题。s

如果你';你真的很合拍,你可能已经猜到我的错误了。如果没有,我';I’我将以一个段落的间隔来引出悬念。。。

...我花了所有的时间来确保在函数中正确处理这里的执行。但我忘了检查函数之间发生了什么。事实上,我已经编写了一些清理代码来设置执行_never _reachs _here=-1在每个函数的末尾!然后就忘了是我干的。

当然,正如我们所说,值-1被认为是真实的。因此,每当编译器遇到函数外部的字符串时——比如全局常量或对象的一部分——它都会自动跳过它并返回零。

修复很简单:只需清除函数之间的执行_never _reach _为零(false),而不是-1。然后一切正常。

但是等等。我没有';我什么都没解释!为什么跳过字符串会导致游戏打印";暴力不是';这个问题的答案是#34;到处都是?

任何用户都熟悉这句话。它';这可能是信息库中最容易识别的信息。它';当你攻击或破坏游戏中没有的东西时会发生什么';t定义为易碎。(或者如果你击中它,或者打架、杀人、谋杀、粉碎、破坏……很多同义词。)

图书馆默认信息是一门微妙的艺术。你需要一个对玩家可能试图打破的任何东西都合适的回应——在任何游戏中,包括你没有打破的游戏';我没想到。

这很棘手!假设库默认值为";那';它牢不可破" 这对打破雕像来说是合适的,但它读出了“搏击青蛙”的键——它';It’青蛙怎么会牢不可破并不明显。打碎酒杯';这完全令人困惑。(是的,作者应该为《打碎酒杯》写一篇更好的回复。但图书馆的工作是提供一些足够的东西,如果他们不提供的话。)

"没有明显的变化";不是';不坏,但它';这也不太好。这意味着玩家试图使用暴力,但没能造成伤害。同样,对于易碎物品来说,这是一种误导。你可以侥幸逃脱";什么都没发生";对于推送动作,因为推送是隐式的。。。有点无聊。(可能物体自由滚动;可能它固定在适当的位置。你可以把这两种结果描述为";什么都没发生。";)但在某些情况下,暴力确实应该产生明显的影响。

我们想要的是一个反应,这意味着玩家决定不这样做,而且应该尝试其他东西,因为没有实施任何攻击行动。但我们可以';不要说";暴力不是';答案是";因为游戏中的其他东西可能是真正可以攻击的!(毕竟,佐克只有三个可以战斗的怪物——但如果你不战斗,你就不会赢。)我们不得不说";暴力不是';这个问题的答案是";离开球员';我们有很多选择。

所以";暴力不是';这个问题的答案是#34;这是一个经过仔细调整的妥协。它';在过去的30年里,它一直保持着良好的状态;这是历史。

(图书馆默认设置的最后一条规则是,它们的语气应该是中立和谦逊的。";暴力不是这个问题的答案。";惊人地失败了——它太令人难忘了!但我们必须允许格雷厄姆·纳尔逊有一些固执己见的时刻,不管怎样,没有它,我的虫子就不会好笑了。还记得我的虫子吗?这是一个非常有趣的问题。)(我在为我的虫子发愁。)

回想我的错误。我在游戏中省略了某些字符串——所有出现在函数之外的字符串——而是将它们的值记录为零。

它';很清楚,看看截图,那";欢迎来到冒险" 在函数内部发生。像";发布";和";序列号";和";图书馆";。但是库版本必须是字符串常量。房间和对象描述也是如此。这些都归零了。出于某种原因,零值被打印为";暴力不是';答案不是这个"

(像";Inside Building";和";the End Of Road";这样的房间名称实际上也是外部功能,但它们重新编译的方式意味着它们是透明的。我不想讨论所有细节。)

身份线尤其混乱。事实证明,游戏试图打印";得分:34分;和";动作:";在右上角。这些都超出了状态线,完全错了。

对错文本的混合可能会变得更加愚蠢。我曾经说过:

你也可以看到暴力不是';答案不是这个。一组按键';这个问题的答案。暴力不是';答案不是这个。美味的食物是';答案不是这个。暴力不是';答案不是这个。小瓶子(暴力不是这个的答案。暴力不是这个的答案。暴力不是这个的答案。暴力不是这个的答案。瓶装水)。

...这看起来难以描述,直到你意识到游戏正在用字符串常量";a";和";有些";。对象名称很好;短词会被替换。

所以这一切都有意义。。。除了我们还没有';t解释了零是如何变成暴力的。

你可能会猜到";暴力不是';这个问题的答案是#34;是游戏中的第一个,或者说是第零个字符串';这是张弦乐桌。而你';你说得对!

但它是怎么做到的?它';It’当然不是按字母顺序排在第一位。它';这不是游戏中定义的第一个字符串常量。它';这不是英语中定义的第一个字符串。h库文件。它';它甚至不是该文件中LanguageLM()函数中定义的第一个字符串。

这让我非常困惑。。。直到我想起I6编译器的一个怪癖。(它有很多怪癖。)在print语句中,短字符串在函数代码中内联编译。(Z代码允许这样做。)更长的字符串——任何超过32个字符的字符串——进入字符串表。

我';我很确定这条规则是一种降低函数大小的方法。它';很难为很长的函数生成代码,因为Z机器分支操作码的范围有限。游戏作者通常不会';他们不会写很长的函数,但他们有时会写一些打印大量文本的函数。如果所有这些文本都被内联,函数就会变得太长,无法管理。因此,编译器进行了调整,将更大的文本块移动到字符串表中,而字符串表没有';我没有同样的尺寸限制。

因此';这是调试链中的最后一个环节"暴力不是';这个问题的答案是#34;不是';t是游戏中的第一个字符串,但它是函数库中出现的第一个长字符串。短字符串被内联;函数之外的字符串会被删除。该库在游戏开始前编译。所以";暴力不是';这个问题的答案是#34;是游戏中第一根进入弦表的弦。这使它成为第0个字符串条目。。。其他一切都随之而来。

"欢迎来到冒险";是(对你和我)一眼就能认出的钩子。然后你发现";在路的尽头";和";大楼内";,这证实了你';在你想一想之后,我们正在看。但它';它充满了";暴力";线,这也是可以识别的,但错了。I6头块很常见,但它';她分手了。你考虑的理论是“34”;暴力";线路被卡在了随机的地方。然后是状态线(熟悉!)用不完整的词再次打破模式,然后是";取笑奥蒂";。到目前为止,你';你完全不知所措。

(你必须打破垃圾,用垃圾来唱,有时是很棒的垃圾。同样的想法。)

老实说,它';这正是冒险文本和";暴力不是';答案不是这个" 如果我手动调整它,我可能会坚持#34;闪亮的黄铜灯";在最后五行的某个地方,只是为了再次绊倒人们。但它没有';我不需要它。

我发誓这完全是偶然的。我没有';除了打字,我什么都不做。有时宇宙只是把它交给你。