800 lines
38 KiB
Plaintext
800 lines
38 KiB
Plaintext
CS1322: Programming Assignment #6 - Fall 2002.
|
|
Assigned: November 11, 2002
|
|
Phase I Instant Feedback Due Date: November 21, 2002 11:59:59
|
|
Phase II Instant Feedback Due Date: November 25, 2002 11:59:59
|
|
DUE Date: November 25, 2002 11:59:59 PM
|
|
|
|
|
|
Program Title: Pacman
|
|
|
|
|
|
Files Provided
|
|
====================================================================
|
|
|
|
o P6.nfo.txt - this file
|
|
o board_1.txt - an input file that specifies board #1
|
|
o board_2.txt - an input file that specifies board #2
|
|
o GhostAI.class - a class that makes decisions on Ghost movement
|
|
o P6Constants.java - Interface needed for P6
|
|
(You must use these constants when appropriate)
|
|
o various image files
|
|
|
|
Learning Objectives
|
|
====================================================================
|
|
|
|
o Advanced DataStructures
|
|
o Advanced GUIs
|
|
|
|
|
|
Other Resources
|
|
====================================================================
|
|
|
|
Website containing screenshots and nfo file with pictures at
|
|
|
|
http://128.61.63.158/cs1322/P6
|
|
|
|
|
|
PROGRAM OVERVIEW
|
|
====================================================================
|
|
Welcome to the 6th and final programming assignemnt! A word of
|
|
caution: This project will be the most difficult one in this course.
|
|
You will be required to utilize all that you have learned in order to
|
|
complete this assignment. START EARLY!
|
|
|
|
This assignment will be very similar to the arcade classic - Pacman.
|
|
For those of you who aren't familiar with the game, Pacman is a little
|
|
yellow smiley face controlled by you, the user. The goal is to have
|
|
Pacman eat all the nibbles (dots on the board). If Pacman completes
|
|
this objective, you win the game. However, there are 4 ghosts
|
|
pursuing Pacman. If one of the ghosts catches him, the game is over.
|
|
Pacman has one defense however: the special nibbles placed on the
|
|
board. If Pacman eats a special nibble, the ghosts can be eaten by
|
|
Pacman for a time. During this period, the ghosts run away from
|
|
Pacman. If a ghost is eaten, it returns to its start location on the
|
|
board and resumes it's role of pursing and attempting to catch Pacman
|
|
even if the others are still running from Pacman. After a time, all
|
|
the ghosts return to the original state of pursuing Pacman.
|
|
|
|
HEY! guess what! START EARLY!
|
|
|
|
|
|
A Word About Imlementation and Grading
|
|
====================================================================
|
|
|
|
In the past five assignments, the TAs and nfo files have largely
|
|
specified exactly which methods, parameters, classes and fields you
|
|
would need to complete the assignment. This assignment will be
|
|
different. While we will supply a recommended design, the exact means
|
|
of implementation in terms of which methods or fields you use is left
|
|
ambiguous. With the exception of the Instant Feedback phases, you
|
|
will be largely responsible for figuring out which methods you wish to
|
|
create to solve a given problem. This assignment will be graded more
|
|
in terms of functionality i.e. "does this feature function correctly?"
|
|
rather than "is this method present with these exact parameters".
|
|
With this in mind, you can feel free to create whatever methods you
|
|
wish to solve the problem. In addition, those of you looking for a
|
|
challenge or wanting to fight "the man" can feel free to deviate as
|
|
much or as little as you want from the design below. (Again, with the
|
|
exception of the Instant Feedback phases. You must do what we tell
|
|
you there.) Keep in mind, however, that as you deviate from the
|
|
recommended design, it becomes difficult to give partial credit if
|
|
things don't work correctly. So if you do choose to go your own way,
|
|
do so at your own risk. If you are not yet comfortable leaving the
|
|
nest, the design below is provided.
|
|
|
|
<cough>Start Early!
|
|
|
|
|
|
Overall Design
|
|
====================================================================
|
|
|
|
In the MVC (Model, View, Controller) paradigm of GUI programming, we
|
|
separate parts of the program into three pieces:
|
|
|
|
The Model - This class or group of classes stores the state of the
|
|
data in the program. It is often a data structure such as a BST or
|
|
Heap. The model typically is independent of any GUI widgets. This
|
|
allows our program to be more portable. If we wanted to use our
|
|
data in some interface other than a GUI, say a text console
|
|
interface, there would be little to no recoding of the model.
|
|
|
|
The View - This gruop of classes is responsible for displaying the
|
|
GUI. Often they extend or hold GUI widget instances. We don't
|
|
want the view to hold the state of any data. This is the role of
|
|
the model.
|
|
|
|
The Controller - This class or group of classes is responsible for
|
|
manipulating the model according to the needs of the program.
|
|
Often is the listener of events from GUI widgets (as EventHandler
|
|
was in P5)
|
|
|
|
The UIDelegate paradigm of GUI programming is very similar to MVC.
|
|
The only difference is that in UIDelegate we combine the roles of View
|
|
and Controller into one class or group of classes.
|
|
|
|
|
|
Now an overview of the classes of Pacman:
|
|
|
|
Model - GamePiece, WallPiece, FloorPiece, MoveablePiece, Ghost, Pacman
|
|
View - GameBoard, PacmanFrame
|
|
Controller - PacmanFileReader, PacmanEngine, PacmanFrame
|
|
|
|
Notice PacmanFrame is both a View and a Controller. This means it
|
|
will be a UIDelegate class.
|
|
|
|
|
|
|
|
PHASE I - The Pieces - Instant Feedback
|
|
====================================================================
|
|
|
|
The classes in this phase will be model classes that represent the
|
|
tiles and pieces on the board. They will be independent of any GUI
|
|
widgets. They merely will hold information about the piece such as
|
|
where it is on the board.
|
|
|
|
Create a class called GamePiece. This class will be an abstract super
|
|
class to all pieces of the game board. GamePiece should include the
|
|
following:
|
|
|
|
o a protected int called "x". This variable will represent which
|
|
column the piece is in. i.e., it's location along the x-axis.
|
|
o a protected int called "y". This variable will represent which
|
|
row the piece is in. i.e., it's location along the y-axis.
|
|
|
|
Together these two variables represent the exact location of the
|
|
board this particular piece is in. The location 0,0 will be the
|
|
upper left hand corner of the game board.
|
|
|
|
o public accessors for both of the above variables.
|
|
|
|
o a public method called setPostition that takes in two
|
|
parameters: x followed by y and sets the location variables to
|
|
these parameters.
|
|
o a constructor that takes in two parameters: x followed by y and
|
|
sets the location variables to these parameters.
|
|
|
|
|
|
Create a class called FloorPiece. This class will extend GamePiece.
|
|
It will also add the functionality of nibbles (The dots that Pacman
|
|
eats) Each FloorPiece can have a regular nibble, a special nibble
|
|
(one that allows Pacman to chase the ghosts), or neither (but not
|
|
both). FloorPiece should have the following:
|
|
|
|
o a private boolean variable called "hasNibble". This variable
|
|
will represent whether or not this FloorPiece has a nibble that
|
|
Pacman eats (the regular ones, not the ones that lets Pacman
|
|
chase Ghosts)
|
|
o a private boolean variable called "hasSpecial". This variable
|
|
will represent whether or not this FloorPiece has a special
|
|
nibble that allows Pacman to chase Ghosts.
|
|
o accessors and modifiers for both of the above
|
|
o a constructor that takes in four parameters: the x and y
|
|
location and two booleans representing whether or not this FloorPiece
|
|
should have a nibble or a special. This constructor should chain
|
|
to the super constructor and set hasNibble and hasSpecial
|
|
according to the parameters.
|
|
|
|
|
|
Create a class called WallPiece. This class will extend GamePiece.
|
|
It will also add the functionality of a wall on the game board.
|
|
Neither ghosts nor Pacman can travel on or through walls. They can
|
|
only travel on FloorPieces. Your WallPiece class should have the
|
|
following:
|
|
|
|
o a constructor that takes in two parameters: the x and y
|
|
locations of this piece. This constructor should chain to the
|
|
super constructor.
|
|
|
|
|
|
Create a class called MoveablePiece. This class will be abstract and
|
|
extend GamePiece. This class will be the super class of both Ghost
|
|
and Pacman. MoveablePiece should have the following:
|
|
|
|
o a constructor that takes in two ints: the x and y location of
|
|
this piece. This constructor should chain to the
|
|
super constructor.
|
|
o a public method named "move" that returns void and takes in an
|
|
int representing a direction. This parameter will be either
|
|
NORTH, EAST, SOUTH, or WEST from P6Constants. This constants
|
|
will represent up, right, down and left respectively. In this
|
|
method you should "move" the piece. This means modify the
|
|
piece's x or y location accordingly. Don't forget that (0,0) is
|
|
the top left corner of the board.
|
|
|
|
|
|
|
|
PHASE II - Ghosts and Pacman - Instant Feedback
|
|
====================================================================
|
|
|
|
These two classes will be model classes for Pacman and an individual ghost.
|
|
|
|
Create a class called Pacman which extends MoveablePiece. This class
|
|
will model the Pacman piece which the user will control. Pacman
|
|
should include the following:
|
|
|
|
o a private int variable named "direction". This variable will
|
|
contain one of the four constants from P6Constants: NORTH, EAST,
|
|
SOUTH, WEST. This variable will represent the direction Pacman
|
|
is currently heading on the board. This variable will be
|
|
initialized to EAST.
|
|
o a constructor that takes in two ints: the x and y location of
|
|
this piece. This constructor should chain to the
|
|
super constructor.
|
|
|
|
Now the Ghosts...
|
|
|
|
Before we get into the details of implementing Ghost, let's talk
|
|
about how the Ghost will chase (or run from) Pacman.
|
|
|
|
Provided to you is a class called GhostAI. This class has a single
|
|
static method:
|
|
|
|
public static int[] getGhostMove(int ghostX, int ghostY,
|
|
int pacmanX, int pacmanY)
|
|
|
|
Here's what the parameters do
|
|
o ghostX: the x location of a ghost
|
|
o ghostY: the y location of a ghost
|
|
o pacmanX: the x location of Pacman
|
|
o pacmanY: the y location of Pacman
|
|
|
|
The return value will always be an int array of size four. The array
|
|
contains each of the four directions from P6Constants in the order of
|
|
priority. So:
|
|
|
|
o index 0: the ghost's first choice of directions to move
|
|
o index 1: the ghost's second choice of directions to move
|
|
o index 2: the ghost's third choice of directions to move
|
|
o index 3: the ghost's fourth (last) choice of directions to
|
|
move
|
|
|
|
So why bother with all this priority stuff? Why not just return the
|
|
direction the ghost wants to move. Well what if we have a case like
|
|
the following:
|
|
|
|
Ghost
|
|
Wall Wall Wall Wall Wall
|
|
Pacman
|
|
|
|
The ghost's first choice of movement will be SOUTH toward Pacman. But
|
|
that's not an option because of the wall. We don't want our Ghosts to
|
|
just sit if they can't go somewhere. So we would try our second
|
|
choice, which in this case would be either EAST or WEST. The last
|
|
choice, in index 3, would be NORTH away from Pacman. Consider the
|
|
following case:
|
|
|
|
Wall Wall
|
|
Wall Wall
|
|
Wall Ghost Wall
|
|
Wall Wall Wall
|
|
Pacman
|
|
|
|
In this case, none of the first three choices will contain viable
|
|
moves. So the ghost's only choice is to go up. If this happens,
|
|
there will be a gap between the Ghost and the bottom wall. On the
|
|
next turn the Ghost can indeed use its first choice and move SOUTH
|
|
(Given that Pacman isn't going anywhere for some reason) If Pacman
|
|
were to stay put, the Ghost would end up oscillating back and forth
|
|
against the bottom wall. What happens if the Ghost is running from
|
|
Pacman after Pacman ate a special nibble. The priorities are just
|
|
reversed. Logically this makes sense. If a ghost's first priority
|
|
was to move SOUTH towards Pacman while chasing Pacman, SOUTH will be
|
|
the last priority when Pacman is chasing the ghost. Unfortunately,
|
|
the Ghost artificial intelligence algorithm we provide is somewhat
|
|
primitive. Consider the following case:
|
|
|
|
Wall Wall Wall Wall Wall Wall
|
|
Wall
|
|
Pacman Wall Ghost
|
|
Wall Wall Wall
|
|
|
|
|
|
If Pacman were to never move, the Ghost would simply run up against
|
|
the West wall over and over. If the Ghost were smart, it would go
|
|
around the South wall to get to Pacman but it doesn't. You must use
|
|
the Ghost movement algorithm we provide for instant feedback.
|
|
However, for the final turn in, you can develop your own, better
|
|
movement algorithm if you wish. (Just make sure it works) This would
|
|
be worth some juicy extra credit.
|
|
|
|
Create a class called Ghost that extends MoveablePiece. This class
|
|
will represent a Ghost. There will be four instances in your program,
|
|
one for each of the four ghosts. Ghost should have the following:
|
|
|
|
o a private int variable called "ghostNum". This will be the
|
|
number of the ghost.
|
|
o a private boolean variable called "runAwayFlag". This will be
|
|
true if this Ghost is running from Pacman and false if chasing
|
|
Pacman.
|
|
o a private int variable called "runAwayCount". This will
|
|
contain the number of returns remaining for this ghost to run
|
|
from Pacman. (Remember that a ghost will run from Pacman for a
|
|
fixed number of moves if Pacman eats a special nibble)
|
|
o a private int variable called "startX". This will contain the
|
|
starting x coordinate for this ghost.
|
|
o a private int variable called "startY". This will contain the
|
|
starting y coordinate for this ghost.
|
|
|
|
The startX and startY variables together represent the start
|
|
coordinates for the Ghost. The ghost will begin the game at this
|
|
location. The ghost will also return to this location if eaten
|
|
by pacman when running from him.
|
|
o accessors for ghostNum, runAwayFlag, startX and startY
|
|
o a public void method named "runAway" which takes in no parameters.
|
|
This sets runAwayFlag to true and initializes runAwayCount to
|
|
RUN_AWAY_DURATION from P6Constants
|
|
o a public void method called "resetGhost" which takes in no
|
|
parameters. This method will be invoked if this ghost is eaten
|
|
by Pacman. It should turn runAwayFlag to false and return the
|
|
ghost to it's start coordinates.
|
|
o a public method named "getMove" that returns an int[] and takes
|
|
in two parameters: pacman's x and y location. This method will
|
|
calculate the array of size four containing this ghost's choices
|
|
of movement as described above. Feel free to use the GhostAI
|
|
class. Keep in mind however that the getGhostMove method
|
|
provided assumes the ghost is chasing pacman. You will need to
|
|
modify the array to have the ghost run away from Pacman if this
|
|
is the case. If the Ghost is indeed running from Pacman, be sure
|
|
to deduct a turn from runAwayCount and check to see if
|
|
runAwayCount is zero, in which case you should have the ghost no
|
|
longer run from Pacman. This method will not actually set the
|
|
location of the ghost based on the GhostAI method. It simply
|
|
returns the array of choices. We do this because this ghost has
|
|
no clue whether any of its choices will take it into a wall. A
|
|
class later in the project will process the choices and decide
|
|
which moves are legal for this ghost. The ghost will then be
|
|
told which way to move.
|
|
o a public constructor that takes in three parameters: an x and y
|
|
start location and a ghostNum representing this ghost's number.
|
|
You should set the instance variable accordingly.
|
|
|
|
|
|
PHASE III - PacmanFileReader
|
|
====================================================================
|
|
|
|
From this point, you can feel free to ignore the design and do your
|
|
own thing if you wish provided you supply all the functionality of the
|
|
design we recommend. Remember the warning about grading though.
|
|
Even if you choose to use your own design, your program must still
|
|
present equivalent capabilities. And if your program doesn't work and
|
|
you used a different design, there is less chance of getting partial
|
|
credit.
|
|
|
|
Also realize that from this point there will be less explicit
|
|
information on what methods you need or how to implement them. There
|
|
are a variety of implementations that fit the suggested design. You
|
|
can choose one that suits you. You do not need to ask if you can
|
|
create a particular method or field. If it seems like you need method
|
|
x to perform some task, by all means, do it.
|
|
|
|
Onward...
|
|
|
|
Create a class called PacmanFileReader. This class will be
|
|
responsible for reading in a board file like one of the two we
|
|
provided you and translating it into an array of GamePiece instances.
|
|
If you open up one of the board files, you'll notice several rows of
|
|
seemingly random characters. Let's discuss the tokens found in
|
|
board_1.txt:
|
|
|
|
w <==> P6Constants.WALL <==> an instance of WallPiece
|
|
f <==> P6Constants.FLOOR <==> an instance of FloorPiece w/out either
|
|
kind of nibbles
|
|
n <==> P6Constants.NIBBLE <==> an instance of FloorPiece with a
|
|
regular nibble
|
|
s <==> P6Constants.SPECIAL <==> an instance of FloorPiece with a
|
|
special nibble
|
|
|
|
The first five rows specify the starting locations of Pacman and the
|
|
ghosts.
|
|
|
|
0 7 <-- Pacman's x,y start coordinates
|
|
1 1 <-- Ghost #1's startX and startY instance variable values.
|
|
13 1 <-- Ghost #2's startX and startY instance variable values.
|
|
13 13 <-- Ghost #3's startX and startY instance variable values.
|
|
1 13 <-- Ghost #4's startX and startY instance variable values.
|
|
|
|
The next rows will contain the four tokens discussed above. There
|
|
will always be 15x15 tokens. Look at the picture on the project
|
|
website under "using board_1.txt". Can you see the relationship
|
|
between the text file and the appearance of the board?
|
|
|
|
In this class, the goal is to translate these tokens into an array of
|
|
GamePieces. We recommend your PacmanFileReader have the following:
|
|
|
|
o a private GamePiece[][] variable named "pieceArray". This
|
|
array will model the board seen in the GUI.
|
|
o a BufferedReader to read the board file
|
|
o instance variables for pacman and four ghosts
|
|
o a constructor that takes in the name of a file to read.
|
|
o a helper method or series of helper methods called by the
|
|
consturctor that does the following:
|
|
- instantiates a BufferedReader to read the board file
|
|
passed into the constructor
|
|
- Reads each of the first five lines of the board file, uses
|
|
a StringTokenizer to get the x and y start coordinates and
|
|
instantiates Pacman or each of the four Ghosts (depending on
|
|
which line you're on)
|
|
- for the rest of the fifteen lines: Keep two counters, one
|
|
for the x index of the board and one for the y index.
|
|
Read in a line and use a StringTokenizer to get each
|
|
token. Instantiate each cell of pieceArray to the
|
|
appropriate GamePiece subclass passing in the x and y
|
|
index as the GamePiece coordinates. After each token
|
|
increment the x counter. After each line increment y and
|
|
reset x.
|
|
|
|
|
|
PHASE IV - The GUI
|
|
====================================================================
|
|
|
|
In this phase we will code the View portion of our MVC design. There
|
|
are two classes: PacmanFrame and GameBoard which will extend JFrame
|
|
and JPanel, respectively. The GameBoard will be responsible for
|
|
drawing or painting the board according to the GamePiece array you
|
|
created in PacmanFileReader. Now it's time for our segway into
|
|
painting.
|
|
|
|
Let's talk a moment about paintComponent:
|
|
|
|
Every GUI component has a protected method called paintComponent that
|
|
is reponsible for making the GUI component look the way it does. The
|
|
unoverrided form draws the default appearance for the widget. For a
|
|
JPanel this is just huge blank gray area. For JButtons there is some
|
|
slight shadowing on the edges of the button. However, you can
|
|
override this method in a subclass of a GUI widget. In this overriden
|
|
method you can make the widget appear almost anyway you want. You can
|
|
draw lines, pictures, shapes, change the widget's color, etc.
|
|
|
|
The paintComponent() function receives a Graphics object that is
|
|
used to draw on the component which owns it (In this case a GameBoard).
|
|
It is important to know that you will NEVER call paintComponent directly;
|
|
if you would like to update your component, call repaint(). The swing
|
|
windowing environment will send the proper Graphics context to
|
|
paintComponent(). Familiarize yourself with all of the methods in the
|
|
Graphics component from the API so you can draw using your Graphics
|
|
object. The Graphics parameter can sort of be thought of as your
|
|
brush and the widget where paintComponent is overriden as your canvas.
|
|
|
|
An example of an overridden paintComponent function:
|
|
|
|
protected void paintComponent( Graphics g ) {
|
|
|
|
// I call the super paintComponent to draw the component the regular
|
|
//way first
|
|
super.paintComponent( g );
|
|
|
|
//set the Graphics object's current color to blue. This is sort of
|
|
like dipping your brush into blue paint
|
|
g.setColor(java.awt.Color.blue);
|
|
|
|
//draw a circle (actually an oval with equal width and height)
|
|
//100 pixels in diameter. The result will be an unfilled
|
|
//blue circle
|
|
g.drawOval( 0, 0, 100, 100 );
|
|
|
|
}// end of paintComponent( Graphics )
|
|
|
|
|
|
Let's talk a moment about images. Images in java are represented by a
|
|
variety of classes. The easiest way to use images is to use the
|
|
ImageIcon class. The constructor of this class takes in a String that
|
|
is the file name of a jpg, gif, etc. In order to draw images on a GUI
|
|
widget, we need to draw an instance of Image not ImageIcon. ImageIcon
|
|
provides a method called .getImage which returns an Image instance
|
|
representation of the image at the file name you passed in. The
|
|
Graphics instance also requires you have an ImageObserver as a
|
|
parameter to the drawImage method. ImageIcons come with another
|
|
method called .getImageObserver() which will fill this requirement.
|
|
|
|
Create a class called GameBoard that extends JPanel. This class should have the
|
|
following:
|
|
|
|
o a private GamePiece[][] variable named "pieceArray". This
|
|
array will model the board seen in the GUI.
|
|
o a constructor that takes in a file name of a board file. This
|
|
file name will be passed off to a PacmanFileReader.
|
|
o a method called from the constructor that initializes the
|
|
provided images of the four ghosts, the image of a ghost in run
|
|
away mode, and of pacman.
|
|
o a protected method called paintComponent that takes in a
|
|
Graphics instance and returns void. This method is fairly
|
|
complicated since it has to draw the entire Pacman board
|
|
including the Pacman image and the ghost images.
|
|
|
|
We will be drawing a series of rectangles on the widget. Each
|
|
rectangle will represent one value of pieceArray. WallPiece
|
|
instances will be drawn blue. FloorPiece instances black.
|
|
FloorPiece instances with regular nibbles will have small yellow
|
|
rectangles or circles in the center of the black rectangle and
|
|
instances with special nibbles will have larger orange
|
|
rectangles or circles in the center of the black rectangle.
|
|
Pacman and the ghosts will be drawn last over the top of the
|
|
board. So how exactly are we supposed to draw one blue or black
|
|
or orange rectangle at an exact location on the board. For this
|
|
we'll have to do a little arithmetic.
|
|
|
|
The number of rectangles drawn per row will be the same number of
|
|
elements in one dimension of piecesArray and the number of rows
|
|
drawn will be the same number in the other dimension of
|
|
piecesArray.
|
|
|
|
The width of one board rectangle will be the width of the whole
|
|
board divided by the number of elements in the X dimension of
|
|
pieceArray. The height of one board rectangle will be the
|
|
height of the whole board divided by the number of elements in
|
|
the Y dimension of pieceArray.
|
|
|
|
The method .fillRect(int, int, int, int) inside Graphics takes in
|
|
four parameters: xStart, yStart, width, height. the "pen" starts
|
|
at pixel xStart and yStart as the top left corner in the
|
|
rectangle and draws a rectangle width wide and height high. So
|
|
how do we get to the correct x and y start locations. Since each
|
|
board rectangle represents an array of GamePieces, we can draw
|
|
the correct GamePiece rectangle in the correct spot by setting
|
|
our x start pixel to be the x index of the array multiplied by
|
|
the width of one rectangle. Respectivley we can set our y start
|
|
pixel to be the y index of the array multiplied by the height of
|
|
one rectangle. See the figure on the P6 website under "Drawing a
|
|
Rectangle on GameBoard". Now we'll discuss what to do in
|
|
paintComponent:
|
|
|
|
- call super.paintComponent to draw the default widget
|
|
first.
|
|
- get the width and height of one rectangle
|
|
- iterate through pieceArray. If the piece we're at is a
|
|
WallPiece instance, draw a blue rectangle. If it's
|
|
a FloorPiece with no nibble, draw a black rectangle. If
|
|
it has a nibble, draw the nibble accordingly. You may
|
|
have to experiment with a centering the nibble in the
|
|
middle of the black rectangle. Here's a hint though:
|
|
you'll be dividing by two a lot.
|
|
- after drawing the GamePieces, draw the pacman image at his
|
|
current location.
|
|
- draw each of the ghosts at their current location. Don't
|
|
forget to draw the ghosts with the correct image. If they
|
|
are being chased you need to draw them appropriately.
|
|
|
|
NOTE: The x and y instance variables in Pacman and Ghost are
|
|
the index the GamePiece they reside on. In other words
|
|
thore values will always be between 0 and 14. The x and y
|
|
values when dealing with the Graphics object are pixels.
|
|
Keep this in mind.
|
|
|
|
Create a class called PacmanFrame which extends JFrame. This class
|
|
will contain a GameBoard and a menu. PacmanFrame should have the
|
|
following:
|
|
|
|
o an instance of GameBoard
|
|
o a JMenu with two JMenuItems:
|
|
- one that activates a new game: This should restart the
|
|
game no matter what the state of the current game.
|
|
- one that exits the program. This should stop the JVM just
|
|
as if we clicked on the X in the top right corner of the
|
|
frame.
|
|
o a default constructor which sets up the frame with a
|
|
BorderLayout, instantiates a new GameBoard and adds it to the
|
|
center. There is a constant in P6Constants called BOARD_FILE
|
|
containing the name of the file to tokenize into a GamePiece
|
|
array.
|
|
o We recommend you set the size of the frame to be about 520x520
|
|
o helper methods that set up the menus.
|
|
o We recommend you have this class be a listener for its own
|
|
JMenuItems. You will need to then have this class implement
|
|
ActionListener.
|
|
o The program should exit gracefully if either the quit menu is
|
|
chosen or the X in the top right is clicked.
|
|
|
|
|
|
|
|
PHASE V - PacmanEngine
|
|
====================================================================
|
|
|
|
The PacmanEngine class will be our controller in the MVC paradigm for
|
|
this project. It will likely be your single biggest class. For lack
|
|
of a better description, PacmanEngine will be the arbiter for the
|
|
game. It will process user control inputs, decide which movements by
|
|
pacman and the ghosts are legal, regulate the rate at which pacman and
|
|
the ghosts, move and determine whether a win or loss condition exists.
|
|
|
|
First let's talk about Swing Timers. The class javax.swing.Timer is a
|
|
class that can be set to fire ActionEvents at a specific
|
|
interval. (yes the same Events fired by JButtons and JMenuItems) We
|
|
will be using two Timers in PacmanEngine. One will regulate Ghost
|
|
movement. Every time the Ghost Timer fires, we want all the ghosts to
|
|
move one square. Where they move will be determined by the four
|
|
element array returned from Ghost's getMove method. A second timer
|
|
will regulate Pacman's movement. This timer will fire at a slightly
|
|
faster rate because we want Pacman to be able to outrun the ghosts.
|
|
Every time the Pacman Timer fires, we want Pacman to move one square
|
|
in the direction specified in Pacman's direction variable. You will
|
|
need to look at the API for specific methods to use.
|
|
|
|
Next let's talk about Key Listening. We've worked with ActionEvents
|
|
and ChangeEvents. Now you will use a third event/listener group that
|
|
listens for events from the keyboard. The game will be controlled by
|
|
six different keyboard keys. The four arrow keys will control
|
|
Pacman's direction. The 's' key will start the game (start Pacman and
|
|
the Ghosts moving) at the beginning of a game. The 'p' key will pause
|
|
and unpause the game. It's important to understand how Pacman will be
|
|
controlled. Pacman does not move only when a key is pressed. He
|
|
progresses in the direction specified by his direction variable at all
|
|
times. The arrow key pressed merely changes this direction variable.
|
|
There are exceptions to this. If pacman's direction takes him into a
|
|
wall, Pacman will halt and wait until an arrow key is pressed to give
|
|
him a new direction.
|
|
|
|
Now let's talk about JOptionPanes. The JOptionPane class provides
|
|
simple pre-packaged diaglog boxes. All you need is a JFrame instance
|
|
and the message you want to show the user. Here's how we will use one
|
|
for this project:
|
|
|
|
JOptionPane.showMessageDialog(<instance of JFrame>,
|
|
<String instance containing message
|
|
you want to show to the user>,
|
|
<String instance containing message
|
|
to be displayed in Dialog Box title bar>,
|
|
<int constant representing icon to be displayed in
|
|
dialog box along with message>);
|
|
|
|
This method call will pop up a small dialog box on top of your PacmanFrame.
|
|
Your instance of JFrame will be your PacmanFrame instance. Your message
|
|
and title should be something meaningful and relevant. You can use any icon
|
|
you want. Check out the API for a listing of icons and how to use them.
|
|
Included in your dialog box will be an "OK" JButton which will close the box
|
|
if pressed. This button is added and handled automatically by the method.
|
|
No effort on your part is needed to add or handle the event from the button.
|
|
|
|
Create a class called PacmanEngine that implements both ActionListener
|
|
and KeyListener. PacmanEngine should have the following:
|
|
|
|
o a javax.swing.Timer called GhostTimer. This variable will fire
|
|
an ActionEvent every P6Constants.GHOST_INTERVAL milliseonds
|
|
causing all the Ghosts to move one square
|
|
o a javax.swing.Timer called PacmanTimer. This variable will fire
|
|
an ActionEvent every P6Constants.PACMAN_INTERVAL milliseonds
|
|
causing Pacman to move one square.
|
|
o a GameBoard instance called board. This will be the GameBoard
|
|
instance used to draw the state of the board
|
|
o a PacmanFrame instance.
|
|
o a Ghost[] variable called ghosts. This array will hold the
|
|
four Ghosts in play.
|
|
o a Pacman variable called pacman. This will be the Pacman
|
|
instance in play.
|
|
|
|
o a constructor which takes in a PacmanFrame and a GameBoard and
|
|
sets the appropriate variables to these parameters. You will
|
|
also need to initialize both Timers. (but don't start them yet.)
|
|
Also set the Ghost array and the Pacman variable to those
|
|
variables initialized by PacmanFileReader.
|
|
|
|
o a method public void actionPerformed which takes in an
|
|
ActionEvent. This method will be called by both Timers at the
|
|
appropriate interval.
|
|
|
|
If this method was called by the PacmanTimer, it is Pacman's turn
|
|
to move. You do not need to worry about any keys pressed in this
|
|
method. Remember Pacman always moves in the direction specified
|
|
by his direction variable every time the Timer fires. Before
|
|
moving Pacman to the next square though, you need to first
|
|
determine that the move is legal (i.e., Pacman won't be moving on
|
|
top of a WallPiece or off the board) You have the GameBoard,
|
|
pacman's current x and y locations and his current direction.
|
|
From this information you can determine whether or not any given
|
|
move is legal. It is recommended that the functionality for
|
|
determining whether a given move is legal be abstracted to a
|
|
helper method since you will be using the same functionality for
|
|
the ghosts. If a given move is found to be legal, call Pacman's
|
|
move method (extended from MoveablePiece) passing in the
|
|
direction. Do not have PacmanEngine directly set the x and y
|
|
location variables.
|
|
|
|
If this method was called by the GhostTimer, you will need to
|
|
iterate through all four ghosts. At each ghost, call the getMove
|
|
method to retrieve each ghost's array of move choices. You
|
|
should then iterate through this array and move the ghost using the
|
|
first legal move you encounter. (Remember the zeroth index holds
|
|
the highest choice of movement for a particular ghost)
|
|
|
|
No matter which Timer called the method, you will need to
|
|
determine if the most recent move by either Pacman or the Ghosts
|
|
resulted in a state that should end the game. If all of the
|
|
FloorPieces are missing nibbles, the player has won the game.
|
|
You should display a notificatoin that the player has won using a
|
|
JOptionPane message dialog. (That's why we have an instance of
|
|
PacmanFrame which extends JFrame) If one of the Ghosts and
|
|
Pacman occupies the same square and that Ghost is not running
|
|
from Pacman, the player loses. Again display an appropriate
|
|
message dialog to the user. If a Ghost and Pacman occupies the
|
|
same square but that ghost is in fact running from Pacman, you
|
|
should reset that ghost. You also need to process any time
|
|
Pacman eats a nibble. If it's a regular simply remove that
|
|
nibble from the board by notifying that particular floor piece to
|
|
turn it's nibble boolean to false. If it's a special nibble, you
|
|
not only need to have the FloorPiece set it's special boolean to
|
|
false but you also need to iterate through all four ghosts and
|
|
have them go into run away mode. The ghosts should run from
|
|
Pacman and begin counting down their runAwayCount variables.
|
|
|
|
After processing all moves, if no end game condition exists, you
|
|
should call repaint() on the GameBoard instance. This will make
|
|
the GameBoard display the new locations of the ghosts.
|
|
|
|
|
|
o a method public void keyPressed which takes in a KeyEvent.
|
|
This method will be automatically called when any keyboard key is
|
|
pressed. There are only six we care about though. If the 's'
|
|
key is pressed, you should start both timers if they are not
|
|
already running. If the 'p' key is pressed, you should stop the
|
|
timers if they are running and start them if stopped. If one of
|
|
the four arrow keys is pressed, you should set Pacman's direction
|
|
variable to reflect the key pressed. You should only set the
|
|
variable, however, if the Timers are running. But how do we
|
|
figure out which key was pressed? The KeyEvent class has a
|
|
method called getKeyCode() which returns an int that represents a
|
|
key. Look through the KeyEvent API to figure out which constant
|
|
represents the appropriate keys.
|
|
|
|
|
|
|
|
PHASE VI - Testing and Turnin
|
|
====================================================================
|
|
|
|
Create a Driver class called "P6". This class should have a main
|
|
method which instantiates PacmanFrame.
|
|
|
|
Swing GUI components can sometimes throw exceptions without the
|
|
program crashing. i.e. you may see a stack trace on the
|
|
console but amazingly the program still appears to be
|
|
functioning. We consider this no different than if the program
|
|
crashed. You should not have any stack traces from exceptions
|
|
of any kind. You should catch and handle all exceptions.
|
|
|
|
This project, again, will require you to look through the API for many
|
|
solutions to a given task. Please make an honest effort to figure out
|
|
things for yourself before you seek outside help. This is the point
|
|
of the exercise.
|
|
|
|
Make sure you turn in the following files:
|
|
|
|
o GamePiece.java
|
|
o WallPiece.java
|
|
o FloorPiece.java
|
|
o MoveablePiece.java
|
|
o Ghost.java
|
|
o Pacman.java
|
|
o PacmanFileReader.java
|
|
o GameBoard.java
|
|
o PacmanFrame.java
|
|
o PacmanEngine.java
|
|
o P6.java
|
|
|
|
These may vary if you deviated from the suggested design.
|
|
|
|
|
|
PHASE VII - Extra Credit (Optional)
|
|
====================================================================
|
|
|
|
There will be a modest amount of extra credit given to those students
|
|
who go above and beyond the call of duty in the project. Make sure
|
|
you do not attempt any extra credit until you can certify that the
|
|
core requirements of the project have been satisfied.
|
|
|
|
Here are some ideas:
|
|
|
|
o In board #2, allow Pacman and/or the ghosts to go through the
|
|
hole on either the left or right side of the board and appear on
|
|
the opposite side. (i.e. wrapping around the board)
|
|
o Make pacman "chew" as he moves along the board. A second
|
|
pacman image with his mouth closed is provided
|
|
o Make pacman and the ghosts move smoothly from square to square
|
|
rather than abruptly appear in the next square.
|
|
o Create a smarter ghost movement algorithm that allows ghosts to
|
|
go around walls to get to pacman rather than just run up against
|
|
the wall.
|
|
o Develop a scoring system and display it somehow in PacmanFrame
|
|
o Put fruit on the board like in Ms. Pacman
|
|
o Use sound!
|
|
o Two player!
|
|
|
|
====================================================================
|
|
|