import argparse
from dataclasses import dataclass
import random
import sys
MIN_ROLL = 1
MAX_ROLL = 100
OVERFLOW = 50
@dataclass
class CombatResult:
chealth1: int
chealth2: int
health1: int
health2: int
p1win: bool
p2win: bool
def mcarlo_combat(health1, health2, bonus1, bonus2, **kwargs):
chealth1 = health1
chealth2 = health2
overflow1 = 0
overflow2 = 0
while chealth1 > 0 and chealth2 > 0:
roll1 = random.randint(MIN_ROLL, MAX_ROLL) + bonus1
roll2 = random.randint(MIN_ROLL, MAX_ROLL) + bonus2
if roll1 > roll2:
overflow1 += roll1 - roll2
if overflow1 >= OVERFLOW:
damage = overflow1 / OVERFLOW
chealth2 -= damage
overflow1 -= OVERFLOW * damage
elif roll2 > roll1:
overflow2 += roll2 - roll1
if overflow2 >= OVERFLOW:
damage = overflow2 / OVERFLOW
chealth1 -= damage
overflow2 -= OVERFLOW * damage
return CombatResult(
chealth1=chealth1,
chealth2=chealth2,
health1=health1,
health2=health2,
p1win=chealth2<=0,
p2win=chealth1<=0,
)
def main() -> int:
parser = argparse.ArgumentParser(
prog='mcarlo_combat',
description='Monte Carlo simulation of combat outcomes',
)
parser.add_argument('--health1', '--h1', metavar='H1', type=int, help='Health for side 1')
parser.add_argument('--health2', '--h2', metavar='H2', type=int, help='Health for side 2')
parser.add_argument('--bonus1', '--b1', metavar='B1', type=int, default=0, help='Net bonus for side 1')
parser.add_argument('--bonus2', '--b2', metavar='B2', type=int, default=0, help='Net bonus for side 2')
parser.add_argument('--iterations', '-i', metavar='I', type=int, default=100000, help='Number of simulations to run.')
args = parser.parse_args()
vargs = vars(args)
p1wins = sum((1 for result in (mcarlo_combat(**vargs) for _ in range(args.iterations)) if result.p1win))
print(f'Side 1 wins {p1wins} of {args.iterations} simulations. Estimated {float(p1wins) / args.iterations * 100}% probability of victory.')
return 0
if __name__ == '__main__':
sys.exit(main())