<?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/05/writing-the-pac-man-game-in-ja-2.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.36290-</id>
  <updated>2009-11-18T23:37:47Z</updated>
  <title>Comments for Writing the Pac-Man Game in JavaFX - Part 3 (http://www.insideria.com/2009/05/writing-the-pac-man-game-in-ja-2.html)</title>
  <generator uri="http://www.sixapart.com/movabletype/">Movable Type 4.21-en</generator>
  <entry>
    <id>tag:www.insideria.com,2009://34.36290</id>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/05/writing-the-pac-man-game-in-ja-2.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=36290" title="Writing the Pac-Man Game in JavaFX - Part 3" />
    <published>2009-05-28T16:00:00Z</published>
    <updated>2009-05-28T16:00:00Z</updated>
    <title>Writing the Pac-Man Game in JavaFX - Part 3</title>
    <summary>We are now ready to create the ghosts in our game. The four ghosts, namely Blinky(red), Pinky(pink), 
Inky(cyan) and Clyde(orange), are trapped inside a cage when a game starts. After some time, they get out of the cage
one by one and start roaming the maze. Their goal is to catch the Pac-Man. The Pac-Man dies if he is touched
by one of the ghosts. If the Pac-Man swallows a magic dot, he has the power to eat ghosts for a while. During this 
time, the ghosts turn hollow and move more slowly.
</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/>
</p>

<p>We are now ready to create the ghosts in our game. The four ghosts, namely Blinky(red), Pinky(pink), 
Inky(cyan) and Clyde(orange), are trapped inside a cage when a game starts. After some time, they get out of the cage
one by one and start roaming the maze. Their goal is to catch the Pac-Man. The Pac-Man dies if he is touched
by one of the ghosts. If the Pac-Man swallows a magic dot, he has the power to eat ghosts for a while. During this 
time, the ghosts turn hollow and move more slowly.
</p>

<p>There are two parts for writing the code for ghosts. First part is to create the animation. The second part is
to implement an algorithm to control how the ghosts move inside the maze. The second part is the most interesting
and crucial thing of this game. We will elaborate the algorithm in the next article. For now, we just use a simpler
one for testing the animation.</p>

<p><b>Animation of Ghosts</b></p>

<p>A ghost can have three kinds of appearance. One is its normal look in its original color. The second is 
a hollow ghost. The third is a flashing hollow style when it is about to turn back to its original color.
So we need three sets of frames for the animation. Just like the Pac-Man character, every set of frames contains  
4 pictures. To make a ghost look differently, we can switch the set of frames when 
the status of a ghost changes. For example, below are three set of pictures for the red ghost Blinky.</p>

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

<p>In terms of moving approaches, the ghosts have three styles: roaming the maze, crawling slowly
when they turn hollow, and circling in the cage. Since the first two are the same except the moving speed is different,
we basically need to have two kinds of logic to handle the moving of a ghost: outside and inside the cage respectively.
</p>

<p>When we wrote the code of the Pac-Man character, we subclassed from <b><tt>MovingObject</tt></b>. This class
abstracts the common logic needed for a character. Let's write the <b><tt>Ghost</tt></b> class by extending 
<b><tt>MovingObject</tt></b> again. Below is the code of <b><tt>Ghost.fx</tt></b>: 
</p>

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

package pacman;

<span class="category1">import</span> java.lang.<span class="category2">Math</span>;
<span class="category1">import</span> javafx.animation.KeyFrame;
<span class="category1">import</span> javafx.animation.Timeline;
<span class="category1">import</span> javafx.scene.CustomNode;
<span class="category1">import</span> javafx.scene.image.Image;
<span class="category1">import</span> javafx.scene.image.ImageView;
<span class="category1">import</span> javafx.scene.Node;
<span class="category1">import</span> pacman.MazeData;

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

<span class="category1">public</span> <span class="category1">class</span> Ghost <span class="category1">extends</span> CustomNode, MovingObject{
 
   <span class="category1">public</span> def TRAPPED=10;
 
   <span class="linecomment">// the pacman character</span>
   <span class="category1">public</span> <span class="category1">var</span> pacMan: PacMan;
 
   <span class="category1">public</span> <span class="category1">var</span> hollowImage1 = Image {
      <span class="category2">url</span>: "<span class="quote">{__DIR__}images/ghosthollow2.png</span>"
      }
   <span class="category1">public</span> <span class="category1">var</span> hollowImage2 = Image {
      <span class="category2">url</span>: "<span class="quote">{__DIR__}images/ghosthollow3.png</span>"
      }
   <span class="category1">public</span> <span class="category1">var</span> hollowImage3 = Image {
      <span class="category2">url</span>: "<span class="quote">{__DIR__}images/ghosthollow1.png</span>"
      }
 
   <span class="linecomment">// images for ghosts when they become hollow</span>
   <span class="category1">public</span> <span class="category1">var</span> hollowImg =
     [ hollowImage1,
       hollowImage2,
       hollowImage1,
       hollowImage2 ];
 
   <span class="linecomment">// images for ghosts when they become hollow and flashing</span>
   <span class="category1">public</span> <span class="category1">var</span> flashHollowImg =
     [ hollowImage1,
       hollowImage3,
       hollowImage1,
       hollowImage3 ];
 
   <span class="linecomment">// time for a ghost to stay hollow</span>
   <span class="category1">var</span> hollowMaxTime: Integer = 80;
   <span class="category1">var</span> hollowCounter : Integer;
 
   <span class="linecomment">// the images of animation</span>
   <span class="category1">public</span> <span class="category1">var</span> defaultImage1: Image;
   <span class="category1">public</span> <span class="category1">var</span> defaultImage2: Image;
 
   def  defaultImg  = [
         defaultImage1,
         defaultImage2,
         defaultImage1,
         defaultImage2,
   ];
 
   <span class="linecomment">// animation images</span>
   <span class="category1">var</span> images = defaultImg;
 
   <span class="linecomment">// initial direction and position of a ghost, used in status reset</span>
   <span class="category1">public</span> <span class="category1">var</span> initialLocationX : <span class="category2">Number</span>;
   <span class="category1">public</span> <span class="category1">var</span> initialLocationY : <span class="category2">Number</span>;
   <span class="category1">public</span> <span class="category1">var</span> initialDirectionX : <span class="category2">Number</span>;
   <span class="category1">public</span> <span class="category1">var</span> initialDirectionY : <span class="category2">Number</span>;
 
   <span class="linecomment">// time to stay in the cage</span>
   <span class="category1">public</span> <span class="category1">var</span> trapTime: Integer;
   <span class="category1">public</span> <span class="category1">var</span> trapCounter: Integer=0;
 
   <span class="linecomment">// variables to decide if ghost should chase man, and with what probability</span>
   <span class="category1">public</span> <span class="category1">var</span> changeFactor = 0.75;
 
 
   <span class="linecomment">// the flag is set if a ghost becomes hollow</span>
   <span class="category1">public</span> <span class="category1">var</span> isHollow: <span class="category2">Boolean</span> = <span class="category1">false</span>;
 
   <span class="linecomment">// the GUI of a ghost</span>
   <span class="category1">var</span> ghostNode : ImageView = ImageView {
      <span class="category2">x</span>: bind imageX  - 13
      <span class="category2">y</span>: bind imageY  - 13
      image: bind images[currentImage]
      }
 
   postinit {
      initialLocationX = <span class="category2">x</span>;
      initialLocationY = <span class="category2">y</span>;
      initialDirectionX = xDirection;
      initialDirectionY = yDirection;
  
      resetStatus();
    }
 
   <span class="linecomment">// reset the status of a ghost and place it into the cage</span>
   <span class="category1">public</span> <span class="category1">function</span> resetStatus() {
      <span class="category2">x</span> = initialLocationX;
      <span class="category2">y</span> = initialLocationY;
  
      xDirection = initialDirectionX;
      yDirection = initialDirectionY;
  
      isHollow = <span class="category1">false</span>;
  
      moveCounter = 0;
      trapCounter = 0;
      currentImage = 0;
  
      imageX = MazeData.calcGridX(<span class="category2">x</span>);
      imageY = MazeData.calcGridY(<span class="category2">y</span>);
  
      images = defaultImg;
      state = TRAPPED;
          
      timeline.keyFrames[0].<span class="category2">time</span> = 50ms;
  
      <span class="category2">visible</span> = <span class="category1">true</span>;
      <span class="category2">start</span>();
    }
 
 
   <span class="category1">public</span> <span class="category1">function</span> changeToHollowGhost() {
      hollowCounter = 0;
      isHollow = <span class="category1">true</span>;
  
      <span class="linecomment">// switch the animation images</span>
      images = hollowImg;
  
      <span class="linecomment">// make it moves slower</span>
      timeline.<span class="category2">stop</span>();
      timeline.keyFrames[0].<span class="category2">time</span> = 140ms;
      timeline.<span class="category2">play</span>();
    }
 
   <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;
       }
    }
 
   <span class="linecomment">// move the ghost horizontally in the cage</span>
   <span class="category1">public</span> <span class="category1">function</span> moveHorizontallyInCage() {
      
      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> ( nextX &lt; 12 ) {
            xDirection = 0;
            yDirection = 1;
          }
         <span class="category1">else</span>
           <span class="category1">if</span> ( nextX &gt; 17) {
              xDirection = 0;
              yDirection = -1;
            }
       }
      <span class="category1">else</span> {
         imageX += xDirection * MOVE_SPEED;
       }
    }
 
   <span class="linecomment">// move the ghost vertically in a cage</span>
   <span class="category1">public</span> <span class="category1">function</span> moveVerticallyInCage() {
  
      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>) + 8;
   
         <span class="category1">var</span> nextY = yDirection + <span class="category2">y</span>;
   
         <span class="category1">if</span> ( nextY &lt; 13 ) {
            yDirection = 0;
            xDirection = -1;
          }
         <span class="category1">else</span>
           <span class="category1">if</span> ( nextY &gt; 15) {
              yDirection = 0;
              xDirection = 1;
            }
       }
      <span class="category1">else</span> {
         imageY += yDirection * MOVE_SPEED;
       }
    }
 
   <span class="category1">public</span> <span class="category1">function</span> <span class="category2">hide</span>() {
      <span class="category2">visible</span>=<span class="category1">false</span>;
          timeline.<span class="category2">stop</span>();
    }
 
   <span class="linecomment">// move one tick</span>
   <span class="category1">public</span> override <span class="category1">function</span> moveOneStep() {
  
      <span class="category1">if</span> ( state == MOVING <span class="category1">or</span> state == TRAPPED ) {
         <span class="category1">if</span> ( xDirection != 0 ) {
            <span class="category1">if</span> ( state == MOVING )
              moveHorizontally()
            <span class="category1">else</span>
              moveHorizontallyInCage();
          }
         <span class="category1">else</span>
           <span class="category1">if</span> ( yDirection != 0 ) {
              <span class="category1">if</span> ( state == MOVING )
                moveVertically()
              <span class="category1">else</span>
                moveVerticallyInCage();
            }
   
         <span class="category1">if</span> ( currentImage &lt; ANIMATION_STEP - 1 )
           currentImage++
         <span class="category1">else</span> {
            currentImage=0;
            <span class="category1">if</span> ( state == TRAPPED ) { 
               trapCounter++;
     
               <span class="category1">if</span> ( trapCounter &gt; trapTime <span class="category1">and</span> <span class="category2">x</span> == 14 <span class="category1">and</span> <span class="category2">y</span> == 13) {
                  <span class="linecomment">// go out of the cage</span>
                  <span class="category2">y</span> = 12;
      
                  xDirection = 0;
                  yDirection = -1;
                  state = MOVING;
                }
             }
          }
       }
  
      <span class="linecomment">// check to see if need to switch back to a normal status</span>
      <span class="category1">if</span> ( isHollow ) {
       
         hollowCounter++;
   
         <span class="category1">if</span> ( hollowCounter == hollowMaxTime - 30 )
           images = flashHollowImg
         <span class="category1">else</span>
           <span class="category1">if</span> ( hollowCounter &gt; hollowMaxTime ) {
              isHollow = <span class="category1">false</span>;
              images = defaultImg;
                    
              timeline.<span class="category2">stop</span>();
              timeline.keyFrames[0].<span class="category2">time</span> = 50ms;
              timeline.<span class="category2">play</span>();
            }
       }
    }
 
   <span class="category1">public</span> override <span class="category1">function</span> create(): Node {
          <span class="category1">return</span> ghostNode;
    }
 
}</pre>
</code>

