Monthly Archives: February 2014

Arduino Pac-Man part 10 – the story so far

So far, there have been nine installments to this rambling series on creating a Pac-Man style game for an Arduino using the TVout video library. During the course of this discussion, many small things have changed so I thought it might be a good time to reboot the series. Here is a brief updated overview of the process so far.

0. Inspiration and Evolution

One night, two weekends ago, I decided to try the TVout video library for Arduino. I cut up an old A/V RCA cable and used two resistors to connect it to my UNO and soon was running the TVout demo. I next created a small program to draw a line, square, and circle. Then I decided to make the circle bounce around the screen like a ball, followed by bouncing multiple balls. I soon replaced the circle with a bitmap graphic so I could bounce a different shape, and ended up making that shape a Pac-Man which I could steer around the screen, avoiding the balls. Somehow this led me to decide to create a full-on Pac-Man style game.

1. Creating the Maze

Pac-man

Pac-Man arcade screen.

I would be using the default TVout video resolution of 120×96. The original Pac-Man resolution is 224×288 and uses a monitor that is turned sideways (portrait mode) so it is taller than it is wide. If I just scaled that screen size down proportionally, it would become 75×96 and make for a very small maze.

I decided to take the approach used by most home versions that would be played on traditional monitors (landscape) and crop off the top and bottom area (score, lives left, level) so only the maze would be left. The maze alone was 224×248.

I began the process of drawing out the maze, pixel by pixel, on top of this scaled down graphic. As I did this, I had to adjust my maze size a bit so everything would line up. Some later research revealed that the original Pac-Man screen was divided up in to 8×8 tiles of 28 across and 36 down. (Or, when the top and bottom where cropped, 28×31.) In order to get 31 tiles vertically out of the TVout’s 96 pixel tall resolution, I did some simple math and divided the height of the TVout screen (96 pixels) by the number of tiles I wanted to display (31). 96/31=3.096. It seemed I would need to round to 3×3 tiles.

pacman-arduino-270x297

Pac-Man Arduino screen.

Going across the screen (28 tiles) at three pixels each was 84 (3*28=84). Going down the screen (31 tiles) at three pixels each was 93 (3*31=93). My scaled down Arduino maze would be 84×93, and that would allow it to be the same number of tiles as the original, just using smaller tiles.

I used a 3×3 grid to draw out the full maze, and took the liberty of making the side walls a bit wider than they really should be to make them look better. (If you look at the thick walls around the ghost house in the center, that is how the entire outer wall should be for the scaled down lines to be the same dimensions. That is the only thing about this maze recreation that isn’t dimensionally accurate.)

I used a website to convert my graphic file in to C data statements that could be displayed by the TVout bitmap library function.

2. Sprites

Pac-Man sprites

Pac-Man Arduino sprites.

I next created the ghost and Pac-Man character sprites to use in the game. The original arcade sprites were just under two 8×8 tiles wide and tall (less than 16×16 pixels) and would fit in the maze corridors. Since my corridors were now 5 pixels tall or wide (just under two 3×3 tiles) it seemed to work out perfectly to make my game characters 5×5. I designed Pac-Man with three frames (mouth closed, partially open and fully open) just like the arcade, and created versions for all four directions. For the ghosts, they had two frames of animation, and a version for all four directions (with the eyes facing that way). I drew these in a graphics program first, then hand entered them as data in the C source code.

2. Moving – some new stuff here!

Moving through the maze was a challenge. Originally I tried to detect walls by looking for set pixels, but I kept running in to places where the characters would get stuck at the rounded corners. After I learned about how Pac-Man used the tile system, that provided a nice and simple solution: I created a binary map that represented all of the tiles on the screen (28×31) and put a 1 where a wall would be and a 0 where a hall/path would be.

Once I had this, I worked out a system which would track which tile a sprite was in, and check for tiles around it to see if it could go in that direction. This is the part of the project that has evolved the most over the past week as I learned that tiles were also used for how the ghosts knew where to move (they head towards a specific tile), and how collisions between ghosts and Pac-Man were done.

Ghost sprite moving right.

Ghost sprite moving right.

Pac-Man considered a sprite to be in whatever tile the center of the sprite was in. Because of this, I would be tracking the X/Y coordinate of each object based on its center. In a 5×5 sprite, the center would be at (3,3) if we used base-1 and started with the top left pixel at (1,1) and the bottom right pixel at (5,5). In order to allow sprites to move smoothly, I would also track an X/Y offset within  pixel. In the diagram to the right, the top ghost appears at the center of, for example, tile (10,10) with an X offset of 0. As it moves to the right (second ghost), it would still be in tile (10,10) but would have an X offset of 1. As it moves to the right again (third ghost), its center would now have it be in tile (11,10) with an X offset of -1 from that tile. Lastly (four ghost), it would be in tile (11,10) with an offset of 0.

I would need, at the very least, for each sprite to contain something like:

uint8_t x,y;
int8_t  xOffset, yOffset;

To figure out where on the screen to draw the sprite, I would take the tile coordinate, and multiple it by the size (3 pixels tall or wide), and then add the offset. In the above example, tile (10,10) with an X offset of 0 (in the center) would correspond to screen pixel (30,30). As the ghost moved to the right, it would be at (31,30) and (32,30). I also needed to add a few pixels to X and Y to compensate for the maze bitmap being a bit wider than the tiles under it. (NOTE: This is simplified. In the actual code, I also add half the width of the tile to keep the X/Y positions representing the center.)

To keep from having to do this kind of math (MAZEX+x*3+xOffset) every time an object was displayed, I thought I might also track the screen position separately:

uint8_t xScreen, yScreen;

I would need to do some testing and see if the overhead of the formula to calculate screen position every time was better than tracking two separate X/Y coordinates (one for tile, one for screen). Therefore, this approach is still something that could change. It would make the code seem simpler, but perhaps at a cost of extra CPU cycles needed (versus just having a variable around).

Objects also need to know what direction they were headed in. Initially, since I had started with a bouncing ball demo, I gave each object a directional offset variable that could either be +1, -1 or 0 depending on if the object was moving right/up, left/down or stopped. I was calling them “mx” and “my” (movement X, movement Y) and to move, I would just do something like:

x = x + mx;
y = y + my;

As I got further in to development, I decided that since no object ever moved diagonally (thus, no mx=1 at the same time my=1), using two variables was not needed. Instead, I could just track the current direction with one, that would be set to UP, LEFT, DOWN or RIGHT:

uint8_t direction;

And, because ghosts look one tile ahead to decide where they will turn when they get there, and because Pac-Man allows the player to push the joystick in a different direction as he is moving and instantly be ready to turn there, I added:

uint8_t nextDirection;

When I share the updated code, this will make more sense, and the new code should also be much simpler.

3. Ghost A.I.

With the basics of a maze and ability to move around it figured out, I then moved on to the logic of the ghosts. Pac-Man ghosts basically just try to move towards a target tile. That tile either causes them to scatter to a corner, or to try to track down Pac-Man, based on the mode the game is in.

To achieve this, I created some functions that let the ghost determine what it’s next direction should be at every intersection. It does this by comparing the tiles it could turn to and seeing which one is the closest to the target tile. Once I had targeting working, the only thing left to do would be add code to change the tiles as the game modes changed (from scattering to the corner, to chasing Pac-Man, to running away frightened after an energizer pellet was eaten).

There are also some special rules that needed to be taken care of, like the four passageways that ghosts are never allowed to turn up, and handling the side tunnels.

4. Dealing with Dots

A big portion of the project, currently left untouched, is figuring out how to handle all the dots on the screen. I have previously shared my idea on how this will be done, but I have yet to write any code to test this idea (this will be next). Once this is done, there will be very little left to do for the “engine” of this game to function.

5. Miscellaneous TO DO

The final part of this project will include coding some specific routines to handle things like:

  1. Scoring as Pac-Man eats dots.
  2. Collision detection between Pac-Man and Ghosts.
  3. Changing mode to FRIGHTENED when Pac-Man eats an energizer pellet.
  4. Displaying bonus fruit at appropriate times and scoring if eaten.
  5. Altering speed of Ghosts and Pac-Man based on levels and mode. Also, ghosts slow down in the tunnels, Pac-Man is slower while eating dots, and Pac-Man can slightly cut corners when turning.
  6. Intro screen, intermissions and high scores.
  7. Sound.
  8. …and probably many other things I haven’t even thought about yet. For instance, just yesterday, I saw a “cut scene” between levels that I had never seen before. I will have to find videos of all of them to try to recreate them.

And with that out of the way, it’s time to resume this project reboot, and share the current state of the code.

To be continued…

Modern ports of CoCo games.

Updates:

  • 2014/03/08: More games added.
  • 2015/03/20: Added Caladuril Flame of Light (thanks to a comment from Hugo).

I previously mentioned that my first home computer was a Commodore VIC-20 in 1982. A year later I would be running a Radio Shack TRS-80 Color Computer (“CoCo”), expanded to 64K by a local Houston CoCo guru named Don Burr. I stuck with that computer (then a CoCo 2, and finally a CoCo 3) and was still using it as a primary machine until around the end of 1995 when I bought my first modern PC laptop (a Toshiba) so I could use the world wide web.

But I digress…

In recent years, it seems several Color Computer programs have been ported or recreated on other systems. I thought it might be fun to share the list I have so far.

  • Airball – an Android port of a game made for the Dragon 32/64.
  • Bedlam, Raakatu, and Pyramid 2000 – these three text adventures were sold on cassette by Radio Shack. Aaron Wolf has ported them to Android in all their green-screen glory.
  • Caladuril Flame of Light – This Diecom/Oblique Triad graphical adventure game was ported to the Palm Pilot PDAs.
  • Donut Delima – Nick Marentes’ platform game for the CoCo 1/2 has been ported to the Maximite retro gaming system.
  • Dungeons of Daggorath – the classic ROM Pak has been ported to a Windows PC and PlayStation Portable! (I’m pretty sure I had a Mac version of it at one point, too.)
  • Doubleback – this 1982 ROM Pak game was sold by Radio Shack, and it’s original author, Dale Lear, has just released an iOS version for iPhone and iPad.
  • Module Man – this 1984 platform game by Spectral Associates was released for the European CoCo clone, the Dragon (and the CoCo too?). You can see a video on YouTube of the original. It was ported to the ColecoVision!
  • Zenix3D – an update to the GOSUB Software game Zenix (Jeremy Spiller and Mike Newell) by one of the original authors. (See this video of the original CoCo 3 version.)

Know of any others? Let me know and I will keep updating this page.

Arduino Pac-Man part 9 – ghosts

See also: part 1part 2part 3part 4part 5part 6part 7 and part 8.

NOTE: As mentioned in the last post, many of these are written at a time then posted over the next week. This is another example of that. Initially, I had planned to write some test code and get the dot stuff working, but I decided that the ghost movement would be more fun to work on. And, as previously mentioned, I have had more time to think about things by the time you read this, so much of what you will read here (“thoughts in progress”) have evolved. I will be sharing new things with you in part 10, including a substantial change to how movement is tracked (which solved two problems I encountered with the approaches I have been taking). Spoilers: below is a short video of how it is turning out… Details soon. Until then, read part 9…

It is time for things to get a bit deep as we look in to making the ghosts act like ghosts. So far, we have have figured out how to have our sprites move across dots and either eat them or redraw them. Now it’s time to figure out how the ghosts will be moving.

Pac-Man ghosts have five basic modes they are in. They may be stuck in the ghost house waiting to exit, they may be disembodied eyes trying to return to the ghost house (after being eaten by Pac-Man), or they may be in SCATTER, CHASE or FRIGHTENED mode.

For excellent descriptions and photos of these modes, see the excellent Pac-Man Dossier site, or the excellent GameInternals site that focuses just on the ghosts. Both are quite excellent.

SCATTER is the simplest of the modes. The ghosts will be trying to reach a specific tile, and simply turn at any intersection in the direction that would get them closest to it. The target tiles are outside the maze, so the ghosts will never reach them. Each ghost has a target tile in a different corner, which is why ghosts appear to have favorite corners they will head to during the game. If left in SCATTER mode, the ghosts would circle endlessly trying to reach the impossible tile.

CHASE changes these targets so they track Pac-Man. The red ghost, Blinky, starts outside of the ghost house and its CHASE target tile is whatever tile Pac-Man is in. In chase mode, Blinky is the only ghost that is directly pursuing Pac-Man.

