Skip to content

Instantly share code, notes, and snippets.

@wouterd
Created February 4, 2025 10:08
Show Gist options
  • Save wouterd/5e93c24b44008b754aea7f0b6e23a939 to your computer and use it in GitHub Desktop.
Save wouterd/5e93c24b44008b754aea7f0b6e23a939 to your computer and use it in GitHub Desktop.
A grav tank and 4 bikes attacking a land raider
import random
class DiceRolls:
def __init__(self, amount: int):
self.rolls = {
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
6: 0
}
for _ in range(amount):
roll = random.randint(1,6)
self.rolls[roll] = self.rolls[roll] + 1
def less_than(self, roll: int):
total = 0
for x in range(1, roll):
total = total + self.rolls[x]
return total
def plus(self, roll: int):
total = 0
for x in range(roll, 7):
total = total + self.rolls[x]
return total
def range(self, start: int, stop: int):
total = 0
for x in range(start, stop):
total = total + self.rolls[x]
return total
def print(self):
for x in range(1, 7):
print(f'{x}: {self.rolls[x]}')
class Target:
def __init__(self, wounds: int, toughness: int, save: int, invul: int):
self.wounds = wounds
self.toughness = toughness
self.save = save
self.invul = invul
def wounds_on(self, strength: int):
if self.toughness * 2 <= strength:
return 2
if self.toughness < strength:
return 3
if self.toughness == strength:
return 4
if self.toughness < 2 * strength:
return 5
if self.toughness >= 2 * strength:
return 6
def attack(self, amount: int, skill: int, strength: int, ap: int, dmg, crits_on = 6, lethal = False, sustained = False, twin_linked = False, wound_bonus = 0):
hits = 0
wounds = 0
hit_rolls = DiceRolls(amount)
hits = hit_rolls.range(skill, crits_on)
if lethal:
wounds = hit_rolls.plus(crits_on)
else:
hits = hits + hit_rolls.plus(crits_on)
if sustained:
hits += hit_rolls.plus(crits_on)
wound_rolls = DiceRolls(hits)
wounds_on = self.wounds_on(strength) - wound_bonus
if wounds_on < 2:
wounds_on = 2
wounds = wounds + wound_rolls.plus(wounds_on)
if twin_linked:
wound_rerolls = hits - wound_rolls.plus(self.wounds_on(strength))
rerolls = DiceRolls(wound_rerolls)
wounds += rerolls.plus(self.wounds_on(strength))
saves_on = self.save + ap
if saves_on < self.invul:
saves_on = self.invul
save_rolls = DiceRolls(wounds)
saves = save_rolls.plus(saves_on)
for _ in range(wounds - saves):
damage = dmg()
self.wounds -= damage
def grav_tank_damage():
return 2 + random.randint(1,6)
def salvo_damage():
return 1 + random.randint(1,6)
def lance_damage():
return 2
scenarios = 50000
target_died = 0
bikes_shot = 0
bikes_charged = 0
for _ in range(scenarios):
target = Target(16, 12, 2, 6)
target.attack(4, 2, 12, 3, grav_tank_damage, lethal=True, twin_linked=True)
if target.wounds > 0:
bikes_shot += 1
target.attack(4, 2, 10, 3, salvo_damage, twin_linked=True)
if target.wounds > 0:
bikes_charged += 1
target.attack(20, 2, 7, 2, lance_damage, crits_on=5, sustained=True, wound_bonus = 1)
if target.wounds <= 0:
target_died += 1
print(f'{target_died} targets died ({target_died / scenarios * 100}%)')
print(f'bikes shot: {bikes_shot} ({bikes_shot / scenarios * 100}%)')
print(f'bikes charged: {bikes_charged} ({bikes_charged / scenarios * 100}%)')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment