Executive Summary
You will be implementing a relatively complex old-school arcade-style game,
which we're calling "Escape!". The game will run in the terminal and be based
on ncurses, which you are familiar with from our prior lab. In this game, you are a player
on a game board:
Honor:
The
course policy
and the instructions it references,
most pertinently
COMPSCIDEPTINST 1531.1D,
spell out
what kinds of assistance are permissible for programming
projects. The instructor can give explicit permission for
things that would otherwise not be allowed.
For this project, you are allowed
- to get help from your instructor and current MGSP leaders (any
assistance must be documented though), and
- to use general purpose C++ resources online (though such
use must be documented).
For this project, you are
not allowed to do the following:
-
Resources that might specifically address this project.
- You are very specifically not allowed to look at previous semesters'
programming project or lab solutions in this course or any other similar
courses that may have addressed
similar issues.
Combining the instructions referenced in the course policy and the
explicit permissions above, and what you get is summarized as:
-
The only help you may receive on a project is from your instructor and MGSP
leaders, and that help must be clearly cited. In turn, you cannot provide
help to any students on this
project.
-
In no circumstance - project, homework or lab - may you copy
code and submit it as your own work. That is the definition
of plagiarism.
-
For projects, you may not look at other
people's project code nor may you show your own code to
others.
-
You can look at online (or other electronic or print) resources for general
purpose C++ programming. You are not allowed to refer to anything specific
to the functionality required of your project.
Part 0: Download Required Files (click on images)
 p3files.tgz
| All required files in one download. This contains everything below. To
extract its files, run the following command:
tar -xvf p3files.tgz
|
The following are the individual files contained in the archive
"p3files.tgz" above.
Caution: You are not allowed to change these files.
 boardTiny.txt
|  board2rm.txt
|  boardCenter.txt
|  boardMaze.txt
|  boardBounce.txt
|
 easycurses.h
|  easycurses.cpp
| Your interface to drawing in the terminal.
|
Pos.h
| Pos.cpp
| These functions and structs will make your life easier.
You must use both Pos.h/Pos.cpp, and again you cannot modify them.
|
 rundebug
|
| The program that handles debug output & opens a 2nd terminal for you.
|
 gameScript.txt
|  tinyScript.txt
| These files will be used for part 5.
|
 Makefile