Pinky, the pink ghost, targets four tiles in front of Pac-Man. A bug in the original Pac-Man code causes the target tile to be wrong when Pac-Man is going UP, and it will be shifted four tiles to the left as well. I plan to replicate this bug. (Interesting note: If Pac-Man heads directly towards Pinky and gets within four tiles, this causes the target to be behind Pinky. If there is an opening in the side of the hallway between Pac-Man and Pinky, Pinky will turn down that opening trying to backtrack and reach the target that is now behind him.)

Inky, the blue ghost, has a much more complex target. It is based on two tiles in front of Pac-Man, and the location of the red ghost. The target tile is created by taking a vector that runs between the red ghost and two tiles in front of Pac-Man, then doubling that length. Yowza. Due to the same Pinky bug, when Pac-Man is facing up, the tile used is actually two up and two left from Pac-Man.

Clyde, the orange ghost, bases his target on how close to Pac-Man he is. If he is eight tiles or more away, he targets Pac-Man just like Blinky. If he is closer, he starts targeting his scatter mode tile and heads to his corner. This means Clyde should never catch Pac-Man unless you get in his way as he is trying to flee back towards his corner! If you park Pac-Man in a corner, Clyde will never reach him unless that corner is on the way to Clyde’s home corner. Cool.

And to think… some folks figured this out back in 1982 and became world champions on this game! (And they did that without MAME and disassemblies of the game code.)

And lastly, we have FRIGHTENED mode. When one of the energizers is eaten, the ghosts reverse direction (as they do when any mode change happens) and then they wander around randomly at a slower speed. During this, if Pac-Man collides with a ghost, the ghost is eaten, points are scored, and the disembodied eyes of the eaten ghost target a tile above the ghost house so they can return there and resurrect as a ghost again.

Just think of all the Pac-Man ripoffs written over the years for home computers and consoles that used randomness for the ghosts… The only bit of randomness in the original Pac-Man was when they were running away, frightened!

There is alot to cover here, so let’s start small with how the ghosts actually target a tile. Every time the ghost moves to a new tile, it looks ahead to the next tile and determines which way it will go when it reaches it. Ghosts never reverse except when switching modes, so there can be up to three choices. If the next tile has only one way to go, that is chosen. If there is more than one choice, the distance between each of the choice tiles and the target tile will be compared and the shortest one will be chosen. In the case that multiple tiles are the same distance to the target, a priority of UP, LEFT and DOWN is used to choose.

It sounds like we need a function to return the distance between two tiles. It might look like this:

// Return the distance between two coordinates. Math is hard.
uint8_t getTileDistance(int8_t x, int8_t y, int8_t targetx, int8_t targety)
{
  int8_t distx, disty;
  distx = targetx-x;
  disty = targety-y;
  return sqrt( (distx*distx)+(disty*disty));
}

Yes, that’s the Pythagorean Formula theaory thing (A squared plus B squared equals C squared.) we learned back in high school algebra to determine the size of the edges of a right triangle. I knew someday it would come in handy, but since this was the first time it ever has, I had to look it up since I couldn’t remember much from classes I took over 30 years ago!

NOTE: Since I wrote this, my buddy Mike has pointed out a much easier way to achieve the same end result. He suggested that since we can easily determine which direction the target is, just checking the distance between X and Target X versus the distance between Y and Target Y should be enough. I will be revising things in a future article to use this approach. A benefit of this is that we will also save some CPU time. The sqrt() function uses floating point math, which is slower than quick integer math if you don’t have dedicated floating point hardware to help out (the Arduino doesn’t, does it?). But I digress. As you were. Nothing to read here, yet.

One more thing to consider is that ghosts cannot reverse direction unless their mode changes. Because of this, as potential directions are scanned, reverse can never be one of them. It looks like we need a simple way to figure out which direction is reverse. We could brute force it like this:

uint8_t getOppositeDir(uint8_t dir)
{
  if (dir==LEFT) {
    return RIGHT;
  } else if (dir==RIGHT) {
    return LEFT;
  } else if (dir==UP) {
    return DOWN;
  } else if (dir==DOWN) {
    return UP;
  } else {
    return dir;
  }
}

But, as Mike pointed out, reversing from DOWN would always be slower since it has to check LEFT, RIGHT and UP before it gets there. Maybe we could do it using switch/case statements:

uint8_t getOppositeDir(uint8_t dir)
{
  switch(dir)
  {
    case LEFT:
      return RIGHT;
    case RIGHT:
      return LEFT;
    case UP:
      return DOWN;
    case DOWN:
      return UP;
    default:
      return dir;
  }
}

The end result is the same, but depending on how the compiler generates code, this could be more efficient since it may be using lookup table so jumping to DOWN would be as quick as jumping to LEFT. (Highly optimizing smart compilers may do things like this with IF/THEN logic anyway. I believe the compiler made at the place Mike and I used to work actually did that, allowing programmers to write less efficient brute force source code in some instances and still generate efficient machine code… Hmm. At my current job, I work with one of the compiler engineers from back then, so I may have to ask his opinion sometime. But I digress…)

Or we could do something more clever…though clever doesn’t always make faster or smaller code. Time for a quick test!

The directions are represented by 0=RIGHT, 1=LEFT, 2=UP and 3=DOWN. (NOTE: This is going to change in the next article, for reasons I will explain then.) This is lucky because if you look a the bit patterns for 0-3, you get 00, 01, 10, 11. If you reverse from RIGHT (00) to LEFT (01), you see the difference is toggling that right most bit. If you reverse from UP (10) to down (11), you see the difference is toggling that right most bit. Knowing this pattern, we could write a simple function to flip the right most bit:

uint8_t getOppositeDir(uint8_t dir)
{
  if (dir & 1) // If rightmost bit is set,
  {
    return dir & ~1; // Turn it off.
  } else {
    return dir | 1;  // Else, turn it on.
  }
}

This wouldn’t have been the case if the directions had been represented clockwise as 0=UP (00), 1=RIGHT (01), 2=DOWN (10), and 3=LEFT (11). If that were the case, switching from LEFT (11) to RIGHT (01) would have toggled the left most bit, and UP (00) to DOWN (10) would have toggled…the left most bit. Oh. Apparently it could have worked that way too, just by changing which bit was being tested.

Well, if the directions were in the order of ghost priority (used to determine which direction the ghost turns in case there is a tie between potential tile distances to the target) 0=UP (00), 1=LEFT (01), 2=DOWN (10) and 3=RIGHT (11), then UP (00) to DOWN (10) would toggle the left most bit, and LEFT (01) to RIGHT (11) would toggle…the left most bit. Okay, that works too.

But SURELY it wouldn’t have worked if the order was more random like 0=UP (00), 1=RIGHT (01), 2=LEFT (10) and 3=DOWN (11). UP (00) to DOWN (11) toggles both bits, and LEFT (10) to RIGHT (01) toggles both bits. That would be  easy to do in C to by using the “~” bitflip thing.

Screw it. It looks like with only four bits there would be a way to flip using bit math. This is useful, since having the directions in the order of priority will come in handy later. (This is the reason for changing directions in a future article.)

NOTE TO SELF: Do that. It makes things easier. (NOTE BACK FROM SELF: Already did, next article. Move on.)

Which one we end up using depends on things like code size, speed, and memory. I compiled a sketch with an empty setup() and empty() loop, and added just these functions, one at a time. Here are the results:

getOppositeDir() if/then version – 466 bytes
getOppositeDir() switch/case version – 466 bytes
getOppositeDir() bit flip version – 466 bytes

How can that be? Because the compiler is smart. When I created the empty test code:

#define RIGHT 0
#define LEFT  1
#define UP    2
#define DOWN  3

void setup()
{
}

void loop()
{
}

uint8_t getOppositeDir(uint8_t dir)
{
  switch(dir)
  {
    case LEFT:
      return RIGHT;
    case RIGHT:
      return LEFT;
    case UP:
      return DOWN;
    case DOWN:
      return UP;
    default:
      return dir;
  }
}

…the compiler realized nothing was actually using that function and discarded it. If anything were using it, the compiler would have to keep the code. Let’s try this:


#define RIGHT 0
#define LEFT  1
#define UP    2
#define DOWN  3

void setup()
{
  Serial.begin(9600);

  Serial.println(getOppositeDir(DOWN));
}

void loop()
{
}

uint8_t getOppositeDir(uint8_t dir)
{
  if (dir==LEFT) {
    return RIGHT;
  } else if (dir==RIGHT) {
    return LEFT;
  } else if (dir==UP) {
    return DOWN;
  } else if (dir==DOWN) {
    return UP;
  } else {
    return dir;
  }
}

By printing the result, the code has to run to get the result. Now we get:

getOppositeDir() if/then version – 2214 bytes
getOppositeDir() switch/case version – 2214 bytes
getOppositeDir() bit flip version – 2214 bytes

We can see that using the Serial.xxx() functions adds quite a bit of code bulk, but the sizes are still the same. If the compiler was getting rid of unused code before, but why are the sizes still the same? Could each function really generate the same amount of code? If it was, then it wouldn’t matter which one we used — at least, not just based on code size. More on this in a moment…

Without looking at the assembly code generated by the compiler, I cannot say which one is more efficient. For now, we will use the switch/case approach since it is really easy to understand and doesn’t make the code any larger. Also, that will work regardless of what values are chosen for UP, DOWN, LEFT and RIGHT so if I change them later (NOTE: which I am doing), I won’t have to rewrite the getReverseDir() function like I would with the bit flipping version.

But wait! There’s more! While standing outside during a file alarm the other day, I mentioned this to the previously mentioned compiler engineer I used to work with… He suggested another option: a lookup table. It might look like this:

// Directions are: 0-RIGHT, 1-LEFT, 2-UP and 3-DOWN
// Therefore, reverse directions in this table would be:
const uint8_t oppositeDir[] = { LEFT, RIGHT, DOWN, UP };

uint8_t getOppositeDir(uint8_t dir)
{
  return oppositeDir[dir];
}

See what that does? If your original order is 0, 1, 2 and 3, you could create an array that contains four numbers in whatever order you want, like 1, 2, 3, and 2. Then if you asked for the element of the array that matches your original direction, it returns whatever is there (which is the opposite direction number). You wouldn’t even have to use a function: reverse = oppositeDir[dir];

getOppositeDir() array version – 2214 bytes

Dambit. Either these really are taking the same amount of space, or there is some compiler stuff going on where the size of the binary is being rounded up, or things are being optimized out.

Okay, one last try. This time, we’ll do more with the function than just change one hard coded value. A really smart compiler could see “oh, this is only being done once, and it’s being done with a 3, so I can just pull in the code that handles the 3 case and discard the rest.” Is this compiler that smart? Here is my test code, with all four variations included:

#define IFTHEN
//#define SWITCHCASE
//#define BITFLIP
//#define LOOKUPTABLE

#define RIGHT 0
#define LEFT  1
#define UP    2
#define DOWN  3

void setup()
{
  uint8_t dir;

  Serial.begin(9600);

  for(dir=0; dir<3; dir++)
  {
    Serial.println(getOppositeDir(dir));
  }
}

void loop()
{
}

#if defined(IFTHEN)
uint8_t getOppositeDir(uint8_t dir)
{
  if (dir==LEFT) {
    return RIGHT;
  } else if (dir==RIGHT) {
    return LEFT;
  } else if (dir==UP) {
    return DOWN;
  } else if (dir==DOWN) {
    return UP;
  } else {
    return dir;
  }
}
#endif

#if defined(SWITCHCASE)
uint8_t getOppositeDir(uint8_t dir)
{
  switch(dir)
  {
    case LEFT:
      return RIGHT;
    case RIGHT:
      return LEFT;
    case UP:
      return DOWN;
    case DOWN:
      return UP;
    default:
      return dir;
  }
}
#endif

#if defined(BITFLIP)
uint8_t getOppositeDir(uint8_t dir)
{
  if (dir & 1) // If rightmost bit is set,
  {
    return dir & ~1; // Turn it off.
  } else {
    return dir | 1;  // Else, turn it on.
  }
}
#endif

#if defined(LOOKUPTABLE)
// Directions are: 0-RIGHT, 1-LEFT, 2-UP and 3-DOWN
// Therefore, reverse directions in this table would be:
const uint8_t oppositeDir[] = { LEFT, RIGHT, DOWN, UP };

