refactoring so that multiple changes can be made with one variable?

J

John Salerno

My code is below. For now I'm focusing on the lines where health (and
armor) are increased in each character class. Let's say I decided to
change the amount of increase in the future. As it is now, I'd have to
go to each character class and change the number so that each is still
in a good relation to the other (right now: 3, 2, 1; later: perhaps 4,
3, 2, 1, if I added a new class -- i.e., change Fighter from 3 to 4,
Thief from 2 to 3, in other words increase them all by 1). So instead of
changing each one, is there a simple, clean way of just changing a
single number so that this change is reflected in all classes? Hope that
makes sense.



class Character(object):
def __init__(self, name, strength, dexterity, intelligence):
self.name = name
self.health = 10
self.armor = self.attack = self.defense = self.magic_attack = \
self.magic_defense = 0
self.strength = strength
self.dexterity = dexterity
self.intelligence = intelligence
self.adjust_attributes()

def adjust_attributes(self):
pass


class Fighter(Character):
def adjust_attributes(self):
self.health += 3
self.armor += 3
self.attack += 2
self.defense += 2
self.strength += 1


class Thief(Character):
def adjust_attributes(self):
self.health += 2
self.armor += 2
self.attack += 1
self.defense += 1
self.magic_defense += 1
self.dexterity += 1


class Mage(Character):
def adjust_attributes(self):
self.health += 1
self.armor += 1
self.magic_attack += 2
self.magic_defense += 2
self.intelligence += 1
 
P

Paddy

John said:
My code is below. For now I'm focusing on the lines where health (and
armor) are increased in each character class. Let's say I decided to
change the amount of increase in the future. As it is now, I'd have to
go to each character class and change the number so that each is still
in a good relation to the other (right now: 3, 2, 1; later: perhaps 4,
3, 2, 1, if I added a new class -- i.e., change Fighter from 3 to 4,
Thief from 2 to 3, in other words increase them all by 1). So instead of
changing each one, is there a simple, clean way of just changing a
single number so that this change is reflected in all classes? Hope that
makes sense.
You could keep a handle on all object instances created then go through
the objects making appropriate changes, e.g:


class Character(object):
instances = []
def __init__(self, name, strength, dexterity, intelligence):
instances.append(self)
# as before ...
def mod_instances(self):
for inst in instances:
inst.some_property += 1 # or whatever
# (Untested)

- Paddy.
 
J

John Salerno

Paddy said:
You could keep a handle on all object instances created then go through
the objects making appropriate changes, e.g:


class Character(object):
instances = []
def __init__(self, name, strength, dexterity, intelligence):
instances.append(self)
# as before ...
def mod_instances(self):
for inst in instances:
inst.some_property += 1 # or whatever
# (Untested)

But doesn't this require that the change be predetermined so you can
code it into the method?

I don't necessarily need a programmatic way to do this, just a simple
way to go back to the code and edit a single thing, instead of having to
update all the numbers.
 
J

James Stroud

John said:
My code is below. For now I'm focusing on the lines where health (and
armor) are increased in each character class. Let's say I decided to
change the amount of increase in the future. As it is now, I'd have to
go to each character class and change the number so that each is still
in a good relation to the other (right now: 3, 2, 1; later: perhaps 4,
3, 2, 1, if I added a new class -- i.e., change Fighter from 3 to 4,
Thief from 2 to 3, in other words increase them all by 1). So instead of
changing each one, is there a simple, clean way of just changing a
single number so that this change is reflected in all classes? Hope that
makes sense.



class Character(object):
def __init__(self, name, strength, dexterity, intelligence):
self.name = name
self.health = 10
self.armor = self.attack = self.defense = self.magic_attack = \
self.magic_defense = 0
self.strength = strength
self.dexterity = dexterity
self.intelligence = intelligence
self.adjust_attributes()

def adjust_attributes(self):
pass


class Fighter(Character):
def adjust_attributes(self):
self.health += 3
self.armor += 3
self.attack += 2
self.defense += 2
self.strength += 1