</div></div>


<p>
The variable <b><tt>defaultImg</tt></b> is a sequence of images used as a ghost normal look. 
The variable <b><tt>hollowImg</tt></b> and <b><tt>flashHollowImg</tt></b> store two sets of images for
the states when a ghost becomes hollow and flashing. Similar to the <b><tt>PacMan</tt></b> class, the moving
logic is handled in the function <b><tt>moveOneStep()</tt></b>. When a ghost is inside the cage, the function
<b><tt>moveHorizontallyInCage()</tt></b> and <b><tt>moveVerticallyInCage()</tt></b> make the ghost turning around
and around inside the cage. When a ghost gets out of the cage, two functions
<b><tt>moveHorizontally()</tt></b> and <b><tt>moveVertically()</tt></b> control its roaming behavior. The variable
<b><tt>trapTime</tt></B> determines how long a ghost stays in the cage before it gets out. Properly choosing 
the values of this instance variable makes four ghosts coming out the cage in a fixed 
order(Blinky-Pinky-Inky-Clyde). The
below code in the function <b><tt>moveOneStep()</tt></b> sets free the ghost after a pre-defined time.
<p>
<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;">
<code language="perl">
<pre> 
  <span class="category1">public</span> override <span class="category1">function</span> moveOneStep() {
         . . . . . .
 
         <span class="category1">if</span> ( state == TRAPPED ) { 
            trapCounter++;
  
            <span class="category1">if</span> ( trapCounter &gt; trapTime <span class="category1">and</span> <span class="category2">x</span> == 14 <span class="category1">and</span> <span class="category2">y</span> == 13) {
               <span class="linecomment">// go out of the cage</span>
               <span class="category2">y</span> = 12;
   
               xDirection = 0;
               yDirection = -1;
               state = MOVING;
             }
          }
 
        . . . . . .
   }</pre>
