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 interface
s. 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);
}
}