Arduino Pac-Man part 4 – maze

In the first entry of this series, I explained how I got started playing with Arduino video output. In the second update, I discussed getting the first TV output displayed. In part 3, I learned how to display bitmaps and animate a simple character. Today, I begin discussing how I built the maze for a Pac-Man style game.

Puck-Man was an arcade game released in Japan in 1980. When it was brought over to America, the name was changed to Pac-Man to avoid having a name that rhymed with a dirty word. To read more on the history of Pac-Man, check out the Wikipedia article:

http://en.wikipedia.org/wiki/Pac-Man

To create an Arduino Pac-Man, I would need to recreate the maze. I was originally going to try to draw out the maze in binary (see part 3). I looked for a screen shot of the actual Pac-Man arcade game so I could recreate the maze as closely as possible. I did a Google image search and found a screen shot like this one from the wikipedia entry:

The tiny image was 224×288 because that was the actual resolution of the arcade game. Many arcade games used standard monitors turned sideways so they were in portrait mode (taller). I would be using a standard landscape TV display and TVout video with a resolution of 120×96. If I was willing to turn a TV set sideways, I could almost make the maze work just by trimming everything in half. Instead, I decided I would get rid of the score at the top, and the lives left/level display at the bottom, and move them to the right side of the screen. This is a common approach taken by home versions of arcade games designed to play on sideways monitors.

I loaded up the image in a graphics program and cropped off the top and bottom, leaving me with something like this:

Since I was having problems trying to draw this out pixel by pixel in binary, I decided to scale the image down to the desired height (96) and then zoom in to it so I could get a closer look at where the pixels should go. This would produce an 87×96 pixel image, leaving 33 pixels to the size of the image to move the score and other items. (The size would have to be adjusted to 86×95 for some reasons I have already forgotten.)

My scaled image looked like this:

That was a bit too tiny to work on, so I zoomed in to 800% and started inspecting the layout.

Now I was able to try to count dots and start recreating the level. I was too lazy to try to recreate this by hand in binary code, so I made a new layer to draw pixels on, and began drawing out the maze. It looked like this (you will notice I was actually using a different screen shot than the Wikipeida page example I showed above):

Since the maze is a mirror image, I was only doing one side of the screen. When I got it done, I would just copy that half, flip it and paste it to the other side. The process to do this was a bit time consuming as I was figuring spacing out. Originally, I thought the sides and thing parts of the maze would be solid (two pixels side by side) but as I did the math, I realize I could make the thin sections of the maze have one pixel space, and the thicker portions have two pixel space, and it will all work out. I did quite a bit of trail and error as I adjusting things to make sure the spacing of every hallway was consistent.

I created a Pac-Man graphic object so I could move it around the maze to test clearance. In my TVout experiment, I was making an 8×7 Pac-Man. To fit in this maze, the character would have to be 5×5, as would the ghosts. Eventually, I had a maze created, including where the dots would go (even the correct number of them). My finished product looked like this:

Screenshot 2014-02-09 16.00.46

The grid lines were set to the spacing of an individual pixel, to help with drawing. You will see the test Pac-Man bitmaps as well as a ghost, which I would then move around the screen in the graphics editor to make sure all the tunnels and hallways were the same size. Eventually, I produced this final graphic:

You may notice that there is some extra space on the right side. I kept the final output to be a multiple of 8-pixels (one per byte) because I thought it might make things easier for what comes next.

At this point, I could have used this graphic and recreated it in binary source code, but I decided to use a program that would convert a graphics file to C data structures. The TVout site suggests a program, but I would have had to compile it and run it from a command line. Instead, a quick Google led me to this site:

http://www.cemetech.net/sc

This web page allows you to upload a graphics file and have it translated in to a variety of formats. It seems to be for graphic calculators, but one of the options was “Prizm/Nspire C (1/2/4/8-Bit Palettized Color)” which did the trick. I uploaded my bitmap graphics file, selected that conversion option, and then it produced the following source code:

1-Bit Palettized Color: Each pixel takes 1 bits.

