Topical Information

This project will help you show your mastery of file access, class design, inheritance, polymorphism, and operator overloading.

Program Information

The classic game for computer play is a maze game. The player wanders around the maze looking for treasure and facing down opponents as she goes. There are items to help her along the way, as well.


One thing standing in your way here is that you are immediately drawn to think graphics, obviously. Don't. Think characters. A grid of X's or #'s can form a maze quite nicely:

       ################################################
       #   #      #          #  #     #    #      #   #
       #   #   ####  #####  ##  ### ##### ##  ####### #
       # ###   #  #  # # #  ##   ## #  ##  #  #     # #
       # # ### #     #   #  ##   ## #   #  #  #     # #
       # # #   #  #  # # #  ##   ## #   #  #  #     # #
       #   ### ####  #####  ##   ## #   #  #  ### ### #
       #   # #                   #  ## ##     #     # #
       # #     ####         #              #     #    #
       ################################################

(Okay, so I'm not an artist...*phbbt*)

Also think turn by turn -- not interactive. In other words, you print the maze, the user tells you which way they want to move, you calculate the new state of the maze, then re-draw it, etc.


The next obstacle you will face is that you'll think items, opponents, treasure, and even the player are all terribly different. As an experienced maze wanderer, I can tell you that they are all quite similar:

Since they all share common traits, maybe they'd make a natural family...? (Tree, hierarchy, ...*Jason smacks you with a clue*)


Some minor extrapolation can lead you to a nice polymorphic hierarchy:


Lastly, how does all of this work together? Well, there's an application format called de-centralized control. In this format, the main merely sets objects in motion. From then on, things that happen are based on messages traveling back and forth between the various objects (methods being called, objects/object pointers being returned or passed, etc.).

A useful model in a maze game is:

        ++----------------------------------->  maze (grid of locations)
        ||
        \/
    location (array of poly pointers)
        /\
        ||
        ++----------------------------------->  items in location (see above)

Here the maze is simply a 2D array of location objects. Each location object represents a single square or space in the maze. It knows what it contains (the player, opponents, treasure, items), but not where it is -- that's the maze's job. (Each location can contain several things -- perhaps limited by the locations dimensions/capacity. Hmm...like an inventory -- you only have so many pockets -- or a bag's volume...)

When the maze is printing itself, for instance, it loops through all the locations and asks each one what character best represents its contents. If the location is empty, it reports back a space. If it has the player in it, it would return their symbol (which you asked them for at the beginning of the game). If it contains an opponent, it would ask (the opponent object) for and return the opponent's symbol. Etc. (This implies a precedence relationship -- players take priority over opponents which take priority over items which take priority over nothing-ness. Item precedence is left to you -- is a shield or a crown more important? Precedence...order...maybe a less than or even an operator?)

An individual thing in the location knows about itself. Nothing more. To find out about the outside world, it must ask its location. It knows its location because when the location asks it a question, the location will pass a pointer to itself along with the request (where sensible). For example, when the location asks a contained thing where it wants to move, that thing gets a pointer to the location. The player will ignore that pointer, most likely -- simply asking the user which way they want to go and returning that direction. (Although the player class may validate the user's choice via the location pointer it received...something like:

    Direction Player::move(Location * loc)
    {
        String s, r;
        cout << "Move:  ";
        cin >> s;
        while (!loc->validate(Direction(s), r))
        {
           cout << "Invalid move:  " << r;
           cout << "\nMove:  ";
           cin >> s;
        }
        return Direction(s);
    }
perhaps... Direction being a class that indicates a direction in the game and r being a String indicating the reason the direction was invalid.)

Items (including treasure) will also ignore the pointer and simply return that they don't want to move. (Unless, of course, you have things like a winged helmet that flies around until it is caught..?) Opponents, however, might ask the location if neighboring locations hold the player -- so they might hunt the player down. (If that happens, the location may then ask the maze either for a list of neighboring locations or may pass on the request to its neighbors. After all, it doesn't know where it is, just what it contains.)

Imagine yourself to be a location in a huge maze. Do you know what is around you? Do you know your location as a distance from the edges of the maze? No. All you see is what/whom is inside you: that chest over by my wall, the knife on the table, that ogre skulking in the corner, and the hero standing by the tapestry. The knife doesn't really have any awareness of other things around it. The chest only knows what it contains (and it isn't telling!). The ogre is hiding. And it only knows what is around if it pays attention -- if it is asleep, it has no idea. The hero also only knows what is around if s/he looks. Speaking of player commands:


Any time it is the player's turn, they can not only move, but also take many other actions. They should be allowed to look around at their present location to see if anything else is there. If items or treasure are present, they should be allowed to pick it up and place it in their inventory or add it to their accrued monetary value (as appropriate). If opponents are present, they might fight or bargain with them (or, of course, simply run away).

This will adjust their movement function, of course:

    Direction Player::move(Location * loc)
    {
        String s, r;
        bool c, v;
        do
        {
            cout << "Move:  ";
            cin >> s;
            if (!(c = iscmd(s)) && !(v = loc->validate(Direction(s), r)))
            {
               cout << "Invalid move:  " << r;
               cout << "\n";
            }
            else if (c)
            {
               proc_cmd(s, loc);
            }
        } while (!v);
        return Direction(s);
    }

Of course, alternatively, iscmd could both return true and process the command if it was look or something non-motile. Either way, the command might be so far-reaching that it would have to be passed back up to the location -- or even to the game itself. Commands like saving or loading the game:

No matter what their location, they should be allowed to save the game, load a saved game, get help on what actions are available, or simply quit the game.

In summary, a player should be allowed to take the following actions (by simply typing them in):

Command TypedAction Taken by You
quitgame's over -- summarize?
savethen ask for file name and verify it worked
loadthen ask for file name and verify it worked
helpprint a screen of these commands
get item/treasureitem is placed in inventory, treasure is accrued to total wealth
drop itemitem is removed from inventory and placed in location
fight opponentsee options below
offer item opponent
(or possibly 'offer opponent item')
see options below
lookshow contents of current location
eastif direction isn't blocked -- moves player from this location to the one east of here
northif direction isn't blocked -- moves player from this location to the one north of here
southif direction isn't blocked -- moves player from this location to the one south of here
westif direction isn't blocked -- moves player from this location to the one west of here


When saving a game, the maze must save all locations (a do_to_all sort of operation, eh?). As each location saves itself, it can save those things it contains. Each of those, in turn will save contained items (a bag's contents, the player's inventory, etc.). This leads us to the fact that everything from the maze down needs to have save and load methods (polymorphism or coincidence?).

Loading is similar, but note you'll have to have a location notice that it contains something and load those items correctly. This can be troublesome in a polymorphic application. Take care! (Perhaps some kind of labeled file format might help?)


Suggestions:


Resources

I collected together some old resources from previous incarnations of this project. Note that any code samples given are arguably either too simplistic to meet this assignment's requirements or too old to still compile or both. Also note that any links not specifically about the maze will be broken. (If you really want to see something, let me know and I'll see what I can do...)

This assignment is (Level 10).

Options

Add (Level 0.5) to make the player 'teleport' from the left edge to the right edge and likewise from top to bottom. (In other words, if the user tries to walk out of your maze through a side, they should immediately reappear on the opposite side of the maze.

Add (Level 0.5) to have positions within the maze which, when the player (or an opponent -- see below) steps on them, will teleport them to a random destination within the maze. Such positions should look like normal walkways (spaces) so that it will be a surprise when they are stepped on. (Should they trigger more than once?)

Add (Level 2) to make opponents wander somewhat purposefully. Make them hunt the player -- sort-of. They will only truly randomly wander when they can't catch sight/wind of the player. If s/he is too far away, the opponent will randomly choose a destination. If the player is closer by, the opponent will try to move that direction. They may get stuck on walls, etc., but that's okay. As long as they are trying to catch the player, that's good enough. (Perhaps the 'too far away' could be adjustable based on the opponent's experience/intelligence? Add an extra (Level 1) for this.)