class Thief(Character):
def adjust_attributes(self):
self.health += 2
self.armor += 2
self.attack += 1
self.defense += 1
self.magic_defense += 1
self.dexterity += 1


class Mage(Character):
def adjust_attributes(self):
self.health += 1
self.armor += 1
self.magic_attack += 2
self.magic_defense += 2
self.intelligence += 1

The place to do this seems to be in the Character class.

class Character(object):
_health_base_inc = 1
_armor_base_inc = 1
# etc
def __init__(self, name, strength, dexterity, intelligence):
self.name = name
self.health = 10
self.armor = self.attack = self.defense = self.magic_attack = \
self.magic_defense = 0
self.strength = strength
self.dexterity = dexterity
self.intelligence = intelligence
self.adjust_attributes()

def adjust_attributes(self):
pass

class Mage(Character):
# for symmetry with Character
_health_char_inc = 1
_armor_char_inc = 1
# etc
def adjust_attributes(self):
self.health += self._health_char_inc + self_health_base_inc
self.armor += self._armor_char_inc + self._armor_base_inc
# etc

James

--
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com/
 
P

Paddy

John said:
Paddy said:
You could keep a handle on all object instances created then go through
the objects making appropriate changes, e.g:


class Character(object):
instances = []
def __init__(self, name, strength, dexterity, intelligence):
instances.append(self)
# as before ...
def mod_instances(self):
for inst in instances:
inst.some_property += 1 # or whatever
# (Untested)

But doesn't this require that the change be predetermined so you can
code it into the method?

I don't necessarily need a programmatic way to do this, just a simple
way to go back to the code and edit a single thing, instead of having to
update all the numbers.

I just put in a simple version of mod_instances. mod_instances could
take a function as an argument the napply the function to all
instances, e.g:

def mod_func1(inst):
inst.abc += inst.xyz # or whatever

class Character(object):
instances = []
def __init__(self, name, strength, dexterity, intelligence):
instances.append(self)
# as before ...
def mod_instances(self, mod_func):
for inst in instances:
mod_func(inst)

You can define different mod_func, like mod_func1 to make whatever
changes to the instances.
 
D

Dennis Lee Bieber

My code is below. For now I'm focusing on the lines where health (and
armor) are increased in each character class. Let's say I decided to

Allow me to ask something silly -- but since your "adjust" methods
are written en-bloc, would it be safe to presume your RPG is
"level-based" (ie, all characteristics go up when the character
increases a level)? (My favored, though unplayed in yes, RPG was NOT
level-based; any characteristic/skill could change state independent of
any other).

If so, you might want to consider making the characteristics
"read-only" properties, with the getter method computing the
characteristic as: ch = base_ch + ( (level-1) * level_increment)
change the amount of increase in the future. As it is now, I'd have to
go to each character class and change the number so that each is still
in a good relation to the other (right now: 3, 2, 1; later: perhaps 4,
3, 2, 1, if I added a new class -- i.e., change Fighter from 3 to 4,
Thief from 2 to 3, in other words increase them all by 1). So instead of
changing each one, is there a simple, clean way of just changing a
single number so that this change is reflected in all classes? Hope that
makes sense.
Sounds more like you are trying to undo the whole reason for
creating a class hierarchy in the first place -- that is to get
/independent/ pluggable components; you are now trying to cross-couple
the classes: having each class know something about the others'
internals. <snip>

Peruse this variation (and watch out for line wrapping):

-=-=-=-=-=-=-=-
class Character(object):
_level_inc = {"health" : 0,
"armor" : 0,
"attack" : 0,
"defense" : 0,
"magic_attack" : 0,
"magic_defense" : 0,
"dexterity" : 0,
"intelligence" : 0,
"strength" : 0 }
_base_adjustments = {"health" : 0,
"armor" : 0,
"attack" : 0,
"defense" : 0,
"magic_attack" : 0,
"magic_defense" : 0,
"dexterity" : 0,
"intelligence" : 0,
"strength" : 0 }
def __init__(self, name, strength, dexterity, intelligence):
self.name = name
self._health = 10
self._strength = strength
self._dexterity = dexterity
self._intelligence = intelligence
self._level = 0
self._armor = 0
self._attack = 0
self._defense = 0
self._magic_attack = 0
self._magic_defense = 0
self._adjust_attributes()

