views:

272

answers:

6

I'm making a game and one of the methods calculates a character's base hit numbers based on skill values. The method currently calculates each value individually, since each skill can be used at short, medium, and long range.

I originally thought I could combine the skills into a tuple and iterate over it, dynamically creating each hit number. But I don't know if it's actually possible, since I currently have each hit number assigned to it's own variable.

I also thought about creating a method for each range, and passing the tuple as an argument. I could create a new tuple or list with the resulting values and then assign them to the individual variables, but I don't see how it would be any better than do it this way, except that it won't look so copy & pasted.

Here's what I currently have:

    def calcBaseHitNumbers(self, dict):
        """Calculate character's base hit numbers depending on skill level."""

        self.skill_dict = dict

        self.rifle = self.skill_dict.get('CRM', 0)
        self.pistol = self.skill_dict.get('PST', 0)
        self.big_gun = self.skill_dict.get('LCG', 0)
        self.heavy_weapon = self.skill_dict.get('HW', 0)
        self.bow = self.skill_dict.get('LB', 0)
        #self.skill_tuple = (self.rifle, self.pistol, self.big_gun, self.heavy_weapon,
        #    self.bow)

#---Short range
##        for skill in self.skill_tuple:
##            self.base_hit_short = skill * 0.6
        self.charAttribs.bhCRM_short = self.rifle * 0.6
        self.charAttribs.bhPST_short = self.pistol * 0.6
        self.charAttribs.bhHW_short = self.heavy_weapon * 0.6
        self.charAttribs.bhLCG_short = self.big_gun * 0.6
        self.charAttribs.bhLB_short = self.bow * 0.6
#---Med range
        self.charAttribs.bhCRM_med = self.rifle * 0.3
        self.charAttribs.bhPST_med = self.pistol * 0.3
        self.charAttribs.bhHW_med = self.heavy_weapon * 0.3
        self.charAttribs.bhLCG_med = self.big_gun * 0.3
        self.charAttribs.bhLB_med = self.bow * 0.3
#---Long range
        self.charAttribs.bhCRM_long = self.rifle * 0.1
        self.charAttribs.bhPST_long = self.pistol * 0.1
        self.charAttribs.bhHW_long = self.heavy_weapon * 0.1
        self.charAttribs.bhLCG_long = self.big_gun * 0.1
        self.charAttribs.bhLB_long = self.bow * 0.1

How would you refactor this so it's more dynamic?


Edit: I guess what I want to do is something like this: Have a tuple (like the one I commented out) and iterate over it 3 times, each time making a new value (for each skill) based on the modifier for each particular range. The resulting value is then automatically assigned to it's respective variable.

In my head, it makes sense. But when I actually try to code it, I get lost. The problem, I think, is that this is the first "real" program I've written; all I've done before are small scripts.

This is only the 0.1 version of my program, so it's not critical to refactor it now. However, it seems very un-Pythonic to do this manually and I also want to "future-proof" this in case things change down the road.

A: 

@Vinko: perhaps make calcBaseHitNumbers, do the "if not self.calculatedBase:" check internally, and just no-op if it's been done before. That said, I can't see the pressing need for precalculating this information. But I'm no Python performance expert.

I deleted my answer as James' was better
Vinko Vrsalovic
You should add that as a comment if you won't propose an answer
Vinko Vrsalovic
I didn't have the rep to post comments when I wrote that.
+1  A: 

Lets see if I understand you scenario: each weapon has its own distinct hit point so a rifle may have 1, a heavy weapon may have 2 etc. Then each character has a short, medium and long value to be multiplied by the hit point of the weapon.

You should consider using a Strategy design. That is create a weapon superclass with a hit point property. Create sub class weapons for rifle, pistol, bow etc. I am sure that the differences between the weapons are more than just the hit points.

Then the Character has one or more weapons depending on your gameplay. To calculate the hit point for a particular weapon is as simple as

current_weapon * self.medium

If you decide to add more weapons later on then you do not have to edit your Character code because your character can handle any weapon.

In Pseudo Python

class Weapon
    hit = 1
    #other properties of weapon

class Rifle(Weapon)
    #other properties of Rifle

class Pistol(Weapon)
    #other properties of Pistol

class Character
   weapon = Rifle()
   long=0.6
   def calcHit()
      return self.long*weapon.hit

john = Character()
john.weapon= Rifle()
john.calcHit
Vincent Ramdhanie
A: 

What sense of dynamic do you mean? What is likely to vary - the number of skills, or the weighting factors, the number of ranges (short, med, long) or all of these?

What happens to the (e.g.) bhPST_* values afterwards - do they get combined into one number?

One thing that leaps out is that the list of skills is hardwired in the code - I would be inclined to replace the bh variables with a method

