Tag: adventure

An Item class, and revising Hero

If you compare GameObjs.cs as it existed at the end of the previous lesson (#25) with its state now (see downloadable source code at end of this post), you'll find numerous differences.

There is the addition of an Item class, and the Hero class has been significantly expended, including the addition of an inventory, a place for the hero to keep his stuff.

I tweaked structure CombatResults to include a member to hold the highest damage the combatant dished out in its most recent combat. And I revised MobileKiller.CombatRunner() to calculate this value for each of two combatants and store it in their member of type CombatResults.

To understand how Hero is developing, you'll not only want to view the updated GameObjs.cs source code, but also the testGameObjs11.cs code to see how it sets up some Item objects and adds them to the hero's inventory.

Deriving the Hero class

In this lesson, I've derived class Hero from Moba. I've overloaded MobileKiller's CombatRunner() method, I've added an additional field to structure CombatResults, and I've updated MobileKiller so that each combatant's best (highest) Attack Roll result from the combat gets stored in the combatant's CombatResults member.

So that the hero can grow more powerful over time, I've added three integer members: mana, prowess, and vigor. I've overridden the Hero class' Attack_Roll() and Defense_Roll() methods, so that instead of an upper bound of 20 applying to the roll generated, the upper bound is instead (20+Prowess).

The test program, testgo9.exe, demonstrates invocation of a method — GetAttackRolls() — I added to Moba that allows code to pass an integer parameter and that will then return an integer array with parameter elements, each containing a value generated by the instance's Attack_Roll() method.

Adding class Room to GameObjs.cs

For this lesson, I've updated GameObjs.cs with a Room class whose members are all private and readonly. We only set them via the constructor. We do, however, allow their values to be retrieved by a getter.

A Room consists of a Title and Description of type string, an ID of type Int, and six members are of type Exit, a structure that holds exit information. I provide two Exit constructors. The second one takes a bool value and creates an Exit instance whose LeadsTo property is set to -1 and whose IsVisible property is set to false. We use that constructor to construct an instance of what I call nilExit in testGameObjs7.cs. The nilExit can then be stored in any of the room's six Exit properties for which no exit should exist (see testGameObjs7.cs and/or compile and run testgo7.exe to see this).

The first Exit constructor takes nine parameters that are used to instantiate a Room object.

If you peruse the code, you'll find that the Exit structure contains information on the type of exit (or portal) we're dealing with (regular doorway, bulkhead on a ship, hole in the ground), the ID of the room to which the Exit links, a byte value coding any effects extant upon the room, and an IsVisible property (normally set to true, but might be false due to magic, blindness, etc.)

I've added a couple of methods to ConsoleInputOutput.cs that have been useful to me in the past and for which I can foresee a use in this game:

Zooming out a little

So far, we've been developing Heroes in a rather MUD (Multi-User Dungeon)-like vein. However, this doesn't mean we won't use a considerable amount of procedural coding when we begin implementing quests, etc. Not all combat will be random encounters with mobiles.

Nevertheless, a significant portion of the game code is, and will be, object-oriented. Since we've been spending so much time on developing class Moba (and most recently, on class MobileKiller), let's zoom out a little and talk about the broader game implementation.

We're going to define areas — zones containing a set number of rooms. And we're going to derive a sealed class from Moba and name it Hero. The hero instance will have a CurrentRoom property that holds a Room instance and informs Seesh Arp what he sees, whether or not there is an exit to the south, etc.

And that room property might have members that can either be set to null or to mobiles (instances of Moba). Funny, huh, how the hero instance can contain the very mobile with which Seesh Arp interacts?

We'll do quite a bit of development of Hero, because it represents the player-of-the-game's alter-ego, the in-game protagonist, Seesh Arp. Our instance of this special class will be instantiated in Main() of heroes.cs and will last throughout the entirety of the program's execution (except in the Main Menu, of course, which is the last watering hole for the user before casting off into the gameworld).

You may find it very helpful to download either or both of the freeware programs ExamDiff and TextCompare, and use them at the end of each of these lessons to get a better idea what exactly has changed in the various source code files. For example, it would be instrumental, for this lesson, to use one of these tools to visually see the differences between the GameObjs.cs source code file at the end of the previous lesson with the same source code file at the end of this lesson.

For example, TextCompare, when you use the "New and Changed Lines" option for comparison, shows exactly the changes I made to GameObjs.cs for this lesson, and even gives the line numbers. But ExamDiff goes one better. It lets me copy/paste those lines. In the following listing, rather than show the totality of GameObjs.cs, I show only what has been added to GameObjs.cs during this lesson.:

In a nutshell, we now have a CombatRunner() that not only determines the victor when two mobiles fight, but also tracks the total damage inflicted by each mobile, how many attacks each mobile landed on its foes, the hit percentage of each mobile, and the average damage each mobile inflicted on his foe over the courses of the combat.

All of that, and we then store this data in both the victor and defeated mobile, as a member variable of type CombatResults. That member's name is MobileCombatResults. We even added a ReportCombatResults() method to our Moba class, as demonstrated in our testgo6.exe test application.

Approximating a functional CombatRunner()

The text of this lesson will be brief. Basically, I made some tweaks to Moba and then expanded the CombatRunner() method into a first approximation of a functional combat runner. The testgo5.exe that can be built from this lesson's progress demonstrates this new functionality. If you want to build the testgo5.exe test application yourself, you can download the build folder here.

Here's a screen shot of the test app in action:

By the way, you can specify an integer between 250 and 10,000 as a command-line argument when running testgo5.exe in the command prompt, and the value you specify will translate into the number of milliseconds of delay between each 'step' in the CombatRunner()'s processing of the combat.

More thoughts toward a Combat Runner

This lesson will be brief. All I do in it is add a CombatRunner() method to the MobileKiller class in source code file GameObjs.cs, and then build an example in testGameObjs4.cs to show its functionality.

You can either copy and paste code from the following two listings, or use the link at the end of this article to download the updated source code files, then extract them into your heroes directory.

Build the testgo4.exe test app. Run it multiple times at the command line. Notice that the axeman and swordman always have different GUIDs. Notice that our mock-up CombatRunner() method demonstrates the viability of passing a method two Moba instances and returning the GUID of the combat victor.

Now save GameObjs.cs and testGameObjs3.cs. Switch to the command prompt and build. Run testgo3.exe and choose Menu Item #4:

So, what have we accomplished so far in this lesson? Well, we now have a way to cause a Moba instance to recalculate its MaxHitPoints. This is useful because the default Moba constructor sets Constitution to 1. In the case of example 4, I didn't want the hassle of having to specify each and every member variables' value, so I used the default constructor to create my dragon instance. Then I changed the mobile's Constitution value to 20. At that point in the code, though, the dragon's MaxHitPoints still reflect a Constitution value of 1. So I called the RecalculateMaxHitPoints() method. Handy!

Note also the use of SetDescriptor() in our Example #4 code. I didn't want a mighty dragon's A_Descriptor to be an empty string, after all!

Class MobileKiller is the third class to be added to GameObjs.cs. That source code file now contains Moba, MobaArgs, and MobileKiller classes.

Beginning to think about Combat

Beginning to think about combat. Hmm. What data do we need in order for two mobile actors (instances of class Moba) to engage one another in combat?

We'd need to know their current lifeforce ("HitPoints" in our implementation). They'd need some way to determine if their attacks succeed or fail. Well, we already have the attack_roll() method that can generate a random integer between 1 and 20, inclusive. How, then, to determine, whether that result is good enough to get through the defending mobile actor's defenses?

Ah! We should add a defense_roll() method to class Moba. And we'll make it (and go back and retroactively make attack_roll()) virtual, in case we want a child class to override it at some point.

Now, should a result of 1 on the d20 roll be considered a failure, regardless of the the value of variable Level that is added to it? Such that a Level 30 mobile fighting a Level 1 commoner fails to hurt the commoner, even when the Level 30 mobile's total is higher than the defender's?

If a d20 result of 1 is a critical failure, then the 31 rolled by the aggressor above fails to harm the Level 01 mobile, even though the defender's defense roll — 5 — is much lower than the aggressor's attack roll.

Likewise, should a d20 result of 20 be considered a critical success, a success so good that it gets through the opponent's defenses, regardless of their defense roll?

If a 1 is a critical failure, then the Commoner in the example above does injure the Lv30 mobile, even though the Commoner's total normally would be insufficient..

The correct answer: sometimes. For certain mobiles, perhaps for those of type Swordmaster, a d20=1 result might not indicate a critical failure, even though for less skillful individuals it would certainly indicate failure. Likewise, for some mobiles, perhaps those of type HouseFly, a d20=20 result might not indicate a critical success, even though for many mobiles it would so indicate.

Given the reasoning delineated above, why not add a couple of boolean members to class Moba to act as flags? Okay, so we do that:

And then we'll need to add getters and setters for those:

In our default constructor, we'll set both these flags to false:

However, to update the overloaded constructor that takes a MobaArgs instance as a parameter, we will first need to update the MobaArgs class. I've circled additions to the code of class MobaArgs:

Then, back in class Moba, I've updated the member variables section with our two booleans:

And here are the updated default and overloaded constructors:

After adding code to testGameObjs3.cs to implement the 3rd menu item (remember that the previous lesson used up menu items 1 and 2), I saved changes to testGameObjs3.cs and GameObjs.cs and built the project.

Now do you see why I created the MobaArgs helper class? Already we have a dozen member variables to set for each instance of Moba, and we'll doubtless add more in future lessons. MobaArgs gives us a way to build up an object containing all the needed parameters for Moba over several lines of code:

We now can specify whether any particular instance of Moba has one or both critical flags set to true. And we have added a defense_roll() method. We have everything we need for basic combat, so we'll progress in that direction in the next lesson.

If you prefer, you can download a zip file containing the updated GameObjs.cs and testGameObjs3.cs source code files. Just extract them into your heroes directory.

You can download the source files for this lesson by clicking here. The archive of source code files and response files needed for building testgo3.exe (our third test app for game objects) can be downloaded by clicking here. It includes code in GameObjs.cs for menu item #4 discussed in the next lesson.

Further GameObjs.cs development

Welcome to Lesson 18.

The first thing you should do is move testGameObjs2.cs and testGameObjs2.rsp to subdirectory testcode. Keep a copy of our second GameObjs.dll testing app, testgo2.exe. Put it wherever you want, but save it in case we wanna see the distribution of d20 attack rolls later.

Have you noticed the voluminous text that scrolls past when you run your build.bat file in the command prompt? Do this: open the response file for each of your source code files and add the /nologo switch. Here are the listings, respectively, for heroes.rsp, ConsoleInputOutput.rsp, ConsoleColors.rsp, GameObjs.rsp, and testGameObjs3.rsp (whose *.cs source code file you'll create — or download — below):

Save those five modified response files, then build again. Much less text. Nicer:

Delete method conShowMultiColor4Words() from ConsoleInputOutput.cs. It's a kludge. If we need its functionality, we'll use a series of conShow() method invocations to get it. Save the source code file.

Next, create a new test app's source code file, and name it testGameObjs3.cs; here is the skeleton code for that file, with the first two menu items taken up by examples making use of our new Moba class (whose code listing appears further down this article).

If you prefer, download the testGameObjs3.cs source file for this lesson here, and extract it into your heroes directory:

Did you notice the static class MethodChecker in the code above? It has a method, HasMethod(), which uses reflection to tell us whether or not the object we pass as a parameter contains a method with the name we've passed in as a string. Will be useful as we continue, though we don't actually use it in this lesson.

Here's an annotated screen shot of testGameObjs.cs in Notepad++:

We showed the contents of our test app's response file in a previous listing. Go into your build.bat file and change line 4 so that it references the new response file:

Take some time and study the code in testGameObjs3.cs and GameObjs.cs. Note that class MobaArgs is basically just a helper class for Moba, making it easier to specify all ten data points needed to declare an instance of Moba.

Thus far, we have the option of declaring a Moba instance using the default constructor or the overloaded constructor that takes an instance of MobaArgs as a parameter.

In the next lesson, we will begin to think about how two instances of our mobile actor (i.e., of our Moba class) can engage in combat with one another.

We'll continue to add to testGameObjs3.cs as we test our GameObjs.cs code.

After modifying our parent class Creature and creating those two child classes in GameObjs.cs, and after modifying the method in ConsoleInputOutput.cs, save the files.

Let's take a look at the child classes. HarmlessCreature inherits from Creature and in its constructor modifies the value that the base class assigns to A_Descriptor, making the new descriptor more apropos to a weak, harmless creature. And it overrides the MakeSound() method so that the harmless creature sorta sounds harmless too.

DangerousCreature inherits a descriptor and a default age from its parent class, just as HarmlessCreature does. DangerousCreature's constructor set the descriptor to something a bit more sinister. It overrides the MakeSound() method so that the noise a dangerous creature produces sounds dangerous.

And class DangerousCreature adds this method called attack_roll(). Hmm, interesting...

Now let's add some code in our testGameObjs2.cs source file, and put these creatures through their paces. I chose to make a bit more involved test app this time. I borrowed menuing code from heroes.cs. The entire testGameObjs2.cs file is over 130 lines long. The easiest thing is to download the completed testGameObjs2.cs file by clicking here; and then, move it into your heroes directory. However, should you wish to copy/paste and then fiddle with formatting stuff in Notepad++, use the listing below:

The first three menu items in the test app demonstrate the behavior of instances of our parent class and its two children. The fourth menu item demonstrates the power of the attack_roll() method that is a member of the DangerousCreature class. This method is quite powerful. It generates a random integer between 1 and 20, inclusive. We will use this and similarly randomly generated numbers to implement a combat system (not much) later in our game's development.