const color_t sprite_palette[2] = {0x0000, 0xffff};
	const unsigned char sprite[1045] = {
		0x3f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,
		0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,
		0x9f,0xff,0xff,0xff,0xff,0x87,0xff,0xff,0xff,0xff,0xe4,
		0xa0,0x00,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x00,0x14,
		0xa0,0x00,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x00,0x14,
		0xa4,0x92,0x49,0x24,0x92,0x49,0x24,0x92,0x49,0x24,0x94,
		0xa0,0x00,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x00,0x14,
		0xa0,0x00,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x00,0x14,
		0xa4,0x7f,0x88,0xff,0xe2,0x49,0x1f,0xfc,0x47,0xf8,0x94,
		0xa0,0x80,0x41,0x00,0x10,0x48,0x20,0x02,0x08,0x04,0x14,
		0xa4,0x80,0x41,0x00,0x10,0x48,0x20,0x02,0x08,0x04,0x94,
		0xae,0x80,0x49,0x00,0x12,0x49,0x20,0x02,0x48,0x05,0xd4,
		0xa4,0x80,0x41,0x00,0x10,0x48,0x20,0x02,0x08,0x04,0x94,
		0xa0,0x80,0x41,0x00,0x10,0x48,0x20,0x02,0x08,0x04,0x14,
		0xa4,0x7f,0x88,0xff,0xe2,0x31,0x1f,0xfc,0x47,0xf8,0x94,
		0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,
		0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,
		0xa4,0x92,0x49,0x24,0x92,0x49,0x24,0x92,0x49,0x24,0x94,
		0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,
		0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,
		0xa4,0x7f,0x88,0xc4,0x7f,0xff,0xf8,0x8c,0x47,0xf8,0x94,
		0xa0,0x80,0x41,0x20,0x80,0x00,0x04,0x12,0x08,0x04,0x14,
		0xa0,0x80,0x41,0x20,0x80,0x00,0x04,0x12,0x08,0x04,0x14,
		0xa4,0x7f,0x89,0x24,0x7f,0xcf,0xf8,0x92,0x47,0xf8,0x94,
		0xa0,0x00,0x01,0x20,0x00,0x48,0x00,0x12,0x00,0x00,0x14,
		0xa0,0x00,0x01,0x20,0x00,0x48,0x00,0x12,0x00,0x00,0x14,
		0xa4,0x92,0x49,0x24,0x92,0x49,0x24,0x92,0x49,0x24,0x94,
		0xa0,0x00,0x01,0x20,0x00,0x48,0x00,0x12,0x00,0x00,0x14,
		0xa0,0x00,0x01,0x20,0x00,0x48,0x00,0x12,0x00,0x00,0x14,
		0xbf,0xff,0x89,0x1f,0xe0,0x48,0x1f,0xe2,0x47,0xff,0xf4,
		0x80,0x00,0x41,0x00,0x10,0x48,0x20,0x02,0x08,0x00,0x04,
		0x7f,0xfe,0x41,0x00,0x10,0x48,0x20,0x02,0x09,0xff,0xf8,
		0x00,0x01,0x49,0x1f,0xe0,0x30,0x1f,0xe2,0x4a,0x00,0x00,
		0x00,0x01,0x41,0x20,0x00,0x00,0x00,0x12,0x0a,0x00,0x00,
		0x00,0x01,0x41,0x20,0x00,0x00,0x00,0x12,0x0a,0x00,0x00,
		0x00,0x01,0x49,0x20,0x00,0x00,0x00,0x12,0x4a,0x00,0x00,
		0x00,0x01,0x41,0x20,0x00,0x00,0x00,0x12,0x0a,0x00,0x00,
		0x00,0x01,0x41,0x20,0x00,0x00,0x00,0x12,0x0a,0x00,0x00,
		0x00,0x01,0x49,0x20,0xff,0xff,0xfc,0x12,0x4a,0x00,0x00,
		0xff,0xfe,0x41,0x20,0xff,0x03,0xfc,0x12,0x09,0xff,0xfc,
		0x00,0x00,0x41,0x20,0xc0,0x00,0x0c,0x12,0x08,0x00,0x00,
		0xff,0xff,0x88,0xc0,0xc0,0x00,0x0c,0x0c,0x47,0xff,0xfc,
		0x00,0x00,0x00,0x00,0xc0,0x00,0x0c,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0xc0,0x00,0x0c,0x00,0x00,0x00,0x00,
		0x00,0x00,0x08,0x00,0xc0,0x00,0x0c,0x00,0x40,0x00,0x00,
		0x00,0x00,0x00,0x00,0xc0,0x00,0x0c,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0xc0,0x00,0x0c,0x00,0x00,0x00,0x00,
		0xff,0xff,0x88,0xc0,0xc0,0x00,0x0c,0x0c,0x47,0xff,0xfc,
		0x00,0x00,0x41,0x20,0xc0,0x00,0x0c,0x12,0x08,0x00,0x00,
		0xff,0xfe,0x41,0x20,0xff,0xff,0xfc,0x12,0x09,0xff,0xfc,
		0x00,0x01,0x49,0x20,0xff,0xff,0xfc,0x12,0x4a,0x00,0x00,
		0x00,0x01,0x41,0x20,0x00,0x00,0x00,0x12,0x0a,0x00,0x00,
		0x00,0x01,0x41,0x20,0x00,0x00,0x00,0x12,0x0a,0x00,0x00,
		0x00,0x01,0x49,0x20,0x00,0x00,0x00,0x12,0x4a,0x00,0x00,
		0x00,0x01,0x41,0x20,0x00,0x00,0x00,0x12,0x0a,0x00,0x00,
		0x00,0x01,0x41,0x20,0x00,0x00,0x00,0x12,0x0a,0x00,0x00,
		0x00,0x01,0x49,0x20,0x7f,0xff,0xf8,0x12,0x4a,0x00,0x00,
		0x7f,0xfe,0x41,0x20,0x80,0x00,0x04,0x12,0x09,0xff,0xf8,
		0x80,0x00,0x41,0x20,0x80,0x00,0x04,0x12,0x08,0x00,0x04,
		0xbf,0xff,0x88,0xc0,0x7f,0xcf,0xf8,0x0c,0x47,0xff,0xf4,
		0xa0,0x00,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x00,0x14,
		0xa0,0x00,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x00,0x14,
		0xa4,0x92,0x49,0x24,0x92,0x49,0x24,0x92,0x49,0x24,0x94,
		0xa0,0x00,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x00,0x14,
		0xa0,0x00,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x00,0x14,
		0xa4,0x7f,0x88,0xff,0xe2,0x49,0x1f,0xfc,0x47,0xf8,0x94,
		0xa0,0x80,0x41,0x00,0x10,0x48,0x20,0x02,0x08,0x04,0x14,
		0xa0,0x80,0x41,0x00,0x10,0x48,0x20,0x02,0x08,0x04,0x14,
		0xa4,0x7c,0x48,0xff,0xe2,0x31,0x1f,0xfc,0x48,0xf8,0x94,
		0xa0,0x02,0x40,0x00,0x00,0x00,0x00,0x00,0x09,0x00,0x14,
		0xa4,0x02,0x40,0x00,0x00,0x00,0x00,0x00,0x09,0x00,0x94,
		0xae,0x92,0x49,0x24,0x92,0x01,0x24,0x92,0x49,0x25,0xd4,
		0xa4,0x02,0x40,0x00,0x00,0x00,0x00,0x00,0x09,0x00,0x94,
		0xa0,0x02,0x40,0x00,0x00,0x00,0x00,0x00,0x09,0x00,0x14,
		0xbe,0x12,0x48,0xc4,0x7f,0xff,0xf8,0x8c,0x49,0x21,0xf4,
		0x81,0x02,0x41,0x20,0x80,0x00,0x04,0x12,0x09,0x02,0x04,
		0x81,0x02,0x41,0x20,0x80,0x00,0x04,0x12,0x09,0x02,0x04,
		0xbe,0x11,0x89,0x24,0x7f,0x8f,0xf8,0x92,0x46,0x21,0xf4,
		0xa0,0x00,0x01,0x20,0x00,0x48,0x00,0x12,0x00,0x00,0x14,
		0xa0,0x00,0x01,0x20,0x00,0x48,0x00,0x12,0x00,0x00,0x14,
		0xa4,0x92,0x49,0x24,0x92,0x49,0x24,0x92,0x49,0x24,0x94,
		0xa0,0x00,0x01,0x20,0x00,0x48,0x00,0x12,0x00,0x00,0x14,
		0xa0,0x00,0x01,0x20,0x00,0x48,0x00,0x12,0x00,0x00,0x14,
		0xa4,0x7f,0xff,0x3f,0xe2,0x49,0x1f,0xf3,0xff,0xf8,0x94,
		0xa0,0x80,0x00,0x00,0x10,0x48,0x20,0x00,0x00,0x04,0x14,
		0xa0,0x80,0x00,0x00,0x10,0x48,0x20,0x00,0x00,0x04,0x14,
		0xa4,0x7f,0xff,0xff,0xe2,0x31,0x1f,0xff,0xff,0xf8,0x94,
		0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,
		0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,
		0xa4,0x92,0x49,0x24,0x92,0x49,0x24,0x92,0x49,0x24,0x94,
		0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,
		0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,
		0x9f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xe4,
		0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,
		0x3f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf0
	};

