2015-10-09



alvinashcraft
shared this story
.

A tutorial for advanced JavaScript techniques.
This text discribes the planing, set up, and development of a complex
JavaScript-application.

This file is related to scripts to be found at
http://www.masswerk.at/JavaPac/legacy/JS-PacManPlus.htm.

Contents

Preface

The game described here differs from the original "Pac-Man"™ by Namco™
<http://www.namco.com/> in some ways:

The original game uses only one layout populated by four ghosts, each ghost
guided by a distinguished strategy and moving at its own speed. The various
levels differ by the ghosts' strategy and the speed of the game.
Opposed to this, the game as described here will be populated by ghosts
employing a shared set of movement strategies. (The speed will probably be
the same as the speed of the pacman character or a fraction of it.) In order
to entertain some difference to the game play each level will have its unique
layout. This conforms with the typical appearance of a Pac-Man clone.

The original "Pac-Man" by Namco and its clones share the following features –
as does the game described here: The player guides a little yellow ball
(the pacman) through a maze-like labyrinth in order to clear all the food
(embodied by tiny white dots) laid out in the passages of the labyrinth.
His opponents are 4 ghosts of different color roaming the labyrinth in search
for the pacman. If any of the ghosts comes in touch with the pacman, the
pacman looses a life and is reset to its original position. Some of the food
dots (usually four of them) are bit bigger and provide a reversed game play,
when eaten by the pacman: For a limited amount of time the pacman now may haunt
and eat the ghosts. (This phase is indicated by the ghosts' color – usually a
deep blue.) If the pacman manages to get grip of a ghost in this special phase,
the ghost is not killed, but set to the central cage, from which it emerged at
the beginning of the level.

Pac-Man™and Namco™ are trademarks of the specific vendor(s).

1) Resources

Before we begin with our script, we should consider the elements of our game
display (the actual user experience), what resources are therefore needed, and
if any structure obliges to these.

For this example we'll base on the following assumptions:

The maze will be displayed on a grid of 20 x 14 tiles

there will be one pacman character

4 ghost characters

food for the pacman

pills for pacman for the 'reverse' game play

for the characters we'll need some states for the animation phases:

for the pacman 3 phases (eating) for every direction but the back view (up)
(we don't see the the mouth there)

for the ghosts only 2 phases (the wobbling bottom) in 4 colors (one color
per ghost)

another set of ghost images for the 'panic' mode and the
neutralizing phase (panic mode is just to be ended)

3 animation phases for a shrinking pacman (life expired)

an image of just eyes for a killed ghost traveling home

To save some resources, we'll skip any scoring indicators (like "200", "400"
and so on for any ghost). And we're not going to implement a direction
dependent looking angle for the ghosts in order to safe some more resources as
well.

All these informations will be displayed as gif-images, so we're left with the
following images:

some tiles for the maze border

3 x 3 + 1 animation phases for the pacman

3 animation phases for a shrinking pacman

6 x 2 + 1 images of ghosts

an extra image to indicate a ghost being caught by the pacman character

+ some extra images for level display (10 figures), a game over display, and
so on.

1.1) Character Image Names

In order to have access to these images via JavaScript, we definitely should
name them in some structured manner. For JavaScript robustness every name will
begin with a character, since we're going to store these names in an
associative array.

Here we decide to name them in the following way:

ghosts:
We'll prefix them with a 'g' for "ghost" followed by a color code (c) and the
animation phase (p).
This gives 'g'+c+p+'.gif' as an image name, where the code c will be a number
(we want to loop through them) – here 1..4 – or a character for the two
special modes – here 'a' for the panic mode and 'n' for the neutralizing phases.
Since we've only 2 animation phases per ghost, we'll use '1' or '2' for p. So
we'll be able to map any states to theses images.

So ghost #1 will loop over 'g11.gif' and 'g12.gif' during normal play and
switch to 'ga1.gif'/'ga2.gif' in panic mode, returning over
'gn1.gif'/'gn2.gif' to normal state. To this we add an extra images for the
eyes traveling home (gx.gif).

