Tiny Tetris

I wanted to experiment with some barebones graphics, so I bought a Nokia 5110 display and hooked it up to an ATmega328p via the SPI bus. I’m a huge fan of tetris, so my end goal for this project is to create a tiny tetris game that I can put around my keychain or in my wallet.

Driving the Screen

I’ve written a simple driver for the screen from scratch that is focused on maximizing framerate. I don’t mean to toot my own horn, but I’m pretty good at Tetris and I want gameplay to be smooth, fast, and responsive.

Data is sent over the SPI bus to the screen. I elected to write my own driver because I couldn’t find one that used sprite based graphics and had could drive the screen at a high frame rate. Most drivers pushed the entire frame buffer over the SPI bus to the screen to draw each frame, resulting in a slow framerate. The driver I wrote uses the Set “X and Y Address of DDRAM” commands to skip over data in the frame buffer that hasn’t changed and only update changed pixels on the screen.

Animations

Animations are currently being handled by updating the x_pos and y_pos variables in the sprite struct and calling draw_sprite. For example, to move a sprite 10 pixels in the x direction, the following loop can be used:

for(int i = 0; i < 10; i++)
{
  spr.x_pos++;
  draw_sprite(&spr);
}

Here is an example of a tetrimino moving from the top to the bottom of the screen with a small delay in between each frame so it’s easier to see.

Next up I’m going to handle collisions. First I’ll do some tests to make it collide with the ground, then I’ll make it collide with other pieces.

Collisions

Collisions are handled by checking to see if the pixel of the sprite being moved is directly adjacent to an existing pixel in the frame buffer. The best way to explain this is with an example.

Let’s say we have a blue L piece and we want to see if it has collided with anything to the left. Let’s say the piece is currently located at pixel (3,0) on the screen and there are already some other fallen pieces on the screen taking up space in the frame buffer.

The algorithm checks the first sprite pixel at (3,0) and determines that there is in fact an active sprite pixel at that location. It then check the pixel in the frame buffer directly to the left of the first pixel at (2,0) and determines that there is no pixel there. So far, the algorithm has not found a collision.

The algorithm then looks at the sprite pixel a (4,0). It sees that there is already an active sprite pixel directly to the left of it at (3,0), so there cannot possibly be a left collision on this pixel. The algorithm moves on.

Next the algorithm moves checks the sprite pixel at (3,1). There is no active sprite pixel here, so there can’t be a collision.

It then checks the sprite pixel at (4,1), sees no frame buffer pixel to the left, and continues.

It moves down again and checks (3,2). There is a frame buffer pixel here, but there is no sprite pixel here, so there can’t be a collision.

Finally, the algorithm checks the sprite pixel at (4,2). There is an active sprite pixel here. To the algorithms relief, there is also an active frame buffer pixel directly to the left at (3,2)!

Collision data is stored in a collision byte inside of the sprite struct. The first 4 bits are flags that are set when a collision is detected. The first bit is the bottom collision bit, the second is for top collisions, the third is for right collisions, and the fourth is for left collisions.

Controls

To control the pieces onscreen, I hooked up 6 tact switches with pulldown resistors. This allows the player to move left and right, rotate left and right, hard drop, and soft drop. The C pins are used as inputs, active high. I’m not using any analog readings for this project, so these pins will work fine.

Code is pretty simple. The ATmega scans the inputs, sees what’s active, and performs the appropriate action. Everything is handled by the scan_inputs function.

Code is here
https://github.com/granthaack/ttris