</code>

</div></div>


<p>The function <b><tt>changeToHollowGhost()</tt></b> turns a ghost into a hollow style. What it does is switching
the animation pictures and slowing down the moving speed of a ghost.
</p>
<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">function</span> changeToHollowGhost() {
     hollowCounter = 0;
     isHollow = <span class="category1">true</span>;
 
     <span class="linecomment">// switch the animation images</span>
     images = hollowImg;
 
     <span class="linecomment">// make it moves slower</span>
     timeline.<span class="category2">stop</span>();
     timeline.keyFrames[0].<span class="category2">time</span> = 140ms;
     timeline.<span class="category2">play</span>();
   }</pre>
</code>

</div></div>



<p>After a ghost becomes hollow, it resumes to its normal color after a period of time. 
The second half of the function <b><tt>moveOneStep()</tt></b> uses a counter to keep track of the 
time and flashes the ghost just before it turns into its normal color. From this part, we can
see how the switching of 3 sets of pictures works. 
</p>
<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;">
<code language="perl">
<pre> 
  <span class="category1">public</span> override <span class="category1">function</span> moveOneStep() {
 
     . . . . . .
 
     <span class="linecomment">// check to see if need to switch back to a normal status</span>
     <span class="category1">if</span> ( isHollow ) {
      
        hollowCounter++;
  
        <span class="category1">if</span> ( hollowCounter == hollowMaxTime - 30 )
          images = flashHollowImg
        <span class="category1">else</span>
          <span class="category1">if</span> ( hollowCounter &gt; hollowMaxTime ) {
             isHollow = <span class="category1">false</span>;
             images = defaultImg;
                   
             timeline.<span class="category2">stop</span>();
             timeline.keyFrames[0].<span class="category2">time</span> = 50ms;
             timeline.<span class="category2">play</span>();
           }
      }
   }</pre>