pacman:
We could prefix the pacman's images by a 'p', but this will only waste some
bytes and use some run time for string operations. So we're going to use a bit
more simple scheme consisting of the direction (d) and the animation phase
(p) only.
We'll code our 4 directions with characters indicating the view ('r' – right,
'l' – left, 'f' – front, 'b' – back). There are 3 animation phases (but one
for the back view) and we're using numbers again, since we are going to loop
over these too.
For the shrinking pacman animation, we're using 'x' for d and 3 animation
phases as well.
So our name scheme for the pacman is d+p+'.gif'.

1.2) Maze Tiles

How many tiles do we need?
Obviously one for the food, one for the pills (power spots), a blank one for
any empty space, and a lot of border tiles.

borders:

The whole game is governed by the 4 directions of a 2D-surface. So most of the
tiles can be ordered in quadruples. Here we'll list them by connected sides:

1 connection

4 tiles (ending in the middle to make nice endings)

2 connections

1 horizontal (left and right)

1 vertical (top and bottom)

4 corners (left/top, left/bottom, right/top, right/bottom)

3 connections

4 tiles with one side blank

4 connections

1 tile (a cross, not used here)

extra tiles

1 to just fill a space (here a small cross with no connections)

3 tiles for the ghosts' cage door

(The cage will consist of 4 empty tiles in the middle of the maze. The door (a
dotted border tile) will be placed at the lower right. In order to have nice
edges of this door we'll use 2 special corner tiles here.)

For the naming scheme we could any names and refer to them via a reference
array. Since we're going to access them only while displaying a new maze at
the beginning of a new level, this operation is not time critical. Here we're
using a binary 4-bit vector, encoding the connected endings prefixed by
'tile', where 'tile0.gif' is just a blank space and 'tile15.gif' the tile with
4 connections (cross). We'll use 'tile16.gif' to 'tile19.gif' for our 4
special tiles.

1.3) Implementing the Maze (HTML)

Since we want to display the maze on a HTML-page, we're going to setup a
HTML-table, with 20 images in 14 rows. In order to access these tiles via
JavaScript, we must name them in some structured manner.
Here we're using a scheme 'r'+row+'c'+col, where row will be an index from 1
to 14 and col an index from 1 to 20.

So an IMG-tag (displaying an empty tile) will look like <IMG
SRC="images/tile0.gif" NAME="r1c1" ...>

1.4) Implementing the Characters (HTML/CSS)

Since our characters will be able to move smoothly, we'll need to implement
them in layers or CSS-divisions. We need a naming scheme here too, since we'll
have to access them via JavaScript.

We'll use 6 layers in total (1 for the pacman, 4 for the 4 ghost's, and 1 to
display a "game over" information.) In order to not interfere with any
possible browser's layout engine, we'll place them just before the
BODY-end-tag (</BODY>). Since we're possibly going to loop through the ghosts,
these layers will obviously be using an indexed name.

These layers hold each one single image to display the according character.
Since these images will have to be replaced for every animation step, the will
be named too.

Here we're using the following definition:

<DIV ID="gLr1" STYLE="height:27; width:27; position:absolute; top:0; left:0; visibility:hidden; z-index:2"><IMG NAME="g_i1" SRC="images/tile0.gif"></DIV>
<DIV ID="gLr2" STYLE="height:27; width:27; position:absolute; top:0; left:0; visibility:hidden; z-index:3"><IMG NAME="g_i2" SRC="images/tile0.gif"></DIV>
<DIV ID="gLr3" STYLE="height:27; width:27; position:absolute; top:0; left:0; visibility:hidden; z-index:4"><IMG NAME="g_i3" SRC="images/tile0.gif"></DIV>
<DIV ID="gLr4" STYLE="height:27; width:27; position:absolute; top:0; left:0; visibility:hidden; z-index:5"><IMG NAME="g_i4" SRC="images/tile0.gif"></DIV>
<DIV ID="pacLr" STYLE="height:27; width:27; position:absolute; top:0; left:0; visibility:hidden; z-index:6"><IMG NAME="pac_i" SRC="images/tile0.gif"></DIV>
<DIV ID="gameOver" STYLE="height:27; width:270; position:absolute; top:0; left:0; visibility:hidden; z-index:7"><IMG NAME="go_i" SRC="images/tile0.gif" WIDTH="270" HEIGHT="27"></DIV>

(We could have used some stand alone images as well, but in order to support
Netscape 4, we have to place them in a layer in order to move them around.)

