The game of Mazes & Monsters is a fairly general concept. It may seem odd at first, as it need have nothing to do with either monsters or mazes. In fact, the title misleads many into discarding the idea altogether. Let's examine it a bit further before out-right abandonment, shall we?
The idea behind M&M is that you have a player trying to succeed at some goal. There are adversaries attempting to thwart our hero/ine. S/He has to traverse an area to achieve the goal which is riddled with adversaries and traps of various kinds. Along the way, the hero/ine gathers items helpful to the cause. Other items may actually prove hurtful to achieving the goal and should be avoided. As long as these tenets are held, you've written an M&M game.
Let's look at a particular scenario to help clarify these points. To make the name clear, as well, I'll look at the original scenario for which M&M was named. The hero/ine is a hearty adventurer trying to clear out the local caves of riff-raff so that the local townsfolk and travellers won't be attacked and robbed near so much. For his/her trouble, the townsfolk have agreed that the hero/ine can keep any treasure that s/he finds in the caves for him/herself. On entering the caves, the player finds that it is really a maze-like network of tunnels. And instead of common human riff-raff, it is bedenizened by evil beings such as orcs, goblins, trolls, and wererats. Likely even worse things lurk deeper into the caves. To rid the cave of this evil will take many moons of all-out warfare. Luckily the hero/ine can find magically enchanted items along the way such as swords and armor to help wage this battle.
Also in the caves, unfortunately, are cursed items that hinder our adventurer. There are also traps that hurt the poor player or make him/her lost -- perhaps dropping him/her down to a deeper level of the maze-like caves.
But, that is just one scenario that fits this genre of game. Another might be that the player is a buddhist monk. A recent storm has cause much damage to the local shrine. To fix it, funds must be raised. Our hero/ine (let's be fair, after all), has been assigned to collect funds in the poorest district of the city. Being of a kind and generous order, s/he can't help but feel for the plight of many of those who live in this poor sector. Whenever they ask him/her for help, s/he must give up some of his/her hard-won funds. From other citizens, s/he must collect those funds. When the monk finally finds his/her way out of the poor district, s/he has won if they have enough for the repairs.
Things that might help the monk include finding a rich patron whose limo driver has gotten lost. To hinder the monk, s/he might be mugged in a shadier corner of the sector. To top it all off, all of the streets here are very similar and it is quite difficult to find your way out.
A CIA agent is trying to rescue a hostage (group thereof?) from terrorists. Once the player finds the hostage, the hostage can just follow the agent out (unless re-kidnapped by terrorists).
Terrorists and booby traps are obvious hinderances. Helpers could be ammo or weapons left in supply closets.
Other possible scenarios we mentioned in class the other day were:
Many other possibilities exist. Be creative in making your's up.
A few things to keep in mind are that you must have a sub-grid view, you must have multiple items in a grid square at once, and you need to have adversaries moving about.
To make the sub-grid view, just make sure your grid is larger than the 80x25 screen you typically have available. Then just index into it surrounding the player's current coordinates by a certain amount. I'd recommend explaining this in game terms as quality of lighting: the better the light source the player has, the more surroundings they can see. I'd limit them to a 20 square 'radius' (keep it square, though -- circles are extremely hard to emulate with a grid structure) at most.
Having multiple items in a square is fairly easy, you just have to have your grid structure be able to hold multiple items at the same time. (Sounds vaguely array-like to me.)
Another possibility here (since displaying many things in a single screen location can be difficult) is to list textually below the sub-grid what is located on this square. You might also support a player command 'look' that will give a 'close-up' of this grid square with items placed at various locations within the square. Then the player can move about the square with normal movement commands until they move across the square boundary or some other command designed to exit look-mode.
Adversaries are easiest to move randomly. Just have them pick a random location until they find one they can enter (not a wall, trap, etc.) and move them there. If you want to put a little intelligence in them, that is fine (and probably worth at least a little bonus credit). An adversary should be allowed to take a move whenever the player does (see combat below). Adversaries may exit or enter the maze at various points, too, if you wish.
Another common problem in M&M style games is inventory. Players (and sometimes adversaries) carry around all of this stuff they pick up. It might be quite heavy in the real world. Or it just might be unweildy. To deal with this, give the mobile entities a strength rating and give each item a heft rating. The sum of the heft ratings of the items a mobile entity is carrying must be less than or equal to their strength rating. (Just make sure the two sets of numbers make sense together.)
Another possible problem is if you allow the player to combat the adversaries. Combat causes time dilation problems. During a fight, actions speed up for those involved (adrenaline and all) while actions around them remain the same. To emulate this, it is often the case that mobile entities in the game, but not involved in the combat are only allowed a move every 6-10 rounds of the combat (a round is when everyone in the combat has had a chance to make a move).
The combat itself is often handled in a simplified manner, as well. Each combatant is assigned a number of 'hit' points. This represents how many hits the combatant can take (be they player or adversary) before falling dead. Often things are so simplified that hand-to-hand combat inflicts one point per successful hit. Weapons inflict more points of damage as they get bigger or more magical:
Weapon | Damage Inflicted ---------------------------------+------------------- dagger/knife | 2 short sword/magical dagger | 3 long sword/magical short sword | 4
You get the idea. A successful hit is determined simplistically, as well. The more armor you have, the more difficult you are to hit. A normal player/adversary might have a 'hittability' of 100%. Each piece of armor is given a rating:
Armor | Rating -----------------+---------- helm/gauntlets | -5 shield | -10 leggings | -10 breast plate | -20
Etc. Add the combatant's armor pieces' ratings to their base
hittability (which may be 100% or some other figure) to get
a hittability rating. By generating a random number between
0 and 100 and seeing if it is less than or equal to the combatant's
hittability rating, you can see if they got hit. If they got hit,
decrease their hit points by the damage rating of the opponent
(either hand-to-hand or weapon damage).
Here's one idea I had that might be useful: inventory. Everything
could be thought of to have an inventory. Rooms hold things,
NPC (non-playing characters -- monsters), the player(s), traps, and even
exits. Players hold items. NPCs might hold items. An item such as
a bag might hold other items. So having inventory is something that
everything does...pretty much.
Likewise, everything in the game might be held by someone/thing.
Also, if you are allowing any kind of magic or spiritual happenings,
pretty much everything could be thought of as doing magic. There are
often magical swords, bags, etc. Sometimes a place (a room) is magical.
And, of course, the player(s) and NPCs should do magic (if you are
allowing that sort of thing). As to spiritual, everything is often
considered to have a soul or spiritual essence: rocks, trees, animals,
even people. So this might be something else that everything shares.
By now you should be well on your way, but since I've gotten many
questions along this vein, I thought I'd share. The game will consist
of a 2D array (this was an extra credit problem a couple of months
ago, you may recall) -- among other data. However, this isn't a
simple 2D array of char
, no. This is a 2D array of
grid points (locations, positions, cells, or whatever term floats
your boat). These objects will consist of the char
to
display for that position as well as an array of base class
pointers (among other possible data). Your class
hierarchy
will consist of things that can be in the maze:
base (abstract -- extract commonalities) /\ -------- --------- / \ items critters /\ /\ ---- ----- ---- ----- / \ / \ weapons gold player enemy
Or something similar. The game and cell class
es are
separate from the polymorphic hierarchy! (The game should NOT be,
but the cell might be -- it shares common traits with the polymorphic
objects -- see below). Common traits should be
extracted to the abstract base class
(abstract means
at least one
virtual
function is set = 0
). Some of
these might include: move (items would ignore this, enemies would
randomly do this, and the player would ask the user for a move),
an inventory list (this would be another array of polymorphic
pointers; player can pick up things, enemy might be allowed to
do so, an item might be a bag and could hold other things), etc.
When it's time to make a move, the game goes through all of its cells and tells each cell to have all its contents move. So the cell goes through its polymorphic array making the move base class call. If any of them return that they want to move, the cell reports their desired direction to the game. The game calculates their new coordinate, checks it for obstructions and validity and sends back to the cell a positive or negative message. If the message is positive (the move can be made), the cell returns the polymorphic pointer to the thing that wanted to move to the game. The game then sends this pointer to the destination cell which places that thing in its polymorphic array.
Game: do { cell->move(who, where); if (where != NOWHERE) { if (isvalid(where,fromwhere,destcell)) { cell->extract(who); destcell->insert(who); } } } while (where != DONE);
The trick is that the cell must keep track of several pieces of information. First it must remember where it was in its contents list between calls to move. This memory should not be affected by the possible extraction of the thing moving. Second, it must not move things that just arrived this turn. For instance, if a critter moves south and you are processing the game grid from north to south, that critter should not be allowed to move again when we reach their destination cell.
One of the tricks here might be the template
d 2D array.
I assigned this as extra credit a while back, but most (if not all)
of you didn't try it (yet). Remember that I said in class that there
are a few ways that will work. The first (and most obvious) is to
create a whole new 2D array class
. I would recommend that
you allocate the dynamic array of data as a 1D array and use the
2D-->1D mapping I showed you ([i][j] --> [i*cols+j]
).
The second way is to alter your 1D array interface to be able to act
as either 1D or 2D. This can be done by either direct alteration of
the 1D interface or by adding a set of functions within the library
(friend
s of the 1D array?) that act as a 2D interface to
the array class
. Both of these first two methods make access
slightly un-natural as you can't do double bracket subscripting. You have
to either use the function call operator or provide an alternate indexing
function. Both of these tacks are also slightly difficult to resize.
The third way is to use the magic of
template
to your advantage by merely declaring your array
to be an Array< Array< ____ > >
. This allows
the most natural access (using double sets of square brackets), but
is a tremendous pain to resize as you have to resize the first dimension
and then walk along (foreach) the second doing the same resize to each
element (which is an array). But this is really just an auxillary
function (friend
...member?) which does these things for
the user of your class
:
resize2D(array, new_first, new_second);
.
More ideas/hints to follow. Stay tuned!
Other sources of information include two previous projects I've
given and the code of a game I wrote (briefly):