{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "#Risk stats\n", "This notebook contains several visualizations to help predict the outcome of a battle in the game of risk. \n", "\n", "For a primer on the rules of risk, check out http://media.wizards.com/2015/downloads/ah/Risk_rules.pdf\n", "\n", "In this notebook, I use the following terminology:\n", " - Roll: a game event where the attacker rolls min(3, n-1) dice and the defender rolls min(2, n) dice\n", " - The outcome of a roll is determined by comparing the attacker's highest with the defender's highest dice and attacker's second highest with defender's second highest dice. Defender wins tie\n", " - Attacker can roll with a max of 3 dice, defender can roll with a max of 2\n", " - Battle: a series of rolls until either the defender is completely destroyed or the attacker can no longer attack\n", " - Success: a battle results in \"success\" if the attacker wins (i.e. if the defender is completely destroyed)\n", "\n", "#Graphs\n", "\n", "There are three types of graphs in this notebook. You may need to scroll through a lot of code to see them. \n", "\n", "##Probability distribution for outcomes of a roll\n", "\n", "There are a limited number of outcomes during a roll:\n", " - The defender loses two armies (e.g. attacker rolls 6, 6 and defender rolls 5, 5)\n", " - The defender loses one army (e.g. attacker rolls 6 and defender rolls 5)\n", " - The attacker and defender both lose one army (e.g. attacker rolls 6, 4 and defender rolls 6, 6)\n", " - The attacker loses one army (e.g. attacker rolls 5 and defender rolls 5)\n", " - The attacker loses two armies (e.g. attacker rolls 4, 4 and defender rolls 6, 4)\n", "\n", "##Probability distribution for outcomes of a battle\n", "\n", "The height of each bar represents the probability that a battle will result in a certain outcome. Outcomes on the right are more favorable to the attacker. Outcomes on the left are more favorable to the defender.\n", "\n", "You will notice that in the middle there is a dip. This is because if one army is almost destroyed, it has fewer dice and thus less chance to make a comeback. \n", "\n", "##Expected outcome matrix for outcomes of a battle\n", "\n", "Each x, y location in the matrix corresponds to a certain starting configuration. The row/y corresponds with the number of attacking armies. The col/x corresponds with the number of defending armies. \n", "\n", "The color of each cell maps to the probability of success for the attacker. Green means the attacker is likely to succeed, red means the defender is likely to succeed, and white means neither party is likely to succeed. \n", "\n", "The first number in each cell is the probability of success described as a percentage. The second line shows the 25th percentile outcome (in terms of how well it went for the attacker). The third line shows the 50th percentile outcome. The fourth line shows the 75th percentile outcome. Each outcome is displayed as an ordered pair where the first element is the number of attacker armies remaining and the second element is the number of defender armies remaining. " ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [], "source": [ "from collections import namedtuple, defaultdict\n", "import itertools\n", "\n", "Armies = namedtuple(\"Armies\", [\"attackers\", \"defenders\"])\n", "def loss_str(armies):\n", " return \"Attacker loses %d, Defender loses %d\" % (-armies.attackers, -armies.defenders)\n", "assert(loss_str(Armies(-4, -6)) == \"Attacker loses 4, Defender loses 6\")\n", "\n", "def remains_str(armies):\n", " return \"%d attackers and %d defenders\" % (armies.attackers, armies.defenders)\n", "assert(remains_str(Armies(6, 3)) == \"6 attackers and 3 defenders\")\n", "\n", "def tuple_str(armies):\n", " return \"(%d, %d)\" % (armies.attackers, armies.defenders)\n", "\n", "def assert_close(actual, expected):\n", " \"assert that the two values are pretty darn close... like they should be equal if they weren't floats\"\n", " if abs(actual - expected) > 0.00001:\n", " raise AssertionError(\"Bad stuff.\\nActual: %s\\nExpected: %s\\n\" % (actual, expected))\n", "\n", "def eval_roll(attacker_dice, defender_dice):\n", " \"Return the delta for each side... (delta_attackers, delta_defenders)\"\n", " attacker_wins, defender_wins = 0, 0\n", " for attacker_roll, defender_roll in zip(sorted(attacker_dice)[::-1], sorted(defender_dice)[::-1]):\n", " if attacker_roll > defender_roll:\n", " attacker_wins += 1\n", " else: #defender wins tie\n", " defender_wins += 1\n", " return Armies(-defender_wins, -attacker_wins)\n", "assert(eval_roll([5, 4, 1], [5, 1]) == Armies(-1, -1))\n", "assert(eval_roll([6, 6, 6], [6]) == Armies(-1, 0))\n", "\n", "def generate_rolls(n_dice):\n", " \"generate every possible roll given n_dice\"\n", " return itertools.product((1, 2, 3, 4, 5, 6), repeat=n_dice)\n", "\n", "def scale_dict(d, scale):\n", " \"scale each value in a dict by |scale|\"\n", " ret = defaultdict(lambda: 0)\n", " for key in d:\n", " ret[key] = d[key] * scale\n", " return ret\n", "\n", "def normalize_dict(d):\n", " \"scale down all dict values so they sum to 1.0\"\n", " return scale_dict(d, 1.0 / sum(d.values()))\n", "\n", "def get_roll_stats(n_attack_dice, n_defence_dice):\n", " \"find the likelyhood of each outcome given x attack dice and y defence dice\"\n", " res = defaultdict(lambda: 0)\n", " for attacker_roll in generate_rolls(n_attack_dice):\n", " for defender_roll in generate_rolls(n_defence_dice):\n", " res[eval_roll(attacker_roll, defender_roll)] += 1\n", " return normalize_dict(res)\n", "assert_close(get_roll_stats(1, 1)[Armies(0, -1)], 15/36)\n", "assert_close(get_roll_stats(1, 1)[Armies(-1, 0)], 21/36)\n", "assert_close(get_roll_stats(3, 2)[Armies(-1, -1)], 2611/7776)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import plotly.plotly as py\n", "import plotly.graph_objs as go\n", "from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot\n", "init_notebook_mode(connected=False)\n", "\n", "def make_roll_chart(n_attacker_dice, n_defender_dice):\n", " \"Generate a pie chart showing the probability of each outcome for a an X by Y roll\"\n", " outcomes = get_roll_stats(n_attacker_dice, n_defender_dice)\n", " labels = [loss_str(key) for key in outcomes]\n", " values = [val for (key, val) in outcomes.items()]\n", " fig = {\n", " 'data': [{\n", " 'labels': labels,\n", " 'values': values,\n", " 'type': 'pie'\n", " }],\n", " 'layout': {\n", " 'title': 'Possible outcomes when rolling %d attack die and %d defence die' %\n", " (n_attacker_dice, n_defender_dice)\n", " }\n", " }\n", " iplot(fig)\n", "\n", "make_roll_chart(1, 1)\n", "make_roll_chart(1, 2)\n", "make_roll_chart(2, 1)\n", "make_roll_chart(2, 2)\n", "make_roll_chart(3, 1)\n", "make_roll_chart(3, 2)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [], "source": [ "import functools\n", "\n", "def join_dicts(a, b):\n", " \"Sum the values by key of a defaultdict\"\n", " keys = set(a.keys()).union(set(b.keys()))\n", " res = defaultdict(lambda: 0)\n", " for key in keys:\n", " res[key] = a[key] + b[key]\n", " return res\n", "_x, _y, _xy = defaultdict(lambda: 0), defaultdict(lambda: 0), defaultdict(lambda: 0)\n", "_x[\"left\"], _x[\"both\"] = 1, 2\n", "_y[\"both\"], _y[\"right\"] = 4, 8\n", "_xy[\"left\"], _xy[\"both\"], _xy[\"right\"] = 1, 6, 8\n", "assert(join_dicts(_x, _y) == _xy)\n", "\n", "def get_best_roll(attacker_armies, defender_armies):\n", " attacker_dice = min(3, attacker_armies - 1)\n", " defender_dice = min(2, defender_armies)\n", " return Armies(attacker_dice, defender_dice)\n", "assert(get_best_roll(6, 1) == Armies(3, 1))\n", "assert(get_best_roll(1, 6) == Armies(0, 2))\n", "\n", "#cache the roll stats so we don't have to recompute them every time\n", "roll_stats = {roll: get_roll_stats(roll.attackers, roll.defenders) for roll in [\n", " Armies(1, 1), Armies(1, 2),\n", " Armies(2, 1), Armies(2, 2),\n", " Armies(3, 1), Armies(3, 2)\n", " ]}\n", "\n", "@functools.lru_cache()\n", "def gen_battle_outcomes(attacker_armies, defender_armies):\n", " \"Determine the likelyhood of each posible outcome given a starting configuration\"\n", " res = defaultdict(lambda: 0)\n", " \n", " if attacker_armies == 1:\n", " res[Armies(attacker_armies, defender_armies)] = 1\n", " return res\n", " if defender_armies == 0:\n", " res[Armies(attacker_armies, defender_armies)] = 1\n", " return res\n", " \n", " roll = get_best_roll(attacker_armies, defender_armies)\n", " for outcome, liklihood in roll_stats[roll].items():\n", " res = join_dicts(res, \n", " scale_dict(gen_battle_outcomes(attacker_armies + outcome.attackers,\n", " defender_armies + outcome.defenders),\n", " liklihood))\n", " res = normalize_dict(res)\n", " return res\n", "\n", "assert_close(gen_battle_outcomes(2, 2)[Armies(1, 2)],\n", " get_roll_stats(1, 2)[Armies(-1, 0)])\n", "assert_close(gen_battle_outcomes(2, 2)[Armies(2, 0)],\n", " get_roll_stats(1, 2)[Armies(0, -1)] *\n", " get_roll_stats(1, 1)[Armies(0, -1)])\n", "assert_close(gen_battle_outcomes(5, 5)[Armies(4, 0)],\n", " (get_roll_stats(3, 2)[Armies(0, -2)] * #(5, 3)\n", " get_roll_stats(3, 2)[Armies(0, -2)] * #(5, 1)\n", " get_roll_stats(3, 1)[Armies(-1, 0)] * #(4, 1)\n", " get_roll_stats(3, 1)[Armies(0, -1)]) + #(4, 0)\n", " (get_roll_stats(3, 2)[Armies(0, -2)] * #(5, 3)\n", " get_roll_stats(3, 2)[Armies(-1, -1)] * #(4, 2)\n", " get_roll_stats(3, 2)[Armies(0, -2)]) * 2) #(4, 0)\n", "\n", "def armies_comparator(a):\n", " \"Sort armies first by attackers then by -defenders (assuming int)\"\n", " return a[0].attackers + 1.0 / (a[0].defenders + 1)\n", "assert(armies_comparator([Armies(5, 6)]) < armies_comparator([Armies(6, 6)]))\n", "assert(armies_comparator([Armies(6, 7)]) < armies_comparator([Armies(6, 6)]))\n", "\n", "def get_percentile_outcome(outcomes, percentile):\n", " outcomes = list(outcomes.items())\n", " outcomes.sort(key=armies_comparator)\n", " x = 0.0\n", " for outcome, probability in outcomes:\n", " x += probability\n", " if x >= percentile:\n", " return outcome\n", " return outcomes[-1][0]\n", "assert(get_percentile_outcome(gen_battle_outcomes(5, 5), 0.25) == Armies(1, 4))\n", "assert(get_percentile_outcome(gen_battle_outcomes(5, 5), 0.5) == Armies(1, 2))\n", "assert(get_percentile_outcome(gen_battle_outcomes(5, 5), 0.75) == Armies(3, 0))\n", "\n", "def get_win_rate(outcomes):\n", " return sum(probability for outcome, probability in outcomes.items() if outcome.defenders == 0)\n", "assert_close(get_win_rate({\n", " Armies(5, 5): 0.3,\n", " Armies(2, 0): 0.3,\n", " Armies(6, 0): 0.2,\n", " Armies(1, 6): 0.2\n", " }), 0.5)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [ { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def make_battle_chart(attacker_armies, defender_armies):\n", " \"Graph the probability distribution for each outcome of a battle of X v Y\"\n", " outcomes = list(gen_battle_outcomes(attacker_armies, defender_armies).items())\n", " outcomes.sort(key=armies_comparator)\n", " \n", " labels = []\n", " values = []\n", " for outcome, probability in outcomes:\n", " labels.append(remains_str(outcome))\n", " values.append(probability)\n", " \n", " trace0 = go.Bar(x = labels,\n", " y = values)\n", " layout = go.Layout(title='Distribution of outcomes with %d attacking armies and %d defending armies' %\n", " (attacker_armies, defender_armies),\n", " margin = go.Margin(b=200, r=150))\n", " fig = go.Figure(data=[trace0], layout=layout)\n", " \n", " iplot(fig)\n", " \n", "make_battle_chart(5, 5)\n", "make_battle_chart(10, 10)\n", "make_battle_chart(15, 15)\n", "make_battle_chart(20, 25)\n", "make_battle_chart(25, 20)\n", "make_battle_chart(25, 25)\n", "make_battle_chart(50, 50)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [ { "data": { "text/html": [ "
d=1d=2d=3d=4d=5d=6d=7d=8d=9d=10d=11d=12d=13d=14d=15d=16
a=1 0%
(1, 1)
(1, 1)
(1, 1)
0%
(1, 2)
(1, 2)
(1, 2)
0%
(1, 3)
(1, 3)
(1, 3)
0%
(1, 4)
(1, 4)
(1, 4)
0%
(1, 5)
(1, 5)
(1, 5)
0%
(1, 6)
(1, 6)
(1, 6)
0%
(1, 7)
(1, 7)
(1, 7)
0%
(1, 8)
(1, 8)
(1, 8)
0%
(1, 9)
(1, 9)
(1, 9)
0%
(1, 10)
(1, 10)
(1, 10)
0%
(1, 11)
(1, 11)
(1, 11)
0%
(1, 12)
(1, 12)
(1, 12)
0%
(1, 13)
(1, 13)
(1, 13)
0%
(1, 14)
(1, 14)
(1, 14)
0%
(1, 15)
(1, 15)
(1, 15)
0%
(1, 16)
(1, 16)
(1, 16)
a=242%
(1, 1)
(1, 1)
(2, 0)
11%
(1, 2)
(1, 2)
(1, 1)
3%
(1, 3)
(1, 3)
(1, 2)
1%
(1, 4)
(1, 4)
(1, 3)
0%
(1, 5)
(1, 5)
(1, 4)
0%
(1, 6)
(1, 6)
(1, 5)
0%
(1, 7)
(1, 7)
(1, 6)
0%
(1, 8)
(1, 8)
(1, 7)
0%
(1, 9)
(1, 9)
(1, 8)
0%
(1, 10)
(1, 10)
(1, 9)
0%
(1, 11)
(1, 11)
(1, 10)
0%
(1, 12)
(1, 12)
(1, 11)
0%
(1, 13)
(1, 13)
(1, 12)
0%
(1, 14)
(1, 14)
(1, 13)
0%
(1, 15)
(1, 15)
(1, 14)
0%
(1, 16)
(1, 16)
(1, 15)
a=375%
(2, 0)
(3, 0)
(3, 0)
36%
(1, 2)
(1, 1)
(2, 0)
21%
(1, 3)
(1, 2)
(1, 1)
9%
(1, 4)
(1, 3)
(1, 2)
5%
(1, 5)
(1, 4)
(1, 3)
2%
(1, 6)
(1, 5)
(1, 4)
1%
(1, 7)
(1, 6)
(1, 5)
0%
(1, 8)
(1, 7)
(1, 6)
0%
(1, 9)
(1, 8)
(1, 7)
0%
(1, 10)
(1, 9)
(1, 8)
0%
(1, 11)
(1, 10)
(1, 9)
0%
(1, 12)
(1, 11)
(1, 10)
0%
(1, 13)
(1, 12)
(1, 11)
0%
(1, 14)
(1, 13)
(1, 12)
0%
(1, 15)
(1, 14)
(1, 13)
0%
(1, 16)
(1, 15)
(1, 14)
a=492%
(3, 0)
(4, 0)
(4, 0)
66%
(1, 1)
(3, 0)
(4, 0)
47%
(1, 2)
(1, 1)
(3, 0)
31%
(1, 3)
(1, 2)
(3, 0)
21%
(1, 4)
(1, 3)
(1, 1)
13%
(1, 5)
(1, 4)
(1, 2)
8%
(1, 6)
(1, 5)
(1, 3)
5%
(1, 7)
(1, 6)
(1, 4)
3%
(1, 8)
(1, 7)
(1, 5)
2%
(1, 9)
(1, 8)
(1, 6)
1%
(1, 10)
(1, 9)
(1, 7)
1%
(1, 11)
(1, 10)
(1, 8)
0%
(1, 12)
(1, 11)
(1, 9)
0%
(1, 13)
(1, 12)
(1, 10)
0%
(1, 14)
(1, 13)
(1, 11)
0%
(1, 15)
(1, 14)
(1, 12)
a=597%
(4, 0)
(5, 0)
(5, 0)
79%
(2, 0)
(4, 0)
(5, 0)
64%
(1, 2)
(3, 0)
(4, 0)
48%
(1, 3)
(1, 1)
(4, 0)
36%
(1, 4)
(1, 2)
(3, 0)
25%
(1, 5)
(1, 3)
(2, 0)
18%
(1, 6)
(1, 4)
(1, 2)
12%
(1, 7)
(1, 5)
(1, 3)
9%
(1, 8)
(1, 6)
(1, 4)
6%
(1, 9)
(1, 7)
(1, 5)
4%
(1, 10)
(1, 8)
(1, 6)
3%
(1, 11)
(1, 9)
(1, 7)
2%
(1, 12)
(1, 10)
(1, 8)
1%
(1, 13)
(1, 11)
(1, 9)
1%
(1, 14)
(1, 12)
(1, 10)
0%
(1, 15)
(1, 13)
(1, 11)
a=699%
(5, 0)
(6, 0)
(6, 0)
89%
(4, 0)
(5, 0)
(6, 0)
77%
(2, 0)
(4, 0)
(5, 0)
64%
(1, 2)
(3, 0)
(5, 0)
51%
(1, 3)
(2, 0)
(4, 0)
40%
(1, 4)
(1, 2)
(4, 0)
30%
(1, 5)
(1, 3)
(3, 0)
22%
(1, 6)
(1, 4)
(1, 1)
16%
(1, 7)
(1, 5)
(1, 2)
12%
(1, 8)
(1, 6)
(1, 3)
8%
(1, 9)
(1, 7)
(1, 4)
6%
(1, 10)
(1, 8)
(1, 5)
4%
(1, 11)
(1, 9)
(1, 6)
3%
(1, 12)
(1, 10)
(1, 7)
2%
(1, 13)
(1, 11)
(1, 8)
1%
(1, 14)
(1, 12)
(1, 9)
a=7100%
(6, 0)
(7, 0)
(7, 0)
93%
(5, 0)
(6, 0)
(7, 0)
86%
(4, 0)
(5, 0)
(6, 0)
74%
(1, 1)
(4, 0)
(6, 0)
64%
(1, 2)
(4, 0)
(5, 0)
52%
(1, 3)
(2, 0)
(5, 0)
42%
(1, 4)
(1, 2)
(4, 0)
33%
(1, 5)
(1, 3)
(3, 0)
26%
(1, 6)
(1, 4)
(2, 0)
19%
(1, 7)
(1, 5)
(1, 2)
15%
(1, 8)
(1, 6)
(1, 3)
11%
(1, 9)
(1, 7)
(1, 4)
8%
(1, 10)
(1, 8)
(1, 5)
6%
(1, 11)
(1, 9)
(1, 6)
4%
(1, 12)
(1, 10)
(1, 7)
3%
(1, 13)
(1, 11)
(1, 8)
a=8100%
(7, 0)
(8, 0)
(8, 0)
97%
(6, 0)
(7, 0)
(8, 0)
91%
(5, 0)
(6, 0)
(7, 0)
83%
(3, 0)
(5, 0)
(7, 0)
74%
(1, 1)
(5, 0)
(6, 0)
64%
(1, 2)
(4, 0)
(6, 0)
54%
(1, 3)
(2, 0)
(5, 0)
45%
(1, 4)
(1, 2)
(4, 0)
36%
(1, 5)
(1, 3)
(4, 0)
29%
(1, 6)
(1, 4)
(3, 0)
22%
(1, 7)
(1, 5)
(1, 1)
17%
(1, 8)
(1, 6)
(1, 2)
13%
(1, 9)
(1, 7)
(1, 3)
10%
(1, 10)
(1, 8)
(1, 4)
7%
(1, 11)
(1, 9)
(1, 5)
5%
(1, 12)
(1, 10)
(1, 6)
a=9100%
(8, 0)
(9, 0)
(9, 0)
98%
(7, 0)
(8, 0)
(9, 0)
95%
(6, 0)
(7, 0)
(8, 0)
89%
(4, 0)
(6, 0)
(8, 0)
82%
(3, 0)
(6, 0)
(7, 0)
73%
(1, 1)
(5, 0)
(7, 0)
64%
(1, 2)
(4, 0)
(6, 0)
55%
(1, 3)
(3, 0)
(5, 0)
46%
(1, 4)
(1, 1)
(5, 0)
38%
(1, 5)
(1, 2)
(4, 0)
31%
(1, 6)
(1, 3)
(3, 0)
25%
(1, 7)
(1, 4)
(1, 1)
20%
(1, 8)
(1, 5)
(1, 2)
15%
(1, 9)
(1, 6)
(1, 3)
12%
(1, 10)
(1, 7)
(1, 4)
9%
(1, 11)
(1, 8)
(1, 5)
a=10100%
(9, 0)
(10, 0)
(10, 0)
99%
(8, 0)
(9, 0)
(10, 0)
97%
(7, 0)
(8, 0)
(9, 0)
93%
(5, 0)
(7, 0)
(9, 0)
87%
(4, 0)
(7, 0)
(8, 0)
81%
(3, 0)
(6, 0)
(8, 0)
73%
(1, 1)
(5, 0)
(7, 0)
65%
(1, 2)
(4, 0)
(6, 0)
56%
(1, 3)
(3, 0)
(6, 0)
48%
(1, 4)
(1, 1)
(5, 0)
40%
(1, 5)
(1, 2)
(4, 0)
33%
(1, 6)
(1, 3)
(4, 0)
27%
(1, 7)
(1, 4)
(2, 0)
22%
(1, 8)
(1, 5)
(1, 2)
17%
(1, 9)
(1, 6)
(1, 3)
14%
(1, 10)
(1, 7)
(1, 4)
a=11100%
(10, 0)
(11, 0)
(11, 0)
99%
(9, 0)
(10, 0)
(11, 0)
98%
(8, 0)
(9, 0)
(10, 0)
95%
(6, 0)
(8, 0)
(10, 0)
92%
(5, 0)
(8, 0)
(9, 0)
86%
(4, 0)
(7, 0)
(9, 0)
80%
(3, 0)
(6, 0)
(8, 0)
72%
(1, 1)
(5, 0)
(7, 0)
65%
(1, 2)
(4, 0)
(7, 0)
57%
(1, 3)
(3, 0)
(6, 0)
49%
(1, 4)
(1, 1)
(5, 0)
42%
(1, 5)
(1, 2)
(5, 0)
35%
(1, 6)
(1, 3)
(4, 0)
29%
(1, 7)
(1, 4)
(3, 0)
24%
(1, 8)
(1, 5)
(1, 1)
19%
(1, 9)
(1, 6)
(1, 2)
a=12100%
(11, 0)
(12, 0)
(12, 0)
100%
(10, 0)
(11, 0)
(12, 0)
99%
(9, 0)
(10, 0)
(11, 0)
97%
(7, 0)
(9, 0)
(11, 0)
94%
(6, 0)
(9, 0)
(10, 0)
91%
(5, 0)
(8, 0)
(10, 0)
85%
(4, 0)
(7, 0)
(9, 0)
79%
(3, 0)
(6, 0)
(8, 0)
72%
(1, 1)
(5, 0)
(8, 0)
65%
(1, 2)
(4, 0)
(7, 0)
58%
(1, 3)
(3, 0)
(6, 0)
51%
(1, 4)
(2, 0)
(6, 0)
43%
(1, 5)
(1, 2)
(5, 0)
37%
(1, 6)
(1, 3)
(4, 0)
31%
(1, 7)
(1, 4)
(3, 0)
26%
(1, 8)
(1, 5)
(2, 0)
a=13100%
(12, 0)
(13, 0)
(13, 0)
100%
(11, 0)
(12, 0)
(13, 0)
99%
(10, 0)
(11, 0)
(12, 0)
98%
(8, 0)
(10, 0)
(12, 0)
96%
(7, 0)
(10, 0)
(11, 0)
93%
(6, 0)
(9, 0)
(11, 0)
90%
(5, 0)
(8, 0)
(10, 0)
84%
(4, 0)
(7, 0)
(9, 0)
79%
(3, 0)
(6, 0)
(9, 0)
72%
(1, 1)
(5, 0)
(8, 0)
66%
(1, 2)
(4, 0)
(7, 0)
58%
(1, 3)
(4, 0)
(7, 0)
52%
(1, 4)
(2, 0)
(6, 0)
45%
(1, 5)
(1, 2)
(5, 0)
39%
(1, 6)
(1, 3)
(4, 0)
33%
(1, 7)
(1, 4)
(4, 0)
a=14100%
(13, 0)
(14, 0)
(14, 0)
100%
(12, 0)
(13, 0)
(14, 0)
100%
(11, 0)
(12, 0)
(13, 0)
99%
(9, 0)
(11, 0)
(13, 0)
98%
(8, 0)
(11, 0)
(12, 0)
96%
(7, 0)
(10, 0)
(12, 0)
93%
(6, 0)
(9, 0)
(11, 0)
89%
(5, 0)
(8, 0)
(10, 0)
84%
(4, 0)
(7, 0)
(10, 0)
79%
(3, 0)
(6, 0)
(9, 0)
72%
(1, 1)
(5, 0)
(8, 0)
66%
(1, 2)
(5, 0)
(8, 0)
59%
(1, 3)
(4, 0)
(7, 0)
53%
(1, 4)
(3, 0)
(6, 0)
46%
(1, 5)
(1, 2)
(5, 0)
40%
(1, 6)
(1, 3)
(5, 0)
a=15100%
(14, 0)
(15, 0)
(15, 0)
100%
(13, 0)
(14, 0)
(15, 0)
100%
(12, 0)
(13, 0)
(14, 0)
99%
(10, 0)
(12, 0)
(14, 0)
98%
(9, 0)
(12, 0)
(13, 0)
97%
(8, 0)
(11, 0)
(13, 0)
95%
(7, 0)
(10, 0)
(12, 0)
92%
(6, 0)
(9, 0)
(11, 0)
88%
(5, 0)
(8, 0)
(11, 0)
83%
(4, 0)
(7, 0)
(10, 0)
78%
(3, 0)
(6, 0)
(9, 0)
72%
(1, 1)
(6, 0)
(9, 0)
67%
(1, 2)
(5, 0)
(8, 0)
60%
(1, 3)
(4, 0)
(7, 0)
54%
(1, 4)
(3, 0)
(6, 0)
47%
(1, 5)
(1, 1)
(6, 0)
a=16100%
(15, 0)
(16, 0)
(16, 0)
100%
(14, 0)
(15, 0)
(16, 0)
100%
(13, 0)
(14, 0)
(15, 0)
100%
(11, 0)
(13, 0)
(15, 0)
99%
(10, 0)
(13, 0)
(14, 0)
98%
(9, 0)
(12, 0)
(14, 0)
96%
(8, 0)
(11, 0)
(13, 0)
94%
(7, 0)
(10, 0)
(12, 0)
91%
(6, 0)
(9, 0)
(12, 0)
88%
(5, 0)
(8, 0)
(11, 0)
83%
(4, 0)
(7, 0)
(10, 0)
78%
(3, 0)
(7, 0)
(10, 0)
73%
(1, 1)
(6, 0)
(9, 0)
67%
(1, 2)
(5, 0)
(8, 0)
61%
(1, 3)
(4, 0)
(7, 0)
55%
(1, 4)
(3, 0)
(7, 0)
a=17100%
(16, 0)
(17, 0)
(17, 0)
100%
(15, 0)
(16, 0)
(17, 0)
100%
(14, 0)
(15, 0)
(16, 0)
100%
(12, 0)
(14, 0)
(16, 0)
99%
(11, 0)
(14, 0)
(15, 0)
99%
(10, 0)
(13, 0)
(15, 0)
98%
(9, 0)
(12, 0)
(14, 0)
96%
(8, 0)
(11, 0)
(13, 0)
94%
(7, 0)
(10, 0)
(13, 0)
91%
(6, 0)
(9, 0)
(12, 0)
87%
(5, 0)
(8, 0)
(11, 0)
83%
(4, 0)
(8, 0)
(11, 0)
78%
(3, 0)
(7, 0)
(10, 0)
73%
(1, 1)
(6, 0)
(9, 0)
67%
(1, 2)
(5, 0)
(8, 0)
61%
(1, 3)
(4, 0)
(8, 0)
a=18100%
(17, 0)
(18, 0)
(18, 0)
100%
(16, 0)
(17, 0)
(18, 0)
100%
(15, 0)
(16, 0)
(17, 0)
100%
(13, 0)
(15, 0)
(17, 0)
100%
(12, 0)
(15, 0)
(16, 0)
99%
(11, 0)
(14, 0)
(16, 0)
98%
(10, 0)
(13, 0)
(15, 0)
97%
(9, 0)
(12, 0)
(14, 0)
95%
(8, 0)
(11, 0)
(14, 0)
93%
(7, 0)
(10, 0)
(13, 0)
90%
(6, 0)
(9, 0)
(12, 0)
87%
(5, 0)
(9, 0)
(12, 0)
83%
(4, 0)
(8, 0)
(11, 0)
78%
(3, 0)
(7, 0)
(10, 0)
73%
(1, 1)
(6, 0)
(9, 0)
68%
(1, 2)
(5, 0)
(9, 0)
a=19100%
(18, 0)
(19, 0)
(19, 0)
100%
(17, 0)
(18, 0)
(19, 0)
100%
(16, 0)
(17, 0)
(18, 0)
100%
(14, 0)
(16, 0)
(18, 0)
100%
(13, 0)
(16, 0)
(17, 0)
99%
(12, 0)
(15, 0)
(17, 0)
99%
(11, 0)
(14, 0)
(16, 0)
98%
(10, 0)
(13, 0)
(15, 0)
97%
(9, 0)
(12, 0)
(15, 0)
95%
(8, 0)
(11, 0)
(14, 0)
93%
(7, 0)
(10, 0)
(13, 0)
90%
(6, 0)
(10, 0)
(13, 0)
87%
(5, 0)
(9, 0)
(12, 0)
82%
(4, 0)
(8, 0)
(11, 0)
78%
(3, 0)
(7, 0)
(10, 0)
73%
(1, 1)
(6, 0)
(10, 0)
a=20100%
(19, 0)
(20, 0)
(20, 0)
100%
(18, 0)
(19, 0)
(20, 0)
100%
(17, 0)
(18, 0)
(19, 0)
100%
(15, 0)
(17, 0)
(19, 0)
100%
(14, 0)
(17, 0)
(18, 0)
100%
(13, 0)
(16, 0)
(18, 0)
99%
(12, 0)
(15, 0)
(17, 0)
99%
(11, 0)
(14, 0)
(16, 0)
98%
(10, 0)
(13, 0)
(16, 0)
97%
(9, 0)
(12, 0)
(15, 0)
95%
(8, 0)
(11, 0)
(14, 0)
92%
(7, 0)
(11, 0)
(14, 0)
89%
(6, 0)
(10, 0)
(13, 0)
86%
(5, 0)
(9, 0)
(12, 0)
82%
(4, 0)
(8, 0)
(11, 0)
78%
(3, 0)
(7, 0)
(11, 0)
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from IPython.display import HTML, display\n", "\n", "\n", "def colorize(success_rate):\n", " \"Assign a color to the stats collected by run_many_battles\"\n", " whiteness = int(255 * (1 - (2 * abs(success_rate - 0.5))**1.5))\n", " if success_rate > 0.5:\n", " return \"#%02xff%02x\" % (whiteness, whiteness)\n", " else:\n", " return \"#ff%02x%02x\" % (whiteness, whiteness)\n", "\n", "def render_matrix_html(attacker_armies, defender_armies):\n", " \"Render the probabilities of success given X attackers and Y defenders as an html table\"\n", " rows = []\n", " \n", " first_row = [\"\"]\n", " for num_defenders in range(1, defender_armies + 1):\n", " first_row.append(\"d=%s\" % (num_defenders))\n", " rows.append(''.join(first_row))\n", " \n", " for num_attackers in range(1, attacker_armies + 1):\n", " cells = [\"a=%s\" % (num_attackers)]\n", " for num_defenders in range(1, defender_armies + 1):\n", " outcomes = gen_battle_outcomes(num_attackers, num_defenders)\n", " success_rate = get_win_rate(outcomes)\n", " first_quartile = get_percentile_outcome(outcomes, 0.25)\n", " second_quartile = get_percentile_outcome(outcomes, 0.5)\n", " third_quartile = get_percentile_outcome(outcomes, 0.75)\n", " color = colorize(success_rate)\n", " cells.append(\"%2.0f%%
%s
%s
%s\" %\n", " (color, success_rate * 100, tuple_str(first_quartile),\n", " tuple_str(second_quartile), tuple_str(third_quartile)))\n", " rows.append(''.join(cells))\n", " \n", " rows = [\"%s\" % (row) for row in rows]\n", " \n", " return HTML(\"%s
\" % (''.join(rows)))\n", "\n", "\n", "display(render_matrix_html(20, 16))" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python [conda root]", "language": "python", "name": "conda-root-py" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 1 }