Matt Platts
< HOME

Web and app development since 1998 using
Python, Perl, PHP, Javascript, HTML5, CSS3, Linux, Apache, MySQL

https://www.linkedin.com/in/mattplatts | https://github.com/matt-platts

Javascript Pacman

Click to play: Version 2.0 (2016 rewrite) | Original game from 1999

I was a huge fan of pacman when I was a child in the '80s, to the point that as soon as I first got a computer as a child, the first thing I tried to do was work out how it was written. I had made an attempt at writing it back then, running out of memory on a 1k ZX81 pretty quickly, later attempting it on an Oric-1 machine. Fast forward to the end of the late '90s and the web was awash with games, mainly written in flash which provided it's own scripting language and games could easily be embedded into a web page. Javascript games were more of a rarity, bar the sadly defunct (and utterly brilliant) Javascript-games.org run by a certain Scott Porter (where is he now?), who even wrote his own 'gamelib' library for games writing. It was in fact on discovering this site that I realised that javascript was much more than a language for creating mouseovers and news tickers. I was pretty new to the web at the time, but it set some bells ringing in my head.

The beginnings

Still struggling to understand the principles of OO, one of the first things I did when I started learning javascript back in 1999 was to write a game of Pacman. I had no idea back then how to do it, but that wasn't going to stop me, and at first it was very much a trial and error approach. You can find it together with some 2016 improvements in the links above, and it remains dotted around the internet on several other sites too.

Importantly for me, the game logic is based on what I wrote on that Oric 1 home computer in BASIC when I was about 12 (itself inspired by experiments on a Sinclair ZX81 which my dad had bought in kit form a couple of years previously), and having swiftly realised that javascript wasn't actually so different, it just had to be done. And it had to be done as close to the early version as I could manage. I think my younger self would have liked that, and when time travel is invented I'll be sure to turn up with a copy..

The original game written in Basic was based around a 2d array containing information on possible moves and whether there is a pill in that cell or not. Javascript didn't support multidimensional arrays - a fact that I still find mind boggling - how did a ZX81 with 1K of memory support these in 1981 - yet nearly 20 years later Javascript did not? So I had to improvise. I hadn't quite got my head round putting arrays in arrays just yet, but after a bit of research I discovered objects (yes, this was the start of OO for me!), and finished up with an array of data which was used to create various objects - in this case the instances were named after how many pixels down the screen each row was and the properties were columns named after how many pixels accross the screen the column was - essentially mimicing my earlier home computer experiments where the array corresponded to not a pixel, but a 'block' on the screen which one character would take up. By mapping the co-ordinates of my pacman onto the objects I was able to access the data of possible moves and thus create the game of pacman according to my original ideas in Basic but in pure javascript. The original maze from the 1999 version was a graphic, in fact a series of graphics for different levels, put together in photoshop, and the occasional small overlap between the sprites and the walls didn't really matter too much.

Hidden complexities

There are 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 was that it slowed them down - if they can't make that move then they don't and wait for the main game loop to iterate before trying again. This I discoverd gave pacman 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 as it was back then.

A few versions of this found their way out onto the internet at various points - it sat on the now defunct japan2uk.com (where it actually generated some fan mail!) and had it's own url on javascript-pacman.com with a 'tutorial' on how the game was put together. It was also picked up by a few people and early bloggers (before the word existed) and even came second highest in a poll of browser based versions of the game - evidently based on playability and not coding standards!

2009 updates

In 2009, the game forgotten about, somebody tracked me down 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, so I fixed that up, and on his request made the game playable by the cursor keys and made a few dsmall improvements including getting rid of alert boxes between lives and added in shiny new divs! I think the div tag and Netscape's layer tag issue was in full swing when I first wrote the game. I also added slightly better ghost images (which I blatantly but respectfully stole of some web site somewhere..)

2016 updates

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 - making the job tedious and therfore easy to make a tiny mistake and break the game.

This time round I thought I'd come up with an easy way to input data first, and work out the code to translate it later. The array below turned out to be the best way to do this. I did experiment with just characters and no code at all, oddly the below was the most readable - the commas helped and simply delaring it as an array is very little of a distraction. Here I am using 0s and 1s to mark spaces pacman could be in (1s) and walls (0s). With the format figured out, I then wrote an 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 that newly designed mazes were playable almost immediately which was good, and being nicely lined up meant none of the issues I had run into with css before - working with uneven blocks of possible move positions - were eradicated. 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. Finally it hit me - I had to draw the walls from the spaces where pacman *couldnt* be in the grid rather than the cells where he could be (to get round the issue of there not being a 'reverse border-radius' css instruction ;)). Once I had this I was able to perfect drawing the maze in CSS 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 intact - all from a 1D array of data:

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);

This was a nice and elegant solution. The data above actually resembles the finished maze in pure code tooi - with a little squinting you can see it! No quotes or parentheses or other coding constructs in the data makes it clean and simple.

With the maze graphics completed, 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 different image for each direction. The ghosts are still the old graphics, and I would love to CSS these at some point and make it entirely 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 new maze data, it needed to be calculated at each junction instead. This was an interesting piece of work. 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 any 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 algorithmic changes created 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 happening per second - but everything is now in variables and I can easily tweak the game and get very different effects just from changing these in tandem. 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.

Update 2019

Two years on, I have just implemented the long threatened 'reverse backtracker' algorithm for playing randomly generated mazes. There are some occasional issues with it creating mazes with closed loops, which is next on the agenda when I get some time.

One outstanding annoyance is that I sped pacman up upon eating a pill rather than slowing the ghosts, this needs to be played with.



For now, 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.

With the original 80s computer expreriments some 16 years before the first javascript version, maybe it'll take another 16 years. Once again, I need to get back to some paid work..

March 31st 2016 (final edits March 26th 2017).


© Matt Platts 2017. contact me.