Remember for support of NS4: Don't use HTML4-tags like <TBODY> in the page to
not confuse the CSS-engine of NS4. (Actually we do not offer a full support of
NS4, since the very first versions of NS4.0 do not recognize CCS-divisions.
But I think, there is probably no installation of generic NS4.0 not updated
left out there.)

2) Setting up the Script

2.1) Resource Setup

For any animation images have to be preloaded and stored in an array. To
access these in a structured way, we are using an associative array (using
names rather than index numbers to identify an entry).

The implementation of this is quite trivial: we're using the base of our
naming scheme (omitting the '.gif') as indices and store them in an array
'pacimgs'. So 'pacimgs.r1' will hold the image object associated with the
first right movement phase of our pacman character. Since we're using a quite
structured naming scheme, we can build some of these names on the fly, while
few others have to be enumerated extensively.

We will trigger this at loading time

The array 'pacimgs' is a global variable, since these resources have to be
accessible all over the script.

2.2) Browser Abstraction / Cross-Browser Animation

The other thing to do before we're going to script the game, is to form a
connection between our HTML-elements and our script. Since this is highly
browser depended, we're going to implement a cross-browser API abstracting
these differences in a common front door.

We are supporting any browser newer than Netscape 3, using JavaScript 1.2 or
higher.

These are basically 3 types of browsers:

Netscape 4 type
NS 4 and compatible like some arcane browsers as Sun's HotJava

MS Internet Explorer 4 type
MSIE 4, MSIE 5/windows, some versions of Opera and other compatible browsers

DOM type (the new generation)
Netscape 6, Netscape 7, MSIE 6, MSIE 5/mac, Mozilla, never versions of
Opera, Safari, and others

Definitely any new browser coming up in the next few years will be of the
DOM-type.

** Besides: Functions and Variables **

In order to understand this approach (this is only one of others possible), we
should understand that a JS-function is just another variable holding a bit of
code. Declaring a function with a name just associates this bit of code with a
variable name. Since in JavaScript passing a complex object from one variable
to another just copies a reference to the object and not the object itself, we
can pass a function's code from one variable to another without much overhead.

****

In fact with JavaScript1.0 the Function constructor was the only object
constructor accessible. So even arrays had to be built using functions:

****

So we can define some GUI functions here and pass a reference to the according
browser function to global variables abstracting any differences. The benefit
of this approach is the absence of any conditions to be evaluated at run time.

Besides: never use eval() since this will start a whole
compilation-interpretation cycle. You should always be able to construct a
reference to the object you want. (Dreamveaver uses eval() all the time, but
this is just poor style in terms of efficiency.) Just keep in mind that a
point-notation of any JS-object is equivalent to a named array reference
(document.layers.someLayer == document['layers']['someLayer']).

We're defining the following global variables and pass references to them
based on the browser in use:

To make things easier, we're going to abstract any layer references and hold
them in a global array 'divisions'.

The array divisions holds references of these types:

(The array is used to not have to call document.getElementById(div) every time
we access a layer/div. Also it is used to abstract some differences of MSIE and
the DOM-API.)

In order to do this, we can call our cross-browser-abstraction-set-up function
only at runtime, since the entries of our 'divisions' array refer to HTML/CSS
elements, and these have to be present at the time the function is called.
Else they will just hold a null reference. (So we'll check this on every call
for a new game. Another reason for this is that 'document.layers' in NS4 is
not present for any external script called in the HEADer-section and would
fail.)

Since we're going to support as many browsers as possible, we're not going to
identify them by some erratic navigator.userAgent sniffer, but on the presence
of some top-level JS-Objects. In fact we did that earlier by checking
'document.images'. So all we need to know what features or API is supported is
a set of well known top level objects:

Our abstracted API will take the following arguments:

where

And we'll use a fifth GUI-function used to change any image just embodied in
the page-body:

So we're done. But there's another issue left:
We've decided to place our maze just in the centered middle of our HTML-page.
So we have to know where exactly the origin of our maze is placed in order to
place our sprites (layers/divs) above it.

