Milestone 1: Due Tuesday, April 16th by 11:59PM

Milestone 2: Due Tuesday, April 23rd by 11:59 PM

Milestone 3: Due Wednesday, May 1st by 11:59 PM

Getting Started

Start by downloading CS101_Assign05.zip, saving it in the directory H:\CS101.

Start a Cygwin Bash Shell and run the following commands:

cd h:
cd CS101
unzip CS101_Assign05.zip
cd CS101_Assign05

Using Notepad++, open the files

H:\CS101\CS101_Assign05\Scene.h

H:\CS101\CS101_Assign05\Scene.cpp

H:\CS101\CS101_Assign05\Player.h

H:\CS101\CS101_Assign05\Player.cpp

You will add your code to these files.

The file Chomp.cpp contains the provided game loop, and the file Const.h includes symbolic constants to use throughout the program. You should not need to modify either of these files.

Your Task

Since many of you have either played or at least seen the retro arcade game Pacman, the purpose of this assignment is to write a similar game using terminal graphics. The object of the game is to move your player around a board collecting pellets and power-ups while avoiding the 4 ghosts. A sample executable is included in the .zip file and can be run in Cygwin by

./ChompSol.exe

(there is also a sample executable compiled for Mac as ChompSolMac but it may be a bit erratic).

Since this is a relatively complex assignment, there will be two intermediate milestones before the final submission. Also we will be using separate files for the game play, scene structure, and player structures.

When you are ready to compile the program, in the Cygwin window type the command

make

To run the program, in the Cygwin window type the command

./Chomp.exe

Milestone 1 - Due Tuesday April 16th

The first milestone will involve loading in the game board and displaying it on the screen.

You have only one function to implement: the draw_Board function. See the item marked TODO below.

The following fields are defined in the Scene structure (in Scene.h)

  • a 2D int array for the board
  • an int for the number of pellets
  • an int for the number of powerups

Loading in the board

The layout of the board is included in the text file board.txt where + indicates walls, . indicates pellets, and O indicates power ups.

The initialize_Scene() function, provided for you, takes a Scene structure as a reference parameter. The function calls load_Board() passing the board, number of pellets, and number of powerup fields by reference. load_board() reads the file and initialize a 2D board array parameter with the objects at each board location. Each element of the array will contain a symbolic constant for the object at that location as either:

  • WALL - for a wall
  • PELLET - for a pellet
  • POWER_UP - for a powerup
  • EMPTY - for an empty corridor

The function will also return the number of pellets (num_pellets) and number of power-ups (num_powerups) through reference parameters.

Later on you will need to modify initialize_Scene to perform other initialization, but for now you won't need to modify it.

Drawing the board

  • TODO: You will need to implement the function named draw_Board() in Scene.cpp (prototype in Scene.h) which takes a 2D array of int's representing the board and renders the board on the screen. Symbolic constants have been defined for the characters to render as:

    • WALL_CHAR - a blank space
    • PELLET_CHAR - a period
    • POWER_CHAR - an uppercase O
  • A function named render_Scene() (in Scene.cpp) has been provided for you. It takes a Scene as a reference parameter and calls the draw_Board() function.

Once you implement the draw_Board function, you should see something like this when you run the program:

images/assign05/drawBoard.png

Milestone 2 - Due Tuesday April 23rd

The second milestone will add the player and allow them to move around the board.

You will need to first create a structure named Player and then add the following fields to the Player structure (in Player.h)

  • an int for the player's current x location
  • an int for the player's current y location
  • an int for the player's current dx velocity
  • an int for the player's current dy velocity
  • a char for the player's character symbol
  • an int for the player's color

Player functions