def _adjust_attributes(self):
for k, v in self._base_adjustments.items():
attr = "_%s" % k
setattr(self, attr, getattr(self, attr) +
self._base_adjustments[k])

def _gethealth(self):
return self._health + self._level * self._level_inc["health"]
health = property(_gethealth, None, None)

def _getarmor(self):
return self._armor + self._level * self._level_inc["armor"]
armor = property(_getarmor, None, None)

def _getattack(self):
return self._attack + self._level * self._level_inc["attack"]
attack = property(_getattack, None, None)

def _getdefense(self):
return self._defense + self._level * self._level_inc["defense"]
defense = property(_getdefense, None, None)

def _getmagic_attack(self):
return self._magic_attack + self._level *
self._level_inc["magic_attack"]
magic_attack = property(_getmagic_attack, None, None)

def _getmagic_defense(self):
return self._magic_defense + self._level *
self._level_inc["magic_defense"]
magic_defense = property(_getmagic_defense, None, None)

def _getstrength(self):
return self._strength + self._level *
self._level_inc["strength"]
strength = property(_getstrength, None, None)

def _getdexterity(self):
return self._dexterity + self._level *
self._level_inc["dexterity"]
dexterity = property(_getdexterity, None, None)

def _getintelligence(self):
return self._intelligence + self._level *
self._level_inc["intelligence"]
intelligence = property(_getintelligence, None, None)

def _getlevel(self):
return self._level + 1
level = property(_getlevel, None, None)

def levelUp(self):
self._level += 1

def __str__(self):
return """%s\t\tLevel:\t%s
Health:\t%s
Armor:\t%s
Attack:\t%s
Defense:\t%s
Magic Attack:\t%s
Magic Defense:\t%s
Dexterity:\t%s
Intelligence:\t%s
Strength:\t%s\n""" % (self.name, self.level,
self.health,
self.armor,
self.attack,
self.defense,
self.magic_attack,
self.magic_defense,
self.dexterity,
self.intelligence,
self.strength)

class Fighter(Character):
_level_inc = {"health" : 2,
"armor" : 2,
"attack" :2,
"defense" : 2,
"magic_attack" : 0,
"magic_defense" : 0,
"dexterity" : 0,
"intelligence" : 0,
"strength" : 1 }
_base_adjustments = {"health" : 3,
"armor" : 3,
"attack" : 3,
"defense" : 2,
"magic_attack" : 0,
"magic_defense" : 0,
"dexterity" : 0,
"intelligence" : 0,
"strength" : 1 }

class Thief(Character):
_level_inc = {"health" : 1,
"armor" : 1,
"attack" : 1,
"defense" : 1,
"magic_attack" : 0,
"magic_defense" : 1,
"dexterity" : 1,
"intelligence" : 0,
"strength" : 0 }
_base_adjustments = {"health" : 2,
"armor" : 2,
"attack" : 1,
"defense" : 1,
"magic_attack" : 0,
"magic_defense" : 1,
"dexterity" : 1,
"intelligence" : 0,
"strength" : 0 }

class Mage(Character):
_level_inc = {"health" : 1,
"armor" : 1,
"attack" : 0,
"defense" : 0,
"magic_attack" : 1,
"magic_defense" : 1,
"dexterity" : 0,
"intelligence" : 1,
"strength" : 0 }
_base_adjustments = {"health" : 1,
"armor" : 1,
"attack" : 0,
"defense" : 0,
"magic_attack" : 2,
"magic_defense" : 2,
"dexterity" : 0,
"intelligence" : 1,
"strength" : 0 }

if __name__ == "__main__":
fighter = Fighter("Wulfraed", 10, 10, 8)
thief = Thief("R'k'n", 8, 13, 11)
mage = Mage("Draigarian", 5, 11, 15)