uint8_t getOppositeDir(uint8_t dir)
{
  return oppositeDir[dir];
}
#endif

To test, I comment out all of the defines at the top except for the one I want to test, then I check each one.

getOppositeDir() if/then version – 2264 bytes
getOppositeDir() switch/case version – 2264 bytes
getOppositeDir() bit flip version – 2244 bytes
getOppositeDir() array version – 2246 bytes

Finally! Indeed, it does appear the compiler was smart enough to figure out that the functions as only being called once with a specific value — so why keep the rest of the code around? (If this is true, that’s pretty cool.) It seems the bit flip version is the smallest, coming in 20 whole bytes smaller than the brute force if/then. But, remember that it would have to be recoded if the direction order ever changes (which it will be for me, so that would have created a bug if I didn’t remember to change it). The array version is 18 bytes smaller, and it, too, would have to change if the directions ever changed. And none of this tells us which one executes faster necessarily, which might be very important to speed up a video game.

But I digress… Optimizations could be the topic for a whole series, and will no doubt come up again in this series.

Wasn’t that fun? Where was I? Oh, right. Looking ahead…

Now all we need to do is look ahead and see what tile options there might be. Here is how it should work:

The next tile in the direction the ghost is running should be determined. Then, from that tile, all the tiles around it (not including reverse, which isn’t an option since a ghost can’t go backwards) should be checked to see how far they are from the target tile. They will be checked in the priority order of UP, LEFT, DOWN and RIGHT. If a shorter distance is found, remember that direction, else continue. This will take care of distance ties and the priority. If UP is checked and it is 5 away, then LEFT is checked and it is 6, it will be skipped. Next, if DOWN is checked and it is also 5, it won’t be recorded since we already have a 5 (UP, a higher priority).

To do this, we need to check the directions in the order of 2 (UP), 1 (LEFT), 3 (DOWN) and 0 (RIGHT). If only these priorities lined up with our directions this would be simple. Since they don’t, we may have to use an array like this: (NOTE: Now see why changing the directions will help out? None of the things involving priorityDirs[] will need to be used, but I am keeping it all in this article to show how the concept evolved.)

PROGMEN const uint8_t priorityDirs[DIRS] = { UP, LEFT, DOWN, RIGHT };

