What's the best way to write this base class?

J

John Salerno

Let's say I'm writing a game (really I'm just practicing OOP) and I
want to create a "Character" base class, which more specific classes
will subclass, such as Warrior, Wizard, etc. Which of the following
ways is better, or is there another way?

Note: I have in mind that when a specific subclass (Warrior, Wizard,
etc.) is created, the only argument that will ever be passed to the
__init__ method is the name. The other variables will never be
explicitly passed, but will be set during initialization. With that in
mind, here are the ways I've come up with:

1)
class Character:

def __init__(self, name, base_health=50, base_resource=10):
self.name = name
self.health = base_health
self.resource = base_resource

2)
class Character:

base_health = 50
base_resource = 10

def __init__(self, name):
self.name = name
self.health = base_health
self.resource = base_resource

3)
BASE_HEALTH = 50
BASE_RESOURCE = 10

class Character:

def __init__(self, name):
self.name = name
self.health = BASE_HEALTH
self.resource = BASE_RESOURCE
 
C

Chris Angelico

1)
class Character:

   def __init__(self, name, base_health=50, base_resource=10):
       self.name = name
       self.health = base_health
       self.resource = base_resource

If you expect to override the health/resource, I'd use this model.

ChrisA
 
B

bruno.desthuilliers

Note: I have in mind that when a specific subclass (Warrior, Wizard,
etc.) is created, the only argument that will ever be passed to the
__init__ method is the name. The other variables will never be
explicitly passed, but will be set during initialization.

__init__ is actually supposed to be the initialization phase, but well
1)
class Character:

If you using Python 2.x, make this:

class Character(object):
    def __init__(self, name, base_health=50, base_resource=10):
        self.name = name
        self.health = base_health
        self.resource = base_resource


If neither base_health nor base_resource are supposed to be passed in,
why make them arguments at all:

class Character(object):
def __init__(self, name):
self.name = name
self.health = 50
self.resource = 10


2)
class Character:

    base_health = 50
    base_resource = 10

    def __init__(self, name):
        self.name = name
        self.health = base_health
        self.resource = base_resource

Did you at least tried this one ? Hint: it won't work.
3)
BASE_HEALTH = 50
BASE_RESOURCE = 10

class Character:

    def __init__(self, name):
        self.name = name
        self.health = BASE_HEALTH
        self.resource = BASE_RESOURCE

This is probably what I'd do.
 
T

Tim Chase

Did you at least tried this one ? Hint: it won't work.

If you want it, you can use

self.health = Character.base_health

Though I'd treat them as semi-constants and capitalize them like
your 3rd case:

class Character(object):
BASE_HEALTH = 50
...
def __init__(...):
...
self.health = Character.BASE_HEALTH

-tkc
 
B

bruno.desthuilliers

If you want it, you can use

   self.health = Character.base_health

Though I'd treat them as semi-constants and capitalize them like
your 3rd case:

   class Character(object):
     BASE_HEALTH = 50
     ...
     def __init__(...):
       ...
       self.health = Character.BASE_HEALTH


If you go that way, then using polymorphic dispatch might (or not,
depending on the game's rules <g>) be a good idea:


class Character(object):
BASE_HEALTH = 50
...
def __init__(self, name):
...
self.health = type(self).BASE_HEALTH


This would allow different Character subclasses to have different
BASE_HEALTH etc..., defaulting to the base class values.
 
M

Mel

John Salerno wrote:
[ ... ]
1)
class Character:
def __init__(self, name, base_health=50, base_resource=10):
self.name = name
self.health = base_health
self.resource = base_resource

2)
class Character:
base_health = 50
base_resource = 10
def __init__(self, name):
self.name = name
self.health = base_health
self.resource = base_resource

3)
BASE_HEALTH = 50
BASE_RESOURCE = 10
class Character:
def __init__(self, name):
self.name = name
self.health = BASE_HEALTH
self.resource = BASE_RESOURCE

For completeness, there's also 4)

Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information..... health = 50
.... def __init__ (self, name):
.... self.name = name
.... print self.name, self.health
....Eunice 50


where the class attribute is used until it's overridden in the instance.

Mel.
 
I

Ian Kelly