To do this we use a function that evaluates the x and y coordinates of the
maze's origin and stores it in the global vars 'mazeX', 'mazeY'. (For example
we could use the position of an image with known ID for DOM and MSIE and an
ILAYER for NS4. Since we placed these elements just next to our maze, we can
easily get the coordinates of the maze's origin.)
The calculations used to get an element's position are not trivial and not
covered here.

We should connect this function to a window.onresize handler, because the
origin of maze will change with any resize of the window. (As for
window.Timeout and all other 'window'-methods, you can omit the 'window.'
portion while in script context.)

Now we have all resources together:

images

pre-load to a reference array

GUI-API

and are ready for our main task...

3) Programming the Game

3.1) Defining the Rules

First we should consider the characters behaviors and how the game play could
be defined.

We define the following rules

1)

the maze has no dead ends (passages are connected at least at 2 sides)

2)

the pacman moves in straight lines

3)

if the pacman encounters a border, it stops

4)

ghosts move in straight lines

5)

ghosts never stop

6)

if ghosts encounter a crossing, the next direction of movement is evaluated

7)

ghost do not take opposite directions on crossings
(they will not reverse directly, but move in nested loops through the maze)

8)

if a ghost encounters another ghost both will reverse
(the pacman can't be there and ghosts spread out more widely)

9)

ghosts can evaluate their move

a)

based on random calculation

b)

based on strategic movement (get nearer to the pacman)

10)

movements of ghosts should be combinations of these

11)

ghosts should behave more strategic in higher levels to make them more difficult

12)

teleports: if a character encounters the absolute border of the maze, it is teleported to the other side.

13)

if a ghost encounters the pacman, the pacman's life ends

14)

the pacman eats any food it encounters in a passage

15)

if all food is eaten, the level ends

16)

if the pacman encounters a pill, the game is reversed:

a)

ghosts are in panic mode (half speed, strategic movement leads away from the pacman)

b)

if the pacman encounters a ghost, the ghost's life ends

17)

the reversed game ends after a short period of time and normal mode is acquired

18)

a pill is food

19)

a dead ghost travels back to the cage's entrance as eyes only.

20)

a dead ghost is revived in the cage

21)

there are 4 basic phases in ghost's life

a)

in the cage

b)

stepping out of the cage

c)

regular maze movement

d)

panic mode

22)

the maze setup varies from level to level

23)

if the last maze design is used, the next level reuses the first level's layout

24)

the cage and its door is placed on the same spot on every level-layout

25)

positions of teleports can vary from level to level (see rule 12)

26)

if the third pacman life is expired, the game ends

As we can see, there is much more to do for the ghosts, and the most
definitions are about movements.

Some of these rules are general features of Pac-Man, while others are specific
to our implementation.
For example the original arcade game lacks the rules 7 and 8, but has rules
for scoring. Here we skip these additional rules in favor of a smaller
download footprint and faster run time cycles.

To be strict, we had to define some additional rules concerning the display ....

In short we define:

27)

the pacman moves in alternating 3 animation phases in 4 directions

28)

a stopped pacman is not animated

29)

a ghost moves in 2 animation phases in 2 directions

30)

there are 4 ghosts in different colors

31)

a ghost changes color in panic mode

32)

a ghost changes to another color just before the end of panic mode

33)

panic and end-of-panic-mode color are the same for all ghosts

34)

dead ghosts homebound are displayed as eyes only

34)

the maze is defined by borders and passages

36)

border tiles can have 0 to 4 connections to other tiles

37)

a pacman life's end is animated as shrinking pacman in 3 phases

38)

a caught ghost is indicated by a single image animation

39)

levels are displayed in a level-display at the bottom of the maze

40)

player lives are indicated as pacman characters at the bottom of the maze

41)

the end of the game is indicated by a special "game over" display

In addition we stick with our naming convention for images:

and the maze tile ids 'r'+row+'c'+col, where row = {1 .. 14} and col = {1 .. 20}

3.2) Basic Layout

Basically the game is a big loop, entered at the start of a new game and only
left when the game is over.
How could we implement that? Obviously not in a 'while'-loop for the following
reasons:

most browsers will generate an endless-loop error

we must control the speed of this loop

we have to leave gaps in our run-time cycle to track user input

The later may need some explanation:
Client-side JavaScript (the version of JavaScript implemented in a browser) is
a single threaded language, meaning there is only one task active at a time
and that any calls are deferred until the current task is executed.