Look familiar? We do not need the “const color_t sprite_palette” structure, and only need to make a minor change to the other one (adding PROGMEM so it is stored in Arduino flash rather than RAM, and adding the width and height values as the start):

PROGMEM const unsigned char playfield[] = {
  88,95,
  0x3f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,
  0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,
  ...

This gave me a bitmap I could display on startup with “TV.bitmap(0, 0, play field)”. I created the new Pac-Man 5×5 bitmaps by hand, as well as the ghosts. For Pac-man, he can face four directions, so I decided to add another dimension to the array: direction. Then, each direction had four frames. And each frame had the array of actual bitmap data. Fun! It looked like this:

#define PLAYERW    5
#define PLAYERH    5
#define PDIRS      2
#define PFRAMES    4
PROGMEM const unsigned char player[PDIRS][PFRAMES][PLAYERH+2] = {
  { // Right
    {
      PLAYERW,PLAYERH,  // width, height
      //12345---
      0b01110000,
      0b11111000,
      0b11111000,
      0b11111000,
      0b01110000
    }
    ,
    {
      PLAYERW,PLAYERH,  // width, height
      //12345---
      0b01110000,
      0b11111000,
      0b11000000,
      0b11111000,
      0b01110000
    }
    ,
    {
      PLAYERW,PLAYERH,  // width, height
      //12345---
      0b01110000,
      0b11100000,
      0b11000000,
      0b11100000,
      0b01110000
    }
    ,
    {
      PLAYERW,PLAYERH,  // width, height
      //12345---
      0b01110000,
      0b11111000,
      0b11000000,
      0b11111000,
      0b01110000
    }
  }
  ,
  { // Left
    {
      PLAYERW,PLAYERH,  // width, height
      //12345---
      0b01110000,
      0b11111000,
      0b11111000,
      0b11111000,
      0b01110000
    }
    ,
    {
      PLAYERW,PLAYERH,  // width, height
      0b01110000,
      0b11111000,
      0b00111000,
      0b11111000,
      0b01110000
    }
    ,
    {
      PLAYERW,PLAYERH,  // width, height
      //12345---
      0b01110000,
      0b00111000,
      0b00011000,
      0b00111000,
      0b01110000
    }
    ,
    {
      PLAYERW,PLAYERH,  // width, height
      //12345---
      0b01110000,
      0b11111000,
      0b00111000,
      0b11111000,
      0b01110000
    }
  }
  ,
};

Since I was just making things up as I went (I started working on this around 10pm or so, and spent a few hours on it), I probably will clean things up in the code I present in these articles. In the above example, the bitmaps are 0=right, 1=left, 2=up and 3=down. I think I may reorder them to follow the “NEWS” format (0=north/up, 1=wast/right, 2=west/left, and 3=south/down) just so there is a pattern. Not that it really matters, since a good program would be using #defines for the directions anyway, so the magic numbers won’t be seen.

For the ghosts, since there are no colors, and the resolution is so small you can’t really even do tricks with checkerboard patterns, I decided all the ghosts needed were two frames (to animate the bottoms), and four bitmaps to represent the ghost looking up, down, left or right. I am still not sure I like the bottom of the ghosts, but here is the data for them:

#define GHOSTS     1
#define GHOSTW     5
#define GHOSTH     5
#define GDIRS      4
#define GFRAMES    2
PROGMEM const unsigned char ghost[GDIRS][GFRAMES][GHOSTH+2] = {
  { // Right
    {
      GHOSTW,GHOSTH,  // width, height
      //12345---
      0b01110000,
      0b11111000,
      0b11010000,
      0b11111000,
      0b10101000
    }
    ,
    {
      GHOSTW,GHOSTH,  // width, height
      //12345---
      0b01110000,
      0b11111000,
      0b11010000,
      0b11111000,
      0b01010000
    }
  }
  ,
  { // Left
    {
      GHOSTW,GHOSTH,  // width, height
      //12345---
      0b01110000,
      0b11111000,
      0b01011000,
      0b11111000,
      0b10101000
    }
    ,
    {
      GHOSTW,GHOSTH,  // width, height
      //12345---
      0b01110000,
      0b11111000,
      0b01011000,
      0b11111000,
      0b01010000
    }
  }
  ,
  { // Up
    {
      GHOSTW,GHOSTH,  // width, height
      //12345---
      0b01110000,
      0b10101000,
      0b11111000,
      0b11111000,
      0b10101000
    }
    ,
    {
      GHOSTW,GHOSTH,  // width, height
      //12345---
      0b01110000,
      0b10101000,
      0b11111000,
      0b11111000,
      0b01010000
    }
  }
  ,
  { // Down
    {
      GHOSTW,GHOSTH,  // width, height
      //12345---
      0b01110000,
      0b11111000,
      0b10101000,
      0b11111000,
      0b10101000
    }
    ,
    {
      GHOSTW,GHOSTH,  // width, height
      //12345---
      0b01110000,
      0b11111000,
      0b10101000,
      0b11111000,
      0b01010000
    }
  }
};

I can now animate the ghosts the same way I did the Pac-Man in the earlier example. My next task would be to put Pac-Man on the screen and let him move around.

To be continued… (After the next part, I will have covered all the work I did that night, and I will begin turning this test in to an actual game. Assuming I have time to work on it.)

One thought on “Arduino Pac-Man part 4 – maze

  1. Pingback: Ardunio Pac-Man part 8 – dot dilemma 2 | Sub-Etha Software

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.