diff options
Diffstat (limited to 'dlr/dlr.py')
-rw-r--r-- | dlr/dlr.py | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/dlr/dlr.py b/dlr/dlr.py new file mode 100644 index 0000000..40e2f3d --- /dev/null +++ b/dlr/dlr.py @@ -0,0 +1,96 @@ +# combat simulator for Warlords 3 Darklords Rising +import random + +# before combat +# add up curse of each side, 4% chance per curse per opponent to remove their bless (apply -1 str) and medals +# add up posion of each side, 4% chance per poison per opponent to apply -2 str (min 1) +# add up disease of each side, 4% chance per disease per opponent to apply -1 hits (min 1) +# add up paralysis of each side ... no effect on combat results, just on movement afterwards + +# calculate stack bonuses +# add your best morale bonus from unit, item, and spell - cap at +5, subtract enemy fear, cap at -1 +# repeat for fortify vs siege +# repeat for leadership vs chaos +# cap total between -3 and +5 + +# calculate unit bonuses +# base strength, cap at 9 +# add stack bonus, cap at 14 +# add item/spell to hero, cap at 15 +# add banding bonuses, cap at 15 + +# special attacks +# calculate net ward (unit's warding plus up to 3 group warding) +# attack with acid, 10% per (acid minus opponent's acid+ward) to halve opponent strength, rounded down +# if acid missed, attack with lightning, 10% per (lightning minus opponent's lightning+ward) to halve opponent hits, rounded down +# (it is arguably a bug that acid hits prevent lightning from happening) +# attack with assassin, 10% per (assassin minus opponent's assassin+ward) to instant kill +# make N missile attacks per combat. hits are instant kills, +3 str against fliers, 4-hit targets are immune if not flying + +class Unit: + strength = 1 + hits = 1 + medals = 0 + + def __init__(self, strength, hits): + self.strength = strength + self.hits = hits + +# normal combat +def calculate_fight(a, b, die_size=20): + # TODO medals + + # roll success odds + p_a = (a.strength / die_size) * (1 - b.strength / die_size) + p_b = (b.strength / die_size) * (1 - a.strength / die_size) + # round success odds + p_a_hits = p_a / (p_a + p_b) + p_b_hits = p_b / (p_a + p_b) + assert(0.999 < p_a_hits + p_b_hits < 1.001) + + return recurse_hits(p_a_hits, p_b_hits, a.hits, b.hits) + +def recurse_hits(p_a, p_b, h_a, h_b): + if h_a == 0: + return 0 + if h_b == 0: + return 1 + return p_a * recurse_hits(p_a, p_b, h_a, h_b - 1) + p_b * recurse_hits(p_a, p_b, h_a - 1, h_b) + + +def simulate_fight(a, b): + a_hits = a.hits + b_hits = b.hits + + while a_hits > 0 and b_hits > 0: + # attempt some rolls + a_roll = random.randint(1, 20) + if a.medals > 0: + a_roll = min(a_roll, random.randint(1, 34 - 4*a.medals)) + + b_roll = random.randint(1, 20) + if b.medals > 0: + b_roll = min(b_roll, random.randint(1, 34 - 4*b.medals)) + + if a_roll <= a.strength and not b_roll <= b.strength: + b_hits = b_hits - 1 + # check here for trample and slayer + elif b_roll <= b.strength and not a_roll <= a.strength: + a_hits = a_hits - 1 + # check here for trample and slayer + + if a_hits <= 0: + return 0 + else: + return 1 + +a = Unit(2,2) +b = Unit(2,1) + +a_wins = 0 +for i in range(10000): + a_wins = a_wins + simulate_fight(a, b) +calc_odds = calculate_fight(a, b) +print(f'{a.strength}/{a.hits} vs {b.strength}/{b.hits}') +print(f'monte carlo: {a_wins/100}%') +print(f'simulated: {calc_odds*100}%') |