Add another (Level 4) to make opponents wander truly purposefully. They shouldn't get stuck behind walls and such any more -- they should map out a way around such barriers. Perhaps they could seek the exit and set an ambush? Perhaps a cooperative corralling move (ala velociraptors)? Perhaps a true targeting system or hunting by scent? Etc.

Add (Level 1.5) to implement a system of combat as suggested above with the fight command. Certain items could enhance your defense or fighting ability (shields or swords). Others might help sustain life during/after a battle (medicine, potions, bandages, etc.).

Add (Level 2.5) to make your combat system more realistic by adding details like different weapons do different amounts of damage (a knife is less effective than a machine gun). Also you could have different armors be better than others (a cloth shirt is less effective than a bullet-proof vest). Also add to the realism by having each 'hit' do a slightly different amount of damage based on the 'circumstances' of the blow. (A good way to do this is to have weapons do a random amount of damage. See the dice labs from 121 for more on this: simple, medium, advanced.)

Add (Level 3) to implement a system of bargaining as suggested above with the offer command. Certain opponents would desire certain items more than others. If they like what you give them, they might just leave you alone. Other items might be accepted or rejected by certain opponents (a vampire won't accept a holy relic, for instance; but a priest might) in trade for safe passage. Perhaps you could even trick them (offer the vampire a chest with a garlic necklace in it; when s/he opens it: *boom*).

Add (Level 1.5) to have passable barriers like doors or fake walls...even windows. Add another (Level 1) to have these barriers openable/closeable at player command.

Add (Level 2.5) to allow your maze to be larger than the screen and simply show a smaller, viewable portion of it onscreen at a time. (Thought-provoker: should opponents offscreen be moving/acting even while not visible to the player?) Add another (Level 0.5) to make this smaller area appear to scroll by keeping the printed view centered on the player at all times.

Add (Level 2.5) to have the maze be dark (or have dark zones). When the player has a light item (torch, lantern, etc.), they'll be able to see. Otherwise, they will just bump around until they get out, die (see combat above), or find a new light. Lights should burn out (torch dies out, lantern runs out of oil, etc.).

Add another (Level 2.5) to have viewable area of the light be roughly circular around the player:

    *********************************
    **************##O  **************
    ************   #,### ************
    ********** $$  #   #   **********
    ********#       @  #####+********
    **********###      #H  **********
    ************Z  #   # ************
    ************** #   **************
    *********************************

(Here the @ is the player, the *'s are darkness, the + is a closed door, the , is an open door, the Z, H, and O are opponents of various kinds, the #'s are walls, and the $'s are treasure.) Some lights should provide more viewable area than others -- allowing the player to see farther around them.

Add another (Level 2) to keep the light from going through walls. We shouldn't really be seeing the H opponent through those walls, now should we? Nor should we know about that door (closed or not) over to the right.

Add (Level 4) to randomly generate your mazes. This is trickier than it sounds since you have to avoid un-reachable rooms and such. Also make sure you have rooms of a decent size and regular proportion: at least 2x2 and rectangular. (Hint: A common technique involves the application of disjoint sets.)

Add (Level 1) to randomly select a maze from a set of pre-generated mazes. (Hint: You might use an index file for this.)

Add (Level 2) to have multiple mazes connected in a 3D fashion. The simplest way is to have 'stairs' at various places and allow the player to go up or down when they are on a stairway. When they go up or down, move them to a new maze. (This new maze might be pre-determined -- so things line up and make sense -- or it may be randomly generated/selected -- see above.)

Add (Level 1) to have your 3D levels connected by elevators instead of stairs. This is slightly more complicated in that the elevator must be 'called' to the level the player is on. Once inside, they can tell you how many levels they'd like to up or down (or perhaps an exact level's number/designation they'd like to go to).