DDD: interaction between 2 objects in a RPG-game

Posted on

Problem

Context

PlayerHouse is a place where the Player (the game character) can refill its health, store its inventory, etc. like in a RPG-game. The user can upgrade it for example to refill player’s health faster and store more inventory items.

Entity in question: PlayerHouse

class PlayerHouse {
    
    private int moneyRequiredForUpgrade;
    private int stonesRequiredForUpgrade;
    private int nextUpgradeExperience;
    private Player player;
    private int defence;
    private int rooms;

    public void upgrade() {
        if (player.getMoney() < moneyRequiredForUpgrade) {
            throw new Exception("Not enough money");
        }
        if (player.getStones() < stonesRequiredForUpgrade) {
            throw new Exception("Not enough stones");
        }
        player.decreaseMoney(moneyRequiredForUpgrade);
        player.decreaseStones(stonesRequiredForUpgrade);
        player.increaseExperience(nextUpgradeExperience);

        moneyRequiredForUpgrade += 100;
        stonesRequiredForUpgrade += 100;
        nextUpgradeExperience += 100;

        this.defence++;
        this.rooms++;
    }

}

This entity has a method that affects its own properties and properties of the Player entity.

Is it the correct way to implement interactions?
I don’t like that I can call Player methods outside PlayerHouse entity.

Alternative: PlayerHouse

Should I create result objects to change Player stats like that:

class PlayerHouse {
    
    public void upgrade() {
       this.defence++;
       this.rooms++;
       this.player.update(new UpgradeResult(someMoney, someStones, someExperience));
    }
}

What is the right name for update method then?

Is there any better solution?

Solution

Domain-Driven (Game) Design

Naming and terminology of a game (and other applications) follows a convention (depending on its genre).

Objects are typically expressed by nouns (N) like: Health and Player, Item, Inventory, House, Room, Location.

Methods are typically expressed by verbs (V) like: refill, regenerate, store, add, move, enter, etc.

Depending on the narrative (story / theme) of your game these terms may vary. Same applies for designing other (business-)applications, where terminology is driven by the subject / domain and it’s language used locally (by users).

Language from the context provided (narrative)

From what you mentioned in the question:

  • player (N, actor)
  • player’s house (N, location)
  • store (V) item (N) to inventory (N, has items)
  • refill (V) health (N, points) of player (N)

Following from what you mentioned in the question’s comment:

Interaction between Player and PlayersHouse

  • user can trigger the Player to purchase (V) an upgrade (A) of its owned PlayersHouse (N)

If Player has enough stones (stones for next upgrade) and money (price for next upgrade), then PlayerHouse will be upgraded.

After the first upgrade these values will become bigger.
There should be a check in the beginning of the upgrade method, if the Player has:

  • enough amount of (resources for upgrading)
  • and enough money

Properties of Player

  • house (N, associated state: exactly 1 unique PlayersHouse) owned by each player
  • stones (N, countable) required for next upgrade
  • money (N, countable) required for next upgrade

Properties of PlayerHouse

  • defence (N, points) increases 1 by each upgrade
  • rooms (N, countable) increases 1 by each upgrade

Properties of UpgradeLevel:

Note: I added this object or noun (N) to save the state of upgrades separately.

  • upgrade experience (N, points) increases 100 by upgrade
  • amount/cost/price of next upgrade:
    • money (N, countable, see Player’s money) increases 100 by upgrade
    • stones (N, countable, see Player’s stones) increases 100 by upgrade

Operations on UpgradeLevel:

  • increase-level, level-up (V) will increase each of its properties (experience, money, stones) by 100

Operations on Player

  • purchase an upgrade (V) or build-up (V) their house (O)

Operations on PlayerHouse

  • upgrade or build-up (V) to next level (associated state of the house)

Design of classes

Note: For simplicity I would recommend to start with the class design. A next iteration could make these classes extensible (Open-Closed principle in SOLID). This can be achieved (as other answer suggests) by making them implement interfaces. Interfaces act as contract which defines the interaction between two or more classes. We say they become “loosely-coupled” which means the dependency between them is weakened/reduced. Less dependency means more freedom for evolution in the future (e.g. other upgrades of other objects, etc.).

Player

class Player {
   // properties that define state or association

   final PlayersHouse house = new PlayersHouse(); // each player starts with his own new unique house

   // below could also be stored as items in Player's inventory
   Integer stones;
   Integer money;


  // operations/methods

  public void refillHealth() {
    this.healhPoints += house.drawRegnerationHealthPoints();
  }

  public void buyHouseUpgrage() { // or "buildUpHouse"
    var spendAll = new Amount(this.money, this.stones);
    if ( house.upgradeAvailableFor(spendAll) ) {
      house.buyUpgrade(spendAll); // or buildUp or addExperience
    }
  }
}

Player’s House

class PlayersHouse {
  
  private static final int REGENERATION_HP = 100;
  
  final Inventory inventory = new Inventory(); // initial empty collection of items
  
  // all initial with 0
  Integer healthPoints; // like a tank
  Integer rooms
  Integer defencePoints; // assumed to defend the house (not player if outside)


  public boolean store(Item item) {
    return inventory.add(item);
  }

  public int drawRegenerationHealhPoints() {
    if (this.healthPoints() <= REGENERATION_HP) {
      return this.healthPoints();
    }

    this.healthPoints -= REGENERATION_HP;

    return  REGENERATION_HP;
  }

  public boolean upgradeAvailableFor(UpgradeAmount amount) {
    return amount.compare(this.nextUpgrade.getPrice()) > 0;
  }

  public void buyUpgrade(UpgradeAmount amount) {
     if (!upgradeAvailableFor(amount)) {
       throw new Exception("Upgrade costs " + this.nextUpgradePrice);
     }

     amount.minus(this.nextUpgrade.getPrice());
     this.rooms++;
     this.defencePoints++
     this.nextUpgrade.levelUp();
  }
}

Left out

Some classes and fine-grained design as well further interactions I left up to you:

  • Amount (as cost for building up(grade) the house of the player)
  • next Upgrade Level (to raise and increase, the first time and later, the difficulty & gain of an upgrade)

Analyze interactions & terminology

When nouns (N) and verbs (V) have been defined, and your objects have been designed with state and behavior, you can start to analyze and design their interaction.

Some of these interactions will ask a questions to define the relation and cooperation between classes or interfaces:

  • “upgrade” asks for a narrative: where does it come from (e.g. can the player purchase an upgrade ?)
  • “stones” asks for the verb: How many stones build a new room (role/purpose) ?
  • “experience” asks for the trigger: When does it level up?

if you have two entities that have not so much in common i suggest to avoid strong coupling!

any method that refers to the other entity looks wrong to me:

House.upgrade(Player player)

Player.upgrade(House house)

even though you don’t use the Entities as parameter in your method (it’s not obvious in your code where it comes from) you have this strong coupling.

Instead i would recommend you to create an Object that handles exactly this relation.
create a class UpgradeAction – doing so you break up the coupling.

and by doing so you create a light-weight object, that is single responsible for upgrading.

class UpgradeAction {

    UpgradeAction(Player player, House house){} //dependency Injection

    void execute(){
        player.upgrade();
        house.upgrade();
    }
}

go farther:

let House and Player implement an interface named Upgradable and make your UpgradeAction class even more independent – as long as Player and House implement this interface you will never be required to change the UpgradeAction class again.

class UpgradeAction{

    UpgradeAction(Upgradable... upgradables){} 

    void execute(){
        upgradables.forEach(Upgrade::upgrade);
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *