{JxLab} Pytris

我想很多程序员之所以与程序结下不解之缘,最大的原因就是小时候被电子游戏里神奇的声光效果勾起了探索的好奇心,至少我是这样。制作游戏是我一直以来的一个愿望,感谢 Python 让我有了梦想成真的机会。

大名鼎鼎的 Tetris(俄罗斯方块)应该很少有人没玩过吧,经典的七个方块造型和背景音乐可以说无人不晓。而且这个游戏本身设计并不算非常复杂,做为编程练习也非常合适。我花了很长时间学习了 Tetromino (一个 Tetris 游戏的 Python 版)的代码,然后把这个游戏重构了一遍。我把重构之后的游戏取名为 Pytris,即 Python + Tetris。

Pytris

Pytris

Tetris 游戏的关键在于方块之间的判断,以及方块在屏幕上位置的计算。states.py 中的 Play 类就是用来实现这些功能的。

class Play(State):
    """
    This is where the main part of the game.
    """
    lastMoveDownTime     = time.time()
    lastMoveSidewaysTime = time.time()
    lastFallTime         = time.time()

<span class="n">movingDown</span>  <span class="o">=</span> <span class="bp">False</span>
<span class="n">movingLeft</span>  <span class="o">=</span> <span class="bp">False</span>
<span class="n">movingRight</span> <span class="o">=</span> <span class="bp">False</span>

<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">score</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">board</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getNewBoard</span><span class="p">()</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">score</span> <span class="o">=</span> <span class="n">score</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">level</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">calculateLevel</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">score</span><span class="p">)</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">fallFreq</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">calculateFallFreq</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">level</span><span class="p">)</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">curPiece</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getNewPiece</span><span class="p">()</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">nextPiece</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getNewPiece</span><span class="p">()</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">playMusic</span><span class="p">()</span>

<span class="k">def</span> <span class="nf">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event</span><span class="p">):</span>
    <span class="k">pass</span>

<span class="k">def</span> <span class="nf">firstDisplay</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">screen</span><span class="p">):</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">screen</span> <span class="o">=</span> <span class="n">screen</span>
    <span class="n">screen</span><span class="o">.</span><span class="n">fill</span><span class="p">(</span><span class="n">BGCOLOR</span><span class="p">)</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">drawBoard</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">board</span><span class="p">)</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">drawStatus</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">score</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">level</span><span class="p">)</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">drawNextPiece</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">nextPiece</span><span class="p">)</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">drawPiece</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">curPiece</span><span class="p">)</span>
    <span class="n">pygame</span><span class="o">.</span><span class="n">display</span><span class="o">.</span><span class="n">update</span><span class="p">()</span>

<span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">game</span><span class="p">):</span>
    <span class="k">pass</span>

<span class="k">def</span> <span class="nf">display</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">screen</span><span class="p">):</span>
    <span class="n">screen</span><span class="o">.</span><span class="n">fill</span><span class="p">(</span><span class="n">BGCOLOR</span><span class="p">)</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">drawBoard</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">board</span><span class="p">)</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">drawStatus</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">score</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">level</span><span class="p">)</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">drawNextPiece</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">nextPiece</span><span class="p">)</span>
    <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">curPiece</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">drawPiece</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">curPiece</span><span class="p">)</span>
    <span class="n">pygame</span><span class="o">.</span><span class="n">display</span><span class="o">.</span><span class="n">update</span><span class="p">()</span></code></pre></figure>

handle 方法处理输入控制,update 方法处理方块的坐标更新,display 方法处理游戏画面的更新。这是 Play 类中最主要的三个方法,因为代码太长就不在这贴出来了。

另外, 游戏中的方块是以“块”为单位绘制和移动的而不是像素,所以需要把背板(也就是游戏中方块能在里面移动的区域)按块重新划分。并且在绘制方块的时候,把方块所处的格子重新转换回像素坐标,这样才能正确绘制。

def convertToPixelCoords(self, x, y):
    # Convert the given x,y coordinates of the board to x,y coordinates of
    # the location on the screen.
    return (XMARGIN + (x  BLOCKSIZE)), ((BOARDTOP + (y  BLOCKSIZE)))

convertToPixelCoords 方法就是用来做转换的。计算方法很简单,将方块在背板的上坐标 (x, y) 分别乘以单位方块的大小 BLOCKSIZE,再分别加上背板与窗口左侧与上方的距离即可求出方块的像素坐标。

Pytris 使用了 Pygame 这个 Python module 做游戏引擎。Pygame 是个不错的游戏制作套件,有非常完善的文档和活跃的社区。如果你也想用 Python 制作小游戏,不妨试试 Pygame。

Pytris 的源码和安装方法等等我都放到了 Github 上 —— https://github.com/Vayn/Pytris,欢迎各位 clone。

最后,感谢 Tetromino 的作者 Al Sweigart

EOF