We will need to add functions to initialize, draw and update the fields of the player.

  • Add a function to Player.cpp (placing the prototype in Player.h) named initialize_Player() which takes a Player structure as a reference parameter, two int parameters for the starting x and y location, two int parameters for the starting dx and dy velocities, one char parameter for the player's character symbol, and one int parameter for the player's color. The function should initialize all the fields of the Player structure with the corresponding parameters.
  • Add a function to Player.cpp (placing the prototype in Player.h) named draw_Player() which takes a Player structure as a reference parameter. The function should move the cursor to the player's current location and draw the player using their symbol and color (from the fields in the structure).
  • Add a function to Player.cpp (placing the prototype in Player.h) named player_AI() which takes a Player structure as a reference parameter and a 2D array of int's for the board. The function should call the cons_get_keypress() terminal graphics function which checks to see if the user has pressed a key and if so, returns the key that was pressed. Symbolic constants are defined for the arrow keys as UP_ARROW, DOWN_ARROW, LEFT_ARROW, and RIGHT_ARROW (in Console.h). Based on the key that was pressed, the player's velocity fields should be set appropriately (or set to 0 if no key was pressed). The function should then call check_Player_Move() (discussed below) to validate desired move.
  • Add a function to Player.cpp (placing the prototype in Player.h) named check_Player_Move() which takes a Player structure as a reference parameter . The function should determine if the player is attempting to move into a wall (setting the velocities to 0 if they are) or moving through the tunnel which is at the edge of the board with y location given by the symbolic constant TUNNEL_Y. If they have entered the tunnel, the player's position should be changed to the opposite side of the board.
  • Add a function to Player.cpp (placing the prototype in Player.h) named update_Player() which takes a Player structure as a reference parameter. The function should update the player's current location based on their current velocity.

Scene functions