So (please take into account I don't know the first thing about Python :) )

def bh_short(self, key)
  skill = self.skill_dict.get(key, 0)
  return skill * 0.6

Now you can keep a list of skills that contribute to hit points and iterate over that calling bh_short etc.

Possibly also pass the range (long med short) unto the function, or return all three values - this all depends on what you're going to do next with the calculated hitpoints.

Basically, we need more information about the context this is to be used in

Paul
+6  A: 

It feels like what you really want is a class representing the weapon, with attributes to handle the base values and calculate hit values with various modifiers. Here's a simple example:

SHORT_RANGE = 'S'
MEDIUM_RANGE = 'M'
LONG_RANGE = 'L'
SHORT_RANGE_MODIFIER = 0.6
MEDIUM_RANGE_MODIFIER = 0.3
LONG_RANGE_MODIFIER = 0.1

class Weapon(object):
    def __init__(self, code_name, full_name, base_hit_value,
                 short_range_modifier=None, medium_range_modifier=None,
                 long_range_modifier=None):
        self.code_name, self.full_name = code_name, full_name
        self.base_hit_value = base_hit_value
        self.range_modifiers = {
            SHORT_RANGE: short_range_modifier or SHORT_RANGE_MODIFIER,
            MEDIUM_RANGE: medium_range_modifier or MEDIUM_RANGE_MODIFIER,
            LONG_RANGE: long_range_modifier or LONG_RANGE_MODIFIER,
        }

    def hit_value(self, range, modifier=1):
        return self.base_hit_value * self.range_modifiers[range] * modifier

From there, you might create instances of Weapon inside your Character object like so:

    self.rifle = Weapon('CRM', 'rifle', 5)
    self.pistol = Weapon('PST', 'pistol', 10)

And then if, say, the character fires the pistol at short range:

    hit_value = self.pistol.hit_value(SHORT_RANGE)

The extra argument to the hit_value() method can be used to pass in character- or situation-specific modifications.

Of course, the next step beyond this would be to directly model the weapons as subclasses of Weapon (perhaps breaking down into specific types of weapons, like guns, bows, grenades, etc., each with their own base values) and add an Inventory class to represent the weapons a character is carrying.

All of this is pretty standard, boring object-oriented design procedure, but for plenty of situations this type of thinking will get you off the ground quickly and provide at least a little bit of basic flexibility.

James Bennett
If precalculation is that important (I really doubt it, though), he can use memoization over the hit_value methods: http://code.activestate.com/recipes/52201/
Vinko Vrsalovic
A: 

Just a quick try, not optimized at all. But you should be able to add weapons and ranges or edit range modifiers at will.

weapon_skills = ('rifle', 'pistol', 'big_gun', 'heavy_weapon', 'bow')

ranges = [  {'name': 'short', 'modifier': 0.6, }, 
            {'name': 'medium', 'modifier': 0.3, }, 
            {'name': 'long', 'modifier': 0.1, },  
]


class Character():

  def __init__(self, wskills=None):
    self.weapon_skills = {}
    self.base_hit = {}
    for weapon in weapon_skills:
      self.weapon_skills[weapon] = 0
      self.base_hit[weapon] = self.calcBaseHit(0)
    if wskills != None:
      for weapon in wskills:
        self.weapon_skills[weapon] = wskills[weapon]
        self.base_hit[weapon] = self.calcBaseHit(wskills[weapon])

  def calcBaseHit(self, wskill):
    ret = []
    for rng in ranges:
      ret.append(wskill * rng['modifier'])
    return ret


def printWeaponSkills(charcter):
  for weapon in sorted(charcter.weapon_skills):
    base_hit_string = ' / '.join(str(x) for x in charcter.base_hit[weapon])
    print '%12s %d: %s' % (weapon, charcter.weapon_skills[weapon], base_hit_string)
  print

printWeaponSkills(Character())
printWeaponSkills(Character({'rifle': 3, 'pistol':5, 'big_gun':2, 'heavy_weapon':1}))
Brutus
A: 

I would have a class for the character's attributes (so you don't have heaps of things in the character class) and a class for a weapon's attributes:

class WeaponAttribute(object):

    short_mod = 0.6
    med_mod = 0.3
    long_mod = 0.1

    def __init__(self, base):
        self.base = base

    @property
    def short(self):
        return self.base * self.short_mod

    @property
    def med(self):
        return self.base * self.med_mod

    @property
    def long(self):
        return self.base * self.long_mod


class CharacterAttributes(object):

    def __init__(self, attributes):
        for weapon, base in attributes.items():
            setattr(self, weapon, WeaponAttribute(base))

Have a CharacterAttributes object in the character class and use it like this:

# Initialise
self.charAttribs = CharacterAttributes(self.skill_dict)
# Get some values
print self.charAttribs.CRM.short
print self.charAttribs.PST.med
print self.charAttribs.LCG.long
monopocalypse