Introduction to my problem
I'm trying to design a very simple video game where there are several Animals
players (they all inherit from Animal
):
Cat
Dog
Rabbit
There are also some performable actions (only one per Animal
) they can perform such as:
Bite
: done only byDogs
(if an animal gets bitten, it will lose an amount of life)Kick
: done only byRabbits
(if an animal gets kicked they won't be able to move for X seconds)Scratch
: done only byCats
(if an animal gets scratched they will reduce their speed movement by -50% for Y seconds)
I was thinking about a design that could be extensible in the future: having new animals (or even new players such as aliens/plants/humans), actions and even being able to randomly assign an action to a player.
My solution so far
Interfaces
First of all, every player that would like to play the game, must implement the Playable
interface (In this case, Animal
would implement Playable
):
The Playable interface
interface Playable {
move(distance: number): void,
getPosition(): number,
attack(): void,
beingBitten(): void,
beingKicked(): void,
beingScratched(): void
}
The Performer interface
Also, I thought about having an interface called Performer
for every action a player could perform over another player:
interface Performer {
perform(enemy: Playable): void
}
Implementations
This way, our Rabbit
's .attack()
implementation would be something like the following:
class Rabbit extends Animal {
private action: Performer;
// ... etc ...
public attack(enemy: Playable): void {
action.perform(enemy);
}
// ... etc ...
}
And Kick
performable action would look something like this:
class Kick implements Performer {
public perform(enemy: Playable): void {
enemy.beingKicked();
}
}
My concerns
In terms of organization and being clearer, I was thinking that I could break the Playable
interface in three:
The Attacker interface
Actions related to attacking would go here.
interface Attacker { attack(enemy: Playable): void }
The Attackable interface
Actions related to being attackable would go here.
interface Attackable {
beingBitten(): void,
beingKicked(): void,
beingScratched(): void
}
The Playable interface
Actions related to being able to move around would go here.
interface Playable extends Attacker, Attackable {
move(distance: number): void,
getPosition(): number
}
I don't have that much experience at all when it comes to OOP, so every time I end up overthinking things and designing some different alternatives but I struggle in deciding which one I should go for.
How do you see all of this? Does it make sense at all splitting Playable
into three in order to make things clearer?
cat.attack(dog)
?dog.beAttackedBy(cat)
? Does it matter what specific attack is being dealt, to either party?beingAttackedBy(enemy: Playable)
can be nice to avoid having to extend theAttackable
interface and implementations each time a new action would be added. What I don't see here is the way to achieve that whensomeAnimal.beAttackedBy(otherAnimal)
happens, theotherAnimal
action is performed oversomeAnimal
. Any ideas?