There's loads of interesting things to think about when creating a game as I discovered, as this version went beyond my simple childhood experiments. Capturing key events is one thing, but my initial version involved you being perfectly lined up with a direction at a junction or the move wouldn't be made. This was fixed by adding a buffer to the key events - the next move is stored until it can be made - if it can't be immediately the current direction persists and movement switches to the buffered direction as and when it becomes possible. Looking at the resulting action and how smooth it is, this is how it works in other games.
Programming the logic for the ghosts again was fun. I didn't want to just recreate the original game (read as "I had no idea how to create the original game!") and wanted to see what I could come up with. The result was that they made decisions randomly at junctions, however if you are actually in line of sight they will change direction and move towards you at any point there is no wall between you and them. This was fairly simple traversal of the arrays through the x and y axes at each co-ordinate where a ghost was. If you had eaten a 'powerpill' they would do the opposite - at least 1999 version they would try and move in any way other than towards you - whether or not it is possible. The result of this is it slows them down - if they can't make that move then they don't. This gives you a chance to catch up with them. This was a decision made because of the speed issue - running the game in a single loop meant everything moved at the same speed and you couldn't ever catch them. I did experiment with two game loops - one for you and one for the ghosts, and switching the loop speeds as and when a powerpill was eaten. I had lots of interesting effects out of this and some were really a lot of fun to play, but I found that I was coming up against the computer processing speed. I discovered this when I sent the game to someone with a far higher powered computer than mine and the whole thing ran so fast that I never had a chance to do anything before getting eaten! Well, the solution that I actually went with was pretty hacky to say the least - essentially it was turning a bug into a feature - but I was extremely pleased with it at the time - it got round the processor speed issue, and it seemed to give the most playable experience and somehow added to the game.
In 2009, the game forgotten about, somebody tracked me down (I have no idea how!) asking if I was the chap that wrote this particular game he'd found and could he have a copy for his pacman game archive. Digging it out I discovered it didn't work in Chrome or Firefox, but fixed that up, made a few minor improvements including getting rid of alert boxes between lives and added in shiny new divs(!), and new slightly better ghost images (which I blatantly but respectfully stole of some web site somewhere..)
I can honestly say that I don't think I've ever had so much fun creating anything in programming, and it's led me to think about it again some 17 years down the line, when I finally had a bit of time on my hands between jobs. What would I do differently now?
Almost all of it, it turned out. First up, given that I have all the data for the maze stored in objects, it seems logical to actually generate the maze image itself out of this data. I had a look on the internet for how other people had achieved this and discovered some great things, including the 'Recursive Backtracker' algorithm for randomly generating mazes. This could make the game very cool indeed, however I had one issue - pacman traditionally has double walls - it's not a simple case of occupying every cell and adding borders as required to create the maze. After a bit of thought about this, my initial experiments in this in March 2016 can be seen in the original code by going to the settings page and changing 'image' for 'css' in the mode field.
Ignoring the recusrive backtracker for now, I started by making borders for the cells as the data was read from left to right on each row. This gave a rough outline but was nowhere near good enough and the double walls were'nt taken into account. After a few experiments I started adding extra bits and pieces using css :before and :after selectors, and eventually was able to create the lower wall from each line. The result was close, but not bad at all, and there were breaks in the walls are because the data is not put into a perfect grid in the first place. Each move of a 30x30px pacman is a 10px move, pills and junctions are roughly in a grid of 50x50px cells, however sometimes they're 60x50px. And at that point, I realised that the only way to do this properly was to do it properly - aka a total rewrite.
Beginning in March 2016, I copied the code to a dev folder. First I needed a robust grid, and something easier and less time consuming to program than the huge arrays of possible moves for each maze, which took a good couple of hours to fill in each time I wanted a new maze and was very easy to make a tiny mistake and break the game. This was done using 0s and 1s to mark spaces pacman could be in (1s) and walls (0s). I then wrote a translation algorithm to translate this data into the same data structure I originally used, but this time in a 2d array rather than an array of objects. This meant the new mazes were playable immediately which was good, and being nicely lined up meant none of the css troubles working with unevenly blocks of possible move positions. With everything happening in 50px increments, I was able to make the maze look pretty decent using my original idea with css :before and :after selectors, but soon realised that I couldn't get the rounded corners building up the walls from the movement cells - I had to draw the walls from the spaces where pacman *couldnt* move in the grid rather than the path spaces (reverse border-radius css anyone? ;)). A further rewrite and I perfected the CSS maze drawring from the data fairly quickly. Finally confident in my approach, I added some other characters to the maze data - 3s to denote the ghosts home space, 4 to add tunnels to the other side of the maze, and a 5 as the entrance to the ghosts home (which has a red barrier over it) allowing me to render a pretty impressive maze and keep all the game functionality from a basic 1D array.
var maze = Array( 2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2, 1,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,0,0,1, 1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1, 1,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,1, 1,1,1,1,1,0,1,1,1,1,1,1,1,0,1,1,1,1,1, 0,0,0,0,1,0,1,0,0,5,0,0,1,0,1,0,0,0,0, 4,1,1,1,1,1,1,0,3,3,3,0,1,1,1,1,1,1,4, 0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,0,0,1, 1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1, 0,0,1,0,1,0,1,0,0,0,0,0,1,0,1,0,1,0,0, 1,1,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,1,1, 1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,1, 2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2);
The 1D array pictured above was chosen as it was the easiest way to create new mazes. The whitespace used above allows it to resemble the finished maze in pure code too, without worrying about quotes and parentheses for any other coding semantics in the data.
With the maze graphics gone from the equation, pacman himself was next. The completed version is a CSS animation of two semicircles which rotate around a pivot according to a CSS keyframes animation, and a css rotate completes the differennt image for each direction. The ghosts are still the old graphics, and I would love to CSS these at some point and make it image free! This sounds like a project in itself but I'm sure it's possible.
Moving to the game logic, I did the tiniest bit of tidying up. When a ghost was eaten in the original it would follow a path to home which was pre-programmed into the maze data array. With this missing from the now binary maze data, it needed to be calculated at each junction instead. This was far easier than anticipated. Simply comparing the co-ordinates of the home barrier (denoted by a 5) to the ghost positions I suggested a direction to move in. Eg. If the home barrier is up and right of where it is, possible moves foer the ghost should be these directions. How to decide whether to go horizontal or vertical though ? I simply picked one arbitrarily so it would always take a horizontal choice if it had one. This meant they all got stuck underneath the ghost house if they were eaten underneath it. Then it became obvious - the last movement they make is down, therefore up/down should be prioritised over left right. One little change and it worked perfectly. The seemingly complex paths they sometimes follow are actually just generated out of this logic, which also explains why at the end of a level the ghosts do a little dance around their home base - they're simply seeking the path to the home position in the array. Later did I read that similar logic applied in the original Namco algorithms.
A little bit more playing with the code and I created a 'headFor' function, which allowed me to send a sprite to any co-ordinates I chose. Not only could I 'home' them, but could send them to the four corners of the maze just like in the original. Then I programmed some mode switching, and implemented 'scatter', 'chase' and 'random'. Using the headFor function I could send them to scatter to the corners and keep circling - as they endlessly try to get to a position in a wall they can't actually ever reach (again, I later discovered this is exactly what Namco did!), or to the position of pacman for chase mode, and to home when they were eaten. Suddenly, the game started behaving eerily like the original! It was fascinating to see a few small algorithms create the seemingly far more complex behaviour that the game is known for.
Finally with some dynamic path calculation, I put in an extra bit of logic so that they could chase pacman a little more aggressively. This made them very aggressive, so I programmed a dice roll as to whether they would head towards you or use the existing logic. With a bit of tweaking of the probabilities, this element of randomness crossed with giving them more of a purpose has improved the game no end.
The last thing I had to do was get the ghosts to look up the tunnels dynamically rather than following pre-programmed co-ordinates for each level. This done, I was able to sit back for a minute and reflect. This was the last bit of logic tweaking that I was going to do. It working and far improved, however at this point the code is too complicated to work with effectively and it needs a rewrite. Even if I end up with essentially the same logic, it's worth it asa there are bits of code all over the place. Everything is in global variables and functions such as getGhostDirection should do exactly that at a junction - not be called several times during a single loop depending on if they've generated an acceptable direction or not... and all those eval statements which I needed once upon a time for a certain browser environment can all go too - as can framesets, separate pages for each level, tables for layouts and mazes that only work at the top left of the window..
One last update.. (maybe...)
A year has passed and it is March 2017. I've made many of the changes mentioned above at odd times here and there, and implemented some other things such as css transitions when resetting the sprite positions. Attempting to play the new version is still annoying me as the movement is too 'jumpy'. I'm still moving in 10px increments, as are the ghosts. So I've just changed this now to 2px increments, implemented a moveInc variable to set the increment so I can play with it easily, and the resulting action is nice and smooth! The game timings have had to be adjusted to deal with the fact that there are now more game loops going on per second. Finally, it feels like I've created something quite good!
So where next? Possibly something where different algorithms are used at different times, random maze generation and create your own mazes.
2 years on, I have just implemented a reverse backtracker algorithm for playing randomly generated mazes.
One outstanding annoyance is that I sped pacman up upon eating a pill rather than slowing the ghosts, this needs to be played with.
The original has been archived as pacman-1999 on github. Version 2.0 is now there as pacman-2016. Version 3.0 is being contemplated, starting with a huge amount of code tidying up in order to make new ideas easy to drop in and try.
March 31st 2016 (final edits March 26th 2017).