So if we would write:

and trigger a user input with a JavaScript handler, the handler will not be
called until our 'while'-loop is ended. That's definitely not what we want.

What could we do?

We break up the loop in a single call that will fall back to idle after its
completion and will be revoked later. This revocation can be done by a
window.setTimeout call.

So we'll write:

In fact our construct will be somewhat more complex for the following reasons:

variable game speed
To provide different animation speeds, we store the timeout delay in a global
variable.

no double control loops
If a user double clicks our "new game" button, he would trigger two different
calls to newGame() causing a doublet of games to be run (the second
interleaved in the gaps left by the first – at least with MSIE as client). In
order to do this, we'll store a reference to our timer in a variable, so we
can clear any second load of this timer, if there should be one.

flatten any run time differences of our loop
Since our control structure will execute quite different tasks per cycle
(e.g.: all sprites are just running straight, a second time all ghosts have to
perform crossing-tasks) and the timeout-delay is just added at the end of each
cycle, we'll track our execution time and modify our timeout-delay
accordingly.

So this is the way to do it:

In fact we'll go further and leave as many timeout gaps as possible to provide
sensible user input at almost any time. Further we could pass the
timeout-string to another function as parameter.

p.e. here the second function has 2 endings, a normal calling the passed
timeout-string, and another changing the flow of our 'loose loop'.

So what are the basic tasks to be performed:

newGame

initiation tasks

set up the basic variables (initiate level counter, life counters)

newLevel

set up level specific information (food counter)

display the maze

set pacman to home position

set ghosts to home position

main loop

get entry time

control the pacman's movement

check, if all food is eaten (-> end of loop, start next level)

check for any crashes with ghosts (-> pacman or ghost expires)

move each ghost

check each ghosts for collisions with the pacman (-> pacman or ghost expires)

special functions

display level information

display "game over" and reset some values

As we can see there are possible alterations at the test for collisions of
ghosts and the pacman, which occurs twice for every ghost, and another, if
all food is eaten. Otherwise we just continue our main loop.

Moving the ghosts is not just as simple. In fact, as we look to our rules
above, we'll have to decide which kind of movement is needed out of the
following:

The ghost is in the cage
This phase is entered when the pacman is set up or when a ghost is reset to the cage.
We limit this phase by a minimum and maximum time controlled by a
counter

the ghost is in the door
just a single moment, but we should know about our next direction.
As supposed by our rules, this could be either up or down.

The ghost is in the maze
If the ghost is placed on a crossing, we have to decide whether to move

a)

based on random or

b)

based on strategic calculations (further to the pacman or away, if in panic mode)

This decission should be weighted by the level the player currently is
in, so in a low level there will be more random movement than in higher
ones. We should check, if the direction chosen is free, or occupied by
another ghost, reversing the direction of movements to avoid ghosts
being stuck in a passage.

The ghost is dead and traveling home
Here we can re-use our strategic movement routine, but use the cage's
entrance as a target in stead of the pacman's position.

3.3) Data

So what (global) data structures do we need?

we do need to know our maze's origin for display purposes (mazeX, mazeY).

we'll have some data associated with the timeout
(cycle delay, a variable to store the timer)

we need to store a reference to our preloaded images

a boolean control variable (flag) to know, if our game is already running

counters for lives, levels, food left, panic mode

individual data for each character

The character based data is obviously best encapsulated in objects. One for the
pacman and a ghost type object held in an array (in order to loop over the
ghosts).

We'll need:

x, y position

animation phase

the movement direction

some space to store intermediate results to calculate them only once per cycle

for the ghosts:

a counter for the individual states

a counter for the in-the-cage phase

information on the individual home position (could be stored elsewhere)

Now we're ready for the "big thing":

3.4) Maze-Data: Layout and Directions

Since this script was designed in the early days and JavaScript as well as
computers were rather slow, it was decissivly important to save as much
runtime as possible. Since this is not a bad habit, we are going a bit into
details:

First, it would be really bad, if any character moving would have to calculate
a 'wish'-direction, then look up the maze map, recalculate again, if there was
a wall and not a passage, and so on. So it would be nice to have some map
encoding any possible direction for the exact position in a handy way.