print fighter
print thief
print mage

fighter.levelUp()
fighter.levelUp()
thief.levelUp()
mage.levelUp()

print fighter
print thief
print mage
-=-=-=-=-=-=-=-

Notice how each subclass, currently, is JUST a pair of dictionaries,
listing the base (creation) adjustment values, and the value increases
per level (my assumption, but it shows how things can be done). All the
code that affects those characteristics is still in the base class.

You could probably store those dictionaries in some file and have an
initialization function the uses the class name to populate the class
with the proper dictionary. That would put those, at least, into one
easily edited location... Maybe an INI file using config_parser?

[_base_adjustment]
attr = value
.....

[_fighter_adjustment]
attr = value
.....
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
 
S

Steven D'Aprano

My code is below. For now I'm focusing on the lines where health (and
armor) are increased in each character class. Let's say I decided to
change the amount of increase in the future. As it is now, I'd have to
go to each character class and change the number so that each is still
in a good relation to the other (right now: 3, 2, 1; later: perhaps 4,
3, 2, 1, if I added a new class -- i.e., change Fighter from 3 to 4,
Thief from 2 to 3, in other words increase them all by 1). So instead of
changing each one, is there a simple, clean way of just changing a
single number so that this change is reflected in all classes? Hope that
makes sense.

Cutting your code down to the minimum that exhibits the behaviour you want:

class Character(object):
def __init__(self, name, strength, dexterity, intelligence):
self.name = name
self.health = 10
# and so on...
self.adjust_attributes()
def adjust_attributes(self):
pass


class Fighter(Character):
def adjust_attributes(self):
self.health += 3

class Thief(Character):
def adjust_attributes(self):
self.health += 2

etc.

Sounds like you want some sort of factory function that returns a class:

# WARNING: untested
def make_character_class(name, adjustments):
class klass(Character):
_adjustments = {}
def adjust_attributes(self):
for key, value in self._adjustments.items():
x = getattr(self, key)
setattr(self, key, x + item)

setattr(klass, klass.__name__, name)
setattr(klass, klass._adjustments, adjustments)
return klass


And now you use it like this:

Fighter = make_character_class('Fighter', {'health': 3})
Thief = make_character_class('Thief', {'health': 2})

Now you can easily change the adjustments, all in just a few lines.

Here's another idea:

character_adjustments = { 'Fighter': {'health': 3},
'Thief': {'health': 2},
'Mage': {'intelligence': 3, 'strength': -1}
}

and change the make_character_class factory function above to only take a
single argument, name. Now if you decide you want to *programmatically*
adjust the adjustments, you can do this:

# still untested...
for key in character_adjustments:
for attribute, value in key.items():
# adjust the values to make up for my poor choices
character_adjustments[key][attribute] = value + 1

(but of course you must do this BEFORE creating your character classes!)


And last but most certainly not least, you can separate the adjustment
values into (say) an INI file, read them in at run-time and pass those
values to the factory function above. Then write another function which
walks through the INI file, adjusting the values in place as needed. This
is obviously going to take the most work, so I strongly suggest you don't
go down this path unless you really have to.
 
D

Dennis Lee Bieber

And last but most certainly not least, you can separate the adjustment
values into (say) an INI file, read them in at run-time and pass those
values to the factory function above. Then write another function which
walks through the INI file, adjusting the values in place as needed. This
is obviously going to take the most work, so I strongly suggest you don't
go down this path unless you really have to.

Nice to see I wasn't the only one to consider extending to an INI
file for this <G>
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
 
N

Neil Cerutti

Nice to see I wasn't the only one to consider extending to an
INI file for this <G>

Coincidentally, I'm just reading "The Pragmattic Programmer" for
the first time. One of guidelines is to pry the details ut of the
code if they might change. The above advice seems like a perfect
example.
 

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

Forum statistics

Threads
473,744
Messages
2,569,480
Members
44,900
Latest member
Nell636132

Latest Threads

Top