If you go that way, then using polymorphic dispatch might (or not,
depending on the game's rules <g>) be a good idea:


class Character(object):
   BASE_HEALTH = 50
   ...
   def __init__(self, name):
       ...
       self.health = type(self).BASE_HEALTH

This of course is equivalent to a simple "self.health =
self.BASE_HEALTH" as long as you haven't explicitly assigned
BASE_HEALTH on the instance.

Tangentially, I wouldn't use inheritance at all for this game. I know
the classic "is-a / has-a" test says that a wizard "is a" character,
but in my experience that method leans toward doing way too much
inheritance. If you have subclasses for character classes, then you
will be tempted to also use subclasses for races, and then when you're
ready to make elf wizards you will have forced yourself into a
multiple inheritance situation, and down that path wait Agony and
Despair.

Instead, I would use composition here. A character has a class (e.g.
Wizard(specialization='fire')) and a race (e.g. Elf(breed='high') --
or maybe just HighElf(), which inherits from Elf). Save inheritance
for broad categories of what it means to be a character (e.g.
PlayerCharacter vs. NonPlayerCharacter or MobileCharacter vs.
MagicMirror, etc., any of which might have the Wizard character
class).

Cheers,
Ian
 
E

Ethan Furman

John said:
1)
class Character:

def __init__(self, name, base_health=50, base_resource=10):
self.name = name
self.health = base_health
self.resource = base_resource

You said above that health and resource will never be explicitly passed,
yet here you have allowed for that possibility. If you are going to
have mosters, etc, also inherit from Character, with different health
and resources, I would go this route with one change:

def __init__(self, name, base_health, base_resoures):

and always specify those numbers on creation.

2)
class Character:

base_health = 50
base_resource = 10

def __init__(self, name):
self.name = name
self.health = base_health
self.resource = base_resource

You do not need to assign health and resource here -- they are already
assigned on the class, so the instance will see them automatically.
When a change is made, the instance will automagically get its own copy.

3)
BASE_HEALTH = 50
BASE_RESOURCE = 10

class Character:

def __init__(self, name):
self.name = name
self.health = BASE_HEALTH
self.resource = BASE_RESOURCE

If *all* characters (player, non-player, monster, etc) will have the
same base health and resources then this is fine -- otherwise I would
use option 1.

~Ethan~
 
J

John Salerno

Whew, thanks for all the responses! I will think about it carefully
and decide on a way. I was leaning toward simply assigning the health,
resource, etc. variables in the __init__ method, like this:

def __init__(self, name):
self.name = name
self.health = 50
self.resource = 10

I never did like the idea of using the parameters if I never intended
to pass them in...just seems wrong. :)

The idea of not using a base Character class at all threw me for a
loop though, so I need to think about that too!
 
C

Chris Angelico

The idea of not using a base Character class at all threw me for a
loop though, so I need to think about that too!

It's easy to fall in love with a concept like inheritance, and use it
in all sorts of things. You then have a choice to make: Is the project
you're writing primarily for its own sake, or primarily so that you
can explore the programming concept? There's nothing wrong with
building a mediocre game on a mediocre basis and using it solely to
play around with OO and inheritance and class structures, but if you
want it to be a good game, sometimes you need to go back on decisions
like that.

And that's where mailing lists like this are awesome. I've learned so
much from the wisdom here... there is an amazing amount of expertise
being offered freely!

Chris Angelico
 
C

Chris Kaynor

Whew, thanks for all the responses! I will think about it carefully
and decide on a way. I was leaning toward simply assigning the health,
resource, etc. variables in the __init__ method, like this:

def __init__(self, name):
self.name = name
self.health = 50
self.resource = 10

I never did like the idea of using the parameters if I never intended
to pass them in...just seems wrong. :)

The idea of not using a base Character class at all threw me for a
loop though, so I need to think about that too!

Having a character class (along with possibly player character, non-player character, etc), make sense; however you probably want to make stuff like health, resources, damage, and any other attributes not be handles by any classes or inheritance in order to allow you to make such data-driven (ie, read from a file). Doing so makes the game much more extendable: using classes, you are likely limited to 5 or 'combinations and a few developers (plus, any designers need to know programming).

A basic way to determine between using subclasses over a data driven approach is: is there significantly different back-end behavior or merely attributedifferences.
 
J

John Salerno

Having a character class (along with possibly player character, non-player character, etc), make sense; however you probably want to make stuff likehealth, resources, damage, and any other attributes not be handles by any classes or inheritance in order to allow you to make such data-driven (ie, read from a file). Doing so makes the game much more extendable: using classes, you are likely limited to 5 or 'combinations and a few developers (plus, any designers need to know programming).

