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?

  1. Through the parent's public interface: accessors ("getters") and mutators ("setters") such as getX() and setPosition(). This is the best default.
  2. Through protected members: data the parent is willing to share with its descendants (but not with the world). Use sparingly: every protected member 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 public and protected from its base
  • add its own data members and member functions
  • be used wherever the base class is expected (a Player is an Entity)

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 protected only 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 protected members)
    • 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
  • 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