ATmega168 Snake

Tuesday, 24th November 2009

In addition to the Tetris game from the previous post, I've added an implementation of snake to the ATmega168 project.

Either game can be selected from a menu that appears when the circuit is powered on. To exit menus I've added a second fire button; this allows you to step back to the main menu to pick a different game if need be. The source code and binary can be downloaded as before.

I've written a number of different Snake implementations in the past. The early versions used a single array to represent every cell that the snake's body lay in (head as the first element, tail as the last element) that I would manually shift every frame and resize when the snake ate some food. This gets slower and slower as the snake gets longer, which isn't very good. When I wrote a version for the TI-83+ calculator in BBC BASIC, I switched to using a ring buffer with a pointer to the head element and another to the tail element that would be shunted along every frame, unless the snake ate some food in which case the tail pointer would stay where it was.

As I have even less memory on the ATmega168, I went for a different tactic again; by using "pretty" graphics for the various parts of the snake in the tilemap, I didn't need to store the snake's path anywhere other than this tilemap. That is, if I wanted to advance the tail one unit, I merely need to look at the current tile graphic being used to represent the tail (which will be pointing up, down, left or right) and follow it along to the tile in front of it. By inspecting this tile, I can see if the snake turned a corner at that point or went straight ahead and so adjust the tail position and graphic accordingly.

void advance_tail(void) {
    // Find the current snake tail graphic.
    char tail = tvtext_buffer[tail_y * TVTEXT_BUFFER_WIDTH + tail_x];
    // Where is the body in relation to the tail?
    int8_t body_x = tail_x, body_y = tail_y;
    switch (tail) {
        case FONT_SNAKE_TAIL_UP:
            --body_y;
            break;
        case FONT_SNAKE_TAIL_DOWN:
            ++body_y;
            break;
        case FONT_SNAKE_TAIL_LEFT:
            --body_x;
            break;
        case FONT_SNAKE_TAIL_RIGHT:
            ++body_x;
            break;
    }
    // Ensure the body is on the buffer.
    if (body_x < WORLD_LEFT) body_x = WORLD_RIGHT;
    if (body_x > WORLD_RIGHT) body_x = WORLD_LEFT;
    if (body_y < WORLD_TOP) body_y = WORLD_BOTTOM;
    if (body_y > WORLD_BOTTOM) body_y = WORLD_TOP;
    // Find the current body graphic.
    char body = tvtext_buffer[body_y * TVTEXT_BUFFER_WIDTH + body_x];
    // Is it a bend? If so, we'll need to rotate the tail graphic.
    switch (body) {
        case FONT_SNAKE_BODY_DOWN_RIGHT:
            tail = (tail == FONT_SNAKE_TAIL_UP) ? FONT_SNAKE_TAIL_RIGHT : FONT_SNAKE_TAIL_DOWN;
            break;
        case FONT_SNAKE_BODY_DOWN_LEFT:
            tail = (tail == FONT_SNAKE_TAIL_UP) ? FONT_SNAKE_TAIL_LEFT : FONT_SNAKE_TAIL_DOWN;
            break;
        case FONT_SNAKE_BODY_UP_RIGHT:
            tail = (tail == FONT_SNAKE_TAIL_DOWN) ? FONT_SNAKE_TAIL_RIGHT : FONT_SNAKE_TAIL_UP;
            break;
        case FONT_SNAKE_BODY_UP_LEFT:
            tail = (tail == FONT_SNAKE_TAIL_DOWN) ? FONT_SNAKE_TAIL_LEFT : FONT_SNAKE_TAIL_UP;
            break;
    }
    // Erase the old tail.
    tvtext_buffer[tail_y * TVTEXT_BUFFER_WIDTH + tail_x] = tvtext_cleared;
    // Draw the new tail.
    tail_x = body_x;
    tail_y = body_y;
    tvtext_buffer[tail_y * TVTEXT_BUFFER_WIDTH + tail_x] = tail;
}

Similar code is used to advance the head and draw the correct tile behind it.

On an unrelated note, I've released a version of BBC BASIC that should run on the Nspire. The Nspire has an emulator on it to run applications for other calculators, but this emulator doesn't implement undocumented instructions. The TI-83+/TI-84+ BBC BASIC host interface makes use of the sl1 instruction, which shifts a register left one bit and sets the least significant bit to 1. Unfortunately, when this code is run on an Nspire it triggers a crash. Apparently the quick fix I've implemented seems to have done the trick, so unless I hear any further bug reports I'll release the latest version formally soon!

ATmega168 Tetris

Sunday, 22nd November 2009

The tvText library I discussed last entry allows you to display text on a PAL TV in black and white using a 20MHz ATmega168 and a pair of resistors. If this doesn't sound terribly exciting, it's probably because it isn't. However, if you bear some limitations in mind and change the font, you can use this text output as a more general tile-mapping system and use it for games that employ simple graphics.

The new circuit, featuring five sloppily-wired input buttons.
The new circuit, featuring five sloppily-wired input buttons.

I added five buttons to the test circuit — up, down, left, right and fire — to act as game input. This circuit is shown in the photograph above. I also added support for 8×8 characters alongside the existing 6×8 characters to the library, set as a compile-time option. This drops the number of characters per line from 32 to 24, but having square tiles makes producing graphics much easier. The reduction in size of the text buffer also frees up more of the precious 1KB of SRAM for the game!

Diagram of the game circuit.

Even though it was always recommended as an excellent game for beginners to write, I don't believe I've ever written a Tetris clone before. Its simple block graphics makes it an ideal candidate for this system, and it always helps to work on a game that's fun to play. Armed with a Game Boy and a stopwatch I attempted to recreate a moderately faithful version of what is probably the most popular rendition of the game.

I think the result plays pretty well, but don't take my word for it — if you have an ATmega168 lying around, you can download the source and binaries here.

Subscribe to an RSS feed that only contains items with the AVR TV game tag.

FirstLast RSSSearchBrowse by dateIndexTags