Next we need to incorporate the player into the scene.

  • Add a Player field to the Scene structure for the user
  • Add code to initialize_Scene() to call initialize_Player() for the user. The arguments in the function call should initialize the fields of the structure so the player starts at location (PLAYER_HOME_X, PLAYER_HOME_Y) with both velocity components equal to 0 (i.e. not moving). The player's symbol and color should be initialized with PLAYER_CHAR and PLAYER_COLOR (which are defined in Const.h if you wish to change them)
  • Add code to render_Scene() to call draw_Player() to draw the player on the board (note this should be done after drawing the board).
  • Add code to update_Scene() (which takes a Scene structure as a reference parameter and returns an int value which will be a flag indicating when the game is over) to call the player functions to see if they have pressed a key (which determines the player's desired velocity and computes their actual velocities) and updates the player's current location based on their actual velocity. The function should then determine if the player has ended up on a pellet or power-up and adjust the board (along with the pellet or power-up counters) accordingly.

At this point you should be able to move the player around the board "gobbling" up the pellets and power-ups.

Milestone 3 - Due Wednesday, May 1st

The final milestone incorporates the ghosts to complete the game play. The ghosts will simply be represented by an array of Player's that uses a different function to determine their desired velocities (basic AI).

You will need to first add a field to the Scene structure (in Scene.h)

  • an array of type Player for the ghosts. It should be of size NUM_GHOSTS (a symbolic constant in Const.h).

and the Player structure (in Player.h)

  • an int for the player's score

Player functions

Note the draw_Player(), check_Player_Move(), and update_Player() functions can be used for both the user and ghosts without modification.

  • Add code to initialize_Player() to initialize the score field.
  • Add a function to Player.cpp (placing the prototype in Player.h) named ghost_AI() which takes a Player structure as a reference parameter. The function should simply keep the current velocities the same with a probablity of 75% (unless both the current velocities are 0), otherwise the velocities should be set by generating random values of +1 or -1 for either dx or dy (but not both as the ghosts cannot move diagonally). Similarly to player_AI(), the function should then call check_Player_Move() to determine the actual velocity for the ghost, i.e. make sure it is not trying to move into a wall and/or adjust for going through the tunnel.

Scene functions

  • Add code to initialize_Scene() to call initialize_Player() for each of the ghosts, i.e. for each element of the ghost array. The arguments in the function call should initialize the fields of the structure so the ghosts start at a random x location within two units of GHOST_HOME_X with the same y location GHOST_HOME_Y. The velocity components should be randomly set to either +1 or -1 for dx and 0 for dy (i.e. randomly moving left or right). The ghost symbol and color should be initialized to GHOST_CHAR and GHOST_COLOR (from Const.h).
  • Add code to render_Scene() to call draw_Player() to draw the ghosts on the board (note this should be done after drawing the board but can be either before or after drawing the player). Also add drawing code to display the player's score next to the board.
  • Add code to update_Scene() to call the functions for the ghosts (with the only difference being that ghost_AI() is used instead of player_AI()). Additionally after updating each ghost, you should check if the player has ended up on a ghost and return "true" to end the game. Update the player's score if they have moved onto a pellet or power_up by amounts PELLET_VAL and POWER_VAL. The game should also end once there are no more pellets or power-ups remaining on the board.

At this point you will have a playable game, but the ghosts will move extremely fast (although not very intelligently). To make the ghosts move at a more reasonable speed, determine a way to only update them every GHOST_DELAY (another constant in Const.h) update cycles. Hint: consider adding a counter field to the Scene structure and checking it in update_Scene().

Grading Criteria

Milestone 1 - Due Tuesday March 16th by 11:59PM - 50 points

50 points - write draw_Board() function

Milestone 2 - Due Tuesday April 23rd by 11:59PM - 75 points

5 points - add Player structure with necessary fields and add field to Scene for player

10 points - write initialize_Player() function

10 points - write draw_Player() function

15 points - write player_AI() function

15 points - write check_Player_Move() function

10 points - write update_Player() function

10 points - update necessary Scene functions

Milestone 3 - Due Monday May 1st by 11:59PM - 75 points

5 points - add fields for ghosts to Scene and for score to Player

5 points - initialize score field

20 points - write ghost_AI() function

15 points - initialize ghost array

10 points - draw ghosts and score

10 points - update ghosts

10 points - check for game over conditions

Extra Credit:

Improve the ghost AI - 10 points

One drastic improvement that can be made to the ghost AI is rather than select a random direction to attempt to move (which often results in moving into a wall), select only from the valid directions the ghost can move (with a strong preference to continue moving forward, a lesser preference to turn if possible, and with a relatively small preference to turn around). This will make the ghost movement appear much smoother (and smarter). Additionally the ghost's prefered direction can be further biased towards the player if they are within a certain range of the player (basically providing a "chase" mode when they are close). There should be a small probability that the ghost will stop chasing otherwise the game will become very difficult to play (the ghosts will essentially ambush the player pinning him in a corner).

Additional levels - 10 points

Alternate levels can be designed using the same symbols as board.txt, i.e. + for walls, . for pellets, O for power-ups, and blanks for empty locations. Make sure your boards are 28 characters wide by 22 characters high (to match the board array used in the load_Board() function). Be careful if you choose to use tunnels that they are at row TUNNEL_Y otherwise you will need to modify the check_Player_Move() function. Once a level is completed, load and begin another level.

Capturable ghosts - 25 points

Probably the most complicated improvement (in the spirit of the original video game) is to allow the ghosts to be "captured" for a brief period of time after the player "eats" a power-up. If the player and ghost are at the same location during this time, the player earns extra points and the ghost is returned to its home location while no longer being capturable. The player should receive some type of warning that the capturable time is about to expire. In the original game, the ghosts would turn blue while they were capturable and flash when they were about to become non-capturable. Consider adding flags to the Player structure that tracks the state of each ghost and a counter to the Scene structure that is set to a value and counts down whenever a power-up is eaten (and can be used to determine when to make the ghosts "flash").

Hints

Note: The board size is set by the symbolic constants WIDTH (=28) and HEIGHT (=22) which can be used to initialize your 2D array.

The overall layout of the program can be graphically illustrated by the following flowchart showing the relationships between the functions and which files they should be placed in:

images/assign05/flowchart.png

Coding style: Make sure that you use meaningful variable names, that your code is properly indented, and that you include some comments explaining how the code works. Credit may be deducted if you use poor coding style.

Submitting

To submit your work, make sure all the source files are saved, and type one of the following commands (depending on which milestone you are submitting):

make submit_ms1
make submit_ms2
make submit_ms3

Enter your Marmoset username and password (which you should have received by email.) Note that your password will not be echoed to the screen. Make sure that after you enter your username and password, you see a message indicating that the submission was successful.

Important: Make sure that you check the file(s) you submitted to ensure that they are correct. Log into the server using the following URL:

https://cs.ycp.edu/marmoset/

You should see a list of labs and assignments. In the row for assign05, click the link labeled view. You will see a list of your submissions. Download the most recent one (which should be listed first). Verify that it contains the correct files.

You are responsible for making sure that your submission contains the correct file(s).