CSCS1320 C/C++ Programming
PROJECT: FUN WITH GAMES (fwg6)
OBJECTIVE
Using your Vircon32 build and installation from fwg0, the v32c++
transpiler from fwg4, and the class hierarchy skills from fwg5,
implement a simple 2D game built around polymorphism: a collection
of different kinds of game objects, all handled through a base class
pointer, each responding to the same function call in its own way via
virtual functions. Along the way you will also try out
overloading: functions that share a name, and operators (+, ==,
…) taught how to work on your own classes.
Your game can be a continuation of your fwg5 game or something entirely
new. The demo fwg6.cpp (see make copy) shows the ideas in miniature;
do not just rename it.
As in fwg5, inheritance is single inheritance only.
BACKGROUND
The problem polymorphism solves
Suppose your game has a player, some drifting rocks, and a chaser. They are all "things that update and draw themselves", but each one does so differently. With plain inheritance you can write:
Player me;
Drifter rock1, rock2;
Chaser stalker;
me.update();
rock1.update();
rock2.update();
stalker.update();
…and every new kind of object means more lines in main(). What you
want is one list of game objects, one loop, one call, with each
object doing the right thing for what it actually is:
for (i = 0; i < NUM_ACTORS; i++)
{
actors[i]->update();
}
That is polymorphism ("many forms"): the same call, many behaviors, the right one chosen at run time by the real type of the object.
Two ingredients
1. A base class pointer can point at a derived object. A Drifter
IS-A Actor, so an Actor * is allowed to hold the address of a
Drifter:
Drifter rock(200, 100, 2, 1);
Actor *a = &rock; // address-of (&): a points at rock
a->update(); // arrow (->): call through the pointer
This is where your pointer skills (address-of &, the arrow ->) pay
off. Since every element of an array has to be the same type, an array
of Actor * can hold the addresses of a mixed bag of derived objects:
Actor *actors[3];
actors[0] = &me; // a Player
actors[1] = &rock; // a Drifter
actors[2] = &stalker; // a Chaser
2. Virtual functions. By default, a->update() on an Actor *
calls Actor's version of update(), because that is the type
the pointer claims. Mark the function virtual in the base class and
C++ instead looks at what the pointer really points at:
class Actor
{
public:
virtual void update(); // "derived classes may replace this"
};
class Drifter : public Actor
{
public:
virtual void update(); // Drifter's own version: an OVERRIDE
};
Now a->update() runs Drifter::update() when a points at a
Drifter, Player::update() when it points at a Player, and so on.
(The word virtual is optional on the override once the base declares
it, but writing it keeps your intentions clear; do so.)
Pure virtual functions and abstract base classes
Sometimes the base class cannot say anything sensible about how a
function should work: what does it mean to draw() a generic Actor?
Declare it pure virtual by giving it = 0 instead of a body:
class Actor
{
public:
virtual void update() = 0; // pure virtual: no body here
virtual void draw() = 0;
};
A class with at least one pure virtual function is an abstract base class:
- you must not create an object of it (
Actor a;is an error in standard C++;v32c++does not check this for you, so the discipline is yours, and it will be checked when your project is evaluated): it is a promise, not a thing - every derived class that wants to be instantiated must override all of the pure virtuals, i.e. it must keep the promise
- you can still have pointers to it (
Actor *a;), which is what makes the polymorphic array above possible
Think of the abstract base class as a contract: "anything that wants to
be an Actor must know how to update() and draw()."
Overriding is not overloading
These two words sound alike and mean different things:
- overriding: a derived class re-implements a virtual function from its base, with the same name and same parameters. Resolved at run time by the object's real type.
- overloading: two or more functions with the same name but different parameters, in the same scope. Resolved at compile time from the arguments you pass.
Function overloading
class Actor
{
public:
void setPosition(int newx, int newy); // two ints...
void setPosition(Vec2 newpos); // ...or one Vec2
};
actor.setPosition(10, 20); // picks the first
actor.setPosition(Vec2(10, 20)); // picks the second
You have already used this without noticing: a class can have more than one constructor, as long as their parameter lists differ.
Operator overloading
Operators like + and == already work on ints. For your own classes
you can teach them what to do. An operator is just a function with an
odd name: operator+, operator==, operator+=, …
class Vec2
{
private:
int x;
int y;
public:
Vec2(int newx, int newy) { x = newx; y = newy; }
Vec2 operator+(Vec2 other) // a + b
{
return Vec2(x + other.x, y + other.y);
}
void operator+=(Vec2 other) // a += b
{
x += other.x;
y += other.y;
}
int operator==(Vec2 other) // a == b
{
return (x == other.x && y == other.y);
}
};
Vec2 pos(10, 10);
Vec2 vel(2, 1);
pos += vel; // same as pos.operator+=(vel)
if (pos == vel) { ... } // same as pos.operator==(vel)
Vec2 next = pos + vel; // same as pos.operator+(vel)
Guidelines: overload an operator only when its meaning is obvious
(adding two vectors with + is; making + launch a missile is not).
Overloading changes how your own types are written, not what the
operator can do for built-in types: 3 + 4 is unaffected.
Access control reminder (from fwg5): notice that operator+ reads
other.x. That is legal because the function is a member of Vec2 and
may touch the private data of any Vec2, not just its own object.
Children still cannot access their parent's private parts, though.
A word about destructors
In ordinary C++ a class that is meant to be used through a base pointer
also gets a virtual destructor so deleting a derived object through a
base pointer cleans up correctly. Our cartridges never new/delete
objects (all of ours live as local variables in main()), so we need no
destructors for this project.
PROGRAM
You are to write a Vircon32 C++ game that:
- has a player-controlled object, moved via the gamepad
- has at least two other kinds of game object that act on their own
- detects collisions between objects, with a meaningful result
- keeps a score (or lives, or timer) displayed or otherwise tracked
- has a win and/or lose condition (even if it simply resets)
- is built around polymorphism:
- define an abstract base class with at least one pure
virtual function (for example
update()anddraw()) - define at least TWO derived classes (publicly, single inheritance) that override the virtual function(s) with behavior of their own
- keep your game objects in an array of base class pointers, holding at least two different derived types
- use a loop to call the virtual function(s) through those
pointers (
->)
- define an abstract base class with at least one pure
virtual function (for example
- demonstrates overloading:
- overload at least one operator for a class of yours (for example a vector or point class), and use it meaningfully in your game code
- demonstrate function overloading with at least one set of functions (or constructors) sharing a name but differing in parameters
NOTE: the first gamepad by default is mapped to the keyboard
NOTE: the transpiler emits an empty function body for each pure virtual
function, and the Vircon32 compiler reports argument "this" is not
used for it. That specific warning is expected and is not held
against you for the "no warning or error" line item; any other warning
or error is.
NOTE: v32c++ is not a full C++ compiler. If a feature is rejected,
simplify and ask in the class chat; documenting what works and what
does not is valuable.
CARTRIDGE BUILD PROCESS
All of these steps can be automated with the use of the provided
Makefile simply by typing make at the prompt. Use make copy to
pull the demo project (fwg6.cpp, Makefile) into your repository
directory, and make update to refresh the Makefile.
The build is the same as for fwg4 and fwg5: v32c++ transpiles your
.cpp and generates the cartridge XML and build script (Make.sh),
which compiles, assembles, converts textures and sounds, and packs the
ROM.
TRANSPILE
yoursystem:~/src/SEMESTER/cprog/fwg6$ v32c++ fwg6.cpp -o fwg6.c
COMPILE
yoursystem:~/src/SEMESTER/cprog/fwg6$ compile fwg6.c -o fwg6.asm
ASSEMBLE
yoursystem:~/src/SEMESTER/cprog/fwg6$ assemble fwg6.asm -o fwg6.vbin
IMAGE CONVERSION
yoursystem:~/src/SEMESTER/cprog/fwg6$ png2vircon sprites.png -o sprites.vtex
<binary path="fwg6.vbin" />
<textures>
<texture path="sprites.vtex" />
</textures>
AUDIO CONVERSION
yoursystem:~/src/SEMESTER/cprog/fwg6$ wav2vircon soundfx.wav -o soundfx.vsnd
<sounds>
<sound path="soundfx.vsnd" />
</sounds>
PACKING THE ROM
yoursystem:~/src/SEMESTER/cprog/fwg6$ packrom fwg6.xml
SUBMISSION
To be successful in this project, the following criteria (or their equivalent) must be met:
- Project must be submit on time, by the deadline.
- Late submissions will lose 33% credit per day, with the submission window closing on the 3rd day following the deadline.
- Executed programs must display in a manner similar to provided output
- output formatted, where applicable, must match that of project requirements
- Processing must be correct based on input given and output requested
- Output, if applicable, must be correct based on values input
- Code must be nicely and consistently indented
- Code must be consistently written, to strive for readability from having a consistent style throughout
- Code must be commented
- Any "to be implemented" comments MUST be removed
- these "to be implemented" comments, if still present at evaluation time, will result in points being deducted.
- Sufficient comments explaining the point of provided logic MUST be present
- Any "to be implemented" comments MUST be removed
- No global variables (without instructor approval), no goto statements, no calling of main()!
- Track/version the source code in your private semester repository
- Submit a copy of your source code to me using the submit tool by the deadline.
Do NOT track or submit generated or binary files (obj/, bin/,
.c, .vbin, .v32); make submit cleans these for you.
SUBMIT TOOL USAGE
lab46:~/src/SEMESTER/cprog/fwg6$ make submit
You should get some sort of confirmation indicating successful submission if all went according to plan. If not, check for typos and or locational mismatches.
COURSE OBJECTIVES ASSESSED
Some of the rubric line items below also serve as evaluations of the course objectives listed on the course homepage:
- Implement functional programs … that satisfy automated test requirements: the cartridge builds with no warning or error
-
Define the relationship between a variable's value and its memory
address using basic pointer syntax: base class pointers holding the
address (
&) of derived objects, called through-> - Distinguish between collections of the same data type (arrays) and collections of differing data types (structures): the array of base pointers holding different derived objects
- Construct modular code by defining functions and utilizing different methods of passing data into them: overriding virtual functions
- Select the most appropriate control structure or data type to solve a specific, predefined logical problem: an abstract base class as the contract for your game objects
- Utilize built-in language libraries: texture and sound use
Not every line item maps to an objective, and that is fine.
RUBRIC
I'll be evaluating the project based on the following criteria:
312:fwg6:final tally of results (312/312)
*:fwg6:code modified to meet project specifications [26/26]
*:fwg6:screenshot or video posted to class DISCORD of game running [26/26]
*:fwg6:abstract base class declares virtual and pure virtual functions [26/26]
*:fwg6:at least two derived classes override a virtual function [26/26]
*:fwg6:virtual functions invoked through base class pointers [26/26]
*:fwg6:array of base pointers holds different derived objects, driven by a loop [26/26]
*:fwg6:at least one operator overloaded in a class [26/26]
*:fwg6:overloaded operator is used meaningfully by the game code [13/13]
*:fwg6:function (or constructor) overloading demonstrated [13/13]
*:fwg6:working collision detection between game objects [26/26]
*:fwg6:game has scoring (or lives/timer) and a win/lose condition [13/13]
*:fwg6:game uses at least one texture and one sound [13/13]
*:fwg6:source file, XML file, build script, and cartridge submit [13/13]
*:fwg6:code compiles, cartridge builds with no warning or error [13/13]
*:fwg6:committed project related changes to private semester repo [13/13]
*:fwg6:grabit of project data by Sunday prior to duedate [13/13]
Additionally:
- Solutions not abiding by spirit of project will be subject to a 50% overall deduction
- Solutions not utilizing descriptive why and how comments will be subject to a 25% overall deduction
- Solutions not utilizing indentation to promote scope and clarity or otherwise maintaining consistency in code style and presentation will be subject to a 25% overall deduction
- Solutions not organized and easy to read (assume a terminal at least 90 characters wide, 40 characters tall) are subject to a 25% overall deduction