# 003 Data Structures
Before we delve any deeper, it’s a good idea to stop and think about the data structures we’ll need to manage our game.
Ignoring UI cruft for now, the core of Tetris is a 10×20 well, and seven tetriminoes. For the sake of simplicity, it’s probably easiest to hold this in 2D arrays of numbers, like so:
well = [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # 19 more rows... ] S_piece = [ [ [1, 0], [1, 1], [0, 1], ], [ [0, 1, 1], [1, 1, 0], ], ]
Within this grid, a 0 is empty space, while a number 1-7 is a block. By giving each tetrimino a different number for its blocks, we will be able to easily track which type of tetrimino a given block came from, and will be able to color it appropriately.
In addition, there is a fair amount of other data we must keep track of:
- Current score
- Current level
- The current piece, and its coordinates
- The next piece
The simplest solution I see is a GameState object containing the well, score, level, and references to our current and next pieces. The tetriminoes themselves will have a module of their own.
And with that, we have an idea of where to begin.
# 004 On Testing…
If you’re following along and are not familiar with Test-Driven Development(TDD), take the time now to go read up on it, both its support and criticism.
TDD is a topic that inspires holy wars on the same scale as OS preferences and static/dynamic typing. I don’t have a particularly strong opinion either way. However, TDD does help you catch stupid mistakes earlier, and encourages tidy code, so I’m going to use it for this project.
We’ll be using Python’s builtin unittest module for writing our test cases. To actually run the test suite, there are plenty of options available — nosetests, py.test, etc. I use a custom framework (details, for those interested, in another post), and will be building my tests accordingly. Those of you following along are, of course, free to use whatever you want.
To start, we’ll write a sanity check test to get our framework up and running:
# tests/test_sanity.py import unittest class TestSanity(unittest.TestCase): def test_fail(self): self.assertTrue(False) def test_pass(self): self.assertTrue(True)
Now, to actually run these tests with our framework, we need to add them to run_tests.py:
#!/usr/bin/env python # -*- coding: utf-8 -*- from lib.test_runner import ModuleTestRunner from tests.test_sanity import TestSanity suite = ModuleTestRunner() suite.addTestList("Sanity", [TestSanity("test_fail"), TestSanity("test_pass"), ]) if __name__ == '__main__': suite.run()
Finally, we run the tests in the terminal:
$ chmod +x run_tests.py $ ./run_tests.py Running tests: SANITY: F. ------------ In test_fail (tests.test_sanity.TestSanity) Traceback (most recent call last): File ".../tetris/tests/test_sanity.py", line 8, in test_fail self.assertTrue(False) AssertionError: False is not true ------------ 2 tests run in 0.000280 seconds, 1 failures, 0 skipped, 0 unexpected errors.
(Note: chmod +x makes run_tests.py executable, so we don’t have to prefix ‘python’ every time we want to run the suite.)
One failure, one pass, and our test suite is properly set up. Remove/comment out the import and references to TestSanity in run_tests.py. (If you’re using nosetests or another test-discovery framework, you will probably want to delete or rename test_sanity.py.)
$ git add . $ git status ... $ git commit -m "Setup test framework"
This post is getting long, so we’ll stop here for today. Next time we’ll get a basic window on screen, and start implementing the data structures mentioned earlier.