Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using inheritance and polymorphism to solve a common game problem

I have two classes; let's call them Ogre and Wizard. (All fields are public to make the example easier to type in.)

public class Ogre
{
  int weight;
  int height;
  int axeLength;
}

public class Wizard
{
  int age;
  int IQ;
  int height;
}

In each class I can create a method called, say, battle() that will determine who will win if an Ogre meets and Ogre or a Wizard meets a Wizard. Here's an example. If an Ogre meets an Ogre, the heavier one wins. But if the weight is the same, the one with the longer axe wins.

public Ogre battle(Ogre o)
{
  if (this.height > o.height) return this;
  else if (this.height < o.height) return o;
  else if (this.axeLength > o.axeLength) return this;
  else if (this.axeLength < o.axeLength) return o;
  else return this;    // default case
}

We can make a similar method for Wizards.

But what if a Wizard meets an Ogre? We could of course make a method for that, comparing, say, just the heights.

public Wizard battle(Ogre o)
{
  if (this.height > o.height) return this;
  else if (this.height < o.height) return o;
  else return this;
}

And we'd make a similar one for Ogres that meet Wizard. But things get out of hand if we have to add more character types to the program.

This is where I get stuck. One obvious solution is to create a Character class with the common traits. Ogre and Wizard inherit from the Character and extend it to include the other traits that define each one.

public class Character
{
  int height;

  public Character battle(Character c)
  {
    if (this.height > c.height) return this;
    else if (this.height < c.height) return c;
    else return this;
  }
}

Is there a better way to organize the classes? I've looked at the strategy pattern and the mediator pattern, but I'm not sure how either of them (if any) could help here. My goal is to reach some kind of common battle method, so that if an Ogre meets an Ogre it uses the Ogre-vs-Ogre battle, but if an Ogre meets a Wizard, it uses a more generic one. Further, what if the characters that meet share no common traits? How can we decide who wins a battle?

Edit: Lots of great responses! I need to digest them and figure out which one works best for my situation.

like image 829
Barry Brown Avatar asked May 04 '10 20:05

Barry Brown


4 Answers

The visitor pattern "is a way of separating an algorithm from an object structure it operates on".

For your example, you can have

class Character {
    boolean battle(BattleVisitor visitor) {
       return visitor.visit(this);
    }
}

class Ogre extends Character {..}
class Wizard extends Character {..}
class Dwarf extends Character {..}

interface BattleVisitor {
    boolean visit(Ogre character);
    boolean visit(Wizard character);
    boolean visit(Dwarf character);
}

class OgreBattleVisitor implements BattleVisitor {
    private Ogre ogre;
    OgreBattleVisitor(Ogre ogre) { this.ogre = ogre; }
    boolean visit(Ogre ogre) {
      // define the battle 
    }

    boolean visit(Wizard wizard) {
      // define the battle 
    }
    ...
}

And whenever a fight occurs:

targetChar.battle(new OgreBattleVisitor(ogre));

Define a Visitor implementation for a Wizard and a Dwarf and whatever appears. Also note that I define the result of the visit method to be boolean (won or lost) rather than to return the winner.

Thus, when adding new types, you will have to add:

  • a method to the visitor to handle fighting the new type.
  • an implementation for handling the fights for the new type

Now, here it turns out that you will have some duplication of code in case "Ogre vs Wizard" == "Wizard vs Ogre". I don't know if this is the case - for example there might be a difference depending on who strikes first. Also, you may want to provide totally different algorithm for, let's say "Swamp battle with ogre" compared to "village battle with ogre". Thus you can create a new visitor (or a hierarchy of visitors) and apply the appropriate one whenever needed.

like image 79
Bozho Avatar answered Oct 23 '22 21:10

Bozho


What should happen in the case where two Ogres have the same height/weight and the same axe-length? According to your example, the one that was lucky enough to get called first would win.

I don't know if this is a suitable alternative, but what if you went for a completely different scheme and attributed a "battle-score" to each character instead of relying on comparing individual traits. You could use a character's attributes in a formula to give some integer that can be compared to another character's. Then you could use a generic battle method to compare the two scores and return the character with the higher one.

For example, what if an Ogre's "battle-score" was calculated by his height plus his weight times his axe-length and a Wizard's score was calculated by his age multiplied by his IQ?

abstract class Character {
    public abstract int battleScore();

    public Character battle(Character c1, Character c2) {
        (c1.battleScore() > c2.battleScore()) ? return c1 : c2;
    }
}

class Ogre extends Character {
    public int battleScore() {
        return (height + weight) * axeLength;
    }
 }

 class Wizard extends Character {
    public int battleScore() {
        return height + (age * IQ);
    }
 }
like image 7
Skrud Avatar answered Oct 23 '22 23:10

Skrud


Sounds like you want double dispatch.

Basically your Ogre and Wizard would (probably) have a common base. When you call the battle method from Ogre's base it takes in the base of Wizard and invokes another function on that base that takes the Ogre as an argument. The polymorphic behaviour on both function calls effectively gives you polymorphism on the two types simultaneously.

like image 5
Troubadour Avatar answered Oct 23 '22 21:10

Troubadour


What about separating the battle logic into its own class, with methods like

Battle(Ogre ogre, Wizard wizard)

Which would return an object containing the winner (or the winner itself, whatever). This would separate the battle logic from the battlers, and also allow you to generercize, i.e.:

Battle(Creature creat1, Creature creat2)

Would be a fallback method for any creature pairing (assuming Wizard/Ogre/etc all have 'Creature' as a base class) that doesn't have specific logic. This would allow you to add/edit/remove battle logic without modifying any of the creatures themselves.

like image 4
Matthew Groves Avatar answered Oct 23 '22 23:10

Matthew Groves