So we're coming up with two types of maze data to store:

one for the layout of the display

and another encoding the direction information

In fact we could calculate the second from the first one, but this would use
some extra time for the game to come up. So we have two sets of arrays for
every level:

the border layout (+ the placing of pills, food, and empty spaces)

the directions map

The layout map is quite trivial: We use any encoding (here alphabetic
characters) referring to our border-tiles. Further a simple blank will refer to
a normal passages, a 'p' to pills, and a 'x' to empty spaces (mainly the
teleport passages and the pacman's start position). Any normal passage will be
inhabited by a food (we'll have to count this at level set up).

For the directions it might be wise to invest some more consideration.
We'll find that, if a character is passing through a straight passage, it just
has to move further (or possibly reverse its direction). So if a character is
on its way, we don't have to consider any directions to be encoded in the map.
We just have to know that there is no crossing.
So all information to be handled are the crossings' possible connections.

On a 2D surface (as our maze obviously is) there are 4 possible directions. So
we could encode them using binary numbers as a 4-bit vector (one bit for every
possible direction).

Here we define (as powers of 2):

leaving 0 (zero) for a straight passage (no crossing).

This enables us to access this information via bitwise operations (| = bitwise "or", & = bitwise "and"):

Now we define an array 't1', a table holding the references between an arbitrary map code
and its according bit-vector-value. (We're going to store a row's code data in
a string of digits; see below.)

Now we can store the maze directions in a handy manner, so we can edit them
manually. Any single digit represents a point on our grid. We'll have to
decode this map to the 't1'-values at the beginning of each level.

So a '1' will indicate a corner with connections on the right and down way
(t1[1] == 9 == 1 | 8).

Exploring our bit-vector approach further, we find that we could store results
of common calculation in arrays just to safe runtime calculations – especially
for the ghosts' movements.

So we define some arrays storing information of this type using our bit-vector
grand design:

'tx' and 'ty' store the delta x and delta y information associated with our
bit-vector definition. We'll need these for movement. Now we know that a
character with direction value d==2 will have a dx value of -1 and a dy value
of 0. (In effect it's for these definitions that our bit-vector-values get some semantical meaning.)

We can simply access this by

No calculations here, just variable-look-ups!

The complex array 't2' encodes possible movement directions for ghosts:
The first index indicates the crossing-code, the second index encodes each
possible direction. In mathematical terms 't2' just enumerates the binary
atoms of any 4 bit digit.

Now we can simply calculate a random movement for a ghost:

Given a ghost is positioned at a crossing holding the direction-vector 'k', we
know that there are t2[k].length possible directions. So with just one random
number 'n' we know our new direction:

or in detail:

or short:

Just one line of code for this!

For strategic movement we'll have to calculate the offset to the pacman and
encode our best move's direction. (If the pacman is above and right, we would
want to move either up or right, giving 1 | 4 == 5)
Now we can find the best possible movement by a bitwise <and> operation with
the crossing's bit-vector:

So if bd == 5 and cd == 13, d == 4 meaning we're going up.
But 'd' could hold a complex bit vector encoding more than one possible
direction, so we'll have to chose a random position here:

In an actual script we would put our random-formula in a function so we're
left with:

or shorter:

Nice, isn't it?

The array 't3' stores opposite directions. So for d==4 => t3[d]==8. We'll need
this to reverse movements.

Just a word on rule 7:

How could it be done simply to not reverse a ghost's movement in a random
choice?

with 'md' being the resulting vector of movements, 'cd' being the current
crossing's value, and 't3[d]' being the opposite of the current direction.
(Since our assignment of bits to encode a direction was just arbitrary, we
can't access the opposites via bitwise operation.)

The operation "c = a & (15^b)" is about masking a 4 bit binary digit. We ex-or
15 (binary 1111) on the direction 'b' and perform a binary <and> on 'a'. So we
just cleared those bits that are set in 'b'.

Now we can use 'md' to get any direction encoded in 'cd' but that in 'd':

That's all.

3.5) Moving Around

Now we're just implementing the stuff necessary for a pacman character to roam
the maze. (So we do not implement ghosts or any food.) We take for granted,
that the layout is being displayed some way and that our crossing-encodings
are stored in the 2D-array 'f2' representing a 20 x 14 grid in rows and columns
(f2[1..20][1..14]).

For this purpose we're mainly interested in the tile's positions (or grid
points). We'll just hop from tile to tile and fix the intermediate steps via
offsets.

To complete this basic script to a full featured game we have to add the
following features

food
On level set up store the position of any food (or pill) in a 2D array (p.e.
'f1'), calculate the total number. If the pacman is on a grid node, check for
any food and adjust a global counter. In case the food is a power pill, we'll
set a counter controlling this special phase of the game.

implement the ghosts
By now the movement of the ghosts should be quite easy to implement using the
rules and techniques described earlier. Probably we'll use an array to store
instances of ghost-objects and loop over them. To keep track of the individual
phases (states) of a ghost's life, we'll use a individual counter property.
While roaming the maze, the ghosts' movements should be evaluated using either
strategic assumptions (based on the pacman's position) or random calculations.
(=> See examples below.)

implement some chrome to display lives left, levels, and so on

*** start edit (2009) ***

Examples: Moving Ghosts by Table Lookups

(Compare the descriptions given in 3.4) Maze-Data: Layout and Directions.)

The following code snipplets were inserted in 05 2009 to exemplify algorithms presented earlier. (It just seemed more suitable to go into detail here, as we refer to this logic in the excursions on the original game's A.I. below.)

Moving a ghost by random (where k is is a bit-vector of directions):

Moving a ghost towards a target position:

Note:
If JavaScript had a native method Math.sign(), we could do all our strategic logic by a single lookup like:
var v = tdelta[Math.sign(ghost.r-tr)][Math.sign(ghost.c-tc])] & k;
which would save the calculations in function ghostMove2Target().
Since JavaScript lacks such a native method, it's faster to do it as shown above.

Putting ghost movements together:

*** end of edit ***

Now we're left with one last question: How could we track any collisions?
Since we're dealing with offsets, comparing just row and col values doesn't do
the thing. So we're left with the task of calculating some kind of bounding
boxes.

For this purpose we decide to store the exact position in px of each character
in the object properties 'posx' and 'posy'. So we do not need to recalculate
them while comparing them.

Now we can implement a function to detect any crashs between objects:

Maybe you wonder, why we're not using some vector projection like:

There are two reasons for this:

we're saving some run time, and

this would detect a collision when characters might overlap
while going around corners. Since they were ok before and are ok
when in straight lines again, the user would experience this as
a bug and not as feature. Luckily just comparing the sum of the
distances will do the thing.

So for basic techniques, we're done here.

With this you should be able to

understand the script and

write your own.

3.6) Ghost Movements Revisited

Please skip this section as it is definitely out of date and refer to the descriptions given in sect. 5 and sect. 6!

The implementation of ghost movements as described above is mainly based on
the need for quick runtime evaluation. The feature that impressed me most of
the ghosts' behaviour in the original game, was that they seem to let you one
single chance to escape, but come down on you when you miss it. The
remodelling of this behaviour with as little resources as possible was the
main design goal of "JavaScript-PacMan".

Meanwhile I found some texts on the original Pacman AI (Artificial
Intelligence), describing the ghosts behaviour as follows:

each ghost has its unique behaviour

the first ghost will move following to a preprogrammed pattern of
movements through the maze (e.g. right, up, left, down, left, ...).
This pattern is always the same for a level.

the second ghost (the red one) moves using a pathfinder routine, trying
to minimize x/y-distance to the pacman (this we described earlier as
"strategic movement")

the third ghost moves on pure random

the forth ghost uses another pathfinder routine, trying to predict the
next movements of the pacman (trying to intercept him)

While this garantees an interesting and quite predictable gameplay, this
approach has some drawbacks: First, for the first ghost, you have to design
carefully an extensive path (movement pattern) per level. Second, the movement
of the forth ghost can only be implemented by defining some key crossings (as
entrances to loops) and by tracking the pacman's movement. So you could
predict the key position the pacman will be likely to be next. But in order to
do this, each level-layout has to tribute to these key positions, meaning that
it has be modelled around these crossings, which will obviously limit the
range of possible designs.

On the other hand our approach - as given above - enables us to set our ghosts
free in almost any maze of any design. This minimizes not only efforts of
level des

Show more