您知道Emacs捆绑了俄罗斯方块的实现吗?只需输入M-xtetris。Emacs的拥护者经常在讨论文本编辑器时提到这一点。“是的,但是你的那个编辑器运行俄罗斯方块吗?”我想知道,这会让人们相信Emacs更好吗?比如,为什么有人会关心他们是否可以在文本编辑器中玩游戏?“是的,但是你的吸尘器可以播放mp3吗?”有人说俄罗斯方块总是很有趣。与Emacs中的所有内容一样,它的源代码是开放的并且易于检查和修改,因此我们可以使它更有趣。更有趣,我的意思是更难。使游戏更难的最简单方法之一是“隐藏下一个方块预览”。你不能故意把一个S/Z块放在一个危险的位置,因为你知道下一个块会填满这个空间——你必须试试你的运气并希望最好的结果。这是没有预览时发生的情况(如您所见,没有预览,我所做的一些选择会产生“可怕的后果”):预览框由一个名为tetris-draw-next-shape1的函数设置:(defuntetris-draw-下一个形状()(dotimes(x4)(dotimes(y4)(gamegrid-set-cell(+tetris-next-xx)(+tetris-next-yy)tetris-blank)))(dotimes(i4)(let((tetris-shapetetris-next-shape)(tetris-rot0))(gamegrid-set-cell(+tetris-next-x(aref(tetris-get-shape-celli)0))(+tetris-next-y(aref(tetris-get-shape-celli)1))tetris-shape))))首先,我们引入一个标志以允许显示下一个预览块2:(defvartetris-preview-next-shapenil"Whennon-nil,shownextblockthepreviewbox.")现在的问题是,我们怎样才能让tetris-draw-next-shape尊重这个标志?最明显的方法是重新定义它:(defuntetris-draw-next-shape()(whentetris-preview-next-shape;;existingtetris-draw-next-shapelogic))但这不是理想的解决方案。同一功能有两个定义很容易造成混淆,如果上游版本发生变化,我们必须维护修改后的定义。更好的方法是使用建议。Emacs通知类似于Python装饰器,但更灵活,因为通知可以从任何地方添加到函数中。这意味着我们可以在不影响原始源文件的情况下修改函数。有许多不同的方法可以使用Emacs通知(请参阅手册),但这里我们只使用advice-add函数和:around标志。建议函数将原始函数作为参数,该函数可能会执行也可能不会执行。在这里,我们让原始函数仅在预览标志非空时执行:(defuntetris-maybe-draw-next-shape(tetris-draw-next-shape)-draw-next-shape)))(advice-add'tetris-draw-next-shape:around#'tetris-maybe-draw-next-shape)此代码将修改tetris-draw-next-shape的行为,它可以存储在一个配置文件中,与实际的俄罗斯方块代码分开。摆脱预览框架是一个简单的改变。更剧烈的变化是让方块随机停在空中:在这张图片中,红色I和绿色T部分没有掉落,它们是固定的。这使得游戏极其困难,但很容易实现。和以前一样,我们首先定义一个标志:(defvartetris-stop-midairt"Ifnon-nil,pieceswillsitesometimeswillstopintheair.")目前,EmacsTetris是这样工作的:活动部分有x和y坐标。在每个时钟周期,y坐标递增(块向下移动一行),然后检查是否与现有块重叠。如果检测到重叠,块将回滚(其y坐标递减)并将活动块设置到位。要让方块停在半空中,我们所要做的就是破解检测函数tetris-test-shape。这个函数在内部做什么并不重要——重要的是它是一个返回布尔值的无参数函数。我们需要它在正常情况下返回booleantrue(否则我们会出现奇怪的重叠情况),但也需要它在其他时候返回true。我敢肯定有很多方法可以做到这一点,这是我的方法:(defuntetris-test-shape-random(tetris-test-shape)shape.(<1tetris-n-shapes);;停止每一个INTERVAL片段。(let((interval7))(zerop(modtetris-n-shapesinterval)));;不要过早停止(它使得游戏无法玩。(let((upper-limit8))(
