<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" 
      xmlns:thr="http://purl.org/syndication/thread/1.0">
  <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html" />
  <link rel="self" type="application/atom+xml" href="http://www.insideria.com/atom.xml" />
  <id>tag:www.insideria.com,2009://34/tag:www.insideria.com,2009://34.36372-</id>
  <updated>2009-11-16T14:55:30Z</updated>
  <title>Comments for Writing the Pac-Man Game in JavaFX - Part 5 (http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html)</title>
  <generator uri="http://www.sixapart.com/movabletype/">Movable Type 4.21-en</generator>
  <entry>
    <id>tag:www.insideria.com,2009://34.36372</id>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://blogs.oreilly.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=34/entry_id=36372" title="Writing the Pac-Man Game in JavaFX - Part 5" />
    <published>2009-06-12T16:10:00Z</published>
    <updated>2009-06-12T16:08:12Z</updated>
    <title>Writing the Pac-Man Game in JavaFX - Part 5</title>
    <summary>In previous articles, we finished writing most of the code of the Pac-Man game. In article 3, we implemented a simple algorithm for the ghosts to catch the Pac-Man. The ghosts randomly decides in which direction they move. They do not chase the Pac-Man even they are very close to him. This makes the game less challenging. In fact, the behavior of the ghosts are the most tricky part of the game. According to Iwatani, the author of the original arcade game, he had designed each ghost with its own distinct personality in order to keep the game from becoming too difficult or boring to play. ( More info) However, there is generally no conclusion on what behavior of the ghosts are good for the players. </summary>
    <author>
      <name>Haining Henry Zhang</name>
      
    </author>
    
    <category term="Features" />
    
    <content type="html" xml:lang="en" xml:base="http://www.insideria.com/">
      <![CDATA[<p><b>Haining Henry Zhang with <a href="http://learnjavafx.typepad.com" target="_blank">James L. Weaver</a></b></p>

<p>
<strong>Previous parts in the Pac-Man series</strong><br/>
<a href="http://www.insideria.com/2009/05/writing-the-pac-man-game-in-ja.html">Writing the Pac-Man Game in JavaFX - Part 1</a><br/>
<a href="http://www.insideria.com/2009/05/writing-the-pac-man-game-in-ja-1.html">Writing the Pac-Man Game in JavaFX - Part 2</a><br/>
<a href="http://www.insideria.com/2009/05/writing-the-pac-man-game-in-ja-2.html">Writing the Pac-Man Game in JavaFX - Part 3</a><br/>
<a href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-3.html">Writing the Pac-Man Game in JavaFX - Part 4</a><br/>
</p>

<p>In previous articles, we finished writing most of the code of the Pac-Man game. 
In article 3, we implemented a simple algorithm for the ghosts to catch the Pac-Man. The ghosts 
randomly decides in which direction they move. They do not chase the Pac-Man even they are
very close to him. This makes the game less challenging. In fact, the behavior of the ghosts
are the most tricky part of the game. According to Iwatani, the author of the original arcade game,
he had designed each ghost with its own distinct personality in order to keep the game from becoming
too difficult or boring to play. (<a href="http://en.wikipedia.org/wiki/Pacman#Behavior">More info</a>)
However, there is generally no conclusion on what behavior of the ghosts are good for the players.
</p>

<p>When writing our game, we need to find a good algorithm to implement the behavior of the ghosts. 
There are a few aspects that we should consider when designing this algorithm. </p>

<p>First, the ghosts can go after the Pac-Man character. The "random" behavior makes the game too easy to play.
Therefore, no matter how far away from the
Pac-Man character, a ghost should always be able to get closer to the Pac-Man and capture him. 
In the arcade game, the most famous chaser is Blinky(the red ghost). Its personality is called "Shadow", 
which means it tails after the Pac-Man
character like a shadow. Sometimes, Blinky can run after the Pac-Man for more than 10 turns. 
It is one of the most exciting moments for a player to shake off Blinky by making lots of turnings along
the track.
</p>


<p>Second, a ghost cannot always trace the Pac-Man character, otherwise the game will be extremely difficult to play.
Once a ghost starts tracing the Pac-Man character, there should be a limit to the time of a continuous tracing.
For this reason, we should have some kind of a counter or a similar mechanism to make the ghost give up 
tracing. 
</p>

<p>Third, we need to introduce some randomness in the algorithm. Given the same condition, a ghost may not
always have the same behavior. Randomness in the behavior avoids patterns in a ghost's movement,
which makes it harder for a player to guess the next move of the ghost.
</p>

<p>Fourth, whether the ghosts have their own personality, i.e. different behavior. We can apply an individual algorithm
for each ghost. The other approach is to have the same algorithm with varied parameters for each ghost.
To simplify programming, we will choose the latter in our code. 
</p>

<p>Fifth, when a ghost determines its moving direction, whether it should consider the position of other ghosts.
For example, two ghosts can surround the Pac-Man by going into two different tracks. It is an implementation
decision of the programmer whether to have this logic in place. In our code, we do not implement this part. 

</p>

<p>We will tackle the above issues one by one. Let's revisit part of the current code(see below) of the class
<b><tt>Maze</tt></b> and <b><tt>Ghost</tt></b> and explain their functionalities in more detail here. 
In <b>Ghost.fx</b>, we have two functions, 
<b><tt>moveHorizontally()</tt></b> and <b><tt>moveVertically()</tt></b>, to handle the moving logic of a ghost.
When a ghost reaches a point of the grid, i.e. at the last frame of an animation cycle, 
it has a chance to decide whether to change its current moving direction or to remain unchanged.
If it hits a wall or is going out of the maze, a change in the direction is necessary. If
a ghost is at an intersection, it can also determine whether to make a left or right turn.
The function <b><tt>changeDirectionXtoY(mustChange: Boolean)</tt></b> and <b><tt>changeDirectionYtoX(mustChange: Boolean)</tt></b>
contain the logic of making a decision. When the boolean argument <b><tt>mustChange</tt></b> is set to 
<b><tt>true</tt></b>, it means a decision of change must be made. Otherwise, a change is optional.
To choose which direction to go next, a ghost could have three possible choices: continuing on the current
direction, making a left turn or making a right turn. The class <b><tt>MoveDecision</tt></b> is used to
evaluate each choice and rank them with a score. Invalid choices, such as hitting a wall, are eliminated
automatically by given a negative score. For those valid moves, a positive score is returned. 
A ghost will finally follow the choice of highest score. If there is a tie in the scores, a random pick 
determines the final choice.


<b>Ghost.fx</b>
<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;">
<code language="perl">
<pre> 
<span class="category1">public</span> <span class="category1">class</span> Ghost <span class="category1">extends</span> CustomNode, MovingObject{
 
   . . . . .
 
   <span class="linecomment">// decide whether to change the current direction of a ghost</span>
   <span class="category1">public</span> <span class="category1">function</span> changeDirectionXtoY(mustChange: <span class="category2">Boolean</span>): Void {
      <span class="category1">if</span> ( <span class="category1">not</span> mustChange <span class="category1">and</span> <span class="category2">Math</span>.<span class="category2">random</span>() &gt; changeFactor ) {
         <span class="category1">return</span>;  <span class="linecomment">// no change of direction</span>
       }
  
      <span class="linecomment">// will change to a Y direction if possible</span>
      <span class="category1">var</span> goUp = MoveDecision {
         <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span>
         <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span> - 1 };
      <span class="category1">var</span> goDown = MoveDecision {
         <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span>
         <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span> + 1
       };
  
      <span class="linecomment">// evaluate the moving choices to pick the best one</span>
      goUp.<span class="category1">evaluate</span>();
      goDown.<span class="category1">evaluate</span>();
  
      <span class="category1">if</span> ( goUp.score &lt; 0 <span class="category1">and</span> goDown.score &lt; 0 )
        <span class="category1">return</span>;  <span class="linecomment">// no change of direction</span>
  
      <span class="category1">var</span> continueGo =  MoveDecision {
         <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span> + xDirection
         <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span>
       };
  
      continueGo.<span class="category1">evaluate</span>();
  
      <span class="category1">if</span> ( continueGo.score &gt; 0 <span class="category1">and</span> continueGo.score &gt; goUp.score
           <span class="category1">and</span> continueGo.score &gt; goDown.score ) {
         <span class="category1">return</span>;
       }
  
      <span class="category1">var</span> decision = -1; <span class="linecomment">// make it goes up first, then decide if we need to change it</span>
      <span class="category1">if</span> ( goUp.score  &lt; 0 )
        decision = 1
      <span class="category1">else</span>
        <span class="category1">if</span> ( goDown.score &gt; 0 ) {
           <span class="linecomment">// random pick</span>
           <span class="category1">if</span> ( <span class="category2">Math</span>.<span class="category2">random</span>() &gt; 0.5 )
             decision = 1;
       }
  
      yDirection = decision;
      xDirection = 0;
  
    }
 
   <span class="linecomment">// decide whether to change the current direction of a ghost</span>
   <span class="category1">public</span> <span class="category1">function</span> changeDirectionYtoX(mustChange: <span class="category2">Boolean</span>): Void {
  
      <span class="category1">if</span> ( <span class="category1">not</span> mustChange <span class="category1">and</span> <span class="category2">Math</span>.<span class="category2">random</span>() &gt; changeFactor )
        <span class="category1">return</span>;  <span class="linecomment">// no change of direction</span>
  
      <span class="linecomment">// will change to X directions if possible</span>
      <span class="category1">var</span> goLeft = MoveDecision {
         <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span> - 1
         <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span>
       };
  
      <span class="category1">var</span> goRight = MoveDecision {
         <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span> + 1
         <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span>
       };
  
      <span class="linecomment">// evaluate the moving choices to pick the best one</span>
      goLeft.<span class="category1">evaluate</span>();
      goRight.<span class="category1">evaluate</span>();
  
      <span class="category1">if</span> ( goLeft.score &lt; 0 <span class="category1">and</span> goRight.score &lt; 0 ) {
         <span class="category1">return</span>;  <span class="linecomment">// no change of direction</span>
       }
  
      <span class="category1">var</span> continueGo = MoveDecision {
         <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span>
         <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span> + yDirection
       };
  
      continueGo.<span class="category1">evaluate</span>();
  
      <span class="category1">if</span> ( continueGo.score &gt; 0 <span class="category1">and</span> continueGo.score &gt; goLeft.score
           <span class="category1">and</span> continueGo.score &gt; goRight.score ) {
         <span class="category1">return</span>;
       }
  
      <span class="linecomment">// make it goes up first, then decide if we need to change it to down</span>
      <span class="category1">var</span> decision = -1;
      <span class="category1">if</span> ( goLeft.score  &lt; 0 )
        decision = 1
      <span class="category1">else</span>
        <span class="category1">if</span> ( goRight.score &gt; 0 ) {
           <span class="linecomment">// random pick</span>
           <span class="category1">if</span> ( <span class="category2">Math</span>.<span class="category2">random</span>() &gt; 0.5 )
             decision = 1;
       }
  
      xDirection=decision;
      yDirection = 0;
    }
 
   <span class="linecomment">// move the ghost horizontally</span>
   <span class="category1">public</span> <span class="category1">function</span> moveHorizontally() {
  
      moveCounter++;
  
      <span class="category1">if</span> ( moveCounter &gt; ANIMATION_STEP - 1) {
         moveCounter=0;
         <span class="category2">x</span> += xDirection;
         imageX= MazeData.calcGridX(<span class="category2">x</span>);
   
         <span class="category1">var</span> nextX = xDirection + <span class="category2">x</span>;
   
         <span class="category1">if</span> ( <span class="category2">y</span> == 14 <span class="category1">and</span> ( nextX &lt;= 1 <span class="category1">or</span> nextX &gt;= 28) ) {
            <span class="category1">if</span> ( nextX &lt; - 1 <span class="category1">and</span> xDirection &lt; 0 ) {
               <span class="category2">x</span>=MazeData.GRID_SIZE;
               imageX= MazeData.calcGridX(<span class="category2">x</span>);
             }
            <span class="category1">else</span>
              <span class="category1">if</span> ( nextX &gt; 30 <span class="category1">and</span> xDirection &gt; 0) {
                 <span class="category2">x</span>=0;
                 imageX= MazeData.calcGridX(<span class="category2">x</span>);
               }
          }
         <span class="category1">else</span>
           <span class="category1">if</span> (nextX &lt; 0 <span class="category1">or</span> nextX &gt; MazeData.GRID_SIZE) {
              changeDirectionXtoY(<span class="category1">true</span>)
            }
         <span class="category1">else</span>
           <span class="category1">if</span> ( MazeData.<span class="category2">getData</span>(nextX, <span class="category2">y</span>) == MazeData.BLOCK ) {
              changeDirectionXtoY(<span class="category1">true</span>)
            }
           <span class="category1">else</span> {
              changeDirectionXtoY(<span class="category1">false</span>);
            }
       }
      <span class="category1">else</span> {
         imageX += xDirection * MOVE_SPEED;
       }
    }
 
   <span class="linecomment">// move the ghost vertically</span>
   <span class="category1">public</span> <span class="category1">function</span> moveVertically() {
        
      moveCounter++;
  
      <span class="category1">if</span> ( moveCounter &gt; ANIMATION_STEP - 1) {
         moveCounter = 0;
         <span class="category2">y</span> += yDirection;
         imageY = MazeData.calcGridX(<span class="category2">y</span>);
   
         <span class="category1">var</span> nextY= yDirection + <span class="category2">y</span>;
         <span class="category1">if</span> ( nextY &lt; 0 <span class="category1">or</span> nextY &gt; MazeData.GRID_SIZE) {
            changeDirectionYtoX(<span class="category1">true</span>);
          }
         <span class="category1">else</span>
           <span class="category1">if</span> ( MazeData.<span class="category2">getData</span>(<span class="category2">x</span>, nextY) == MazeData.BLOCK ) {
              changeDirectionYtoX(<span class="category1">true</span>);
            }
           <span class="category1">else</span> {
              changeDirectionYtoX(<span class="category1">false</span>);
            }
       }
      <span class="category1">else</span> {
         imageY += yDirection * MOVE_SPEED;
       }
    }
 
   . . . . . .
}</pre>
</code>

</div></div> 

<b>MoveDecision.fx</b>


<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;">
<code language="perl">
<pre> 
<span class="category1">public</span> <span class="category1">class</span> MoveDecision {
 
   <span class="linecomment">// x and y of an intended move</span>
   <span class="category1">public</span> <span class="category1">var</span> <span class="category2">x</span>: <span class="category2">Number</span>;
   <span class="category1">public</span> <span class="category1">var</span> <span class="category2">y</span>: <span class="category2">Number</span>;
 
   <span class="category1">public</span> <span class="category1">var</span> score: <span class="category2">Number</span>;
 
   <span class="linecomment">// evaluate if the move is valid,</span>
   <span class="linecomment">// if it is invalid, returns -1;</span>
   <span class="linecomment">// if it is valid, compute its score for ranking the final decision</span>
   <span class="category1">public</span> <span class="category1">function</span> <span class="category1">evaluate</span>( ):Void {
      <span class="category1">if</span> ( <span class="category2">x</span> &lt; 1 <span class="category1">or</span> <span class="category2">y</span> &lt; 1 <span class="category1">or</span> <span class="category2">y</span> &gt;= MazeData.GRID_SIZE <span class="category1">or</span> <span class="category2">x</span> &gt;= MazeData.GRID_SIZE){
         score = -1;
         <span class="category1">return</span> ;
       }
  
      <span class="category1">var</span> <span class="category2">status</span> = MazeData.<span class="category2">getData</span>(<span class="category2">x</span>, <span class="category2">y</span>);
      <span class="category1">if</span> ( <span class="category2">status</span> == MazeData.BLOCK ) {
         score = -1;
         <span class="category1">return</span> ;
       }
  
      <span class="linecomment">// rank it as a default score</span>
      score = 1;
    }
}</pre>
</code>

</div></div> 

<p>The key logic of making a decision lies in the function <b><tt>MoveDecision.evaluate()</tt></b>.
We are going to modify this function to always force the ghost to run towards the Pac-Man. An optimized
algorithm for a ghost to chase the Pac-Man is to always follow the shortest path to catch the Pac-Man.
However, the complexity of such an algorithm to find a shortest path on a grid is at least of the order of n*n. 
This makes it almost impossible to use in a game. For this reason, we need to find a simpler algorithm. 
</p>

<p>After some thinking, I found that the distance between a ghost and the Pac-Man
is a good ranking metric. The shorter the distance is, the higher the score is given to a particular choice.
The advantages of using the distance as a metric are obvious. It is very simple and can be caculated easily. 
Besides, this algorithm makes a ghost move in the direction that has the shortest distance to the
Pac-Man. To illustrate this algorithm, let's look at the below figure.

</p>

<p><img src="http://www.insideria.com/upload/2009/05/ghostmoving.png"></p>

<p>In the figure, the ghost Blinky is moving into an intersection from the right to the left. When it reaches 
the intersection, it has
three possible choices of its next movement: to go up, to go down and to continue heading left. Going down is
not a valid move because it hits the border of the maze. So we need to compare the other two options. The below
table shows the computation of the distance of the two possible moves: </p>

<p>
<table border=1>
<tr>
<td>Choice</td><td>X distance</td><td>Y distance</td><td>Total</td>
</tr>
<tr>
<td>Intersection</td><td>3</td><td>10</td><td>13</td>
</tr>
<tr>
<td>Up</td><td>3</td><td>9</td><td>12</td>
</tr>
<tr>
<td>Left</td><td>4</td><td>10</td><td>14</td>
</tr>
</table>
</p>

<p>
As shown in the table, the distance from the intersection to the Pac-Man character is 13 (The distance
between two adjacent dots is 1). If Blinky goes up, 
the distance is reduced to 12. If it heads left, the distance becomes 14. Therefore, going up seems a 
better choice for Blinky. In this way, Blinky should be able to get closer and closer to the Pac-Man 
and eventually catches him.
</p>

<p>
Of course, this simple algorithm does not take into consideration for the walls in the maze. 
For this reason, sometimes the calculated score does not in fact represent the shortest path. However, 
this inaccuracy makes the ghosts appear "stupid" in the game, which is the randomness we want to achieve in
the behavior. So we are going to implement it in our code. We rewrite the class <b><tt>MoveDecision</tt></b>.
When the function <b><tt>evaluate()</tt></b> calculates a score, it takes in two arguments: the reference to
Pac-Man instance and whether the ghost is in a hollow state. The variable <b><tt>distance</tt></b>
is used to compute the score. If the ghost is going after the Pac-Man character, the score is 
<b><tt>500-distance</tt></b>, which means a shorter distance yields a higher score. If the Pac-Man is
hunting the ghosts(when they are hollow), the score is caculated as <b><tt>500+distance</tt></b>. This 
makes the ghosts running away from the Pac-Man.


<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;">
<code language="perl">
<pre> 
<span class="blockcomment">/*
 * MoveDecision.fx
 *
 * Created on 2009-1-28, 14:42:00
 */</span>

package pacman;
<span class="category1">import</span> java.lang.<span class="category2">Math</span>;
<span class="category1">import</span> pacman.MazeData;
<span class="category1">import</span> pacman.PacMan;

<span class="blockcomment">/**
 * @author Henry Zhang
 */</span>

<span class="category1">public</span> <span class="category1">class</span> MoveDecision {
 
   <span class="linecomment">// x and y of an intended move</span>
   <span class="category1">public</span> <span class="category1">var</span> <span class="category2">x</span>: <span class="category2">Number</span>;
   <span class="category1">public</span> <span class="category1">var</span> <span class="category2">y</span>: <span class="category2">Number</span>;
 
   <span class="category1">public</span> <span class="category1">var</span> score: <span class="category2">Number</span>;
 
   <span class="linecomment">// evaluate if the move is valid,</span>
   <span class="linecomment">// if it is invalid, returns -1;</span>
   <span class="linecomment">// if it is valid, compute its score for ranking the final decision</span>
   <span class="category1">public</span> <span class="category1">function</span> <span class="category1">evaluate</span>( pacMan: PacMan, isHollow: <span class="category2">Boolean</span> ): Void {
      <span class="category1">if</span> ( <span class="category2">x</span> &lt; 1 <span class="category1">or</span> <span class="category2">y</span> &lt; 1 <span class="category1">or</span> <span class="category2">y</span> &gt;= MazeData.GRID_SIZE <span class="category1">or</span> <span class="category2">x</span> &gt;= MazeData.GRID_SIZE){
         score = -1;
         <span class="category1">return</span> ;
       }
  
      <span class="category1">var</span> <span class="category2">status</span> = MazeData.<span class="category2">getData</span>(<span class="category2">x</span>, <span class="category2">y</span>);
      <span class="category1">if</span> ( <span class="category2">status</span> == MazeData.BLOCK ) {
         score = -1;
         <span class="category1">return</span> ;
       }
  
      <span class="category1">var</span> distance = <span class="category2">Math</span>.<span class="category2">abs</span>( <span class="category2">x</span> - pacMan.<span class="category2">x</span> ) + <span class="category2">Math</span>.<span class="category2">abs</span>( <span class="category2">y</span> - pacMan.<span class="category2">y</span> );
  
      <span class="category1">if</span> ( isHollow )
        score = 500 + distance  <span class="linecomment">// mode to run away from Pac-Man</span>
      <span class="category1">else</span>
        score = 500 - distance; <span class="linecomment">// mode to chase Pac-Man</span>
    }
}</pre>
</code>

</div></div> 

</p>

<p>In the class <b><tt>Ghost</tt></b>, we modify two functions:
<b><tt>changeDirectionXtoY()</tt></b> and <b><tt>changeDirectionYtoX()</tt></b> and let the ghosts always pick the 
direction with the highest score. 

<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;">
<code language="perl">
<pre> 
  <span class="linecomment">// decide whether to change the current direction of a ghost</span>
  <span class="category1">public</span> <span class="category1">function</span> changeDirectionXtoY(mustChange: <span class="category2">Boolean</span>): Void {
     <span class="category1">if</span> ( <span class="category1">not</span> mustChange <span class="category1">and</span> <span class="category2">Math</span>.<span class="category2">random</span>() &gt; changeFactor ) {
        <span class="category1">return</span>;  <span class="linecomment">// no change of direction</span>
      }
 
     <span class="linecomment">// will change to a Y direction if possible</span>
     <span class="category1">var</span> goUp = MoveDecision {
        <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span>
        <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span> - 1 };
     <span class="category1">var</span> goDown = MoveDecision {
        <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span>
        <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span> + 1
      };
 
     <span class="linecomment">// evaluate the moving choices to pick the best one</span>
     goUp.<span class="category1">evaluate</span>(pacMan, isHollow);
     goDown.<span class="category1">evaluate</span>(pacMan, isHollow);
 
     <span class="category1">if</span> ( goUp.score &lt; 0 <span class="category1">and</span> goDown.score &lt; 0 )
       <span class="category1">return</span>;  <span class="linecomment">// no change of direction</span>
 
     <span class="category1">var</span> continueGo =  MoveDecision {
        <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span> + xDirection
        <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span>
      };
 
     continueGo.<span class="category1">evaluate</span>(pacMan, isHollow);
 
     <span class="category1">if</span> ( continueGo.score &gt; 0 <span class="category1">and</span> continueGo.score &gt; goUp.score
          <span class="category1">and</span> continueGo.score &gt; goDown.score ) {
        <span class="category1">return</span>;
      }
 
     <span class="category1">var</span> decision = -1; <span class="linecomment">// make it goes up first, then decide if we need to change it</span>
     <span class="category1">if</span> ( goUp.score  &lt; 0 )
       decision = 1
     <span class="category1">else</span>
       <span class="category1">if</span> ( goDown.score &gt; 0 ) {
          <span class="category1">if</span> ( goDown.score &gt; goUp.score) {
            decision = 1;
           }
        }
 
     yDirection = decision;
     xDirection = 0;
   }

  <span class="linecomment">// decide whether to change the current direction of a ghost</span>
  <span class="category1">public</span> <span class="category1">function</span> changeDirectionYtoX(mustChange: <span class="category2">Boolean</span>): Void {
 
     <span class="category1">if</span> ( <span class="category1">not</span> mustChange <span class="category1">and</span> <span class="category2">Math</span>.<span class="category2">random</span>() &gt; changeFactor )
       <span class="category1">return</span>;  <span class="linecomment">// no change of direction</span>
 
     <span class="linecomment">// will change to X directions if possible</span>
     <span class="category1">var</span> goLeft = MoveDecision {
        <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span> - 1
        <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span>
      };
 
     <span class="category1">var</span> goRight = MoveDecision {
        <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span> + 1
        <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span>
      };
 
     <span class="linecomment">// evaluate the moving choices to pick the best one</span>
     goLeft.<span class="category1">evaluate</span>(pacMan, isHollow);
     goRight.<span class="category1">evaluate</span>(pacMan, isHollow);
 
     <span class="category1">if</span> ( goLeft.score &lt; 0 <span class="category1">and</span> goRight.score &lt; 0 ) {
        <span class="category1">return</span>;  <span class="linecomment">// no change of direction</span>
      }
 
     <span class="category1">var</span> continueGo = MoveDecision {
        <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span>
        <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span> + yDirection
      };
 
     continueGo.<span class="category1">evaluate</span>(pacMan, isHollow);
 
     <span class="category1">if</span> ( continueGo.score &gt; 0 <span class="category1">and</span> continueGo.score &gt; goLeft.score
          <span class="category1">and</span> continueGo.score &gt; goRight.score ) {
        <span class="category1">return</span>;
      }
 
     <span class="linecomment">// make it goes up first, then decide if we need to change it to down</span>
     <span class="category1">var</span> decision = -1;
     <span class="category1">if</span> ( goLeft.score  &lt; 0 )
       decision = 1
     <span class="category1">else</span>
       <span class="category1">if</span> ( goRight.score &gt; 0 ) {
          <span class="category1">if</span> ( goRight.score &gt; goLeft.score) {
             decision = 1; 
           }
        }
 
     xDirection=decision;
     yDirection = 0;
   }</pre>
</code>

</div></div> 

<p>If we run the program again, wow, four ghosts are endlessly chasing the Pac-Man.
The game becomes very difficult to play. 
So we are going to introduce some randomness into the algorithm to make it easier
for the player. First, we define two variables in the 
<b><tt>Ghost</tt></b> class: <b><tt>chaseFactor</tt></b> and <b><tt>chaseCount</tt></b>.
The <b><tt>chaseFactor</tt></b> is a probability for a ghost to chase the Pac-Man.
The <b><tt>chaseCount</tt></b> is the continuous period of time that a ghost can go after
the Pac-Man character. Its initial value is a random number. Every time a ghost chase
the Pac-Man, the value of <b><tt>chaseCount</tt></b> is decreased by 1.
If <b><tt>chaseCount</tt></b> reaches 0, we let the ghost have 
a random move. By choosing the proper values of these variables, we can adjust
the probabilty that a ghost runs after the Pac-Man character. The corresponding code 
of this revised algorithm is shown below:

<b>Ghost.fx</b>

<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;">
<code language="perl">
<pre> 
<span class="category1">public</span> <span class="category1">class</span> Ghost <span class="category1">extends</span> CustomNode, MovingObject{
 
   . . . . . .
 
   <span class="linecomment">// variables to determine if a ghost should chase pacman, </span>
   <span class="linecomment">// and the probability</span>
   <span class="category1">public</span> <span class="category1">var</span> changeFactor = 0.75;
   <span class="category1">public</span> <span class="category1">var</span> chaseFactor = 0.5;
   <span class="category1">public</span> <span class="category1">var</span> chaseCount = 0;  
 
   <span class="linecomment">// decide whether to change the current direction of a ghost</span>
   <span class="category1">public</span> <span class="category1">function</span> changeDirectionXtoY(mustChange: <span class="category2">Boolean</span>): Void {
      <span class="category1">if</span> ( <span class="category1">not</span> mustChange <span class="category1">and</span> <span class="category2">Math</span>.<span class="category2">random</span>() &gt; changeFactor ) {
         <span class="category1">return</span>;  <span class="linecomment">// no change of direction</span>
       }
  
      <span class="linecomment">// will change to a Y direction if possible</span>
      <span class="category1">var</span> goUp = MoveDecision {
         <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span>
         <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span> - 1 };
      <span class="category1">var</span> goDown = MoveDecision {
         <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span>
         <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span> + 1
       };
  
      <span class="linecomment">// evaluate the moving choices to pick the best one</span>
      goUp.<span class="category1">evaluate</span>(pacMan, isHollow);
      goDown.<span class="category1">evaluate</span>(pacMan, isHollow);
  
      <span class="category1">if</span> ( goUp.score &lt; 0 <span class="category1">and</span> goDown.score &lt; 0 )
        <span class="category1">return</span>;  <span class="linecomment">// no change of direction</span>
  
      <span class="category1">if</span> ( <span class="category2">Math</span>.<span class="category2">random</span>() &lt; chaseFactor <span class="category1">and</span> chaseCount == 0 )
        chaseCount += <span class="category2">Math</span>.<span class="category2">random</span>() * 10 + 3;
  
      <span class="category1">var</span> continueGo =  MoveDecision {
         <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span> + xDirection
         <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span>
       };
  
      continueGo.<span class="category1">evaluate</span>(pacMan, isHollow);
  
      <span class="category1">if</span> ( continueGo.score &gt; 0 <span class="category1">and</span> continueGo.score &gt; goUp.score
           <span class="category1">and</span> continueGo.score &gt; goDown.score <span class="category1">and</span> chaseCount&gt;0) {
         chaseCount--;
         <span class="category1">return</span>;
       }
  
      <span class="category1">var</span> decision = -1; <span class="linecomment">// make it goes up first, then decide if we need to change it</span>
      <span class="category1">if</span> ( goUp.score  &lt; 0 )
        decision = 1
      <span class="category1">else</span>
        <span class="category1">if</span> ( goDown.score &gt; 0 ) {
           <span class="category1">if</span> ( chaseCount &gt; 0 ) {
              <span class="category1">if</span> ( goDown.score &gt; goUp.score) {
                 decision = 1;
                 chaseCount -- ;
               }
            }
           <span class="category1">else</span> {
              <span class="linecomment">// random pick</span>
              <span class="category1">if</span> ( <span class="category2">Math</span>.<span class="category2">random</span>() &gt; 0.5 )
                decision = 1;
            }
         }
  
      yDirection = decision;
      xDirection = 0;
    }
 
   <span class="linecomment">// decide whether to change the current direction of a ghost</span>
   <span class="category1">public</span> <span class="category1">function</span> changeDirectionYtoX(mustChange: <span class="category2">Boolean</span>): Void {
  
      <span class="category1">if</span> ( <span class="category1">not</span> mustChange <span class="category1">and</span> <span class="category2">Math</span>.<span class="category2">random</span>() &gt; changeFactor )
        <span class="category1">return</span>;  <span class="linecomment">// no change of direction</span>
  
      <span class="linecomment">// will change to X directions if possible</span>
      <span class="category1">var</span> goLeft = MoveDecision {
         <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span> - 1
         <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span>
       };
  
      <span class="category1">var</span> goRight = MoveDecision {
         <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span> + 1
         <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span>
       };
  
      <span class="linecomment">// evaluate the moving choices to pick the best one</span>
      goLeft.<span class="category1">evaluate</span>(pacMan, isHollow);
      goRight.<span class="category1">evaluate</span>(pacMan, isHollow);
  
      <span class="category1">if</span> ( goLeft.score &lt; 0 <span class="category1">and</span> goRight.score &lt; 0 ) {
         <span class="category1">return</span>;  <span class="linecomment">// no change of direction</span>
       }
      
      <span class="category1">if</span> ( <span class="category2">Math</span>.<span class="category2">random</span>() &lt; chaseFactor <span class="category1">and</span> chaseCount == 0 )
        chaseCount += <span class="category2">Math</span>.<span class="category2">random</span>() * 10 + 3;
  
      <span class="category1">var</span> continueGo = MoveDecision {
         <span class="category2">x</span>: <span class="category1">this</span>.<span class="category2">x</span>
         <span class="category2">y</span>: <span class="category1">this</span>.<span class="category2">y</span> + yDirection
       };
  
      continueGo.<span class="category1">evaluate</span>(pacMan, isHollow);
  
      <span class="category1">if</span> ( continueGo.score &gt; 0 <span class="category1">and</span> continueGo.score &gt; goLeft.score
           <span class="category1">and</span> continueGo.score &gt; goRight.score <span class="category1">and</span> chaseCount&gt;0 ) {
         chaseCount --;
         <span class="category1">return</span>;
       }
  
      <span class="linecomment">// make it goes up first, then decide if we need to change it to down</span>
      <span class="category1">var</span> decision = -1;
      <span class="category1">if</span> ( goLeft.score  &lt; 0 )
        decision = 1
      <span class="category1">else</span>
        <span class="category1">if</span> ( goRight.score &gt; 0 ) {
           <span class="category1">if</span> ( chaseCount &gt; 0 ) {
              <span class="category1">if</span> ( goRight.score &gt; goLeft.score) {
                 decision = 1;
                 chaseCount--;
               }
            }
           <span class="category1">else</span> { <span class="linecomment">// random pick</span>
              <span class="category1">if</span> ( <span class="category2">Math</span>.<span class="category2">random</span>() &gt; 0.5 )
                decision = 1;
            }
         }
  
      xDirection=decision;
      yDirection = 0;
    }
 
   . . . . . . 
}</pre>
</code>

</div></div> 



<p>Now, we have a better behavior of the ghosts and the game becomes much more interesting. 
Though our algorithm may not be optimal, we can control the difficulty of the game based
on the level. For example, as the level goes up, we can increase the <b><tt>chaseFactor</tt></b> to let the
ghosts chase the Pac-Man more frequently. Personality of ghosts can also be achieved by assigning 
different sets of values to the parameters of the algorithm. 
</p>

<p><b>Conclusions</b></p>

<p>We have walked through the process of building the Pac-Man game in JavaFX. We explored some of the
key features of the JavaFX language, such as binding, animation, transformation, Java class
integration and multiple inheritant. In general, these features reduces our coding effort 
in building rich GUI application. </P>

<p>To celebrate our hard work, let's play the completed game by clicking the below button:</p>

<p><a href="http://www.javafxgame.com/v10/pacman.jnlp">
<img src="http://www.insideria.com/upload/2009/05/maze12.png" border=0><br><br>
<img src="http://www.insideria.com/upload/2009/05/launch.gif" border=0></a>
</p>


<p><a href="http://www.insideria.com/upload/2009/05/javafxsource5.zip">Download Source Code</a></p>]]>
      
    </content>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.36372-comment:2066112</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.36372" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html"/>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html#comment-2066112" />
    <title>Comment from a on 2009-06-12</title>
    <author>
        <name>a</name>
        <uri></uri>
    </author>
    <content type="html" xml:lang="en" xml:base="">
        <![CDATA[<p>This was posted on oreilly Radar and describes how the ghosts move in pac man.  Its quite interesting.</p>

<p></p>

<p><a href="http://home.comcast.net/~jpittman2/pacman/pacmandossier.html">http://home.comcast.net/~jpittman2/pacman/pacmandossier.html</a></p>

<p><br />
Found here:<br />
<a href="http://radar.oreilly.com/2009/02/four-short-links-20-feb-2009.html">http://radar.oreilly.com/2009/02/four-short-links-20-feb-2009.html</a><br />
</p>]]>
    </content>
    <published>2009-06-12T18:44:20Z</published>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.36372-comment:2066141</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.36372" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html"/>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html#comment-2066141" />
    <title>Comment from Haining Henry Zhang on 2009-06-13</title>
    <author>
        <name>Haining Henry Zhang</name>
        <uri>http://www.javafxgame.com</uri>
    </author>
    <content type="html" xml:lang="en" xml:base="http://www.javafxgame.com">
        <![CDATA[<p>Thanks for your comments. The design of a more sophisicated chasing algorithm can borrow some ideas from that site.</p>

<p>The latest code had been updated for JavaFX 1.2. Please check out the download page on <a href="http://www.javafxgame.com">http://www.javafxgame.com</a> </p>]]>
    </content>
    <published>2009-06-13T09:11:47Z</published>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.36372-comment:2066171</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.36372" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html"/>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html#comment-2066171" />
    <title>Comment from Rex Guo on 2009-06-13</title>
    <author>
        <name>Rex Guo</name>
        <uri></uri>
    </author>
    <content type="html" xml:lang="en" xml:base="">
        <![CDATA[<p>It worked nicely on my Mac. There’s no sound though.<br />
Great job showcasing what JavaFX can do. I’ve been looking forward to demos of such quality ever since JavaFX 1.0 was released.</p>]]>
    </content>
    <published>2009-06-14T03:17:47Z</published>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.36372-comment:2067187</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.36372" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html"/>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html#comment-2067187" />
    <title>Comment from Jill on 2009-06-25</title>
    <author>
        <name>Jill</name>
        <uri>http://www.insidehoustonrealestate.com</uri>
    </author>
    <content type="html" xml:lang="en" xml:base="http://www.insidehoustonrealestate.com">
        <![CDATA[<p>So I just spent awhile playing the pacman game .  One thing I like about this java version was that the gameplay was much cleaner.  When I played the arcade version the old controls often don't pick up when I would make a turn.  This gameplay seemed much smoother by comparison.  Since the basics are done it would be cool to add some new options.  Maybe you could plug in graphics (like your bosses face) for the ghost.  It could keep all the old rules.  It would just be a way to spice it up. Jill <a href="http://www.insidehoustonrealestate.com/category/houstonrealestateblog">Houston Real Estate Blog</a></p>]]>
    </content>
    <published>2009-06-26T00:03:26Z</published>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.36372-comment:2067979</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.36372" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html"/>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html#comment-2067979" />
    <title>Comment from cheap web hosting on 2009-07-08</title>
    <author>
        <name>cheap web hosting</name>
        <uri>http://www.envisionwebhosting.com</uri>
    </author>
    <content type="html" xml:lang="en" xml:base="http://www.envisionwebhosting.com">
        <![CDATA[<p>After the release of JavaFX 1.0, I wrote a Pac-Man game using the JavaFX API. Many people were quite interested in the game. They either enjoyed playing it or asked me for the details fo the JavaFX code. JavaFX expert Jim Weaver invited me to write some articles about the process of building the game. After a few weeks’ hard work, with Jim’s constructive ideas and great help in proofreading, I finished the articles. Now they are published on insideRIA.com, an O’Reilly’s web site, as featured articles. <a href="http://www.envisionwebhosting.com/business.php">business opportunity</a> There will be 5 articles in total and they will run through the coming 5 weeks. </p>]]>
    </content>
    <published>2009-07-09T06:58:36Z</published>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.36372-comment:2068778</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.36372" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html"/>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html#comment-2068778" />
    <title>Comment from ok on 2009-07-21</title>
    <author>
        <name>ok</name>
        <uri></uri>
    </author>
    <content type="html" xml:lang="en" xml:base="">
        <![CDATA[<p>I still don't get why ever javafx example is a webstart app.<br />
</p>]]>
    </content>
    <published>2009-07-22T04:50:55Z</published>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.36372-comment:2069490</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.36372" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html"/>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html#comment-2069490" />
    <title>Comment from Nut on 2009-07-29</title>
    <author>
        <name>Nut</name>
        <uri></uri>
    </author>
    <content type="html" xml:lang="en" xml:base="">
        <![CDATA[<p>Excuse me I have some problem. I can't see my pacman, but when <br />
pacman is eaten I can see it. How can I solve this problem.</p>

<p>thank you.</p>]]>
    </content>
    <published>2009-07-30T03:24:20Z</published>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.36372-comment:2099225</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.36372" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html"/>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-4.html#comment-2099225" />
    <title>Comment from Anonymous on 2009-09-15</title>
    <author>
        <name>Anonymous</name>
        <uri></uri>
    </author>
    <content type="html" xml:lang="en" xml:base="">
        <![CDATA[<p>Very nice</p>]]>
    </content>
    <published>2009-09-15T17:47:30Z</published>
  </entry>

</feed
