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.1C, 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 have explicit permission
- to get help from other current IC210 instructors as well as your own
(any assistance must be documented though).
- to discuss the "big picture" of how to approach the project with current IC210 MGSP leaders. You may not, however, discuss or view any code that is specific to the project
(see the instructors for such discussions), so this will probably be most useful when you are just getting started. If you are stuck on concepts, you may find it helpful to discuss
homeworks, lab, or lecture examples with MGSP leaders.
- 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, say looking through code of programs that
implement a maze-navigation game using ncurses, are not allowed.
- You are very specifically not allowed to look at previous semesters'
IC210 programming project or lab solutions that may have addressed
similar issues.
Put 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 or the other IC210 instructors , and that help must be clearly cited. In turn, you
cannot provide help to any IC210 students on this
project.
- You may also discuss the "big picture" of how to approach the project with current IC210 MGSP leaders, but may not discuss or view any code (yours or anyone else's) that is specific to 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 resources for general purpose C++
programming, but not for help writing games with ncurses
or anything else that is 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
|
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 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: *.h *.cpp
g++ part1.cpp easycurses.cpp Pos.cpp -l ncurses -o part1
part2: *.h *.cpp
g++ part2.cpp easycurses.cpp Pos.cpp -l ncurses -o part2
part3: *.h *.cpp
g++ part3.cpp easycurses.cpp Pos.cpp -l ncurses -o part3
part4: *.h *.cpp
g++ part4.cpp easycurses.cpp Pos.cpp -l ncurses -o part4
part5: *.h *.cpp
g++ part5.cpp easycurses.cpp Pos.cpp -l ncurses -o part5
partx: *.h *.cpp
g++ partx.cpp easycurses.cpp Pos.cpp -l ncurses -o partx
run_%: %
./$<
dummy:
echo ""
Later you will be creating additional .cpp files to hold parts of your code -- when you do so add your additional cpp files to the g++ commands in Makefile as necessary.
- To compile your solution to Part 1, execute the following command in the terminal:
make part1
You can compile other parts similarly.
Part 0.3: How to submit
Check Online Submission near the end.
Part 1: Read the board! [45pts]
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.
Z
s 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 appropriate error message (you pick the exact message) and return if the file is not
found!
-
Reads the board file and stores the information in it, then prints the board on
the screen using easycurses (not cout!!!!). Don't "refresh" the window until everything is printed.
If the terminal window is too
small, the program must exit easycurses, print an appropriate error message (you pick the exact message),
and exit the program.
Note 1: Don't print spawn spots or the player's start spot, but do
print X for the goal.
Note 2:
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 that this will read in ALL whitespace characters; you need to be prepared for this!
-
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. See example screenshots below.
Note 3: 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. When creating your struct, look at the provided code in Pos.h and Pos.cpp first -- what can you use from here?
Note 4: You must split your program into .h and .cpp files as we have
discussed. It's up to you how many. If you are confused on this, it's okay
to just use part1.cpp for now, but talk to your instructor (and see the lecture notes on Multi-file programs) to figure out how to split things up later (required for full credit).
hit enter
press y
Another example:
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! [65pts]
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.
-
The player (represented in the game by a "P") can either be
moving or stopped. It starts the game stopped, and at the
position marked on the input file by a "Y".
-
The r key stops a moving player. The a,s,d,w keys set the
player's direction to W, S, E, or N respectively and, if
the player is stopped, set it to moving.
A moving player moves one step forward per round in its current
direction.
- Sleep for 0.1 seconds in-between rounds. You can change this if it seems
too fast or slow for you (to play or to debug!).
-
The player must bounce off the walls. Check out the detail on bouncing. Note that
bouncing may affect both the direction and the position of the player.
-
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.
- Recall part 1 said "You must split your program into .h and .cpp files as we have
discussed. It's up to you how many." If you didn't do that for part 1, now is the time!
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):
Important details on submit
As before, follow the Online Submission instructions.
But, for part 2 (and all later parts) the online system does NOT automatically grade your submission.
Passing the testcase for part 2 means only that your code compiles. So, you definitely need to pass this test, but doing so does not mean that
your code is correct -- you need to assess that yourself! This also means
that minor variations in spelling/messages won't affect your score in "submit".
Part 3: Implement the space ships [80pts]
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.
Part 4: Implement the Hunters [90pts]
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:
1. let dc = (Player column position) - (Hunter column position)
2. let dr = (Player row position) - (Hunter 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 Hunter's direction to rdir, otherwise set Hunter's direction to cdir
Note: Check out this video of a running solution.
Part 5: Make it a game [100pts]
Copy part4.cpp into part5.cpp
Part 5 builds on Part 4 to make this a real game: if you die three
times the game's over, you get "points" as you progress,
if you win one board you go on to the next, each
board getting 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:
- You no longer read any filenames from the user; instead you will
get all filename information from
gameScript.txt
.
You will, however, output the filename of each board when using it,
so the screenshots you see below still contain lines that look like
boardFile: boardMaze.txt
(along with some extra info, see examples below).
-
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 works the game plays the board as
described by the first line of gameScript.txt
until either dying three times (on that board) 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. Display a special message if some very talented
player finishes the whole script (see example below).
-
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 every attempt at a board (where a victory or not), call
endCurses()
so that you can see the messages about "Player Start", "Spawn Spots", and win/loss scoring messges. Later, call startCurses()
again, if starting a new attempt on a board.
-
After a victory on a given board, your score for that board is the points value for that board
plus 500 minus the number of rounds/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.
- After a death on some level, print a message, like before. No additional
score is earned.
-
After three deaths on the same level, exit but print the total score (sum over all boards where the player had a victory) first.
Note: For debugging, you may want to use the provided tinyScript.txt
instead of gameScript.txt
.
Note: Check out this video of a
running solution. Note this program changes the player's character from a 'P' to an 'I' to indicate when they are running in "immortal" mode.
Screenshots containing important messages:
Extra Credit [+10Pts]
Copy part5.cpp into partx.cpp
You may earn extra points by adding on interesting (or difficult to implement)
features to the basic game. Extra credit will be considered only for correct
working solutions to Part 5.
How much extra credit you get is at your instructor's discretion. The amount
will be based on creativity, improvement of the game experience, and difficulty
or sophistication of implementation.
Using a linked list as part of your extra features will definitely improve
your chances of getting big points! Here's some extra information that
might be useful in adding extra credit features.
Adding Color [+2pts]
To add color to ncurses we need to start color support. This will require you
to look into easycurses.h and make the proper addition in the right place.
-
Add
start_color();
when ncurses is first initialized.
start_color();
- Then we need to initialize colors we want. This doesn't
turn them on yet, but makes them easily available.
init_pair(1, COLOR_RED, COLOR_BLACK); // Red on Black
init_pair(2, COLOR_BLACK, COLOR_RED); // Black on Red
init_pair(3, COLOR_RED, COLOR_RED); // Red on Red (Red Block)
init_pair(4, COLOR_GREEN, COLOR_GREEN); // Green on Green (Green Block)
-
To actually use colors in plotting use
functions
attron(.)
and attoff(.)
like this:
attron(COLOR_PAIR(1)); // Start using Red on Black
drawCharNoRefresh('X',10,15);
attroff(COLOR_PAIR(1)); // Stop using Red on Black
RefreshWindow();
Using an extended character set [+2pts]
Full unicode in ncurses seems to be a bit messy. But it
does support a more modest extended character set pretty
nicely:
altcharset chart,
extended
charset chart.
The altcharset has predefined constants like
ACS_CHKBOARD
. You write them to the screen
like this:
waddch(W, ACS_CKBOARD | A_ALTCHARSET);
For the extended charset there are simply numeric codes.
If you look at chart you'll see that the copyright symbol
has code 169, so you'd write it like this:
waddch(W, 169 | A_ALTCHARSET);
Other interesting ideas:
-
You could add some items in the map that the player can use. Of course, all the
previous parts should still work even after you add this feature.
-
You could put some portals in the map so that the player can teleport here and there.
-
Show your creativity and implement other interesting features!
Deadline
- Part 1
- You must meet all Part 1 requirements.
- Due 2359 on Tuesday, November 26. If you are on OPINFO, the
same deadline applies; email all your code to your instructor if needed.
- The full project
- Due 2359 on Thursday, December 5.
- Penalty
- Late days for the final project will be counted based on the course policy.
- Late days for Part 1 are different: minus 3% (from the overall grade) for each calendar day late.
Offline Submission
- Fill out and turn in coversheet.
- The codeprint of your source code files. If your code works correctly up
to Part n, then you can print the files only for Part n; you don't need to
print out the code for Part 1,..., Part n-1.
These paper portions of your submission should be stapled together and slid
under your instructor's door.
Online Submission
Your electronic submission must include:
-
All .cpp and .h files required to compile and run your program.
-
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
For electronic submission, continue to use the same method from the previous
projects.
For part 1:
Submit your work to proj03m instead of proj03.
For proj03m, the submit system WILL test the functionality of your code.
~/bin/submit -c=IC210 -p=proj03m files-to-include
For the full project:
For proj03 (parts 2 and beyond), the submit system will NOT test the functionality of your code -- you will pass each testcase if and only if your code for that part compiles. It's up to you to test the functionality!
~/bin/submit -c=IC210 -p=proj03 files-to-include
Where files-to-include is a list of all
.cpp and .h files specific to this project, as well as Makefile,
your gameScript.txt file, and all board-files required to run your project.
Submitting more than you need to submit is always
OK. Just be sure
that all the files needed to compile and run your project
(including the extra credit version of your project) are
there. So, assuming you have a directory for this project and
that you are in that directory, you could submit like this:
How projects are graded / how to maximize your points
Things to keep in mind to receive full marks:
- Submit all required items (as described above) on time.
-
Make sure the form of your output matches the form of the
example output in all
ways.
-
Make sure you follow the IC210 Style
Guide. This means proper indentation, use of whitespace within lines of
code, logical organization of chunks of code,
-
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.