1
\$\begingroup\$

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?

\$\endgroup\$
7
  • 4
    \$\begingroup\$ To design the interaction between Player and PlayerHouse it would help to express the relation or use-case in human language answering questions like: (a) What is a "PlayerHouse" ? (b) When does that change the "Player" ? Or even (c) Why are "Players" in a "house" ? Short: provide some domain-context of your game? \$\endgroup\$
    – hc_dev
    Jul 5, 2021 at 17:11
  • 1
    \$\begingroup\$ 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. \$\endgroup\$
    – user244597
    Jul 5, 2021 at 17:27
  • 1
    \$\begingroup\$ OK, added your answer to the question as context. This edit could have be done by you .. now please also tell from the game-play where does amounts of "some..." come from ? Which game-object (or engine or user) does trigger the "upgrade" of PlayerHouse ? If questions/answers come domain-driven then they will automatically contribute to a design that seems fit and comprehensible ;-) \$\endgroup\$
    – hc_dev
    Jul 5, 2021 at 18:09
  • 1
    \$\begingroup\$ Thank you. "Some" values are properties that belongs to PlayerHouse. After the first upgrade these values will become bigger. I didn't add the check if the Player has enough amount of stones (resources for upgrading) and money in the beginning of the upgrade method. The user can trigger upgrade and if Player has enough resources and money PlayerHouse will be upgraded. \$\endgroup\$
    – user244597
    Jul 6, 2021 at 4:57
  • 2
    \$\begingroup\$ This narrative you commented is actually DDD's event-storming, thanks! 👉️ Please edit your question and paste it there. Then I can reflect it in my answer 😀️ \$\endgroup\$
    – hc_dev
    Jul 6, 2021 at 10:52

2 Answers 2

2
\$\begingroup\$

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?
\$\endgroup\$
0
\$\begingroup\$

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);
    }
}
\$\endgroup\$
8
  • \$\begingroup\$ Thanks for your answer. Why do you think it is a strong coupling? One Player has one House. It's the one-to-one relationship. Other Player should not have an access to the first Player's House. Only the owner can upgrade his House. Upgrade method for the Player is a bit confusing. As an application client I can call this method and the Player will get some experience and lose some money without any changes of the Player's house, I can also call the upgrade method of the House entity and it's Player will not get any experience and will not spend any resources. \$\endgroup\$
    – user244597
    Jul 7, 2021 at 7:31
  • \$\begingroup\$ if you change the Player class it is very likely that you have to change the house class as well. that is strong coupling. you cannot test your house class, if you don't have a player class. try to keep things seperated. make them independ. doing so you make your code more SOLID (special here: the O part, open for change, closed for manipulation) (special here: the S part, single responsibility [not player & house ,but one of them]) \$\endgroup\$ Jul 7, 2021 at 8:18
  • \$\begingroup\$ for your special cases, on different upgrade actions: you would create seperate classes for each action or provide dedicated methods in the Action class. \$\endgroup\$ Jul 7, 2021 at 8:23
  • \$\begingroup\$ Doesn't this violate encapsulation principles? The Player and The House have public methods, but they are used only by the Action class. \$\endgroup\$
    – user244597
    Jul 7, 2021 at 8:43
  • 1
    \$\begingroup\$ Great OO-design discussion: would agree to all SOLID-points touched. Thanks to both of you, particularly for the event-storming (= building domain-context), so I could "upgrade" my answer from DDD/naming perspective (starting tightly coupled / classes only). \$\endgroup\$
    – hc_dev
    Jul 7, 2021 at 19:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.