In [49]:
import math

class Weapon(object):
    def __init__(self, name, s, w_inf, a, ap = 0, rngd = False):
        # Mandatory fields
        self.name = name
        self.s = float(s)
        self.w_inf = float(w_inf)
        self.a = float(a)
        self.ap = float(ap)
        self.rngd = rngd
        
        # Optional fields
        self.is_psn = False
        self.psn = 7.0
        
        self.is_rndm_inf = False
        self.rndm_inf = 6.0
        
        self.is_rr_hits = False
        self.is_rr_w = False
        
    # weapon can reroll to wounds
    def set_rr_w(self):
        self.is_rr_w = True
        
    # weapon can reroll to hits
    def set_rr_hits(self):
        self.is_rr_hits = True
        
    # the weapon always wounds on a roll of psn
    def set_psn(self, psn):
        self.psn = psn
        self.is_psn = True
        
    def avg_w(self):
        if self.is_rndm_inf:
            return (1.0 + self.rndm_inf) / 2.0
        return self.w_inf
        
    # str >= 2* tough => 2+
    # str > tough => 3+
    # str = tough => 4+
    # str < tough => 5+
    # 2*str <= tough => 6+
    def probability_to_wound(self, t):
        s = self.s
        wound_on = 5.0
        if self.is_psn:
            wound_on = self.psn
        elif s >= 2*t:
            wound_on = 2.0
        elif s > t:
            wound_on = 3.0
        elif s == t:
            wound_on = 4.0
        elif 2*s < t:
            wound_on = 6.0
            
        basic = (7.0 - wound_on) / 6.0
        p_wound = basic
        if self.is_rr_w:
            p_wound = 1.0 - (1.0 - basic) * (1.0 - basic)
            
        return p_wound

# Define the stat sheet for offense
class Stats(Weapon):
    def __init__(self, name, ws, bs, s, t, w, a, sv):
        # weapon fields
        self.name = name
        self.s = float(s)
        self.w_inf = 1.0
        self.a = float(a)
        self.ap = 0.0
        self.rngd = False
        # Optional weapon fields
        self.is_psn = False
        self.psn = 7.0
        self.is_rndm_inf = False
        self.rndm_inf = 6.0
        self.is_rr_hits = False
        self.is_rr_w = False
        
        # required fields
        self.ws = float(ws)
        self.bs = float(bs)
        self.t = float(t)
        self.w = float(w)
        self.sv = float(sv)
        
        # invulnerable save
        self.inv = 7.0
        self.has_inv = False
        
        # wound recovery
        self.has_w_ig = False
        self.w_ig = 7.0
        
        # by default we assume melee so use characters stat block
        self.weapon = self
        
    def set_inv(self, inv):
        self.inv = inv
        self.has_inv = True
        
    def set_weapon(self, weapon):
        self.weapon = weapon
        
    # set the chance to ignore the wound
    def set_w_ig(self, w_ig):
        self.has_w_ig = True
        self.w_ig = w_ig
        
    def probability_to_hit(self):
        atk = self.ws
        if self.weapon.rngd:
            atk = self.bs
        
        basic = (7.0 - atk) / 6.0
        p_hit = basic
        if self.weapon.is_rr_hits:
            p_hit = 1.0 - (1.0 - basic) * (1.0 - basic)
        
        return p_hit
    
    
    def probability_to_save(self, attacker):
        # determine save
        save = self.sv + attacker.weapon.ap
        if self.has_inv:
            if self.inv < save:
                save = self.inv
                    
        return (7.0 - save) / 6.0
    
    
    def probability_to_ignore_wound(self):
        if self.has_w_ig:
            return (7.0 - self.w_ig) / 6.0
        
        return 0.0
        
    def mean_attacks_to_kill(self, defender):
        # probability of hitting
        prob_hit = self.probability_to_hit()
        
        # probability of wounding
        t = defender.t
        
        p_w = self.probability_to_wound(t)
        prob_ignore = defender.probability_to_ignore_wound()
        prob_wound = p_w - p_w * prob_ignore
        
        # probability of saving against the wound
        prob_save = defender.probability_to_save(self)
        
        attacks_to_dmg = int(1.0 / (prob_hit * prob_wound * (1.0 - prob_save)))
        dmg_to_kill = int(math.ceil(defender.w / self.weapon.avg_w()))
        attacks_to_kill = attacks_to_dmg * dmg_to_kill
        print "Probability of " + self.name + " hitting " + defender.name + ": " + str(prob_hit) + "\nProbability of wounding: " + str(prob_wound) + "\nProbability of saving: " + str(prob_save) + "\nProbability of ignoring: " + str(prob_ignore) + "\nTotal attacks to kill: " + str(attacks_to_kill)
        return attacks_to_kill
    
    def models_to_kill(self, defender):
        matk = self.mean_attacks_to_kill(defender)
        mtk = int(math.ceil(matk / self.weapon.a))
        print "Number of " + str(self.name) + " models requied to kill one " + str(defender.name) + " in one turn is " + str(mtk)
        return mtk

In [45]:
wych_in_combat = Stats(name = "Wych", ws = 3, bs = 3, s = 3, t = 3, w = 1, a = 2, sv = 6)
wych_in_combat.set_inv(4)
wych_in_combat.set_w_ig(6)

necron_warrior = Stats(name = "Necron Warrior", ws = 3, bs = 3, s = 4, t = 4, w = 1, a = 1, sv = 4)

In [46]:
wych_in_combat.models_to_kill(necron_warrior)


Probability of Wych hitting Necron Warrior: 0.666666666667
Probability of wounding: 0.333333333333
Probability of saving: 0.5
Probability of ignoring: 0.0
Total attacks to kill: 9
Number of Wych models requied to kill one Necron Warrior in one turn is 5
Out[46]:
5

In [47]:
necron_warrior.models_to_kill(wych_in_combat)


Probability of Necron Warrior hitting Wych: 0.666666666667
Probability of wounding: 0.555555555556
Probability of saving: 0.5
Probability of ignoring: 0.166666666667
Total attacks to kill: 5
Number of Necron Warrior models requied to kill one Wych in one turn is 5
Out[47]:
5

In [52]:
hekatrix = Stats(name = "Wych Hekatrix", ws = 3, bs = 3, s = 3, t = 3, w = 1, a = 3, sv = 6)
hekatrix.set_inv(4)
hekatrix.set_w_ig(6)

agonizer = Weapon(name = "Agonizer", s = 3, w_inf = 1, a = 3, ap = 2)
agonizer.set_psn(4)
hekatrix.set_weapon(agonizer)

In [53]:
hekatrix.mean_attacks_to_kill(necron_warrior)


Probability of Wych Hekatrix hitting Necron Warrior: 0.666666666667
Probability of wounding: 0.333333333333
Probability of saving: 0.166666666667
Probability of ignoring: 0.0
Total attacks to kill: 5
Out[53]:
5

In [ ]: