MIX09’ing it up with a Tetris Version of Flotzam
For MIX09, our team decided to create a new look for Flotzam that was to be shown prior to the keynotes. We’d thought about possibly creating a Vegas slot machine version, but we ended up choosing Tetris, since it was very much in line with the #MIX09 theme. It sounded like an exciting project, so I jumped on it! Tim Aidlin designed the user experience and the look and feel, and Karsten Januszewski modified the data object model to support binding a single data template type to any one of the available Flotzam data sources collected from the various feeds: Twitter, Flickr, RSS, etc.
We brainstormed about what we wanted it to do, and settled on creating a somewhat simplified version of Tetris that avoided rotating the blocks. Flotzam Tetris needed to specifically target the 16x9 format used in the MIX09 keynote screens. This translated into 1280px by 720px dimensions, a fairly low resolution by today’s standards, which forced us to experiment with shape sizes. We needed to keep a careful balance between ensuring readability of the text in the back row of the keynote sessions while looking esthetically pleasing; the less blocks on screen, the less compelling it appears.
The Tetris logic itself was simple enough; I found several samples on the web to draw from. I chose to borrow some ideas from the CodePlex WPF Tetris that Karsten showed me, but this only got us about halfway. Absent was how to programmatically solve which Tetris blocks should fall based on the pieces that had already fallen. There are probably a few ways to solve this problem. I chose to take a commonly used game approach of using a buffer for the Tetris shapes defined as an array of integers, which I’ll call a table. Zero represents an empty space, and a one represents a space occupied by a block, the fundamental unit that makes up Tetris shapes. It turned out to be convenient to use integers since it very was handy to add up columns and rows for calculating when a row was full or calculating the total height of a stack of shapes for a particular column.
The shapes are defined in the TetrisShape class. The individual blocks that compose a shape are represented in a Point array offset from a (0,0) origin. The TetrisShape class also contains a Position property that maintains the absolute position of a TetrisShape shape in the table.
There are two other interesting parts to the TetrisShape class. The first is a Weight property that allows us to define how important the shape is, so that we can get a better spread of shape usage. It’s used in determining the probability that it will be chosen as the “next” shape to be dropped. The second is the ShapeSignature() method which returns a series of numbers, as a string, that define the “signature” of the bottom of a shape. So for example a 2 x 2 square would have a signature of “00”, or a 3 x 2 Tee shape would have a signature of “101”. The numbers represent the distance to the bottom block from an imaginary line running under the shape. This reason that this signature is returned as a string is so that string operators can be used to match the signature of a shape to the signature of all the other shapes settled in the table.
Fig. 1: Example of the signature of a Square shape vs. a Tee shape.
The GetShapeNeededInTable() method returns the signature of entire width of the bottom of the table. This is a function that examines the height of the columns of the settled shapes in the table to obtain the signature. Once we have this, we can see if a shape can fit in the table by simply calling: ShapeNeeded.Contains(ts.ShapeSignature()). If the call returns true it’s a suitable shape and the shape is stored it in a temporary list that gets sorted based on the TetrisShape Weight property. If there’s more than one shape in the temporary list it randomly chooses one of the shapes with the two highest weights and returns the chosen shape.
The shape chosen then gets bound to the data source and gets added to the VisualTree and displayed on screen. When a row is completed it causes all the “settled” shapes to drop down one level. If a shape drops below the bottom threshold it gets removed from the VisualTree and also the table shape list to prevent a memory leak.
The main loop is the RunTetrisFlotzam() method, which is called at regular intervals by a timer (.7 seconds in by default.) This method contains a method that determines whether a shape can drop down or whether it is “settled” it also calls a function that animates the shapes from one position to the next in a programmatic manner. We wanted the animation to have easing, but it would take a bit of reorganization of the code to make this happen.