A basic way to determine between using subclasses over a data driven approach is: is there significantly different back-end behavior or merely attribute differences.

Can you give a basic example of how this data-driven approach would
work? You don't have to provide any code, just a description would be
helpful. Such as, do I create a data file per character, and then have
each character instance read/write to that file? Is it good to have so
many files open at once, or would they only need to be read, closed,
then opened again at the end to write?
 
B

Benjamin Kaplan

Can you give a basic example of how this data-driven approach would
work? You don't have to provide any code, just a description would be
helpful. Such as, do I create a data file per character, and then have
each character instance read/write to that file? Is it good to have so
many files open at once, or would they only need to be read, closed,
then opened again at the end to write?
--

I'm pretty sure he means that if the only difference between classes
is configuration (i.e. you aren't actually going to change code
between character classes, just base stats, growth rates, and a list
of available skills or something of that nature), then you should
store the configurations in a config file rather than making a new
class. So rather than having
class WizardCharacter(Character) :
base_health = 50
...
class WarriorCharacter(Character) :
base_health=70
...
You make a config file

--- characterclasses.ini ---
[Wizard]
base_health=50
[Warrior]
base_health=70

Then, when you make a new character, rather than doing a
WizardCharacter() or a WarriorCharacter(), you do a
Character(job='Wizard') and then look up the various defaults in your
config file. Doing it this way makes it trivial to add a new class. If
you want to use an old-fashioned INI file, you can use the
ConfigParser class to read them. If you want to nest attributes (for
instance, a list of sub-items), you'll probably want to go with XML
and ElementTree. I guess you can also use JSON (which uses a syntax
similar to Python's dictionaries) but I've never really tried to make
one of those by hand before so I'm not sure how well it will work out.
 
M

Mel

John said:
Can you give a basic example of how this data-driven approach would
work? You don't have to provide any code, just a description would be
helpful. Such as, do I create a data file per character, and then have
each character instance read/write to that file? Is it good to have so
many files open at once, or would they only need to be read, closed,
then opened again at the end to write?

Battle for Wesnoth is set up this way. I don't know what the code does, but
you can go wild creating new classes of character by mixing up new
combinations of attribute settings in new configuration files, and injecting
them into the standard game config files.

AFAIK you are stuck with the attributes the game is programmed for. I've
seen no way to create a new dimension for the game -- Conversation, for
instance, with currently unknown attributes like vocabulary or tone.

Mel.
 
T

Terry Reedy

Can you give a basic example of how this data-driven approach would
work? You don't have to provide any code, just a description would be
helpful. Such as, do I create a data file per character, and then have
each character instance read/write to that file? Is it good to have so
many files open at once, or would they only need to be read, closed,
then opened again at the end to write?
--

I'm pretty sure he means that if the only difference between classes
is configuration (i.e. you aren't actually going to change code
between character classes, just base stats, growth rates, and a list
of available skills or something of that nature), then you should
store the configurations in a config file rather than making a new
class. So rather than having
class WizardCharacter(Character) :
base_health = 50
...
class WarriorCharacter(Character) :
base_health=70
...
You make a config file

--- characterclasses.ini ---
[Wizard]
base_health=50
int = 70
[Warrior]
base_health=70
int = 30

[Gandolf]
base_health = 60
int = 100

My point here being that with a data approach, non-programmers can also
define named, unique NPCs with custom stats as well as generic classes
 
I

Ian Kelly

Battle for Wesnoth is set up this way.  I don't know what the code does, but
you can go wild creating new classes of character by mixing up new
combinations of attribute settings in new configuration files, and injecting
them into the standard game config files.

AFAIK you are stuck with the attributes the game is programmed for.  I've
seen no way to create a new dimension for the game -- Conversation, for
instance, with currently unknown attributes like vocabulary or tone.

The Dwarf Fortress data files are also well worth taking a look at in
this regard. Virtually everything is configurable, from basic
attributes like size and language all the way down to intricate
details like types and quantity of body parts, what tissues said body
parts are made of and what roles they play. To get an idea of the
level of customization possible have a look at:

http://df.magmawiki.com/index.php/Modding#Modding_the_creatures
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,020
Latest member
GenesisGai

Latest Threads

Top