In my ever-continuing quest to make games for furcadia, I've taken on tetris this time. For several weeks I've wandered around with the idea, slowly refining the various things I would need until I felt confident that I could make it. I think the dream speaks for itself in how well I succeeded.
This page will not go into full detail on how every aspect of the dream works, but will instead focus on the way I constructed the entire game. I will also deal with a few portions of the DS that I think might be interesting. Other than that I suggest you read the DS as it's fairly well commented (or so I like to think).
The global picture
In short my version of tetris is a state machine with 51 states. These states vary from initialisation to waiting for a furre to advancing a block down the pit or removing lines. We can distinguish between the following states (with the actual states between braces):
- Cleanup (0)
Clear the pit and any variables associated with the game, so that it's ready for the next player.
- Waiting for player (1)
Do nothing except deal with any furres stepping onto the start-tile.
- Process next block (2)
This checks the preview image and switches to the correct state based on what block will follow.
- Initialise block (10-16)
These states will draw the respective blocks, set the variables, move the furre to the right spot and then check whether the new block was drawn over existing blocks (if so, the pit was full so we end the game).
- Advance block (20-21, 30, 40-41, 50-53, 60-61, 70-73, 80-83)
These states deal with the various ways the blocks can be moved. They will automatically move the block down and deal with left/right/turning movements. Note that there's a state for every possible orientation of every block.
- Bypass states (22-23, 31, 42-44, 54-57, 64-67, 74-77, 84-87)
The sole purpose of these states is to bypass one or more states. (For example: if you turn a block in state 20, you don't want to switch directly to state 21, as it would trigger the turning again (only for state 21). Instead we go to state 23, thereby bypassing the state 21 DS block, before the state 23 DS block changes the state to 21).
- Clear lines (90)
This state will clear any full lines and add up the score (if needed).
- Drop blocks (91)
When lines have been cleared we need to move the rest of the pit down. This state takes care of that and checks for a pit-bonus when it's done.
- Game over (100)
End of the game. Display the score to the player, notify the rest and then go to state 0 to reset it all for the next player.
The entire game functions by switching between these states at the appropriate times. To make sure that there were no unexpected surprised while switching between states, I made a couple of rules that each block of DS needs to adhere to, which I will list below.
Keeping it correct
Throughout the DS you will notice that one variable appears in just about every location: %blockpos. This variable contains the position of the 'center' of the current block. Each block of DS has been designed in such a way that this ALWAYS holds (even though it wasn't needed in a few cases). I did this to ensure that I could always trust the contents of %blockpos, no matter what state preceded the current one.
Coupled with this requirement was the fact that, if a block of DS altered %blockpos, it would also have to draw the block in the right position in the pit. This to ensure that %blockpos stayed the 'center' of the drawn block.
Similar to %blockpos, %furpos maintains the position of the furre-avatar in the game. The rules for %furpos are that it is always in the third column of the board (experimentation showed to me that that column provided best visibility) and that %furpos is always level with %blockpos, so when %blockpos moves down, %furpos does too. And as before, if a block of DS changes %furpos then it should move the furre to that position as well.
The structure of the pit
You might have noticed that, while playing the game, you don't see the furre walkabout anywhere. The trick I used to accomplish this is to make the actual tetris-pit float above the furre. This is also the reason why wings and such are advised against, because these will poke through the pit, destroying the illusion that it's only the block that's moving. Just take a look at items 0-16 in the iteme.fsh file, as these make up the pit.
But not only the objects for the pit are special. The floor is special too. I use floortiles 500 and 501 (custom designs), with 500 meaning that there is no fixed block in that position and 501 indicating that a fixed block is already occupying that space. This can be used to check whether a direction is clear for movement or not (more on this when I discuss the way I moved the blocks). Tiles 500 and 501 are unwalkable to make sure that the furre can't move away from his designated spot under the pit. Though I could've just moved him back whenever he tried to move, this way of doing things avoids some ugly 'bouncing' on the screen. Whenever a furre needs to move I temporarily switch to 502 and 503 (walkable varieties of 500 and 501), move the furre, then switch them back to 500 and 501.
Moving the blocks
Before you can move a block in any direction, you have to make sure it's allowed to move there first. The trick I used here was to add up the floortiles below all the spaces where the block would move into. I could then check this value to see whether the region was empty, because it would have to equal a power of 500. So for example: when you move down a horizontal yellow block, you have to check all four spaces below it before you can move it down. By adding up the floortiles, I would get a value of 2000 if it was empty or a value from 2001 to 2004 if one of the spots already held a fixed block.
If you take a look at the DS, you'll see that most movements have two blocks of DS attached. The first block is the one that adds up the floortiles at whatever spots the block would move into. The second block checks this value and performs an action based upon it. In most cases you only want something to happen when the new position is empty. The only exception is when a block moves down, as it should become fixed the moment it hits the pit-edge or another fixed block.
Removing filled lines
To easily check whether a line was filled I added an extra column outside the pit. Within this column the floortiles would indicate how many blocks occupied that specific line. Since the pit is 8 blocks wide, a value of 8 in any field in this column would indicate that it was full and that it should be removed.
This could be done by checking every single line in the pit, but I decided on a slightly shorter, though more complex, solution. Instead of checking every single line, I only check the lines to which the block might have been added. This way I only need to check four lines instead of fifteen.
Depending on whether it removed any empty lines, the following two things could happen: either no lines were removed, in which case we return to state 2 to process the next block or one or more lines were removed, which means that there might be blocks that need moving.
Moving the blocks was simply the most tedious part of the DS. While in state 91, I check all the lines each frame to see whether there might be might be an empty line directly below one with blocks. If so I move that entire line down one notch before moving on to the line above. If done often enough, you'll reach a point where there are no more empty lines with a non-empty line above it, in which case you can move onto the next block.
Once this algorithm was done, it was also a very good spot to check for a pit-bonus. We only need to check one value and that is the block-count for the bottomline. If it is zero, then all lines above it must be zero as well (this follows from the algorithm), so the pit must be empty, so we can award the pit-bonus.
I've tried to explain the way I implemented this version of tetris, though I can't help but feel that nobody will understand a word I wrote. If you have questions or think I have missed something, feel free to email me or contact me on any of the messengers. I'll try to answer your question and, if I think it's really something I missed, I might even update this page. For now, I bid you adieu and wish you lots of fun playing the game.