CSCS1320 C/C++ Programming
PROJECT: FUN WITH GAMES (fwg5)
OBJECTIVE
Using your Vircon32 build and installation from fwg0, the v32c++
transpiler from fwg4, and the game foundations from fwg1 through fwg4,
implement a simple 2D game in which your classes are related by
inheritance. Rather than writing out the same position, velocity,
and size members in every class, you will factor what your game objects
have in common into a base class, and let derived classes build
on it.
Your game can be a continuation of your fwg4 game (refactored to use
inheritance), or something entirely new. The demo fwg5.cpp (see
make copy) shows the idea in miniature; do not just rename it.
This project is single inheritance only: each derived class has exactly ONE base class. (Multiple inheritance is a topic for another day.)
BACKGROUND
Why inheritance?
In fwg4 you might have noticed your Player and your Ball (or
Enemy, or Bullet) carrying very similar data: an x, a y, a
dx, a dy, a width, a height. Copy/pasting that into each class
works, but now a bug (or a change) has to be fixed in every copy.
Inheritance lets one class acquire the members of another:
- the base class (also: parent, superclass) holds what is common
- the derived class (also: child, subclass) gets all of that for free, and adds or specializes what makes it different
The litmus test is the "is-a" relationship. A Player IS-A
Entity. A Banner IS-A Entity. If you cannot say "a __ is a __"
with a straight face, inheritance is probably the wrong tool (a Player
HAS-A score; that is just a data member, not a base class).
Syntax
class Entity // the base class: nothing special
{
...
};
class Player : public Entity // Player is derived from Entity
{
...
};
The : public Entity part says "Player inherits from Entity". Use
public inheritance: it preserves the is-a relationship, and it is the
only flavor we will use.
Everything that is accessible in Entity (see below) is now usable
through a Player object:
Player me(20, 20);
me.move(); // move() is defined in Entity, not in Player
Access control: who gets to see what?
You met public and private in fwg4. Inheritance introduces a third
level, protected, and now the question "who can touch this?" has
three audiences: the class itself, its derived classes, and
everyone else.
| member declared | the class itself | derived classes | everyone else |
|---|---|---|---|
public |
yes | yes | yes |
protected |
yes | yes | no |
private |
yes | no | no |
The key line is the private row: a derived class does NOT get to
touch its parent's private data, even though it inherited it (the
data is there inside every child object, but the child cannot name it).
As we say in class:
Children cannot access their parent's private parts.
If that sounds harsh, consider why: if any child could rummage through the parent's private data, then "private" would protect nothing; all anyone would need to do is write a derived class. Access control is only worth anything if inheritance cannot be used to get around it.
So how does a child get at what it needs?
-
Through the parent's public interface: accessors ("getters") and
mutators ("setters") such as
getX()andsetPosition(). This is the best default. -
Through
protectedmembers: data the parent is willing to share with its descendants (but not with the world). Use sparingly: everyprotectedmember is a promise to all current and future children.
A good rule of thumb: start everything private. Loosen to protected
only when a derived class has a real need, and to public only when
outside code does.
class Entity
{
private:
int x, y; // only Entity's own functions touch these
protected:
int dx, dy; // Entity and its children may touch these
public:
int getX() { return x; }
void setPosition(int nx, int ny) { x = nx; y = ny; }
};
class Player : public Entity
{
public:
void stop() { dx = 0; dy = 0; } // OK: protected in Entity
void home() { x = 0; y = 0; } // ERROR: x and y are private
void home2() { setPosition(0, 0); } // OK: via the public interface
};
Constructors and inheritance
When you create a Player, the Entity part of it has to be built too,
and it is built first: base class constructor, then the derived
class constructor. If the base class constructor needs arguments, the
derived constructor supplies them using an initializer list (the
part after the colon):
class Player : public Entity
{
private:
int score;
public:
Player(int startx, int starty)
: Entity(startx, starty, 10, 20) // build the Entity part first
{
score = 0; // then our own extras
}
};
If the base class has a constructor that takes arguments and the derived constructor does not say which one to call, the build fails; there is nothing sensible for the compiler to guess.
What a derived class can do
- use everything
publicandprotectedfrom its base - add its own data members and member functions
- be used wherever the base class is expected (a
Playeris anEntity)
The class hierarchy
Draw it. Arrow from child to parent:
Banner Player
\ /
\ /
Entity
Two derived classes, one base: that is the minimum 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 one other kind of game object (enemy, obstacle, ball, projectile, pickup, etc.) that acts on its 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 inheritance:
- define a base class holding the data and behavior common to your game objects (at minimum: position and size, plus a constructor, accessors, and a mutator or two)
- the base class must have private and/or protected data;
use
protectedonly where a derived class truly needs it - define at least TWO derived classes, each inheriting publicly from the base, from a single base class
- each derived class constructor must invoke the base class constructor (initializer list)
- each derived class must add something of its own: data and/or member functions that the base class does not have
- derived classes must not touch the base class's private data;
go through the public interface (or
protectedmembers) - create objects of your derived classes and let them do the work
through their member functions;
main()coordinates
NOTE: you are free to use arrays of objects if your game calls for it, but the emphasis of this project is on the design of the class hierarchy and on respecting access control while using it.
NOTE: if v32c++ rejects a C++ feature, keep it simple and ask in
the class chat; learning where the tool's limits are is part of the
experience.
NOTE: the first gamepad by default is mapped to the keyboard
CLASS REMINDERS
A skeleton of the sort of thing we are after (yours will differ):
class Entity // BASE CLASS
{
private:
int x; // position: private
int y;
protected:
int dx; // motion: shared with children
int dy;
int width;
int height;
public:
Entity(int startx, int starty, int w, int h);
int getX();
int getY();
void setPosition(int newx, int newy);
void move();
};
class Enemy : public Entity // DERIVED CLASS
{
private:
int strength; // new data, only Enemy has it
public:
Enemy(int startx, int starty)
: Entity(startx, starty, 10, 20) // call the base constructor
{
strength = 3;
}
void update(); // new behavior
};
Remember: a derived object is a base object, so every public member function of the base class works on it, too.
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 (fwg5.cpp, Makefile) into your repository
directory, and make update to refresh the Makefile.
The build is the same as for fwg4: v32c++ transpiles your .cpp and,
as a side effect, generates the cartridge XML and the build script
(Make.sh), which then compiles, assembles, converts any textures and
sounds, and packs the ROM.
TRANSPILE
yoursystem:~/src/SEMESTER/cprog/fwg5$ v32c++ fwg5.cpp -o fwg5.c
Do NOT track the generated .c file; it is a build product.
COMPILE
yoursystem:~/src/SEMESTER/cprog/fwg5$ compile fwg5.c -o fwg5.asm
ASSEMBLE
yoursystem:~/src/SEMESTER/cprog/fwg5$ assemble fwg5.asm -o fwg5.vbin
IMAGE CONVERSION
yoursystem:~/src/SEMESTER/cprog/fwg5$ png2vircon sprites.png -o sprites.vtex
Note that you'll also want to ensure the XML file has been updated to reference these textures:
<binary path="fwg5.vbin" />
<textures>
<texture path="sprites.vtex" />
</textures>
AUDIO CONVERSION
yoursystem:~/src/SEMESTER/cprog/fwg5$ wav2vircon soundfx.wav -o soundfx.vsnd
<sounds>
<sound path="soundfx.vsnd" />
</sounds>
PACKING THE ROM
yoursystem:~/src/SEMESTER/cprog/fwg5$ packrom fwg5.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/fwg5$ 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
- Label data structures as either scalar or composite: the base class and its data/interface
- Construct modular code by defining functions and utilizing different methods of passing data into them: derived constructors passing data up to the base class
- Select the most appropriate control structure or data type to solve a specific, predefined logical problem: modeling shared and specialized game objects with a class hierarchy
- 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:
286:fwg5:final tally of results (286/286)
*:fwg5:code modified to meet project specifications [26/26]
*:fwg5:screenshot or video posted to class DISCORD of game running [26/26]
*:fwg5:base class defined with protected/private data and a public interface [26/26]
*:fwg5:at least two classes derive publicly from a base (single inheritance) [26/26]
*:fwg5:derived constructors invoke the base class constructor [26/26]
*:fwg5:access control respected: children do not touch their parent's private parts [26/26]
*:fwg5:derived classes add their own data and/or behavior [26/26]
*:fwg5:working collision detection between game objects [26/26]
*:fwg5:game has scoring (or lives/timer) and a win/lose condition [13/13]
*:fwg5:game uses at least one texture and one sound [13/13]
*:fwg5:source file, XML file, build script, and cartridge submit [13/13]
*:fwg5:code compiles, cartridge builds with no warning or error [13/13]
*:fwg5:committed project related changes to private semester repo [13/13]
*:fwg5: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