# risk-calculator.py

Published
Last Updated
Author(s)
Enimihil
Tags
#code #game probabilities #python #risk

[Game of World Domination] battle probability calculator. A python script to calculate the success of conquering a territory.

```#!/usr/bin/env python

from __future__ import division
import sys

def cross(*args):
if not args:
yield []
return
for i in args:
for n in cross(*(args[1:])):
yield tuple([i] + list(n))

# http://linux.derkeiler.com/Mailing-Lists/Fedora/2005-11/4432.html
def combinations_r( list_of_items, number_of_picks ):
k = number_of_picks
n = len(list_of_items)
if k==0 or n==0:
return
b = *k # make list [0,0,0,....,0] - of length k
bdone = *k
while True:
choice = [list_of_items[i] for i in b]
yield tuple(choice)
# Modify b in place (an addition operation in base-n)
for i in range(k):
b[i] += 1
if b[i] < n:
break # break our of for loop, no need to carry
b[i] = 0 # carry
if b == bdone:
break # break out of while loop, no more choices left
return

RANGE = range(1,7)

DIE_PROB = 1/max(RANGE)

SINGLE_DIE = list((x,) for x in RANGE)
TWO_DICE = list(combinations_r(RANGE, 2))
THREE_DICE = list(combinations_r(RANGE, 3))

dice_rolls = {
1: SINGLE_DIE,
2: TWO_DICE,
3: THREE_DICE,
}

def risk_wins(attack, defense):
at_victory = 0
de_victory = 0
for at,de in zip( sorted(attack, lambda x, y: y-x),
sorted(defense, lambda x, y: y-x) ):
if at > de:
at_victory += 1
else:
de_victory += 1
return at_victory, de_victory

def make_probability(nattack, ndefense):
attacker = dice_rolls[nattack]
defender = dice_rolls[ndefense]

probs = {
(2, 0): 0,
(1, 0): 0,
(1, 1): 0,
(0, 1): 0,
(0, 2): 0,
}
values = list(cross(attacker, defender))
for at_roll, de_roll in values:
at, de = risk_wins(at_roll, de_roll)
probs[(at,de)] += 1

l = len(values)
for k in probs:
probs[k] /= l

return probs

def calculate_round(nattack, ndefense):
probs = make_probability(nattack, ndefense)
diffs = {}
for k, v in probs.items():
at, de = k
diffs[(-de, -at)] = v
return diffs

def decision_sum(decision):
ats, des = 0, 0
cum_prob = 1.0
for (at, de), prob in decision:
ats += at
des += de
cum_prob *= prob
return ats, des, cum_prob

def make_decisions(nattack, ndefense, iterations):
decisions = []
from pprint import pprint
for i in range(iterations):
if not decisions:
decisions = [ [(o, p)] for o, p in calculate_round(min(nattack-1, 3), min(ndefense, 2)).items() if p != 0.0]
continue
for decision in decisions[:]:
at, de, _ = decision_sum(decision)
try:
round = calculate_round(min(nattack+at-1, 3), min(ndefense+de, 2))
except (KeyError, AssertionError):
continue
for outcome, prob in round.items():
if prob == 0.0:
continue
try:
decisions.remove(decision)
except:
pass
decisions.append(decision + [(outcome,prob)])
return decisions

def calculate_outcomes(nattack, ndefense, iterations):
decisions = make_decisions(nattack, ndefense, iterations)
results = []
for decision in decisions:
ats, des, cum_prob = decision_sum(decision)
if cum_prob == 0.0:
continue
results.append( (nattack+ats, ndefense+des, cum_prob) )
assert str(sum(prob for na, nd, prob in results)) == "1.0"
return results

def print_outcomes(results, n):
winp = 0.0
losep = 0.0
draws = {}
for nat, nde, prob in results:
if nat == 1:
losep += prob
elif nde == 0:
winp += prob
else:
if (nat, nde) not in draws:
draws[(nat,nde)] = 0
draws[(nat,nde)] += prob
draws = [ (nat, nde, prob) for (nat, nde), prob in draws.items() ]

print "Chance of victory in %d battles: %3.2f%%" % (n, winp*100)
print "Chance of defeat (1 remaining) in %d battles: %3.2f%%" % (n, losep*100)
for at, de, prob in draws:
print "%3.2f%% chance of:" % (prob*100)
print "\tAttacker remaining: %d" % at
print "\tDefender remaining: %d" % de

def print_result(probs):
for result, prob in probs.items():
if prob == 0.0:
continue
at, de = result
print "Attacker Loses: %d, Defender Loses: %d (probability %1.2f)" % (de, at, prob)

def testmain():
for at, de in cross(range(1,4),range(1,3)):
print "------------------------------------------------------------------------"
print " Attacker: %d dice Defender: %d dice" % (at, de)
print "------------------------------------------------------------------------"
print_result(make_probability(at, de))

def main():
at, de, n = int(sys.argv), int(sys.argv), int(sys.argv)
print "Attacking Units: %d\t\tDefending Units: %d\t\t# of Battles: %d" % (at, de, n)
print_outcomes(calculate_outcomes(at, de, n), n)

if __name__=='__main__':
sys.exit(main())
```