Mock Battle
So now that we have all of our classes and subclasses set up we should take a look at using them. We'll do this through code, and observe the output in the Corona console.
If you want to follow along, you can download the demo project which contains the "battle" code as well as all the classes in a Corona project.
Here I'm just going to point out some important parts of the code. You can view the full code further below.
Code Setup
At the start of the file we localize a few string
methods for output. You might notice we don't need to require
the Classy OOP plugin here. As a matter of fact we've only needed the plugin in a couple of the code class pages.
Classes
Characters
Now we bring in the Character classes that we created:
... local Mage = require("classes.Mage") local Warrior = require("classes.Warrior") ...
You should notice that we don't need to bring in the base Character class file. We only need the subclasses, as they already have references to the base Character class.
Weapons
We follow up with the Weapon classes:
... local Wand = require("classes.Wand") local Melee = require("classes.Melee") ...
Again, we don't need the base Weapon class file. We only need the subclasses.
Instances
Weapons
Now let's create our weapon instances. We are doing this first so that we can "equip" them on our upcoming character instances. We could of course equip them after the fact using the character class setWeapon
method, but this is just as well.
... local sword = Melee:new('Titan Blade', 'sword', 30) local axe = Melee:new('Gilded Axe', 'battle axe', 50) local wand = Wand:new('Enchanted Stick', 'wand', 10) ...
Above we are creating two Melee class weapons, and one Wand. The Weapon base class takes a name
, isType
, and attack
argument, which we pass for each of the instaces we created.
We now have three weapon instances that we can "equip" on our characters.
Characters
Building the character instances is very much the same as our weapons:
... local hero = Warrior:new('Thundar', 'warrior', 100, 100, sword) local boss = Warrior:new('Azarak', 'warrior', 200, 200, axe) local mage = Mage:new('Chalice', 'mage', 60, 60, wand, 100, 100) ...
Above we have created two warriors, one a hero and the other a boss (baddie). We also have a mage along with us.
The Warrior class needs a name
, isType
, health
, maxHealth
, and weapon
argument passed to it. The Mage is a special subclass that also takes a mana
and maxMana
argument along with the others.
As you can see, we pass the previously generated weapon instances directly to the character instances.
Output Methods
After our charactes and weapons there are a handful of utility methods to help print out the "battle" status as it takes place. These should be fairly self-explanatory.
There is one method that should be pointed out:
... local function attackStatus(attacker, attacked) local weapon = attacker:getWeapon() psf("%s attacks %s with %s for %d HP", attacker:getName(), attacked:getName(), weapon:getName(), weapon:getAttack()) --Specialized for Mage type if attacker:instanceOf(Mage) then psf("%s now has %d MP", attacker:getName(), attacker:getMana()) end end ...
In the method above, we are getting the "attack" status after an attack
method has been called in battle. Since a Mage has an extra mana
property, we need to treat it a little differently. We can use the instanceOf
method to do a type check (in this case for a Mage type) and then add our logic as needed. In this case printing out the remaining mana
.
Battle Methods
The "battle" itself takes place in a couple of rounds. We actually only use two methods through the entire thing, those being attack
and heal
.
The majority of the methods are used to print out the results after each method is called.
attack
The default attack
method is defined in our base Character class. It looks like this:
... function Character:attack(character) character:hit(self.weapon:getAttack()) end ...
The method is called like so:
<attacker>:attack(<attacked>)
In our "battle" code it ends up like this, depending on whose doing the attacking:
... hero:attack(boss) ...
The Mage character class is a little different to facilitate its mana
property. The default attack
method is overidden, and looks like so in the Mage class:
... --override parent method function Mage:attack(character) local weapon = self:getWeapon() self:useMana(weapon:getManaCost()) --Call parent attack method Mage.super.attack(self, character) end ...
In the overridden method, we want to deplete some of the mages mana
, and then we call the Mage.super.attack
method, which does the rest of the work of the default Character attack
method.
The attack
method is still used the same in the "battle" code:
... mage:attack(boss) ...
heal
The heal
method is defined in the base Character class, and looks like so:
... function Character:heal(amount) amount = self:getHealth() + amount self:setHealth(math.min(amount, self:getMaxHealth())) end ...
We simply call this method in the "battle" on the instance we want to heal, along with the amount
to heal with:
... mage:heal(20) ...
The "Battle Code
If you'd like to run the full project in Corona, and see the output, download the demo project. The main "battle" code is shown below:
--############################################################################# --# Classy OOP - Mock Battle Demo --# (c)2018 C. Byerley (develephant) --############################################################################# local sf = string.format local rep = string.rep --############################################################################# --# Character + Weapon Classes --############################################################################# local Mage = require("classes.Mage") local Warrior = require("classes.Warrior") local Wand = require("classes.Wand") local Melee = require("classes.Melee") --############################################################################# --# Create Weapon Instances --############################################################################# local sword = Melee:new('Titan Blade', 'sword', 30) local axe = Melee:new('Gilded Axe', 'battle axe', 50) local wand = Wand:new('Enchanted Stick', 'wand', 10) --############################################################################# --# Create Character Instances --############################################################################# local hero = Warrior:new('Thundar', 'warrior', 100, 100, sword) local boss = Warrior:new('Azarak', 'warrior', 200, 200, axe) local mage = Mage:new('Chalice', 'mage', 60, 60, wand, 100, 100) --############################################################################# --# Battle Messaging / Utilities --############################################################################# local psf = function(str, ...) print(sf(str, ...)) end local pblock = function(str) print(rep("#", 80)) print("# "..str) print(rep("#", 80)) end local function warriorPresence(warrior) psf("The %s %s has appeared with %d HP", warrior:getType(), warrior:getName(), warrior:getHealth()) local weapon = warrior:getWeapon() psf("%s welds %s, a %s with %d attack", warrior:getName(), weapon:getName(), weapon:getType(), weapon:getAttack()) end local function magePresence(mage) psf("The %s %s has appeared with %d HP and %d MP", mage:getType(), mage:getName(), mage:getHealth(), mage:getMana()) local weapon = mage:getWeapon() psf("%s welds %s, a %s with %d attack", mage:getName(), weapon:getName(), weapon:getType(), weapon:getAttack()) end local function attackStatus(attacker, attacked) local weapon = attacker:getWeapon() psf("%s attacks %s with %s for %d HP", attacker:getName(), attacked:getName(), weapon:getName(), weapon:getAttack()) --Specialized for Mage type if attacker:instanceOf(Mage) then psf("%s now has %d MP", attacker:getName(), attacker:getMana()) end end local function healStatus(character, amount) psf("%s heals for %d HP, now has %d HP.", character:getName(), amount, character:getHealth()) end local function isDead(character) if character:isDead() then psf("%s has been killed!", character:getName()) end end local function healthStatus(character) psf("%s now has %d HP", character:getName(), character:getHealth()) isDead(character) end pblock("Classy OOP - Mock Battle Demo") --############################################################################# --# Battle Start Introductions --############################################################################# pblock("Battle Start") --# Hero (warrior) warriorPresence(hero) --# Mage magePresence(mage) --# Boss (warrior) warriorPresence(boss) --############################################################################# --# Attack Round One --############################################################################# pblock("Round One") --# Mage attacks Boss mage:attack(boss) attackStatus(mage, boss) healthStatus(boss) --# Hero attacks Boss hero:attack(boss) attackStatus(hero, boss) healthStatus(boss) --# Boss attacks Mage boss:attack(mage) attackStatus(boss, mage) healthStatus(mage) --# Boss attacks Hero boss:attack(hero) attackStatus(boss, hero) healthStatus(hero) --############################################################################# --# Attack Round Two --############################################################################# pblock("Round Two") --# Mage attacks Boss mage:attack(boss) attackStatus(mage, boss) healthStatus(boss) --# Heal Mage mage:heal(20) healStatus(mage, 20) --# Hero attacks Boss hero:attack(boss) attackStatus(hero, boss) healthStatus(boss) --# Boss attacks Hero boss:attack(hero) attackStatus(boss, hero) healthStatus(hero) --# Boss attacks Mage boss:attack(mage) attackStatus(boss, mage) healthStatus(mage) pblock("Battle Done") --############################################################################# --# UI --############################################################################# display.newText({ text = "See console for demo output.", x = display.contentCenterX, y = display.contentCenterY, font = native.systemFontBold })