</code>

</div></div>



<p><b>Roaming the Maze</b>
</p>

<p>As we mentioned previously, the algorithm that governs the ghosts' moving is the heart of this program.
For the purpose of testing the ghosts' animation, for now, we apply a "random" moving algorithm, ie. the ghosts
run arbitrarily inside the maze. In next article, we will implement a more complex algorithm. The function
<b><tt>changeDirectionYtoX( Boolean )</tt></b> and <b><tt>changeDirectionXtoY( Boolean )</tt></b> give out
decisions of whether a ghost should keep its current direction, or make a left or right turn. For illustration,
let's take an 
in-depth look at the function <b><tt>changeDirectionYtoX( Boolean )</tt></b>. 
</p>
<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> 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 a X direction 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;
   }</pre>
</code>

</div></div>


<p>




When a ghost is moving
vertically, this function determines the next direction. Possible decisions include: turning left, turning right,
and continue with the current direction. A class <b><tt>MoveDecision</tt></b> is used to model a
tentative decision. See the below code: 
</p>
<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="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>( ):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 <b><tt>evaluate()</tt></b> function evaluates a moving decision and gives a score. 
A ghost simply picks the decision with highest ranking score. In a random moving algorithm, all decisions are given
an equal score 1. If a move leads the ghost to hitting a wall, the ranking score is (-1), which automatically
eliminates it from being a candidate decision. If a ghost reaches a wall, the argument <b><tt>mustChange</tt></b>
of <b><tt>changeDirectionYtoX(Boolean)</tt></b> is set so that a ghost always gets a change of direction. If this argument
is false, the decision to change direction is affected by a random factor <b><tt>changeFactor</tt></b>. This
allows the moving behavior of a ghosts more unpredictable, hence the player cannot guess the moving pattern
of a ghost. The <b><tt>changeDirectionXtoY(Boolean)</tt></b> has a similar logic and it determines the moving decision
when a ghost is going horizontally. 
</p>