|
| You will use this file when compiling your code.
|
Part 0.1: Review Material
Easycurses.h: Review Lab 10 for
easycurses.h and how to use it.
Debugging: easycurses takes over your terminal, so you can't just
cout helpful debug messages anymore. Look at Parts 1b and 1c of Lab 10 for a reminder of how to do this with
2 terminals.
Important: be sure to check out further information on design suggestions!
Part 0.2: How to compile your code
- Open Makefile with your editor. Makefile looks as follows:
part1:
part2:
part3:
part4:
part5:
################## DON'T TOUCH BELOW ##################
run1: part1
@./part1
run_%: %
@test -f $* && echo "$*: Code compiled successfully"
- To compile your solution to Part 1, execute the following command in the terminal:
make part1
You can compile other parts similarly.
Add the compilation details for each part in Makefile as necessary.
Part 0.3: How to submit
Check Submission near the end.
Part 1: Read the board! [20pts]
Name your .cpp file containing main() part1.cpp
Board files look something like this:
10 x 20 1
####################
# #
# Z ######## #
# # #
##### # #
# # # #
# X # # #
# ##### # Y #
# # #
####################
The first line gives number of rows x number of columns,
followed by the number of Z's you'll find on the board.
The X marks the goal position on the board. This is where the player is trying
to get to.
The Y marks the player's starting position.
Zs are "spawn spots". These are positions that moving,
death-dealing objects spawn from. There can be multiple spawn spots.
Note: Walls are always # characters, and walls always surround the edges
of the board.
A valid Part 1 solution
This part is about reading in the board, and not much else. Don't do it all at
once, break this part into your own smaller steps.
-
Reads a
filename for a board from the user (before ncurses is initialized!);
it must print an error message and return if the file is not
found!
-
Reads the board file and stores the information in it.
Note 1:
Spaces are important here. You'll have to use the special ifstream function
get() which returns char by char each time you call it:
char ch = fin.get();
rather than fin >> because you have to
read in the whitespace characters.
Note 2:
Each line in the file ends with a newline character (ASCII code 10). This
newline character is what indicates the end of the current line and the
beginning of the next one. It is important to understand that when you use
fin.get(), it will also read this newline character along with any other
characters on the line. If the program encounters a newline character, it should
recognize that the next line is starting, but it should not include the
newline character in the board array. This ensures that only the actual
content of the line is added, not the delimiter marking the line's end.
- Enter the graphics mode using easycurses. Prints the board on
the screen. If the terminal window is too small, the program must exit
easycurses, print an appropriate error message, and exit the program.
Note 3: Don't print spawn spots or the player's start spot, but do print
X for the goal.
-
After the board is printed to the screen, use the following loop;
char c;
do {
usleep(150000); // pause (sleep) for .15 seconds
c = inputChar();
} while(c != 'y'); //loop exits with a 'y'
... so that the board stays on the screen until the user presses y on the
keyboard. After exiting the loop, the program must exit easycurses by calling
endCurses(), and then print out the row,col coords of the player
start spot and the spawn spots, just so we're sure we've got them right.
Note 4: You should make a struct to represent all the info you read
from the file, and make a separate function to read in the file and give values
to all the members of that struct.
Note 5: You must split your program into .h and .cpp files as we have
discussed.
hit enter
press y
Note: Check out this video of a running solution.
Important: be sure to check out further information on debugging with ncurses!
Part 2: Implement the player! [45pts]
Copy part1.cpp into part2.cpp
If you like, you can use the arrow keys to move rather
than a,s,d,w. Here's how to do that:
- It turns out that inputChar() actually returns an
integer.
- You can check
for characters like
kb == 'a', but you
can also compare kb to the constants
KEY_LEFT,
KEY_RIGHT,
KEY_UP,
KEY_DOWN that ncurses defines.
So, for example,
int kb = inputChar();
// if a-key was pressed
if( kb == 'a' ) { }
// if left-arrow was pressed
if( kb == KEY_LEFT ) { }
A valid Part 2 solution
This part adds on to the Part 1 solution by implementing the player —
i.e. the figure in the game that the user controls. Here are the details:
Note: please use Pos.h and Pos.cpp code for changing the direction.
DO NOT modify the files.
-
The player (represented in the game by a "P") can either be
moving or stopped.
-
Initially, the player starts the game stopped, and at the position marked
on the input file by a "Y".
-
The a,s,d,w keys set the player's direction to W, S, E, or N respectively. If
the player is stopped, set it to moving. A moving player should keep moving
one step forward per round in its current direction.
-
The r key stops a moving player.
-
The player must bounce off the walls. Check out the detail on bouncing. Note that you have
to determine both the direction and the position.
-
The game stops when the player comes within distance 1 or
less of the X.
-
If the game stops because you reach the goal, print out the
player's "score", which is 500 minus the number of
steps (i.e. times through the main loop) it took to get
to the goal.
- The score should keep counting down when the user pauses the player's
movement.
- The score is allowed to go negative if there are more than 500
iterations.
Note: Check out this video of a running solution.
In the end, your solution should print out the player's score as follows (see
the last line in the picture below):
Part 3: Implement the space ships [65pts]
Copy part2.cpp into part3.cpp
A Valid Part 3 solution
Part 3 adds on to Part 2 by implementing "space ships", which are
randomly moving figures (represented by *'s
in the game) that kill the player if they collide with them.
These ships bounce off walls, but simply pass through one another.
Here are the details:
-
At the start of the game, there are five overlapping ships
on each spawn spot, each with a randomly chosen direction.
-
Just as with the player, ships move one step per round in
their current direction, and they bounce off walls in the same
manner as the player. Colliding ships simply pass through
one another.
-
Every round there is a 1-in-10 chance that the ship will turn
before stepping (not counting turning because of walls). This
turn will be a 90deg left turn with probability 1/2, and
otherwise a 90deg right turn.
You're using our provided Pos.h and Pos.cpp code, right??
-
If a ship and a player collide, the
player dies (the game should then pause for two seconds,
then exit the program). Here's how to define "collision":
After each object has made its step for the round, we will say
that player P and ship S have collided if
P's current position and S's current position are the same, or
both P's current position is the same as S's previous position
and P's previous position is the same as S's current position.
Important: be sure to check out
further information on
on collisions and death!
This may seem a bit complicated, but we have to handle the
situation in which P and Q are in adjacent squares and heading
straight for one another. In this case, after one step they will
have crossed, swapping positions. So even though their previous
positions are different from one another and their current
positions are different from one another,
we still want to consider them to have collided.
Note:
Because of this rule, I strongly recommend that whatever struct
you use to represent moving objects actually has a data member that
stores the previous position.
Note:
I also strongly recommend that you implement a cheat — for instance the i
key could make you immortal (i.e. collisions don't kill you). That makes life
a whole lot easier when debugging along the way.
-
When the player dies, you have to print a message "You lost, they got you!" as
shown in the picture below.
Note: Check out this video of a running solution.
Segmantation fault?
The segmentation faults usually come from common mistakes that students make:
- Some variables are not initialized, and the code runs with a garbage value.
Double check your code and make sure that variables are properly initialized
(e.g., row, col, direction, and so forth).
- Refering to the board with an invalid index. Your program would contain
code to access the board, e.g.,
board[row][col]. Obviously, if
row or col is negative or too big, your program will
crash. This usually stems from wrong bouncing logic.
To debug this mistake:
- Use
cerr these indices of row and
col --- especially before and after calling
step() function (you may want to cerr direction
as well)!
- Run your program with
rundebug. Then, you will see when
these indices become illegal.
Part 4: Implement the Hunters [80pts]
Copy part3.cpp into part4.cpp
A valid part 4 solution
Part 4 adds on to Part 3 by implementing "hunters" which are
game figures that kill the player on collision but which, unlike
ships, actually track down the player. Here are the details:
-
At the start of the game, there is one hunter
on each spawn spot, each with a randomly chosen direction.
- Hunters are represented on screen by K
- Hunters move (and kill) exactly like ships except that instead of
having a 1-in-10 chance each turn of turning randomly either left or right,
a hunter has a 1-in-2 chance each turn of reassessing its direction. When
it does so, it chooses a direction toward the Player. according to the following
rule:
For each hunter:
1. let dc = (Player column position) - (the hunter's column position)
2. let dr = (Player row position) - (the hunter's row position)
3. if dc < 0 then let cdir = 3 // West
4. if dc >= 0 then let cdir = 1 // East
5. if dr < 0 then let rdir = 0 // North
6. if dr >= 0 then let rdir = 2 // South
7. With prob 1/2 set the hunter's direction to rdir, otherwise set it to cdir
Note: Check out this video of a running solution.
Part 4.5: Make your own board [85pts]
This isn't really programming, but you should do it anyway. Make your own
board file, call it boardYYXXXX.txt, where YYXXXX is your alpha
code. Be creative, but make sure your program still works with your board file
as input!
Note: be sure to put walls on all edges!
Part 5: Make it a game and use a linked list [100pts]
Copy part4.cpp into part5.cpp.
Part 5 builds on Part 4 to make this a real game:
- On a given game board, if you die three times in a row, the game's over.
- You get "points" as you progress.
- If you win one board you go on to the next, and each board gets more difficult but worth more points.
Looked at one way, Part 5 is a fairly major rewrite of Part 4.
Looked at another way, if you package your entire Part 4
solution up into a function, Part 5 is nothing more than
calling that function repeatedly.
Here are the details:
-
The file gameScript.txt contains lines like
the following:
,-|number of ships spawned per spawn-spot
/
board2Rm.txt 5 1 points = 1000 ←|number of points for winning this board
------------ \
board file name `-|number of hunters spawned per spawn-spot
A player working through the game plays the board as
described by the first line of gameScript.txt
until either dying three times or winning. If the player
wins, the game moves on to the board described by the
second line of gameScript.txt,
and so on and so forth.
- Read a gamescript file in a linked list.
A valid Part 5 solution must read the gameScript.txt file completely at the very beginning
of the program, and store the relevant information in it in a linked list.
Yes, it has to be a linked list! After it's in the linked list you can
transfer it into an array if you want, but you have to read it into a linked
list. Why? Because you don't know ahead of time how many lines there will be
in the file ... and also because I told you so.
-
After a victory on a given board, your score is calculated as follows:
B - S + 500
- B: the points value for that board
- S: the number of steps it took you to get to the goal
-
After a victory, pause for 2 seconds (the video below doesn't pause,
but your program must pause for 2 seconds). Then, restart the game with the
next board on the list. Instead of a fixed value of five ships and one
hunter per spawn spot, use the values given in the line from gameScript.txt.
- In each level, the player is given three chances. If the player dies three
times on a given board, the game ends. Of course, if the player clears all the
boards, the game also ends.
- When the game ends, print the total score and exit the program.
Note: Check out this video of a
running solution.
Screenshots containing important messages:
Grading
-
70% of your grade will be based on functionality (i.e., whether your
program works as specified), mostly based on the test cases that you see
when you submit, but perhaps also extra test cases according to the
specifications of the project. The code you submit must
work if you expect to earn a passing grade.
- 30% of your grade will be based on coding style.
- Readability: You should use good spacing and proper
indentation, and avoid long lines longer than 80
characters.
- Documentation:
- Use meaningful variable names and write helpful
comments to make it obvious what your code is doing.
- For each header file, you have to give a good overview of the library.
Check Pos.h and easycurses.h
for example.
- For each function prototype, explain what the function does and what
the input parameters are, and what the output value is.
- Design:
- Make sure you do a good job of putting things in functions.
- Create and erase the arrays appropriately.
- Make sure you do a good job of putting things in functions, packaging
data into structs, and breaking the program into .h and .cpp files.
- Once you create a library, the library should be applicable to all
parts. Don't create a library customized to only a single part.
Deadlines
- Parts 1 and 2
- You must meet all the requirements of Parts 1 and 2.
- Due 2359 on Monday, November 25.
- The full project
-
Due 2359 on Monday, December 9.
Due 0800 on Tuesday, December 10
Early bonus and late penalty
- For Parts 1-2
- 5% penalty if you didn't submit parts 1 and 2 on time.
- For the final project:
- 1 day early: 5% bonus
- 1 day late: 10% penalty (1 minute late = 1 day late)
- 2 or more days late: 100% penalty
Submission
For this project, you don't need to turn in a coversheet.
Your submission must include:
-
All .cpp and .h files required to compile and run your program.
-
Your boardYYXXXX.txt file, your gameScript.txt file, and all board files
(e.g. boardCenter.txt) it references, including those that
are linked off this page
How to submit
~/bin/submit -c=IC210 -p=proj03 Makefile *.cpp *.h *.txt