Merge pull request #17513 from lujun9972/translate-MjAxOTAxMTQgU29tZSBBZHZpY2UgZm9yIEhvdyB0byBNYWtlIEVtYWNzIFRldHJpcyBIYXJkZXIubWQK

translate done: 20190114 Some Advice for How to Make Emacs Tetris Har…
This commit is contained in:
Xingyu.Wang 2020-02-23 21:03:17 +08:00 committed by GitHub
commit 6b47fac947
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 161 additions and 157 deletions

View File

@ -1,157 +0,0 @@
[#]: collector: (lujun9972)
[#]: translator: (lujun9972)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (Some Advice for How to Make Emacs Tetris Harder)
[#]: via: (https://nickdrozd.github.io/2019/01/14/tetris.html)
[#]: author: (nickdrozd https://nickdrozd.github.io)
Some Advice for How to Make Emacs Tetris Harder
======
Did you know that Emacs comes bundled with an implementation of Tetris? Just hit M-x tetris and there it is:
![img](https://nickdrozd.github.io/assets/2019-01-14-tetris/tetris-normal.png)
This is often mentioned by Emacs advocates in text editor discussions. “Yeah, but can that other editor run Tetris?” I wonder, is that supposed to convince anyone that Emacs is superior? Like, why would anyone care that they could play games in their text editor? “Yeah, but can that other vacuum play mp3s?”
That said, Tetris is always fun. Like everything in Emacs, the source code is open for easy inspection and modifcation, so its possible to make it even more fun. And by more fun, I mean harder.
One of the simplest ways to make the game harder is to get rid of the next-block preview. No more sitting that S/Z block in a precarious position knowing that you can fill in the space with the next piece you have to chance it and hope for the best. Heres what it looks like with no preview (as you can see, without the preview I made some choices that turned out to have dire consequences):
![img](https://nickdrozd.github.io/assets/2019-01-14-tetris/tetris-no-preview.png)
The preview box is set with a function called tetris-draw-next-shape[1][2]:
```
(defun tetris-draw-next-shape ()
(dotimes (x 4)
(dotimes (y 4)
(gamegrid-set-cell (+ tetris-next-x x)
(+ tetris-next-y y)
tetris-blank)))
(dotimes (i 4)
(let ((tetris-shape tetris-next-shape)
(tetris-rot 0))
(gamegrid-set-cell (+ tetris-next-x
(aref (tetris-get-shape-cell i) 0))
(+ tetris-next-y
(aref (tetris-get-shape-cell i) 1))
tetris-shape))))
```
First, well introduce a flag to allow configuring next-preview[2][3]:
```
(defvar tetris-preview-next-shape nil
"When non-nil, show the next block the preview box.")
```
Now the question is, how can we make tetris-draw-next-shape obey this flag? The obvious way would be to redefine it:
```
(defun tetris-draw-next-shape ()
(when tetris-preview-next-shape
;; existing tetris-draw-next-shape logic
))
```
This is not an ideal solution. There will be two definitions of the same function floating around, which is confusing, and well have to maintain our modified definition in case the upstream version changes.
A better approach is to use advice. Emacs advice is like a Python decorator, but even more flexible, since advice can be added to a function from anywhere. This means that we can modify the function without disturbing the original source file at all.
There are a lot of different ways to use Emacs advice ([check the manual][4]), but for now well just stick with the advice-add function with the :around flag. The advising function takes the original function as an argument, and it might or might not execute it. In this case, well say that the original should be executed only if the preview flag is non-nil:
```
(defun tetris-maybe-draw-next-shape (tetris-draw-next-shape)
(when tetris-preview-next-shape
(funcall tetris-draw-next-shape)))
(advice-add 'tetris-draw-next-shape :around #'tetris-maybe-draw-next-shape)
```
This code will modify the behavior of tetris-draw-next-shape, but it can be stored in your config files, safely away from the actual Tetris code.
Getting rid of the preview box is a simple change. A more drastic change is to make it so that blocks randomly stop in the air:
![img](https://nickdrozd.github.io/assets/2019-01-14-tetris/tetris-air.png)
In that picture, the red I and green T pieces are not falling, theyre set in place. This can make the game almost unplayably hard, but its easy to implement.
As before, well first define a flag:
```
(defvar tetris-stop-midair t
"If non-nil, pieces will sometimes stop in the air.")
```
Now, the way Emacs Tetris works is something like this. The active piece has x- and y-coordinates. On each clock tick, the y-coordinate is incremented (the piece moves down one row), and then a check is made for collisions. If a collision is detected, the piece is backed out (its y-coordinate is decremented) and set in place. In order to make a piece stop in the air, all we have to do is hack the detection function, tetris-test-shape.
It doesnt matter what this function does internally what matters is that its a function of no arguments that returns a boolean value. We need it to return true whenever it normally would (otherwise we risk weird collisions) but also at other times. Im sure there are a variety of ways this could be done, but here is what I came up with:
```
(defun tetris-test-shape-random (tetris-test-shape)
(or (and
tetris-stop-midair
;; Don't stop on the first shape.
(< 1 tetris-n-shapes )
;; Stop every INTERVAL pieces.
(let ((interval 7))
(zerop (mod tetris-n-shapes interval)))
;; Don't stop too early (it makes the game unplayable).
(let ((upper-limit 8))
(< upper-limit tetris-pos-y))
;; Don't stop at the same place every time.
(zerop (mod (random 7) 10)))
(funcall tetris-test-shape)))
(advice-add 'tetris-test-shape :around #'tetris-test-shape-random)
```
The hardcoded parameters here were chosen to make the game harder but still playable. I was drunk on an airplane when I decided on them though, so they might need some further tweaking.
By the way, according to my tetris-scores file, my top score is
```
01389 Wed Dec 5 15:32:19 2018
```
The scores in that file are listed up to five digits by default, so that doesnt seem very good.
Exercises for the reader
1. Using advice, modify Emacs Tetris so that it flashes the messsage “OH SHIT” under the scoreboard every time the block moves down. Make the size of the message proportional to the height of the block stack (when there are no blocks, the message should be small or nonexistent, and when the highest block is close to the ceiling, the message should be large).
2. The version of tetris-test-shape-random given here has every seventh piece stop midair. A player could potentially figure out the interval and use it to their advantage. Modify it to make the interval random in some reasonable range (say, every five to ten pieces).
3. For a different take on advising Tetris, try out [autotetris-mode][1].
4. Come up with an interesting way to mess with the piece-rotation mechanics and then implement it with advice.
Footnotes
============================================================
[1][5] Emacs has just one big global namespace, so function and variable names are typically prefixed with their package name in order to avoid collisions.
[2][6] A lot of people will tell you that you shouldnt use an existing namespace prefix and that you should reserve a namespace prefix for anything you define yourself, e.g. my/tetris-preview-next-shape. This is ugly and usually pointless, so I dont do it.
--------------------------------------------------------------------------------
via: https://nickdrozd.github.io/2019/01/14/tetris.html
作者:[nickdrozd][a]
选题:[lujun9972][b]
译者:[lujun9972](https://github.com/lujun9972)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://nickdrozd.github.io
[b]: https://github.com/lujun9972
[1]: https://nullprogram.com/blog/2014/10/19/
[2]: https://nickdrozd.github.io/2019/01/14/tetris.html#fn.1
[3]: https://nickdrozd.github.io/2019/01/14/tetris.html#fn.2
[4]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Advising-Functions.html
[5]: https://nickdrozd.github.io/2019/01/14/tetris.html#fnr.1
[6]: https://nickdrozd.github.io/2019/01/14/tetris.html#fnr.2

View File

@ -0,0 +1,161 @@
[#]: collector: (lujun9972)
[#]: translator: (lujun9972)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (Some Advice for How to Make Emacs Tetris Harder)
[#]: via: (https://nickdrozd.github.io/2019/01/14/tetris.html)
[#]: author: (nickdrozd https://nickdrozd.github.io)
让 Emacs 俄罗斯方块变得更难的一些建议 (Advice)
======
你知道吗,**Emacs** 与 **俄罗斯方块** 的实现捆绑在一起了?只需要输入 `M-x tetris` 就行了。
![img](https://nickdrozd.github.io/assets/2019-01-14-tetris/tetris-normal.png)
在文本编辑器讨论中Emacs 倡导者经常提到这一点。“没错,但是那个编辑器能运行俄罗斯方块吗?”
我很好奇,这会让大家相信 Emacs 更优秀吗?比如,为什么有人会关心他们是否可以在文本编辑器中玩游戏呢?“是的,但是那台吸尘器能播放 mp3 吗?”
有人说,俄罗斯方块总是很有趣的。像 Emacs 中的所有东西一样,它的源代码是开放的,易于检查和修改,因此 **我们可以使它变得更加有趣**。所谓更多的乐趣,我意思是更难。
让游戏变得更困难的一个最简单的方法就是“不要下一个块预览”。你无法再在知道下一个块会填满空间的情况下有意地将 S/Z 块放在一个危险的位置——你必须碰碰运气,希望出现最好的情况。
下面是没有预览的情况(如你所见,没有预览,我做出的某些选择带来了“可怕的后果”):
![img](https://nickdrozd.github.io/assets/2019-01-14-tetris/tetris-no-preview.png)
预览框由一个名为 `tetris-draw-next-shape` 的函数设置:
```
(defun tetris-draw-next-shape ()
(dotimes (x 4)
(dotimes (y 4)
(gamegrid-set-cell (+ tetris-next-x x)
(+ tetris-next-y y)
tetris-blank)))
(dotimes (i 4)
(let ((tetris-shape tetris-next-shape)
(tetris-rot 0))
(gamegrid-set-cell (+ tetris-next-x
(aref (tetris-get-shape-cell i) 0))
(+ tetris-next-y
(aref (tetris-get-shape-cell i) 1))
tetris-shape))))
```
首先,我们引入一个标志,决定是否允许显示下一个预览块:
```
(defvar tetris-preview-next-shape nil
"When non-nil, show the next block the preview box.")
```
现在的问题是,我们如何才能让 `tetris-draw-next-shape` 遵从这个标志?最明显的方法是重新定义它:
```
(defun tetris-draw-next-shape ()
(when tetris-preview-next-shape
;; existing tetris-draw-next-shape logic
))
```
但这不是理想的解决方案。同一个函数有两个定义,这很容易引起混淆,如果上游版本发生变化,我们必须维护修改后的定义。
一个更好的方法是使用 **advice**。Emacs 的 advice 类似于 **Python 装饰器**,但是更加灵活,因为 advice 可以从任何地方添加到函数中。这意味着我们可以修改函数而不影响原始的源文件。
有很多不同的方法使用 Emacs advice([ 查看手册 ][4]),但是这里我们只使用 `advice-add` 函数和 `:around` 标志。advise 函数将原始函数作为参数,原始函数可能执行也可能不执行。我们这里,我们让原始函数只有在预览标志是非空的情况下才能执行:
```
(defun tetris-maybe-draw-next-shape (tetris-draw-next-shape)
(when tetris-preview-next-shape
(funcall tetris-draw-next-shape)))
(advice-add 'tetris-draw-next-shape :around #'tetris-maybe-draw-next-shape)
```
这段代码将修改 `tetris-draw-next-shape` 的行为,而且它可以存储在配置文件中,与实际的俄罗斯方块代码分离。
去掉预览框是一个简单的改变。一个更激烈的变化是,**让块随机停止在空中**:
![img](https://nickdrozd.github.io/assets/2019-01-14-tetris/tetris-air.png)
本图中,红色的 I 和绿色的 T 部分没有掉下来,它们被固定下来了。这会让游戏变得 **及其难玩**,但却很容易实现。
和前面一样,我们首先定义一个标志:
```
(defvar tetris-stop-midair t
"If non-nil, pieces will sometimes stop in the air.")
```
目前,**Emacs 俄罗斯方块的工作方式** 类似这样子:活动部件有 x 和 y 坐标。在每个时钟滴答声中y 坐标递增(块向下移动一行),然后检查是否有与现存的块重叠。
如果检测到重叠,则将该块回退(其 y 坐标递减)并设置该活动块到位。为了让一个块在半空中停下来,我们所要做的就是破解检测函数 `tetris-test-shape`
**这个函数内部做什么并不重要** —— 重要的是它是一个返回布尔值的无参数函数。我们需要它在正常情况下返回布尔值 true( 否则我们将出现奇怪的重叠情况),但在其他时候也需要它返回 true。我相信有很多方法可以做到这一点以下是我的方法的
```
(defun tetris-test-shape-random (tetris-test-shape)
(or (and
tetris-stop-midair
;; Don't stop on the first shape.
(< 1 tetris-n-shapes )
;; Stop every INTERVAL pieces.
(let ((interval 7))
(zerop (mod tetris-n-shapes interval)))
;; Don't stop too early (it makes the game unplayable).
(let ((upper-limit 8))
(< upper-limit tetris-pos-y))
;; Don't stop at the same place every time.
(zerop (mod (random 7) 10)))
(funcall tetris-test-shape)))
(advice-add 'tetris-test-shape :around #'tetris-test-shape-random)
```
这里的硬编码参数使游戏变得更困难,但仍然可玩。当时我在飞机上喝醉了,所以它们可能需要进一步调整。
顺便说一下,根据我的 `tetris-scores` 文件,我的 **最高分**
```
01389 Wed Dec 5 15:32:19 2018
```
该文件中列出的分数默认最多为五位数,因此这个分数看起来不是很好。
**给读者的练习**
1。使用 advice 修改 Emacs 俄罗斯方块,使得每当方块下移动时就闪烁显示讯息 “OH SHIT”。消息的大小与块堆的高度成比例(当没有块时,消息应该很小的或不存在的,当最高块接近天花板时,消息应该很大)。
2。在这里给出的 `tetris-test-shape-random` 版本中,每隔七格就有一个半空中停止。一个玩家有可能能计算出时间间隔,并利用它来获得优势。修改它,使间隔随机在一些合理的范围内(例如,每 5 到 10 格)。
3。另一个对使用 Tetris 使用 advise 的场景,你可以试试 [`autotetris-mode`][1]。
4。想出一个有趣的方法来打乱块的旋转机制然后使用 advice 来实现它。
附注
============================================================
[1][5] Emacs 只有一个巨大的全局命名空间,因此函数和变量名一般以包名做前缀以避免冲突。
[2][6] 很多人会说你不应该使用已有的命名空间前缀而且应该将自己定义的所有东西都放在一个预留的命名空间中,比如像这样 `my/tetris-preview-next-shape`,然而这样很难看而且没什么意义,因此我不会这么干。
--------------------------------------------------------------------------------
via: https://nickdrozd.github.io/2019/01/14/tetris.html
作者:[nickdrozd][a]
选题:[lujun9972][b]
译者:[lujun9972](https://github.com/lujun9972)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://nickdrozd.github.io
[b]: https://github.com/lujun9972
[1]: https://nullprogram.com/blog/2014/10/19/
[2]: https://nickdrozd.github.io/2019/01/14/tetris.html#fn.1
[3]: https://nickdrozd.github.io/2019/01/14/tetris.html#fn.2
[4]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Advising-Functions.html
[5]: https://nickdrozd.github.io/2019/01/14/tetris.html#fnr.1
[6]: https://nickdrozd.github.io/2019/01/14/tetris.html#fnr.2