<p><b>Running the Game</b></p>

<p>Now we are ready to put things together and have some fun running the program. 
We add in some code to <b><tt>Maze.fx</tt></b>, putting four ghosts on stage: 
</p>
<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> Maze <span class="category1">extends</span> CustomNode {
 
   . . . . . 
 
   <span class="category1">public</span> <span class="category1">var</span> ghostBlinky = Ghost {
      defaultImage1: Image {
         <span class="category2">url</span>: "<span class="quote">{__DIR__}images/ghostred1.png</span>"
       }
  
      defaultImage2: Image {
         <span class="category2">url</span>: "<span class="quote">{__DIR__}images/ghostred2.png</span>"
       }
  
       maze: <span class="category1">this</span>
       pacMan: pacMan
       <span class="category2">x</span>: 17
       <span class="category2">y</span>: 15
       xDirection: 0
       yDirection: -1
       trapTime: 1
      };
 
    <span class="category1">public</span> <span class="category1">var</span> ghostPinky = Ghost {
       defaultImage1:Image {
           <span class="category2">url</span>: "<span class="quote">{__DIR__}images/ghostpink1.png</span>"
        }
  
       defaultImage2:Image {
          <span class="category2">url</span>: "<span class="quote">{__DIR__}images/ghostpink2.png</span>"
        }
       
       maze: <span class="category1">this</span>
       pacMan: pacMan
       <span class="category2">x</span>: 12
       <span class="category2">y</span>: 14
       xDirection: 0 
       yDirection: 1
       trapTime: 10
     };
 
    <span class="category1">public</span> <span class="category1">var</span> ghostInky = Ghost {
       defaultImage1:Image {
          <span class="category2">url</span>: "<span class="quote">{__DIR__}images/ghostcyan1.png</span>"
        }
       defaultImage2:Image {
          <span class="category2">url</span>: "<span class="quote">{__DIR__}images/ghostcyan2.png</span>"
        }
  
       maze: <span class="category1">this</span>
       pacMan: pacMan
       <span class="category2">x</span>: 13
       <span class="category2">y</span>: 15
       xDirection: 1
       yDirection: 0
       trapTime: 40
     };
 
    <span class="category1">public</span> <span class="category1">var</span> ghostClyde = Ghost {
       defaultImage1:Image {
           <span class="category2">url</span>: "<span class="quote">{__DIR__}images/ghostorange1.png</span>"
        }
       defaultImage2:Image {
          <span class="category2">url</span>: "<span class="quote">{__DIR__}images/ghostorange2.png</span>"
        }
  
       maze: <span class="category1">this</span>
       pacMan: pacMan
       <span class="category2">x</span>: 15
       <span class="category2">y</span>: 14
       xDirection: -1
       yDirection: 0
       trapTime: 60
     };
 
    <span class="category1">public</span> <span class="category1">var</span> ghosts = [ghostBlinky, ghostPinky, ghostInky, ghostClyde];
 
     . . . . . . 
 
    postinit {
  
      . . . . . .
  
       insert pacMan into group.content;
  
       insert ghosts into group.content;
  
       insert WallBlackRectangle{ x1:-3, y1:13, x2:0, y2:15 } into group.content;
       insert WallBlackRectangle{ x1:29, y1:13, x2:31, y2:15 } into group.content;
     }</pre>
</code>

</div></div>



<p>Run the program and you can see four ghosts roaming the maze. You can control the Pac-Man character by keyboard
to eat dots. However, the ghosts cannot eat the Pac-Man even they meet each other. We will implement this 
part in next article. Click on the below screenshot and see it for yourself: 
</p>

<p><a href="http://www.javafxgame.com/v7/pacman.jnlp">
<img src="http://www.insideria.com/upload/2009/05/maze8.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/javafxsource3.zip">Download Source Code</a></p>

<p><strong>Related Features</strong></p>
<ul><li><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></li>
<li><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></li>
</ul>]]>
      
    </content>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.36290-comment:2070745</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.36290" type="text/html" href="http://www.insideria.com/2009/05/writing-the-pac-man-game-in-ja-2.html"/>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/05/writing-the-pac-man-game-in-ja-2.html#comment-2070745" />
    <title>Comment from Gene Techno on 2009-08-20</title>
    <author>
        <name>Gene Techno</name>
        <uri></uri>
    </author>
    <content type="html" xml:lang="en" xml:base="">
        <![CDATA[<p>I'm having a problem</p>

<p>public class PacMan extends CustomNode, MovingObject {<br />
public class Ghost extends CustomNode, MovingObject{</p>

<p>Both have error on them saying "Only non-mixin based class allowed."</p>

<p>I tried redoing it, and downloading the source codes</p>

<p>Going on ahead, still same error, could some one help me?</p>

<p>tedo379@yahoo.com</p>

<p>Thanks</p>]]>
    </content>
    <published>2009-08-20T17:53:06Z</published>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.36290-comment:2070781</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.36290" type="text/html" href="http://www.insideria.com/2009/05/writing-the-pac-man-game-in-ja-2.html"/>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/05/writing-the-pac-man-game-in-ja-2.html#comment-2070781" />
    <title>Comment from Henry Zhang on 2009-08-20</title>
    <author>
        <name>Henry Zhang</name>
        <uri>http://www.javafxgame.com</uri>
    </author>
    <content type="html" xml:lang="en" xml:base="http://www.javafxgame.com">
        <![CDATA[<p>Hi Tedo379,</p>

<p>This is due to JavaFX 1.2 removed multi inheritance, please refer to the comments of article 3 for more detail:</p>

<p><a href="http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-3.html">http://www.insideria.com/2009/06/writing-the-pac-man-game-in-ja-3.html</a></p>]]>
    </content>
    <published>2009-08-21T03:49:44Z</published>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.36290-comment:2130773</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.36290" type="text/html" href="http://www.insideria.com/2009/05/writing-the-pac-man-game-in-ja-2.html"/>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/05/writing-the-pac-man-game-in-ja-2.html#comment-2130773" />
    <title>Comment from domino online on 2009-10-07</title>
    <author>
        <name>domino online</name>
        <uri>http://www.dominoesstars.com</uri>
    </author>
    <content type="html" xml:lang="en" xml:base="http://www.dominoesstars.com">
        <![CDATA[<p>Does it have an online feature for multipile players? Can you go FFA? This is my fovorite type of game. It allows players who are actually good to win for their own skills a beat a bunch of pro or noobs.</p>]]>
    </content>
    <published>2009-10-07T16:16:29Z</published>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.36290-comment:2192574</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.36290" type="text/html" href="http://www.insideria.com/2009/05/writing-the-pac-man-game-in-ja-2.html"/>
    <link rel="alternate" type="text/html" href="http://www.insideria.com/2009/05/writing-the-pac-man-game-in-ja-2.html#comment-2192574" />
    <title>Comment from Gabriel Hirjoi on 2009-11-18</title>
    <author>
        <name>Gabriel Hirjoi</name>
        <uri></uri>
    </author>
    <content type="html" xml:lang="en" xml:base="">
        <![CDATA[<p>Hi .. <br />
Very nice tutorial .. <br />
Thank you for sharing ..<br />
I have only one question .. <br />
Do you have the Pac-Man game implementation for Java ?</p>

<p>free2b_thesame@yahoo.com<br />
Best regards ..</p>]]>
    </content>
    <published>2009-11-18T23:37:39Z</published>
  </entry>

</feed
