{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "#Risk stats\n", "The table below shows expected outcomes for large battles in risk. The row corresponds to the size of the attacking army, the column corresponds to the size of the defending army.\n", "\n", "The greener a cell is, the more likely it is the attacker can take control of a territory. The redder a cell is, the more likely it is the defender will maintain control of a territory.\n", "\n", "Each cell contains a number corresponding to an expected outcome. If the number is positive, it represents the number of armies the attacker will likely be left with. If the number is negative, it represents the number of armies the defender will likely be left with. \n", "\n", "##Details\n", "For a primer on the rules of risk: http://media.wizards.com/2015/downloads/ah/Risk_rules.pdf\n", "Each configuration was simulated 5,000 times to determine the frequency of outcomes. " ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "Armies(attackers=1, defenders=5)" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from collections import namedtuple\n", "import random\n", "rand = random.Random()\n", "\n", "Armies = namedtuple(\"Armies\", [\"attackers\", \"defenders\"])\n", "\n", "class Side:\n", " ATTACKER = 1\n", " DEFENDER = 2\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", "\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 roll_dice(num):\n", " return [rand.randint(1, 6) for i in range(num)]\n", "\n", "def run_battle(armies):\n", " attacking, defending = armies\n", " while attacking > 1 and defending > 0:\n", " delta_attacking, delta_defending = eval_roll(\n", " roll_dice(min(attacking - 1, 3)),\n", " roll_dice(min(defending, 2)))\n", " attacking += delta_attacking\n", " defending += delta_defending\n", " return Armies(attacking, defending)\n", "\n", "run_battle(Armies(6, 5))" ] }, { "cell_type": "code", "execution_count": 54, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def get_winner(armies):\n", " if armies.defenders == 0:\n", " return Side.ATTACKER\n", " else:\n", " return Side.DEFENDER\n", "\n", "def run_many_battles(armies, num_iterations=2500):\n", " \"Given a starting configuration of |armies|, simulate |num_iterationgs| battles and return stats on the results\"\n", " attacker_wins, defender_wins = 0, 0\n", " attacker_remainings, defender_remainings = [], []\n", " for i in range(num_iterations):\n", " res = run_battle(armies)\n", " if get_winner(res) == Side.ATTACKER:\n", " attacker_wins += 1\n", " attacker_remainings.append(res.attackers)\n", " else:\n", " defender_wins += 1\n", " defender_remainings.append(res.defenders)\n", " \n", " attacker_mean = 0.0\n", " defender_mean = 0.0\n", " \n", " all_mean = sum(attacker_remainings + [-x for x in defender_remainings]) / len(attacker_remainings + defender_remainings)\n", " try:\n", " #attacker_mean = max(set(attacker_remainings), key=attacker_remainings.count)\n", " attacker_mean = sum(attacker_remainings) / len(attacker_remainings)\n", " except:\n", " pass\n", " \n", " try:\n", " #defender_mean = max(set(defender_remainings), key=defender_remainings.count)\n", " defender_mean = sum(defender_remainings) / len(defender_remainings)\n", " except:\n", " pass\n", " \n", " return (attacker_wins / num_iterations, defender_wins / num_iterations, attacker_mean, defender_mean, all_mean)\n", "\n", "\n", "def generate_battle_matrix(max_attackers=20, max_defenders=20):\n", " \"Call run_many_battles on various starting configurations\"\n", " matrix = [[0 for x in range(max_defenders + 1)] for y in range(max_attackers + 1)]\n", " for attackers in range(max_defenders + 1):\n", " for defenders in range(max_defenders + 1):\n", " matrix[attackers][defenders] = run_many_battles(Armies(attackers, defenders))\n", " return matrix\n", "\n", "battle_results = generate_battle_matrix()" ] }, { "cell_type": "code", "execution_count": 55, "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=16d=17d=18d=19d=20
a=1-1.0-2.0-3.0-4.0-5.0-6.0-7.0-8.0-9.0-10.0-11.0-12.0-13.0-14.0-15.0-16.0-17.0-18.0-19.0-20.0
a=20.3-1.4-2.6-3.6-4.7-5.7-6.7-7.6-8.7-9.7-10.7-11.7-12.7-13.7-14.7-15.6-16.6-17.7-18.7-19.7
a=31.8-0.1-1.4-2.7-3.8-4.8-5.9-6.8-7.9-8.8-9.9-10.8-11.8-12.8-13.8-14.8-15.8-16.8-17.9-18.8
a=43.21.70.4-1.0-2.1-3.2-4.3-5.4-6.5-7.5-8.5-9.5-10.5-11.5-12.5-13.5-14.4-15.6-16.5-17.5
a=54.43.01.70.5-0.8-1.9-3.1-4.1-5.3-6.3-7.4-8.4-9.3-10.4-11.5-12.4-13.4-14.4-15.4-16.4
a=65.54.23.21.90.7-0.5-1.5-2.8-3.9-5.0-6.2-7.0-8.2-9.2-10.2-11.3-12.1-13.2-14.1-15.2
a=76.55.24.33.22.01.0-0.4-1.5-2.7-3.9-4.8-5.8-7.0-8.0-9.0-9.9-10.9-12.1-13.1-14.1
a=87.56.45.44.53.32.31.0-0.1-1.2-2.3-3.4-4.5-5.6-6.7-7.8-8.8-9.7-10.8-11.8-12.9
a=98.57.46.65.54.53.42.41.1-0.2-1.1-2.4-3.5-4.3-5.3-6.4-7.6-8.5-9.7-10.6-11.7
a=109.58.47.66.75.74.83.42.31.20.1-1.0-2.0-3.0-4.1-5.3-6.3-7.3-8.4-9.5-10.7
a=1110.59.48.77.66.75.74.73.72.61.50.3-0.6-1.8-2.9-3.8-5.0-6.2-7.2-8.1-9.3
a=1211.510.49.78.87.97.05.74.83.72.81.60.6-0.6-1.7-2.9-3.8-4.8-6.1-6.9-8.1
a=1312.511.510.79.78.88.07.16.05.13.82.91.80.3-0.3-1.6-2.5-3.5-4.6-5.7-6.9
a=1413.512.511.710.89.98.98.17.16.45.04.02.91.61.2-0.1-1.2-2.3-3.5-4.4-5.5
a=1514.513.512.711.810.810.19.18.27.36.35.34.13.22.41.00.1-1.2-2.0-3.2-4.1
a=1615.514.413.612.711.911.010.39.18.37.56.45.54.43.42.31.40.2-1.1-1.8-2.9
a=1716.515.514.613.812.912.211.310.39.48.47.76.55.54.53.52.41.50.6-0.7-2.3
a=1817.516.515.714.914.013.112.211.210.59.68.47.46.75.74.83.42.81.60.5-0.5
a=1918.517.516.715.714.914.213.212.311.310.49.68.77.66.85.74.93.52.81.80.8
a=2019.518.517.716.915.915.014.213.412.611.510.69.99.18.07.06.05.23.92.82.0
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from IPython.display import HTML, display\n", "\n", "\n", "def colorize(res, attackers_start, defenders_start):\n", " \"Assign a color to the stats collected by run_many_battles\"\n", " rate, _, attackers_left, defenders_left, all_left = res\n", " \n", " \"\"\"\n", " if rate > 0.5:\n", " whiteness = int(255 * (1 - (attackers_left / attackers_start)**2.0))\n", " return \"#%02xff%02x\" % (whiteness, whiteness)\n", " else:\n", " whiteness = int(255 * (1 - (defenders_left / defenders_start)**2.0))\n", " return \"#ff%02x%02x\" % (whiteness, whiteness)\n", " \"\"\" \n", " whiteness = int(255 * (1 - (2 * abs(rate - 0.5))**1.5))\n", " if rate > 0.5:\n", " return \"#%02xff%02x\" % (whiteness, whiteness)\n", " else:\n", " return \"#ff%02x%02x\" % (whiteness, whiteness)\n", "\n", "def render_matrix_html(matrix):\n", " \"Render the results of generate_battle_matrix as an html table\"\n", " rows = []\n", " \n", " first_row = [\"\"]\n", " for num_defenders, _ in enumerate(matrix[0]):\n", " if num_defenders == 0:\n", " continue\n", " first_row.append(\"d=%s\" % (num_defenders))\n", " rows.append(''.join(first_row))\n", " \n", " for num_attackers, stats in enumerate(matrix):\n", " if num_attackers == 0:\n", " continue\n", " \n", " cells = [\"a=%s\" % (num_attackers)]\n", " for num_defenders, res in enumerate(stats):\n", " if num_defenders == 0:\n", " continue\n", " \n", " successRate = res[0]\n", " remainingArmies = 0\n", " if successRate >= 0.5:\n", " remainingArmies = \"+%2.1f\" % res[2]\n", " else:\n", " remainingArmies = \"-%2.1f\" % res[3]\n", " color = colorize(res, num_attackers, num_defenders)\n", " remainingArmies = \"%2.1f\" % res[4]\n", " cells.append(\"%s\" % (color, remainingArmies))\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(battle_results))" ] } ], "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 }