blindluke has asked for the wisdom of the Perl Monks concerning the following question:

I'm playing around with a game concept. Here is my basic enemy.

package Enemy; use Moos; has wounds => (); has att_type => (); has def_type => (); sub BUILDARGS { my ($self, $type) = @_; my %monster = ( 'Rat' => { wounds => 1, att_type => 'light', def_type => 'dodge', }, ); die "Unknown monster type" unless exists $monster{$type}; $monster{$type}{'type'} = $type; return $monster{$type}; } 1; } my $monster = Enemy->new('Rat');

I'm having some trouble with the design of the attack / defense traits of an enemy. I would like to have multiple attacks with some statistics of their own. Something like this:

my %attack = ( basic_light => { type => 'light', damage => '1', defmod => { block + => '0', dodge => '0' } }, basic_heavy => { type => 'heavy', damage => '2', defmod => { block + => '0', dodge => '10' } }, );

During the fight, I will call something like:

$a = $attacker->choose_attack(); # with the intention of # $a == { type => 'light', damage => '1', defmod => { block => '0', do +dge => '0' } }

Still, I don't want to pass anything too complex to the constructor. I think that I have to keep the attack definitions within the Enemy class, and to have something to translate the attack type to a pattern.

For example: att_type => 'medium' would translate to an array of ( $attack{basic_light}, $attack{basic_heavy}), and the choose_attack() method would pick an attack from this array at random. Which would correspond to an enemy that hits heavy about 50% of the time.

How can I add it to the code in an elegant way? Should I drop the att_type and just initialize the monster with an attack pattern? I humbly seek your words of wisdom. Any advice will be welcome.

- Luke

Replies are listed 'Best First'.
Re: Game related OO design question
by McA (Priest) on Nov 27, 2014 at 14:08 UTC

    Hi,

    I'm not an OO expert (Toby, where are you?), but this line is the starting point for some questions:

    my $monster = Enemy->new('Rat');

    You instantiate an 'Enemy' object, but not every Enemy-Object will be the same because you can instatiate several 'types' of Enemy objects. But this is exactly what OO is for. When you have several 'types' (classes) of Enemy object why don't you declare them as being of that type knowing that they share a common behaviour (Enemy).

    This 'Rat' isa (certain) 'Enemy' relationship is exactly what inheritance or Roles are for.

    So, aks yourself the other way: When you have a variable $e and you know that this is a reference to an Enemy, would it not be better to know whether this Enemy is a Rat but can be used everywhere an Enemy is needed in terms of behaviour?

    So, without knowing the details, think about having a base class Enemy and descendants which specialize the behaviour in terms of wound, attack and defense behaviour.

    Another note: You can always create helper functions or class methods for simplifying or factoring out the instantiation of that objects (kind of factory classes/objects).

    I'm really interested in other comments and ideas.

    Regards
    McA

      Thank you for the reply. I'm probably going to subclass the Enemy, but only if the design shows that it's behaviour will extend upon the base class.

      Right now, all the enemies behave in exactly the same way, in fact - there is only one enemy available. And even if I add another type of monster, and want to know what monster it is, I think that it's better to extend the traits (and make the name one of them) than to introduce inheritance.

      If I find that I need to introduce a monster that does not choose it's attack at random, but follows an attack pattern like "first light, then light, then heavy" (and thus, has a choose_attack method that is different than the default), then this would be a good argument to introduce subclasses inheriting from Enemy. If not, I'll just initialize the same class, with the same behaviour, but with a different set of traits.

      Right now, I'm not concerned with this. I'm focusing on the combat flow, and on the question of introducing attack types into the Enemy object in an elegant way, with the choose_attack() method behaving in my desired way. This is my foremost concern.

      - Luke

Re: Game related OO design question
by choroba (Cardinal) on Nov 27, 2014 at 14:16 UTC
    Earlier this year, byterock had a blog series on D&D in Moose at blogs.perl.org.
    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      Thank you. I've not seen those posts before.

      Unfortunately it seems that byterock has abandoned the project before he implemented some game mechanics. But there is still much to read here, for example this neat idea.

      Again, thank you.

      - Luke

Re: Game related OO design question
by tobyink (Canon) on Nov 28, 2014 at 00:46 UTC

    Yeah, I concur with McA. Create a new class for each type of enemy.

    package Enemy { use Moo::Role; has wounds => (is => 'ro'); ...; } # Look how tiny these classes are. # Creating lots of classes doesn't have to be painful. package Rat { use Moo; with 'Enemy' } package Cat { use Moo; with 'Enemy' }

    Also, please don't use Moos for anything serious. I'm an officially listed as a co-author of it, so I'm entitled to say that. ;-)

    Use Moo, or Moose, or Moops. Here's my above example using Moops, showing how it eliminates a lot of boiler-plate code.

    use Moops; role Enemy { has wounds => (is => 'ro', isa => Int); } class Rat with Enemy; class Cat with Enemy;

    Let's extend that a little with some attack types:

    use Moops; class Attack { has name => (is => 'ro', isa => Str); has damage => (is => 'ro', isa => Int); method light ($class: ) { state $me = $class->new( name => 'light attack', damage => 1 ); return $me; } method heavy ($class: ) { state $me = $class->new( name => 'heavy attack', damage => 3 ); return $me; } } role Enemy { requires "get_attack"; has wounds => (is => 'ro'); method is_weak () { $self->wounds > 5 } } class Rat with Enemy { method get_attack (Object $target) { return Attack->light; } } class Cat with Enemy { has lives => (is => 'rw', isa => Int, default => 9); method get_attack (Object $target) { return Attack->heavy; } around is_weak () { return false if $self->lives > 2; return $self->$next(@_); } } class Monkey with Enemy { method get_attack (Object $target) { $target->is_weak || $self->is_weak ? Attack->light : Attack->heavy +; } } class Ninja with Enemy { method get_attack (Object $target) { return Attack->new(name => 'ninja attack', damage => int rand(5)); } } class Priest with Enemy { method get_attack (Object $target) { $target->is_sinful ? Attack->heavy : Attack->light; } }

    That kind of thing.

      This is excellent advice. And exactly what I was looking for - especially the design of the Attack class (and the idea to make it its own class, which eluded me completely). Thank you.

      Moops looks excellent indeed, and you are right that when syntactic sugar makes subclassing so cheap, it's no point not to use it. Still, if I could ask you one more question, I'm very curious about those words:

      Also, please don't use Moos for anything serious. I'm an officially listed as a co-author of it, so I'm entitled to say that. ;-)

      Could you elaborate on that a bit? What's wrong with Moos?

      Thanks again. And good luck with your book, I'm sure I'm not alone in waiting eagerly for its completion.

      - Luke

        I don't know of anything specifically wrong with Moos, but Moose and Moo are hundreds of times better tested. Moos is slightly lighter weight than Moo, but it barely seems worth worrying about Moo's fairly small dependency chain.

        If you do feel the need for something smaller than Moo, then Class::Tiny is probably the best solution.