// Do this for each ghost.
for (i=0; i<GHOSTS; i++)
{
  // Get the file in front of us.
  getNextTile(gx[i], gy[i], ghostDir[i], &nextX, &nextY);

  // Now, from that tile, see what options might exist.

  // We cannot reverse, so we should ignore that direction.
  // Storing this in a variable for speed.
  uint8_t reverseDir = reverseDir(ghostDir[i]);

  for (j=0; j<DIRS; j++)
  {
    uint8_t potentialX, potentialY;
    uint8_t tempDist;
    uint8_t shortestDist = 255; // Max distance.

    // Skip reverse dir since it's not a choice.
    if (j==reverseDir) continue;

    // What tile is in that direction?
    getNextTile(nextX, nextY, priorityDir[j], &potentialX, &potentialY);

    // Get distance from potential tile to target tile
    tempDist = getDistance(potentialX, potentialY, targetX, targetY);
    // If closer than previous closest,
    if (tempDist < shortestDist) {
    {
      shortestDist = tempDist;
      tempDir = priDir[j];
    }
  }
  // The next direction the ghost should go is...
  ghostDir[i] = tempDir;
}

Could this actually work??? To find out, we can combine this mechanism with the dot merchanism, and see if the ghosts will now SCATTER to their corners and behave like Pac-Man ghosts should behave…

To be continued…

Arduino memory and optimizations

My first computer in 1982 was a Commodore VIC-20. In 1982, this was the “first color computer for under $300” with a price of $299.99. It had 4K of memory and a 1mhz 6502 CPU (the same chip used in the Apple 2). Captain Kirk himself was the original spokesperson:

I wrote full programs in the 3583 bytes of memory that were available to BASIC on startup. One of these years, I hope to retrieve some of the VIC-20 programs I wrote off a cassette tape and see them again via a modern emulator. But I digress…

Today, few seem to care about generating optimized code. There was a time when programmers had to worry about every last byte of memory, and every last cycle of CPU time, depending on what they were doing. But today, with gigabytes of RAM and multi-core CPUs, none of this really matters for most programmers.

But what about something like an Arduino? With only 2K of RAM and 32K flash for program storage, optimization can matter for all but the simplest programs.

Recently, I found myself trying to create a Pac-Man game for Arduino using the TVout video library. Along the way I have been documenting my efforts, including much trial and error as I come up with better ways to do things. One of the simple things I recently addressed was a way to determine the opposite direction that a game character was moving. Consider these four directions:


#define UP    0
#define LEFT  1
#define DOWN  2
#define RIGHT 3

If the player were currently going DOWN (direction 2), the reverse would be UP (direction 0). In a previous post, I detailed several different ways this could be achieved: if/then, switch/case, array table lookup, and bit flipping.

I created some test code to see what the size difference was when compiled for the Arduino, and the differences were small.

So if I really wanted to save every last byte of flash space, I might go for the version that compiled to the smallest code. However, this was a video game, and speed might be more important in some situations. I decided to write a quick program to test the differences in speed for each approach. The results might surprise you.

Here are three simple functions:

// if/then
uint8_t getReverseDir_ifthen(uint8_t dir)
{
  if (dir==UP) {
    return DOWN;
  }
  else if (dir==LEFT) {
    return RIGHT;
  }
  else if (dir==DOWN) {
    return UP;
  }
  else if (dir==RIGHT) {
    return LEFT;
  }
  else {
    return dir;
  }
}

// switch/case
uint8_t getReverseDir_switchcase(uint8_t dir)
{
  switch(dir)
  {
  case UP:
    return DOWN;
  case LEFT:
    return RIGHT;
  case DOWN:
    return UP;
  case RIGHT:
    return LEFT;
  default:
    return dir;
  }
}

// bit flipping
// 00-UP, 01-LEFT, 10-DOWN, 11-RIGHT
uint8_t getReverseDir_bitflip(uint8_t dir)
{
  if (dir & (1<<1)) {
    return dir & ~(1<<1);
  }
  else {
    return dir | (1<<1);
  }
}

Each one takes a different approach to solving the same problem. Another solution could be done without writing a function: a lookup table.

// Directions are: 0-UP, 1-LEFT, 2-DOWN, and 3-RIGHT
const uint8_t revDir[DIRS] = { DOWN, RIGHT, UP, LEFT };

reverseDir = revDir(currentDir);

Above, if the current direction is RIGHT (3), it would get the 3rd entry in the table, which is LEFT. Simple.

So which would you choose? I’d normally argue that the most straight forward and easy to understand code should win if there isn’t any significant savings to doing it in a more convoluted way. Making something confusing to save 8 bytes might not be a good idea unless you really need those 8 bytes.

So which method would you choose? Before you answer, let’s throw one more thing in the mix. On the Arduino, constant variable like strings are stored in RAM. This means printing a long string is actually using up RAM to store that string:

Serial.println("This is a really long message that will take up much RAM.");

If you have a ton of prints in your code, you may run out of that 2K quite easily. This is due to the nature of the Harvard Architecture that the Arduino uses. Read this:

http://en.wikipedia.org/wiki/Harvard_architecture

Basically, RAM and program space are in separate memory spaces and cannot be accessed the same way. Normally, constant strings like the message above would be compiled in to code space, and when it came time to use that string, it would be retrieved from code space. But, this is not how Arduino works, so strings all go in to RAM.

To get around this, there are extensions that allow storing strings and other constant values in flash, and then retrieving them through special access calls. A nice macro exists that puts a string in flash: F()

All you have to do is wrap your string with that macro, and then it goes in flash:

Serial.println(F("This is a really long message that will take up flash."));

Here is an example, with a bit of code to show free RAM:

void setup()
{
  Serial.begin(9600);

  Serial.println("This is a really long message. I wonder where it goes?");

  Serial.println(freeRam());
}

void loop()
{
}

unsigned int freeRam() {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

This compiles to 2304 bytes, and when it runs, it prints the following:

This is a really long message. I wonder where it goes?
1783

Now, by putting F() around the string, it compiles to 2432 bytes and prints:

This is a really long message. I wonder where it goes?
1839

Just using the F() macro saved 56 bytes of RAM! But, the code size grew by 128, so there is some overhead.

You can specify constant data to be stored in flash by using the keyword PROGMEM like this:

PROGMEM const char msg[] = "This is a really long message. I wonder where it goes?";

PROGMEM const uint8_t revDir[DIRS] = { DOWN, RIGHT, UP, LEFT };

Do you see where I am going with this? The small directional table of DOWN, RIGHT, UP and LEFT was being stored in RAM. Those four bytes may not matter, but what if it were a larger table? By storing it in flash (since it will never change), that RAM can be saved. BUT, you cannot directly access flash storage. You have to use special functions. You can find them defined in this header file:

http://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html

If the table is in RAM, you can access it like you would expect:

#define UP         0
#define LEFT       1
#define DOWN       2
#define RIGHT      3
#define DIRS       4

const uint8_t revDir[DIRS] = { DOWN, RIGHT, UP, LEFT };

void setup()
{
  Serial.begin(9600);

  for (int i=0; i<DIRS; i++)
  {
    Serial.print(i);
    Serial.print(" - ");
    Serial.println(revDir[i]);
  }

  Serial.println(freeRam());
}

But if they are stored in flash, you have to get to the uint8_t bytes using “pgm_read_byte_near()” and give it the address of the byte you want to read. The change looks like this:

#define UP         0
#define LEFT       1
#define DOWN       2
#define RIGHT      3
#define DIRS       4

PROGMEM const uint8_t revDir[DIRS] = { DOWN, RIGHT, UP, LEFT };

void setup()
{
  Serial.begin(9600);

  for (int i=0; i<DIRS; i++)
  {
    Serial.print(i);
    Serial.print(" - ");
    Serial.println(pgm_read_byte_near(&revDir[i]));
  }

  Serial.println(freeRam());
}

“PROGMEM” is added before the array, and “pgm_read_byte()” is used (note the “&” to get the address) to get the actual byte.

If your program is small enough that RAM isn’t an issue, there’s really no reason to bother with this. But, if RAM is running out, perhaps putting everything you can in flash is the way to go.

Or is it?

There is a speed penalty to accessing data stored in flash. Getting back to the start of this article, I was trying to test several ways of doing the reverse direction lookup to see which one was faster. During this, I created two versions of the lookup array test — one using RAM, and the other using PROGMEM storage. I was surprised at the difference in speed. Here is what I did.

I used the millis() call to get milliseconds since the program started. Since an individual check would be too quick to detect, I run each test MAX times (a #define number, like 10000). For the actual test, I query all four directions to get the reverse of them. Since the compiler is smart enough to know when something isn’t being used, I used the keyword “volatile” over my direction variable so the compiler leaves it alone and doesn’t try to optimize this “unused” variable out.

Here is the final test code, using three functions, and the array lookup done out of RAM and flash:

#define UP         0
#define LEFT       1
#define DOWN       2
#define RIGHT      3
#define DIRS       4

#define TESTS      5
#define MAX        10000

// Directions are: 0-UP, 1-LEFT, 2-DOWN, and 3-RIGHT
const uint8_t revDir[DIRS] = { DOWN, RIGHT, UP, LEFT };

// Same, but stored in flash.
PROGMEM const uint8_t revDirPROGMEM[DIRS] = { DOWN, RIGHT, UP, LEFT };

void setup() {
  unsigned long i;
  uint8_t j;
  volatile uint8_t dir; // volatile, so its not optimized out

  unsigned long timeStart, timeEnd;
  unsigned long timeTotal[TESTS];
  unsigned long timeLongest;

  Serial.begin(9600);

  Serial.println();
  Serial.println();

  Serial.print("Running each test ");
  Serial.print(MAX);
  Serial.println(" times...");
  Serial.println();

  Serial.println("0. array test");
  timeStart = micros();
  for (i=0; i<MAX; i++)
  {
    for (j=0; j<DIRS; j++)
    {
      dir = revDir[j];
    }
  }
  timeEnd = micros();
  timeTotal[0] = calcTimeTaken(timeStart, timeEnd);

  Serial.println();
  delay(1000);

/*---------------------------------------------------------------------------*/

  Serial.println("1. array test using PROGMEM");
  timeStart = micros();
  for (i=0; i<MAX; i++)
  {
    for (j=0; j<DIRS; j++)
    {
      dir = pgm_read_byte_near(&revDirPROGMEM[j]);
    }
  }
  timeEnd = micros();
  timeTotal[1] = calcTimeTaken(timeStart, timeEnd);

  Serial.println();
  delay(1000);

/*---------------------------------------------------------------------------*/

  Serial.println("2. if/then test");
  timeStart = micros();
  for (i=0; i<MAX; i++)
  {
    for (j=0; j<DIRS; j++)
    {
      dir = getReverseDir_ifthen(j);
    }
  }
  timeEnd = micros();
  timeTotal[2] = calcTimeTaken(timeStart, timeEnd);

  Serial.println();
  delay(1000);

/*---------------------------------------------------------------------------*/

  Serial.println("3. switch/case test");
  timeStart = micros();
  for (i=0; i<MAX; i++)
  {
    for (j=0; j<DIRS; j++)
    {
      dir = getReverseDir_switchcase(j);
    }
  }
  timeEnd = micros();
  timeTotal[3] = calcTimeTaken(timeStart, timeEnd);

  Serial.println();
  delay(1000);

/*---------------------------------------------------------------------------*/

  Serial.println("4. bit flip test");
  timeStart = micros();
  for (i=0; i<MAX; i++)
  {
    for (j=0; j<DIRS; j++)
    {
      dir = getReverseDir_bitflip(j);
    }
  }
  timeEnd = micros();
  timeTotal[4] = calcTimeTaken(timeStart, timeEnd);

  Serial.println();
  delay(1000);

/*---------------------------------------------------------------------------*/

  // Gather some results. First, find the longest time...
  timeLongest = 0;
  for (i=0; i<6; i++)   {      if (timeTotal[i] > timeLongest)
    {
      timeLongest = timeTotal[i];
    }
  }
  Serial.print("Longest time was : ");
  Serial.println(timeLongest);

  // Now see what percentage each was...
  for (i=0; i<TESTS; i++)
  {
    Serial.print(i);
    Serial.print(". ");
    Serial.print(timeTotal[i]);
    Serial.print(" = ");
    Serial.println((float)((float)timeTotal[i]/(float)timeLongest));
  }
}

/*---------------------------------------------------------------------------*/

void loop() {
}

// Time taken!
unsigned long calcTimeTaken(unsigned long timeStart, unsigned long timeEnd)
{
  unsigned long timeTaken = timeEnd-timeStart;

  Serial.print("Time was: ");
  Serial.print(timeStart);
  Serial.print(" to ");
  Serial.print(timeEnd);
  Serial.print(". Total: ");
  Serial.print(timeTaken);
  Serial.println();

  return timeTaken;
}

// if/then
uint8_t getReverseDir_ifthen(uint8_t dir)
{
  if (dir==UP) {
    return DOWN;
  }
  else if (dir==LEFT) {
    return RIGHT;
  }
  else if (dir==DOWN) {
    return UP;
  }
  else if (dir==RIGHT) {
    return LEFT;
  }
  else {
    return dir;
  }
}

// switch/case
uint8_t getReverseDir_switchcase(uint8_t dir)
{
  switch(dir)
  {
  case UP:
    return DOWN;
  case LEFT:
    return RIGHT;
  case DOWN:
    return UP;
  case RIGHT:
    return LEFT;
  default:
    return dir;
  }
}

// bit flip
// Bits: 00-UP, 01-LEFT, 10-DOWN, 11-RIGHT
uint8_t getReverseDir_bitflip(uint8_t dir)
{
  if (dir & (1<<1)) {
    return dir & ~(1<<1);
  }
  else {
    return dir | (1<<1);
  }
}

This produced the following results:

Running each test 10000 times…

0. array test
Time was: 836 to 10324. Total: 9488

1. array test using PROGMEM
Time was: 1030552 to 1071568. Total: 41016

2. if/then test
Time was: 2073064 to 2105848. Total: 32784

3. switch/case test
Time was: 3107384 to 3150876. Total: 43492

4. bit flip test
Time was: 4152396 to 4192096. Total: 39700

Longest time was : 43492
0. 9488 = 0.22
1. 41016 = 0.94
2. 32784 = 0.75
3. 43492 = 1.00
4. 39700 = 0.91

As you can see, the slowest version was switch/case. The bit flip test was used 91% as much CPU time (9% faster), and the if/then test used 75% as much CPU time (25% faster). Clearly, the brute force if/then appears to be the best function.

When it comes to just using a lookup table, that uses 22% as much CPU time (78% faster!) Clearly, that is the best approach. But, that four byte table is in RAM. Four bytes isn’t very much, but if it was, we’d want to store it in PROGMEM and access those bytes using “pgm_read_byte_near()”. Doing that was the second slowest approach taking up 94% as long (only 6% faster than the slowest).

So there you go — from the fastest approach to next to the slowest just by access PROGMEM.

I think I will be using four bytes of RAM for mine.

By the way, I did some extensive research in to this last year, and created the following macros I use on most of my big programs:

#ifdef USE_FLASH
#define FLASHMEM PROGMEM
#define FLASHSTR(x) (const __FlashStringHelper*)(x)
#define FLASHPTR(x) (const __FlashStringHelper*)pgm_read_word(&x)
#else
// If not using FLASH, no special casting or keywords.
#define FLASHMEM
#define FLASHSTR(x) (x) //(const char *)(x)
#define FLASHPTR(x) (x) //(const char *)(x)
#endif

Then, by using these macros, I can create code that will compile using RAM or flash and only have to change one thing (“#define USE_FLASH”) based on how I want it to compile.

Instead of using PROGMEM, I use FLASHMEM. If USE_FLASH is not defined, this will be empty and thus the variable will go in to RAM:

byte mac[] FLASHMEM = { 0x2A, 0xA0, 0xD8, 0xFC, 0x8B, 0xEF };

And for strings, instead of having to use the F() macro, I wrap all strings in FLASHSTR():

Serial.println(FLASHSTR("This may be in RAM, or may be in flash. Who knows!"));

Again, this is so if I decide not to compile for flash, I won’t have to go back and remove all the F() macros.

And lastly, for arrays of pointers:

// Store these strings in Flash to save RAM.
const char SEstr[]   FLASHMEM = "SE";
const char NOPstr[]  FLASHMEM = "NO";
const char DMstr[]   FLASHMEM = "DM";
...
// Create an array of pointers to Flash strings, in Flash.
const char *telnetCmd[] FLASHMEM = // 240-255
{
  SEstr, NOPstr, DMstr, ...
};

Serial.print(FLASHPTR(telnetCmd[...]));

I will have to do a full article on this sometime (if I haven’t already done so – I know it was included as part of my article on writing a fully functional telnet server for Arduino).

Enjoy.

Ardunio Pac-Man part 8 – dot dilemma 2

See also: part 1part 2part 3part 4part 5part 6 and part 7.

NOTE: I tend to sit down and write up a bunch of things at one time, then publish them over the next week. This article was originally written on 2/17, and often by the time they are published I have had time to think about things and reconsider. Much of what you will read in the next two articles is being replaced by a better approach, but I still want to share the thought process. Maybe we will end up in the same place! Here we go… (Also, I created a Facebook page for Sub-Etha as well. It will update every time I post a new article.)

I believe I have solved the dot dilemma, and it looks like it might be easier than I expected. First, a recap on what this dilemma is.

The Pac-Man game considers a collision to be when the center of a sprite crosses over the center of another object. This is how collisions with the ghosts, dots, energizer pellets and fruits are handled. Since the center is used for this, it is possible for two sprites to touch and even overlap slightly without anything happening. This is why it’s possible to brush up against ghosts in the arcade game without losing a life. It is also why if Pac-Man approaches a dot halfway, then turns around without getting to the center point, the dot is not eaten.

As previously discussed, a simple way to keep track of the dots would be to use variables to track the on/off status of each dot. 240 dots could be represented by the bits of some bytes (8 bits per byte) and would take up 30 bytes of RAM. Since I only have around 200 bytes of RAM available, using 15% of available RAM for this may not be practical. Plus, it would also require some sort of table that links each bit to which grid tile on the screen the dot is in. While this may be doable, I have decided to try to come up with a different approach.

Instead of tracking each dot, I will be detecting them as each sprite moves around. If a ghost is moving right and is about to cover up a dot, it will remember that and, as the ghost passes over where the dot is, it will redraw the dot behind it. Some considerable has to be taken for when a sprite changes directions while covering a dot, and I think I have this all worked out.

For Pac-Man, the story is the same, except instead of redrawing the dot, it will score points when the Pac-Man sprite is over the center of where the dot was.

Since objects are constantly being drawn, erased, and redrawn in new locations as they move around the screen, some care must be taken with the order that these things happen to ensure a dot is properly detected. I have a few ideas on how this might be handled, from one that will “most certainly” work but take more CPU time and code, to one that will “quite possibly” work and leave more CPU time free for other game tasks.

Brute force programming is not something I generally do beyond initial tests to see if something will work.

Once again, let’s revisit our screen. The screen is made up of a grid of tiles. Each tile is 3×3 in size. The character sprites are 5×5. A dot is in the center of a tile, so every three pixels there can be a dot. This means a 5×5 sprite can actually be covering two dots.

Screen Shot 2014-02-17 at 7.19.42 PM

If a 5×5 sprite begins in the center of a 3×3 tile, it will extend one pixel past each edge of the 3×3 tile. There are four cross paths in the maze where there could be dot touching each of the four sides of a sprite, but we really only need to track the ones in front of the character and remember to draw it back once the character is past it, or count it for the score once the sprite is dead center over it.

Screen Shot 2014-02-17 at 7.32.47 PMBecause of how the math works out, any time the sprite is at a tile offset of 0 (in the center), we should be able to check the tile in the direction we are going to see if there is a dot there. If there is, we can increment a counter variable that will indicate how many dots are under us. For this game, this counter will never be higher than 2, but if we were animating a long snake through a maze, we could use the same system to count being on top of many more dots.

Also at the 0 offsets, we check to see if we need to redraw a dot behind us that we just passed over. We do this by checking the counter to see if it is greater than 0. If it is, we draw a dot behind us and decrement the counter.

If we are moving over a dot and have just incremented the counter, the next frame will be to draw the sprite on the current tile with an offset of 1 in that direction (moving one pixel to the right, for example, and now covering the dot). As the character continues moving right, it moves to the next tile, with an offset of -1. When it moves right one more pixel, the offset is 0 and once again we check to see if there is another dot in front, and if a dot needs to be redrawn behind.

This seems like it will work quite easily, but we also have to take in to consideration what might happen if the sprite changes direction. If it backs up, the counter is still set (>0 = we are covering a dot) and I expect the same code may work, since we reverse back and expose where a dot used to be, and just draw one. Some test code is needed to see if it can really be this simple…

Since all turns are done at offsets of 0 in the middle of a tile, there is never a situation where the two dots being covered would be around a corner, but even if there was, it seems like this system would still work.

Some test code will need to be written.

Arduino Pac-Man part 7 – what’s next?

See also: part 1, part 2, part 3, part 4, part 5 and part 6.

What started out as a simple evening experiment with Arduino video output has escalated in to a full-on attempt to recreate a classic 1980s arcade game as accurately as possible given the limitations of the platform. Ignorance is bliss, as they say, and if I hadn’t discovered such detailed Pac-Man dissection sites, I would have been done by now with something that looked like a dot eating game, but didn’t actually play like the dot eating game it was trying to look like.

At this stage in development, I have recreated the maze layout very accurately, and even recreated the tile grid system that is the key to collision detection with walls and, eventually, with the ghosts. The current Arduino sketch displays the maze and four non-moving animated ghosts and allows Pac-Man to move around the maze, complete with animation in all four directions. (Not quire true; I did some work after that and now have one ghost roaming around randomly, erase dots as it goes.)

There are now two challenges to be solved, and this posting will describe my proposed solutions. The two challenges are dot tracking and ghost targeting.

Dot tracking involves detecting if Pac-Man has passed over a dot and eaten it, or if a ghost has passed over a dot without doing anything to it. My proposed solution is to have each character check to see if it is about to erase a dot and then either eat it (Pac-Man), or redraw it once the character has passed over it.

Ghost targeting is the key to how the ghosts move through the maze, tracking Pac-Man or running away from him. In order for this to work, each ghost must be able to determine what tile is in front of it, then make a decision on which way to turn (if there is a turn) once it reaches that tile.

I believe both of these challenges can be solved by using the grid system the maze is based on. This article will discuss the framework that will be used to determine which tile a character is about to enter. I will be working with the dots for the rest of this article.

A dot is in the center of a 3×3 grid. The sprites are 5×5. A sprite that is perfectly centered on a grid tile will cover all 3×3 pixels of the tile, plus extend one pixel in to each adjoining tile. This will make the sprite be touching any dots that are on any side of it.

Screen Shot 2014-02-17 at 7.19.42 PM

I propose to do dot detection based on any time a sprite is about to be drawn with a tile x and y offset of 0 (meaning it is centered on a tile). If there is a dot pixel set in the direction the sprite is moving, a flag will be set to either eat or redraw it. Once the offset reaches -1 or 1, the 5×5 sprite should now be over the dot. Once the offset reaches 0 of the tile in the direction the sprite is moving, the dot will be considered eaten (scored), or ignored (ghost). Once the character is two tiles away from the original with an offset of 0, the dot will be redrawn on the side opposite of the direction.

Screen Shot 2014-02-17 at 7.32.47 PM

To the right is a diagram demonstrating this.

  1. Pac-Man is at the center of the first tile, offset 0. Here it would detect there is a pixel about to be covered. Dot counter goes from 0 to 1.
  2. The next row is Pac-Man moving one pixel to the right, offset +1. It would be covering the dot.
  3. Pac-Man’s center has entered the second tile, offset -1.
  4. Pac-Man is at the center of the second tile, offset 0, and detects another dot is about to be covered. Dot counter goes from 1 to 2.
  5. Pac-Man moves one pixel to the right, offset +1.
  6. Pac-Man’s center has entered the third tile, offset -1.
  7. Pac-Man is in the center of the third tile, offset 0. A dot is eaten (Pac-Man) or drawn behind him (ghost). Dot counter goes from 2 to 1.

There will probably need to be a secondary counter that counts how many tiles have been passed, since the sprite has to move two tiles to the right before the dot is visible/eaten. We’ll figure that out shortly…

A few bits of code need to be created to achieve this. The first will be a method to determine what the next tile is the sprite is heading towards. Since a character can be moving up, down, left or right, and it’s location is based on an X/Y coordinate in a grid, it makes things a bit tricky. Here is an example:

Screen Shot 2014-02-17 at 7.44.51 PM

Above, the character is currently in position (X,Y) and is moving to the right. The next tile it reaches will be (X+1, Y). Or, if it were moving to the left, it would be (X-1, Y). If it were moving up, it would be (X, Y-1) and down would be (X, Y+1). A nice if/then or switch/case block could handle this:

int8_t px, py;
int8_t nextTileX, nextTileY;

px = 5;
px = 5;
pacDir = RIGHT;

if (pacDir==RIGHT) {
  nextTileX = px + 1;
  nextTileY = py + 0;
} else if (pacDir==LEFT) {
  nextTileX = px -1;
  nextTileY = py + 0;
} else if (pacDir==UP) {
  nextTileX = px + 0;
  nextTileY = py - 1;
} else if (pacDir==DOWN) {
  nextTileX = px + 0;
  nextTileY = py + 1;
}

This would let us calculate the X and Y coordinate of the next tile in whatever direction the character was facing. This works, but isn’t very elegant. A more elegant approach might be to us an array to hold the “next tile” X and Y coordinates.

First, I created a special structure that represents an X and Y coordinate. A C structure lets you create custom data types that can have many elements, and then access those elements by name.

typedef struct {
  int8_t x, y;
} Coordinate;

I can now create a new variable of type “Coordinate” just like I might create a variable of type int or char, and set the elements of it (X, Y):

Coordinate foo;

foo.x = 10;
foo.y = 5;

Now foo represents the coordinates (10, 5). Since the elements (x and y) are signed. they can be negative or positive numbers. In this example, instead of representing a physical X and Y coordinate, they will be used as X and Y offsets to be added to the current location coordinate. Here are the offsets represented by Coordinates:

Coordinate tileRight, tileLeft, tileUp, tileDown;

tileRight.x = 1;
tileRight.y = 0;

tileLeft.x = -1;
tileleft.y = 0;

tileUp.x = 0;
tileUp.y = -1;

tileDown.x = 0;
tileDown.y = 1;

In this case, each Coordinate is the offset (X and Y) to the tile in that direction. If I knew Pac-Man was at location 5,5, I could simply add the appropriate offsets and get the coordinate of the tile in that direction. Here is the previous example done using these new Coordinate structures:

int8_t px, py;
int8_t, nextTileX, nextTileY;

px = 5;
px = 5;
pacDir = RIGHT;

if (pacDir==RIGHT) {
  nextTileX = px + tileRight.x;
  nextTileY = py + tileRight.y;
} else if (pacDir==LEFT) {
  nextTileX = px + tileLeft.x;
  nextTileY = py + tileLeft.y;
} else if (pacDir==UP) {
  nextTileX = px + tileUp.x;
  nextTileY = py + tileUp.y;
} else if (pacDir==DOWN) {
  nextTileX = px + tileDown.x;
  nextTileY = py + tileDown.y;
}

It doesn’t look like we gained anything, because we haven’t. However, since we are already tracking directions in multiple places (like which Pac-Man sprite bitmap to display, and which direction the joystick is being pressed), we can continue to use this by creating an array of “next tile” coordinates in the same order as the directions (0=right, 1=left, 2=up, 3=down).

A note on arrays: If you create an array of integers, like “int foo[10];”, you can initialize them one at a time like “foo[0]=1;” and “foo[9]=42;”, or you can initialize them all at the same time like “int foo[10] = {1,2,3,4,5,6,7,8,9,10};” You can also do this with structures, but instead of just putting a single number there, you are putting in initializer values for each element of the structure. Using extra braces around each one makes this example more clear:

#define RIGHT 0
#define LEFT  1
#define UP    2
#define DOWN  3
#define DIRS  4

Coodinate nextTileOffset[DIRS] = {
 {  1, 0 }, // 0=right
 { -1, 0 }, // 1=left
 {  0,-1 }, // 2=up
 {  0, 1 }  // 3=down
};

C would have allowed you to just do “Coordinate nextTileOffset[DIRS] = {1, 0, -1, 0, 0, -1, 0, 1};” but please don’t do that. It’s icky.

So above, nextTileOffset[0].x is 1, and nextTileOffset[0].y is 0, and nextTileOffset[1].x is -1 and nextTileOffset[1].y is 0. Great. So what?

Once this is done, the next tile can be figured out by applying whatever set of coordinates is in the array element that corresponds to the direction (0-3):

int8_t px, py;
int8_t, nextTileX, nextTileY;

px = 5;
px = 5;
pacDir = RIGHT;

nextTileX = px + nextTileOffset[pacDir].x;
nextTileY = py + nextTileOffset[pacDir].y

A function could even be written to do this, by letting you pass in X, Y and DIRECTION, and then two variables by reference that will be modified and returned from the function:

bool getNextTile(int8_t x, int8_t y, uint8_t dir, int8_t *nextX, uint8_t *nextY)
{
  // We might want to error check.
  if (dir>DIRS) return false;

  *nextX = x + nextTileOffset[dir].x;
  *nextY = y + nextTileOffset[dir].y;

  return true;
}

Now we could do it like this:

int8_t px, py;
int8_t, nextTileX, nextTileY;

px = 5;
px = 5;
pacDir = RIGHT;

getNextTile(px, py, pacDir, &nextTileX, &NextTileY);

And if you enjoy checking for potential errors…


if (getNextTile(px, py, pacDir, &nextTileX, &nextTileY)==true)
{
  // We were able to determine the next tile.
} else {
  // Bad things happened.
}

The error condition currently just makes sure DIR is valid to avoid accessing a non-existent array member and potentially crashing the system, but it could be modified to return some status about what is in the next tile - wall? dot? Ghost? Bacon?

The reason I decided to make a function for this is because it will be used for other purposes, like the ghost logic where they look ahead one tile to determine which direction to go.

Now that we have a simple way to determine what tile is in front of our sprite, we can proceed to see if that tile contains a dot...

To be continued...

Raspberry Pi – what a difference a year (or more) makes!

Like many things that I post, this is a long and rambling story that could be summarized like this: Wow, it sure is easier to set up a Raspberry Pi image today than it used to be!

…and now, the long version:

I began my current embedded C programming job in April 2012. This is what (indirectly) got me exposed to Arduino and Raspberry Pi. While I was certainly aware of both of them (and had even considered buying an Arduino UNO to play with), I had never actually used either. I have previously mentioned how getting to use an Arduino at work led me to using them for a local Halloween haunted house input controller project. I also ended up ordering a Raspberry Pi to have a cheap Linux-style system to learn on.

In July 2012, I ordered  Raspberry Pi from Newark/Element14 and it arrived a month later. (I actually got mine sooner than a coworker who had ordered his as soon as RaspberryPi.org started taking pre-orders.) Getting this Pi going was cumbersome, and involved downloading special utilities that could format an SD card on a Mac in the Linux file format so it could then be booted on the Pi.

As I have mentioned in other posts, I have lately had an interest in retro gaming (a very affordable alternative when you can’t justify buying a $400 PlayStation 4). A friend of mine, that I will call Mike (because that is his name), informed me that a cheap $35 Raspberry Pi could be made in to a nice retro emulation system running MAME and other open source emulators (see the PiMAME project). If you are interested in this, all you need is a Raspberry Pi, an enclosure, USB power supply and an SD card. The whole thing could be bought for around $60.

Unfortunately, I couldn’t get all the emulators to recognize the controller I wanted to use, and Mike had to loan me a USB keyboard so I could navigate the menus since most of them didn’t accept joystick input. Plus, getting game ROMs on to the Pi was difficult since you couldn’t just take the Linux-formatted SD card out and copy games to it directly. Most folks just put their Pi on the home network and used FTP. That’s fine, but I had no ethernet available in the living room where my Pi was hooked to a TV. The Pi has two USB ports, but all the sites say you shouldn’t try to run a wireless adapter without using a powered hub. The end result would have ben a Pi with power supply, a USB hub with power supply, a USB keyboard, USB mouse, a WiFi dongle, plus a cable running to the joystick (if I could get it to work). (Mike used a hybrid keyboard/trackpad thing that cut down on cables a bit.)

Even without getting this far, and just playing games using the USB keyboard, I found that PiMAME wasn’t fast enough to some of the later games I tried. It was, like many open source projects, a clunky mess to get things all going and working together. I am told the PiMAME team is working on making a much nicer front end that would at least take care of the user interface challenges.

Thus, I gave up on PiMAME and switched over to a commercially available Ouya Anrdoid device (thank you, Amazon, for the chance to review this interesting product). The Ouya plays games that the Pi had trouble with, plus has WiFi built in, can read a PC-formatted drive, and also comes with a wireless controller. It still doesn’t seem to support the USB joystick I want to use, but it did recognize my Atari 2600 USB joystick just fine — great for the 2600 emulator!

So tonight I traded my original Pi to Mike, and I decided I would download a fresh Pi image and reset my PiMAME box back to stock Linus. Boy have things changed!

Today, you just go get an SD formatting utility (for Mac or Windows), use it to format the SD card, and then extract a zip file to the card. The Pi can boot off that, and then the rest of the install can be done via a keyboard/TV. There was even a smaller version of the zip that would download the rest over the internet.

I just tried it and it worked fine. One downside: If you choose the NOOBS image, a keyboard is required (but not a mouse; you can use arrow keys and hot keys to do the install). If you choose Raspberian directly, it starts up with SSH enabled so you can hook the Pi to a computer and ssh in to it and complete the install/setup without ever hooking up a monitor or keyboard. Thank goodness I forgot to return the USB keyboard to Mike tonight, else I would have been starting all over with that image. (I haven’t had a wired keyboard/mouse in ages.)

Good job, Pi team. This is so much easier than the first time around.

UPDATE: I have now learned that the “easy” part of this is the NOOBS distribution. If you want to directly install Rasperian, that still requires writing out a raw Linux disk image using special software. The Rasperian image has SSH enabled by default (I think) so you can just insert the SD card, and then plug the Pi up to your router and boot it. You can log in to your router to find the IP address the Pi is using, then ssh in to it to finish configuration. The NOOBS install is a front end to install an OS, so it has no remote capability enabled. Thus, you have to have an HDMI tv/monitor (or composite monitor) and USB keyboard to get it to install Rasperian (or other OS).

UPDATE 2: And, I have found out that my system no longer likes my Transcend 8GB SD card, which used to work fine. Previously, my system had locked up after a system update and I assumed it had just messed up and bricked the image. I never got around to restoring the SD card until the other night. While it boots NOOBS and installs, it hangs during the Raspberrian reboot with some disk error I find in numerous reports online… My card model appears on the compatible list, so perhaps it’s just going bad? Dunno. My Pi is dead for now, until I get the SD card issue figured out.

2014/12/31 UPDATE: The December 2014 release of NOOBS has fixed the issues and now that SD card boots just fine. Win!

CoCo SDC – compatible disk controller using SD cards

10/20/2014 Update: They made more! Ed Snider is producing them with Darren’s permission. Contact him at zippster278@gmail.com

12/12/2014 Update: Tim Lindner is making plexiglass enclosures for the CoCoSDC, too (in case you don’t have a dead FD502 cartridge to repurpose).

Update: The first run of these packs was sold out, and no more are available unless they decide to make more later. As a hobby project, these were being hand assembled as a fun project. Still, it’s a very interesting project to read about.

A project that I mentioned last year in my CoCoFEST! report has made it to distribution. The CoCo SDC project by Darren A. is designed to plug in to a Tandy/Radio Shack TRS-80 Color Computer (CoCo) cartridge port and act like a floppy drive controller. Instead of hooking up old 5 1/4″ or 3.5″ low density floppy drives, the device uses an SD memory card. Disk images can be placed on the card, and then the CoCo doesn’t know the difference. This is huge, since it provides the first nearly 100% compatible virtual drive experience the CoCo has ever had.

Every drive replacement solution over the years, including the original “most compatible” RGB-DOS for hard drives, only worked with programs that used DISK BASIC calls to do the disk I/O. This eliminated many games that did their own disk I/O for speed or copy protection. Also, even the virtual floppy drive system of RGB-DOS (and later modifications, such as HDB-DOS from Cloud-9) had drawbacks. While it might be possible to have 255 virtual floppies on a CoCo, and the system allowed doing things like “DIR 201” to access higher drive numbers, most CoCo software only expected drive numbers 0-3. Multi-disk programs might expect the main disk in DRIVE 0 and the second disk in DRIVE 1. None of these could work directly without physically being on those drives, or having the software patched.

CoCo SDC solves this by simply presenting virtual disk images as drives 0-3. Extensions to DISK BASIC allow selecting the file and on which drive letter it should appear as a valid floppy.  Very clever.

Swapping out virtual disks is done using DISK BASIC commands (extended to support this).

Check it out today:

http://cocosdc.blogspot.com

Arduino Pac-Man part 6 – moving

See also: part 1, part 2, part 3, part 4 and part 5.

It’s funny how easy something can be done when you aren’t trying to do it, and how difficult it can be once you actually start to focus on the task at hand. For the past few days, I have spent very little time coding, and quite a bit of time thinking. And coding small examples to see if my thinking was correct.

As I learn more about Pac-Man from sites such as Jamey Pittman’s Pac-Man Dossier, I realize there is much more to this simple dot eating game than I ever imagined. Fortunately, the design work has already been done and all I should have to do is create a clean room version of the game. (In this case, clean room refers to never seeing the actual original code, but having a rather extensive list of functionality so a workalike could be created. It was this technique that allowed for the explosion of IBM-PC compatible computers in the early 1980s when other companies created clean room versions of the BIOS chip that made a PC a PC. But I digress.)

The bane of my existence the past few days has been this simple image from the Pac-Man Dossier site:

CorneringI have heard that Pac-Man players could evade ghosts by turning corners, but I never knew why. It turns out, in the original Pac-Man game, Pac-Man can basically cut corners (now those rounded edges have a purpose!) while the ghosts always turn at 90 degree angles. If you recall my earlier posting about wall detection, recreating these rounded corners was causing me a bit of a problem since it was not easy to just detect set pixels – my Pac-Man could get stuck on those corners. My plan was to use the tile grid system and just make my Pac-Man not need to detect wall pixels… But once I saw that the original game actually used this for something, I figured I would try to cut corners with my version.

Over the course of this week, I thought about different approaches to this, and even coded a few samples, including some that would let my Pac-Man round those corners nicely. I was still able to create a situation where Pac-Man could get stuck. Basically, if Pac-Man was moving LEFT down a hallway, and the joystick was pressed DOWN, my new code would start hugging the wall and moving down/left around a corner. But, if at the very moment Pac-Man was on the curved edge, the player decided to move UP, the code would start moving Pac-Man up and he would be one pixel out of alignment with the maze grid and get stuck.

I had concluded that I would simply have to know when Pac-Man was moving in a diagonal so I could force the character to keep moving until it ended up back in the center of a tile before letting the player switch directions. My solution was to detect when Pac-Man was off the grid center, and adjust accordingly. (I wonder what the arcade game does if you try this?)

As it turns out, It was going to be quite a bit of work, and even implementing this would not truly replicate this feature in Pac-Man. For my low resolution display, I really would have only one step that could be saved between moving left and moving down. One pixel. But in the original arcade game, there were multiple variations. Observe this image also from the Pac-Man Dossier site:

cornering_example2Above, the yellow square in the center of the green and red squares is where Pac-Man could turn at a 90 degree angle. In the top left image, you can see he would have to move four pixels to the right, then four pixels down (8 pixels total) doing a 90 degree turn, or he could go down as soon as possible and start moving diagonally down/right (like the left side of a right triangle) and get from start to destination in only four pixels. I suppose in my implementation, moving from taking two pixels to one pixel would be the same type of advantage (half as much time). But, all those other lines show how there were actually other places the turn could be made, each saving a different amount. I simply do not have the resolution to replicate this fully.

So, for now, I am going to skip this feature and let my Pac-Man only turn corners at the center of a tile (90 degree turn), just like the ghosts. This brought me back to the wall detection system, and my thoughts of using the tile grid. Here is what the screen looks like with each grid spot marked. It is like the arcade tile layout (28×36), except there are five rows of tiles above and below the maze (score, players left, level, etc.) that I am not displaying.

Tiles for the Pac-Man game.The tiles that contain maze parts look like this:

Pac-Man maze tiles.But since a tile was smaller than a Pac-Man or ghost sprite (just like in the arcade), I could not just use a tile X/Y location for the game. My tiles were 3×3, and the characters were 5×5. In order to move a character smoothly, I still needed to track the screen X/Y coordinate. For instance, once my maze was drawn, my Pac-Man character would be displayed at position 41,70. This would correspond to tile 14,23. As the character moved right, it would move three pixels before entering the next tile. So, the tile position would remain 14,23 for three moves, while the screen position moved from 41,70 to 44,70. (I really need drawings for this. This isn’t making much sense to me as I type it out.)

Basically, I would need to track both screen position and tile position. After testing several methods, I think I finally decided on one.

First, let’s revisit the tiles. I created an array of bytes that represent the walls and the hallways. It looks like this:

#define TILEW    28
#define TILEH    31

#define TILEBYTESPERROW ((TILEW-1)/8+1) // bytes per row in mazeTiles

PROGMEM const unsigned char mazeTiles[TILEH][TILEBYTESPERROW] = {
  0b11111111, 0b11111111, 0b11111111, 0b11110000,
  0b10000000, 0b00000110, 0b00000000, 0b00010000,
  0b10111101, 0b11110110, 0b11111011, 0b11010000,
  0b10111101, 0b11110110, 0b11111011, 0b11010000,
  0b10111101, 0b11110110, 0b11111011, 0b11010000,
  0b10000000, 0b00000000, 0b00000000, 0b00010000,
  0b10111101, 0b10111111, 0b11011011, 0b11010000,
  0b10111101, 0b10111111, 0b11011011, 0b11010000,
  0b10000001, 0b10000110, 0b00011000, 0b00010000,
  0b11111101, 0b11110110, 0b11111011, 0b11110000,
  0b00000101, 0b11110110, 0b11111010, 0b00000000,
  0b00000101, 0b10000000, 0b00011010, 0b00000000,
  0b00000101, 0b10111001, 0b11011010, 0b00000000,
  0b11111101, 0b10100000, 0b01011011, 0b11110000,
  0b00000000, 0b00100000, 0b01000000, 0b00000000,
  0b11111101, 0b10100000, 0b01011011, 0b11110000,
  0b00000101, 0b10111111, 0b11011010, 0b00000000,
  0b00000101, 0b10000000, 0b00011010, 0b00000000,
  0b00000101, 0b10111111, 0b11011010, 0b00000000,
  0b11111101, 0b10111111, 0b11011011, 0b11110000,
  0b10000000, 0b00000110, 0b00000000, 0b00010000,
  0b10111101, 0b11110110, 0b11111011, 0b11010000,
  0b10111101, 0b11110110, 0b11111011, 0b11010000,
  0b10001100, 0b00000000, 0b00000011, 0b00010000,
  0b11101101, 0b10111111, 0b11011011, 0b01110000,
  0b11101101, 0b10111111, 0b11011011, 0b01110000,
  0b10000001, 0b10000110, 0b00011000, 0b00010000,
  0b10111111, 0b11110110, 0b11111111, 0b11010000,
  0b10111111, 0b11110110, 0b11111111, 0b11010000,
  0b10000000, 0b00000000, 0b00000000, 0b00010000,
  0b11111111, 0b11111111, 0b11111111, 0b11110000
};

It is a multidimensional array of 32 rows of 3 bytes each. I would need a routine that could take a tile X,Y coordinate and tell me if it was empty (0) or a wall (1). I ended up with something that looks like this:

// Given tile x/y, return whether that tile is path (0) or wall (1)
uint8_t getTileStatus(uint8_t tilex, uint8_t tiley)
{
  uint8_t byte = pgm_read_byte_near(&mazeTiles[tiley][tilex/8]);
  uint8_t bit = tilex-((tilex/8)*8);
  return bitRead(byte, 7-bit);
}

To test this out, I created a few routines that would try to draw the maze tiles (like the earlier screen shots). Here is one that draws the wall tiles:

// Draw tiles for the walls.
void drawWallTiles()
{
  // Draw grid center dots.
  for (uint8_t tilex=0; tilex<TILEW; tilex++)
  {
    for (uint8_t tiley=0; tiley<TILEH; tiley++)
    {
      if (getTileStatus(tilex, tiley)!=0)
      {
        TV.draw_rect(tilex*3, tiley*3, 2, 2, WHITE, WHITE);
      }
    }
  }
}

And if I wanted to draw just the path (non wall) sections, it might look like this:

// Draw tile center dots.
void drawTileCenters()
{
  for (uint8_t tilex=0; tilex<TILEW; tilex++)
  {
    for (uint8_t tiley=0; tiley<TILEH; tiley++)
    {
      if (getTileStatus(tilex, tiley)==0)
      {
        TV.set_pixel((tilex*3)+1, (tiley*3)+1, WHITE);
      }
    }
  }
}

Clever readers may notice that this is the same function, except it looks for empty tiles (0) and then just puts a dot in the center instead of drawing a box.

Ah, a quick word about coordinates. Originally I was using X and Y to represent the top left of an item. Since Pac-Man uses the center of the character, I have adjusted accordingly. If I were to draw a 5×5 ghost on the screen at position 0,0, the center would be 2,2. I will be using the center coordinate, meaning that any time I draw a ghost or Pac-Man, I would draw it at X-2,Y-2. This adds a bit of overhead every single time due to the extra subtraction, but for now it will make things easier. If I ever decide to write a series on optimizations, I can share many of the methods I use to increase code speed or reduce code size by improving this example.

So now I would be using X,Y as the center of an object, and using the tile X,Y to look for walls (and, later, the ghost behaviors which are entirely based on targeting tiles).

This next part is going to get even more confusing…

If I decide Pac-Man should start out on tile 14,23, that could be converted to a screen position like this:

  uint8_t pactilex, pactiley;
  uint8_t px, py;

  pactilex = 14;
  pactiley = 23;

  px = pactilex*TILESIZE+1;
  py = pactiley*TILESIZE+1;

(TILESIZE is 3.) The +1 is to get to the center of a 3×3 tile. px and py would now be the center for where Pac-Man is. But, since Pac-Man is not going to jump three pixels at a time, I decided I would need an offset value as well. The offset would be -1, 0 or 1 depending on where the character was in relation to the center of a tile.

  uint8_t pactilex, pactiley;
  int8_t  pacoffx, pacoffy;

  uint8_t px, py;

  pactilex = 14;
  pacoffx = -1;

  pactiley = 23;
  pacoffy = 0;

  px = pactilex*TILESIZE+1+pacoffx;
  py = pactiley*TILESIZE+1+pacoffy;

The offset could change to move the character one position left or right of the center of a tile. Then, when moving a character, I will use the tile X,Y position, and the offset. As a character moves to the right, it first enters a tile at the far left, with an offset of -1. Then the offset goes to 0 and the character is in the center. Then it goes to the right and the offset is 1. Then it moves to the next tile with an offset of -1, and so on. Trust me, it works.

And, by doing this, I now know when a character is at the center of a tile (offset is 0) and that’s when I can check for walls, etc. Also, collision with ghosts is based on the center, so if a ghost is at 12,16 with an offset of 0, and Pac-Man is at 12,16 with an offset of 1, they have not yet collided. Ditto for eating the dots. Everything is based on the center of the tile or the center of the character. It looks like the pieces are starting to fall together… Again.

I would handle moving the same way I did in the demo, using mx and my to represent the movement along the X or Y axis. mx=1 means the character is moving to the right. mx=-1 means it is moving to the left. mx=0 means it is not moving.

After checking the input and setting mx and my accordingly, I could do something like this:

    if (mx!=0 && pacoffx==0) // Moving LEFT or RIGHT.
    {
      if (getTileStatus(pactilex+mx, pactiley)!=0) mx = 0;
    }

    if (my!=0 && pacoffy==0) // Moving UP or DOWN.
    {
      if (getTileStatus(pactilex, pactiley+my)!=0) my = 0;
    }

This means, if we are moving along X (mx!=0), and we are in the center of a tile (x offset 0), now would be a good time to check for a wall. We either check the left or right tile (based on mx being -1 or 1) on our current tile row, and if we find a wall, we stop moving.

Instant wall detection based on the tile map!

To actually do the moving, things get a bit messier and I expect I can rewrite this and make it clever. Right now, it’s this…

    // Move...
    if (mx!=0 && pacoffy==0)
    {
      px = px + mx;
      pacoffx = pacoffx + mx;
      if (pacoffx<-1) {
        pacoffx=1;
        pactilex--;
      }
      if (pacoffx>1) {
        pacoffx=-1;
        pactilex++;
      }
    }

    if (my!=0 && pacoffx==0)
    {
      py = py + my;
      pacoffy = pacoffy + my;
      if (pacoffy<-1) {
        pacoffy=1;
        pactiley--;
      }
      if (pacoffy>1) {
        pacoffy=-1;
        pactiley++;
      }
    }

In the first part, if we are moving LEFT or RIGHT (mx!=0) and we are vertically in the center of a tile (pacoffy==0) then we know we could allow turning UP or DOWN (if there is no wall). The pacoffy==0 prevents us from turning around the corner and getting stuck (problem solved!). If it looks like we can move this direction, we apply the movement (px = px + mx) and then we do the same for the tile offset (adding -1 or 1 or 0 to it). We then check for rollovers… If the offset is less than -1, we have moved left in to the next tile so we decrement the tile X and then set the offset to 1 (we are at the right side of the tile to the left of where we started). If we are moving to the right, we do the same but opposite… If greater than 1, we move tile X to the right and reset the offset to the left of that tile, -1.

And now we have easy movement that, so far, does not let me get stuck in a corner.

Here is a rough sample of how this works — using dots in the center of each tile that would be a wall, and a square for the player character. I have included a few other draw routines you can play with if you want to see the grid blocks or the maze wall blocks.

#include <TVout.h>

TVout TV;

#define JOYSTICKXPIN 1
#define JOYSTICKYPIN 0

#define DIRRIGHT     0
#define DIRLEFT      1
#define DIRUP        2
#define DIRDOWN      3

#define TILESIZE 3
#define TILEW    28
#define TILEH    31

#define TILEBYTESPERROW ((TILEW-1)/8+1) // bytes per row in mazeTiles

PROGMEM const unsigned char mazeTiles[TILEH][TILEBYTESPERROW] = {
  0b11111111, 0b11111111, 0b11111111, 0b11110000,
  0b10000000, 0b00000110, 0b00000000, 0b00010000,
  0b10111101, 0b11110110, 0b11111011, 0b11010000,
  0b10111101, 0b11110110, 0b11111011, 0b11010000,
  0b10111101, 0b11110110, 0b11111011, 0b11010000,
  0b10000000, 0b00000000, 0b00000000, 0b00010000,
  0b10111101, 0b10111111, 0b11011011, 0b11010000,
  0b10111101, 0b10111111, 0b11011011, 0b11010000,
  0b10000001, 0b10000110, 0b00011000, 0b00010000,
  0b11111101, 0b11110110, 0b11111011, 0b11110000,
  0b00000101, 0b11110110, 0b11111010, 0b00000000,
  0b00000101, 0b10000000, 0b00011010, 0b00000000,
  0b00000101, 0b10111001, 0b11011010, 0b00000000,
  0b11111101, 0b10100000, 0b01011011, 0b11110000,
  0b00000000, 0b00100000, 0b01000000, 0b00000000,
  0b11111101, 0b10100000, 0b01011011, 0b11110000,
  0b00000101, 0b10111111, 0b11011010, 0b00000000,
  0b00000101, 0b10000000, 0b00011010, 0b00000000,
  0b00000101, 0b10111111, 0b11011010, 0b00000000,
  0b11111101, 0b10111111, 0b11011011, 0b11110000,
  0b10000000, 0b00000110, 0b00000000, 0b00010000,
  0b10111101, 0b11110110, 0b11111011, 0b11010000,
  0b10111101, 0b11110110, 0b11111011, 0b11010000,
  0b10001100, 0b00000000, 0b00000011, 0b00010000,
  0b11101101, 0b10111111, 0b11011011, 0b01110000,
  0b11101101, 0b10111111, 0b11011011, 0b01110000,
  0b10000001, 0b10000110, 0b00011000, 0b00010000,
  0b10111111, 0b11110110, 0b11111111, 0b11010000,
  0b10111111, 0b11110110, 0b11111111, 0b11010000,
  0b10000000, 0b00000000, 0b00000000, 0b00010000,
  0b11111111, 0b11111111, 0b11111111, 0b11110000
};

void setup()
{
  TV.begin(NTSC, 120, 96);

  TV.clear_screen();

  //drawTileGrid();
  //drawWallTiles();
  drawTileCenters();
}

void loop()
{
  uint8_t px, py;
  int8_t  mx, my; // Movement direction (-1 to 0 to +1)

  uint8_t pactilex, pactiley;
  int8_t  pacoffx, pacoffy;

  pactilex = 14;
  pacoffx = -1;
  pactiley = 23;
  pacoffy = 0;

  px = pactilex*TILESIZE+1+pacoffx;
  py = pactiley*TILESIZE+1+pacoffy;

  mx = my = 0;

  while(1)
  {
    uint8_t dir;

    dir = readJoystick();

    switch(dir)
    {
    case DIRLEFT:
      mx = -1;
      break;
    case DIRRIGHT:
      mx = 1;
      break;
    case DIRUP:
      my = -1;
      break;
    case DIRDOWN:
      my = 1;
      break;

    default:
      break;
    }

    // Wall detection.
    if (mx!=0 && pacoffx==0) // Moving LEFT or RIGHT.
    {
      if (getTileStatus(pactilex+mx, pactiley)!=0) mx = 0;
    }

    if (my!=0 && pacoffy==0) // Moving UP or DOWN.
    {
      if (getTileStatus(pactilex, pactiley+my)!=0) my = 0;
    }

    TV.delay_frame(1);

    // Erase player.
    TV.draw_rect(px-2, py-2, 4, 4, BLACK);

    // Move...
    if (mx!=0 && pacoffy==0)
    {
      px = px + mx;
      pacoffx = pacoffx + mx;
      if (pacoffx<-1) {
        pacoffx=1;
        pactilex--;
      }
      if (pacoffx>1) {
        pacoffx=-1;
        pactilex++;
      }
    }

    if (my!=0 && pacoffx==0)
    {
      py = py + my;
      pacoffy = pacoffy + my;
      if (pacoffy<-1) {
        pacoffy=1;
        pactiley--;
      }
      if (pacoffy>1) {
        pacoffy=-1;
        pactiley++;
      }
    }

    // Draw player.
    TV.draw_rect(px-2, py-2, 4, 4, WHITE);
  }
}

// Given tile x/y, return whether that tile is path (0) or wall (1)
uint8_t getTileStatus(uint8_t tilex, uint8_t tiley)
{
  uint8_t byte = pgm_read_byte_near(&mazeTiles[tiley][tilex/8]);
  uint8_t bit = tilex-((tilex/8)*8);
  return bitRead(byte, 7-bit);
}

// Draw tiles for the walls.
void drawWallTiles()
{
  // Draw grid center dots.
  for (uint8_t tilex=0; tilex<TILEW; tilex++)
  {
    for (uint8_t tiley=0; tiley<TILEH; tiley++)
    {
      if (getTileStatus(tilex, tiley)!=0)
      {
        TV.draw_rect((tilex*3), (tiley*3), 2, 2, WHITE, WHITE);
      }
    }
  }
}

// Draw tile center dots.
void drawTileCenters()
{
  for (uint8_t tilex=0; tilex<TILEW; tilex++)
  {
    for (uint8_t tiley=0; tiley<TILEH; tiley++)
    {
      if (getTileStatus(tilex, tiley)!=0)
      {
        TV.set_pixel((tilex*3)+1, (tiley*3)+1, WHITE);
      }
    }
  }
}

// Draw tile grid.
void drawTileGrid()
{
  for (uint8_t x=0; x<TILEW; x++)
  {
    for (uint8_t y=(x & 1); y<TILEH; y=y+2)
    {
      TV.draw_rect(x*TILESIZE, y*TILESIZE, 2, 2, WHITE, WHITE);
    }
  }
}

// Read joystick.
uint8_t readJoystick()
{
  uint16_t joy;

  joy = analogRead(JOYSTICKXPIN);
  if (joy<24)
  {
    return DIRLEFT;
  }
  else if (joy>1000)
  {
    return DIRRIGHT;
  }

  joy = analogRead(JOYSTICKYPIN);
  if (joy<24)
  {
    return DIRDOWN; // reversed on iTead Joystick Shield
  }
  else if (joy>1000)
  {
    return DIRUP;
  }
}

Next time, I will move this movement system in to the maze bitmap display, and see if I can’t get the animated Pac-Man running around the maze without getting stuck. Of course, after all this work, it won’t look like much more than the first demo I put together… But it looks much different on the inside…

To be continued…

Arduino Pac-Man part 5 – dot dilemma

(This bit was written on 2/10/2014. I am setting these posts to go up on a schedule so I don’t just dump five new articles in one day. It’s like time travel. By the time you read this, and notice problems in what I have written, I may have already figured that out, in the past. Time travel is cool.)

See also: part 1, part 2, part 3 and part 4.

In today’s part of this series I will be stepping away from coding for a bit to discuss some problems that popped up once I actually took some time to think about the task at hand: writing a Pac-Man game for an Arduino.

In the previous four parts, you have seen how I went from hooking up two resistors to get video out of an Arduino, to experimenting with the TVout library to display things on a TV screen and, ultimately, creating the shell of a Pac-Man style dot eating game. Initially, things went very fast and easy. In the video demo I originally posted…


…you see what appears to be a somewhat working game engine, complete with an animated character that can be moved around a maze (without going through the walls) and passing over dots which disappear. What can I say? I got lucky, and remember doing things like this in BASIC on my first computer, a Commodore VIC-20 and then on a Radio Shack TRS-80 Color Computer.

But there was a problem with the demo. The way Pac-Man is kept from running through walls is by checking pixels in the direction he is moving. If there is a conflict, I stop the motion. As you see in the video, this simple trick works quite well…unless you have rounded corners. To demonstrate, let’s revisit the layout graphic I created:

Screen Shot 2014-02-10 at 9.43.56 PMHere, the red Pac-Man is heading to the right, and it can easily check for the white wall in front of it by checking above or below where the dot would be (so it does not detect a dot and stop on it). BUT, I kept finding the Pac-Man getting stuck and then even running through and over the screen, crashing in to unknown memory. After a bit of experimenting, I understood why.

Screen Shot 2014-02-10 at 9.47.57 PMIn this second photo, if Pac-Man was moving down, and then at this moment he tried to move right, the program would allow it since there was no pixel to the top right or bottom right. It was because of this that my Pac-Man would get stuck and, sometimes, get out of alignment with the maze and end up going through walls.

I experimented with various types of hot spots, and thought this one might work:

Screen Shot 2014-02-10 at 9.50.26 PMMy thought was that I would first check the green dots in the direction Pac-Man was moving. If they were clear, then I would check the light blue dots. As you can see, it works for this corner situation. Unfortunately, it also stopped the Pac-Man when it was a full pixel from any solid wall since the green dots would pass, but the light blue were on the wall. Fail! If I had just made the walls square, I could cheat and do it like this quite easily, but I am trying to replicate the arcade game as close as I can given the limitations of these graphics.

If you have already seen a solution, I’d love to hear it. But as it turns out, I won’t be doing it this way as all because while I was trying to figure this out, I ran in to a much larger problem: the dots.

I already knew I couldn’t just check for pixel collision. I had to ignore the dots. I had planned to just check above and below where the dot would be. In Invaders09 (my Space Invaders-style game I did in 6809 assembly for the Color Computer), I checked for screen pixels for my collision and it worked great. But, I had a multicolor screen and I could check for pixels of a certain color and determine what to do based on that. (I could have put white stars in the background and ignored them, but stopped on anything not white to test for a hit.)

But that’s not the problem… I was already looking ahead to the ghosts and, indeed, my current version of the software has four ghosts (three in the “Ghost House” and one on top in starting position) sitting there, animating away (but not moving around yet). As those ghosts wander the maze (needing the same type of wall detection as our hero would need), I realized they would pass over the dots and erase them, just like Pac-Man does. (Yeah, in my demo it looked like he was eating the dots, but it was really just erasing them.)

Normally, I might just track all of the dots on the screen as game objects. But, with only 2K of memory on an Arduino UNO (and most of that being used for the video output — I had about 230 bytes free), there was not even enough RAM to give each dot a status byte. I needed something more clever.

First, I could just leave them on the screen and do pixel detection. I would have to have the ghosts detect walls AND see if they hit a dot. If there was a dot, I would redraw it as the ghost passed. If Pac-Man hit a dot, I could score it and not redraw it. Not a bad idea.

Second, I could have the dot positions stored in Flash in an array (so it did not use RAM) and use  an RAM array of bytes where each bit represented the status of a dot. There are 240 dots, so dividing that by 8 (eight bits per byte, so each byte could represent eight dots) would take up only 30 bytes. As things moved around the screen, I could see if there were on a “dot spot” and check the status of the dot by looking it up in a table and checking those RAM bytes. Also not a bad idea.

Third, I worried that even 30 bytes might be too much, and considered just setting pixels along the right side of the screen and using video memory for scratch memory. As the game played, there would be an annoying set of dots/lines somewhere on the right side that would slowly vanish as Pac-Man ate up the dots. This would probably look awful. This was a bad idea.

While I was worrying about the dots and wall detection, I then realized I have no idea how Pac-Man really works. If I were being lazy (like oh so many 80s home versions of dot eating games), I would just let the ghosts wander around the maze randomly or in the general direction of the player. It would be a fine dot gobbling game, but it wouldn’t be Pac-Man to anyone who really knew how to play it. I have long heard that Pac-Man had patterns that could be used to solve each level, and that each ghost had its own personality with how it moved. Most home versions did not play the same.

pacman-cocoThinking back, I remembered an Australian fried of mine, Nick Marentes, had come out with a Pac-Man Tribute game for the Tandy Color Computer 3 years ago. He lovingly recreated the look of the game and the sounds of the game, but since I was not much of a player of the arcade version, I had no idea if it played remotely like the original.

So I asked him.

No. I didn’t have any of those luxuries when I did Pacman. I came up with my own algorithm.

But it came out very similar in concept although I didn’t have the multiple Ghost personalities. – Nick M.

Nick is a real game programmer going back to the early 1980s on a TRS-80 Model I. He even had hist games sold by Radio Shack. His website is a great read. Here is his Pac-Man Tribute website:

http://www.members.optusnet.com.au/nickma/ProjectArchive/pacman.html

And his current game project, Popstar Pilot (which is what inspired me to document my humble efforts at a game):

http://www.members.optusnet.com.au/nickma/PopstarPilot/

If Nick couldn’t figure it out, what chance did I have? But, he wrote his tribute back in 1997. The public internet was barely getting started (remember AOL?). Today, you can find just about anything! And indeed, this is the case. Nick referred me to a webpage called “Game Internals”:

http://gameinternals.com/post/2072558330/understanding-pac-man-ghost-behavior

This site broke down how the ghosts behaved, and in doing so, also solved my wall detection problem. (See also: Jamey Pittman’s “Pac-Man Dossier”). Everything seems to have been reverse engineered about this game, allowing someone to create a very accurate playalike, if they desire.

The modern programmer has no excuse nowadays. – Nick M.

If you visit either of those sites, you will see that the Pac-Man screen is broken up in to 8×8 tiles, and the entire 224×288 resolution display holds 28×36 tiles. The ghosts use these tiles for targeting where they will move. A ghost will decide which route to take, for instance, by determining which open tile (left, right, up, down) from the ghost is closer to the target square. In some cases, this causes the ghosts to do stupid things, like taking a much longer route than necessary. Math is hard.

The tiles are also used for collision detection. Since the Pac-Man game sprites are larger than a tile, a sprite is considered to be in whatever tile the CENTER of the sprite touches. And if a ghost and Pac-Man are in the same tile, there is a collision.

I decided I would create a binary map of the tiles to represent walls or spaces. 28×36 tiles is 1008, and if I used bits, I could represent that with an array of 126 bytes in flash memory (1008/8). I created the tile map in a text editor:

XXXXXXXXXXXXXXXXXXXXXXXXXXXX
X            XX            X
X XXXX XXXXX XX XXXXX XXXX X
X XXXX XXXXX XX XXXXX XXXX X
X XXXX XXXXX XX XXXXX XXXX X
X                          X
X XXXX XX XXXXXXXX XX XXXX X
X XXXX XX XXXXXXXX XX XXXX X
X      XX    XX    XX      X
XXXXXX XXXXX XX XXXXX XXXXXX
     X XXXXX XX XXXXX X
     X XX          XX X
     X XX XXX--XXX XX X
XXXXXX XX X      X XX XXXXXX
          X      X          
XXXXXX XX X      X XX XXXXXX
     X XX XXXXXXXX XX X
     X XX          XX X
     X XX XXXXXXXX XX X
XXXXXX XX XXXXXXXX XX XXXXXX
X            XX            X
X XXXX XXXXX XX XXXXX XXXX X
X XXXX XXXXX XX XXXXX XXXX X
X   XX                XX   X
XXX XX XX XXXXXXXX XX XX XXX
XXX XX XX XXXXXXXX XX XX XXX
X      XX    XX    XX      X
X XXXXXXXXXX XX XXXXXXXXXX X
X XXXXXXXXXX XX XXXXXXXXXX X
X                          X
XXXXXXXXXXXXXXXXXXXXXXXXXXXX

The Pac-Man tiles include the whole screen, including the top and bottom of the screen that my version would not display, but that was okay since there was no need for walls up there anyway – no way to get there. BUT, I found that there were four target tiles the ghosts would use that were were outside of the maze. I worked out that I could still the tile map represent everything (for ghost targeting) but only care about the “visible” section that would map to the screen.

My Pac-Man maze was 84×95. If I made my tiles 3×3, that would be 84 pixels across (28*3=84). Since my screen did not include the top and bottom section (score, players left, fruits, etc.), I was really only showing about 31 vertical tiles of the 36 the arcade had (31*3=93). With this in mind, I went back to my layout graphic to see what I would have to change, if anything.

Screen Shot 2014-02-10 at 10.32.56 PMIf you count the grid blocks across the top of the image, you will see there are 30 versus the arcade’s 28. I had made each grid a size that divided evenly with the maze with (all based on math, which is hard). My grids were 3×3, and just like in the Pac-Man arcade tiles, a dot was in the center of a grid. But why was I off by 2 tiles? Because my low resolution graphics didn’t let me draw the thin walls around the outside of the maze as thin as they really needed to be. If I took out the 1-pixel gap around the outside wall, you would see that my maze actually would be 28 tiles across. Perfect!

Even though that would be more accurate, it wouldn’t look as nice, so at this point I decided to keep my maze like it was, and adjust so the tiles started one pixel in to my drawing. Basically, I would ignore my extra left and right grid columns, and pretendI just had 28 since those were all the game cared about.

I got lucky! Here is what my display looks like with the tiles drawn where the walls are:

Screen Shot 2014-02-10 at 11.17.28 PM

Now, I would be able to use this tile map to know if Pac-Man could move somewhere, and I would also be able to use it for the ghost targeting. All I would need to do is translate between the larger screen resolution and the tile map. i.e., if Pac-Man were at (41,69) based on his top left corner — and that is his starting position in the maze — the center of Pac-Man (a 5×5 sprite) is at (43,71). That represents tile (14,whateveritis). I just had to do some math (math is hard) and adjust the physical X/Y coordinates from the extra space I had to the left or right, and then do some converting stuff.

Math is hard.

The end result, I hope, will be some functions that let me pass in a screen X/Y coordinate and get back the appropriate tile the object is in. I will also need a way to query a tile and see if it is available for Pac-Man to move in. Recall my earlier code:

  // Initialize player.
  px = 41;
  py = 69;
  mx = 0;
  my = 0;

Pac-Man’s on screen top-left coordinate is (px,py). If the joystick is pressed to the right, mx=1 is set. Then, every time we move the object, we do “px = px + mx;” so px=41 becomes px=42 (moving one pixel to the right). All I would have to do is query that new (x,y) to see if Pac-Man could move there, and disallow it if he could not. (Update: As I review this before posting, I have found a flaw in this logic. I will discuss that in an upcoming post.)

For ghosts, each time Pac-Man moved, it would loop through each of the four ghosts to see what tile they were currently in. To save processing time, I’d probably stash that away somewhere so I wouldn’t have to do all the math (math is hard) every single time through for multiple ghosts.

AND… maybe I could do something else kinda neat. Here is a photo from the webpage of Simon Owen:

pacman-sprites

He captured the sprite data from the arcade Pac-Man. These are the objects used to draw the arcade game (raw objects, color is applied when they are placed on screen based on palette settings I think). Notice the row of blue/red shapes in the center? Those are all the sprites that make up the maze. Rather than me having to have an entire bitmap of the maze I want to draw, I think I could just scan that tile table (the thing I drew with X’s earlier) and blast the appropriate maze tile on the screen! My maze bitmap data takes up 1045 bytes of flash storage, and if I could draw the maze based on the size of those tiles plus my tile map, I would be saving quite a bit of flash.

It’s not quite as easy as this. I could draw all the line portion of the maze based on the map and not use tiles at all, but the ghost house in the center is a special case so I’d have to either draw it manually, draw it using tiles, or just bitmap that object there. Any of those would save flash over how I currently do it.

Consider this added to the “to try” list for this project.

But before I go, look at those sprites again… Notice there aren’t enough of them for Pac-Man? The arcade hardware has Pac-Man facing right and down, but not up and left. Apparently it takes the existing sprite data and just flips it to make the other versions of Pac-Man. What a neat way to save some memory back in 1982! Unfortunately, for Arduino we have more flash than RAM, so these types of tricks won’t help us. Still, it’s neat.

In the next installment, maybe I will have had time to work on what I have discussed here and can tell you if it worked. Or not. Math is hard.