How to Conquer the World

Genetic algorithms and the game of Risk

Rogier van der Geer

Data Charmer @ GoDataDriven

Risk Analysis

In [1]:
%matplotlib inline

Overview

  • The game of Risk

  • Risk in Python

  • Genetic Algorithms

  • Using a genetic algorithm to play Risk

Risk

Invented in 1957 as The Conquest of the World,
since 1959: Risk: The Game of Global Domination.


According to the publishers:

  • Players: 2-6
  • Playing time: 1-8 hours

The game board

risk.png

Assignment of territories

risk.png

Placing armies

risk.png

A turn consists of three stages:

  • Reinforcement
  • Combat
  • Fortification
  • Reinforcement: the player places additional armies:
    • one army per three territories,
    • bonus armies for owning a full continent,
    • additional bonus for a set of reinforcement cards.
  • Combat: the player may attack neighboring territories:
    • the battle is decided using dice,
    • the attacker has a good chance to win if he has more armies,
    • if the attacker conquers a territory he gets a reinforcement card,
    • the player may attack indefinitely.
  • Fortification: the player may move armies:
    • may make only one fortification move,
    • from one territory to a neighboring territory,
    • with as many armies as he likes.

Missions

Each player is assigned a mission. By completing the mission the player wins the game.

Missions are, for example:

  • Conquer africa and north america
  • Destroy the yellow player
  • Conquer at least 24 territories
  • ...

Risk in Python

We built a framework that handles all aspects of the Risk game. It consists of five classes:

  • Board, which handles the armies on the game board,
  • Cards, which handles the reinforcement cards of a player,
  • Mission, which describes the mission of a player,
  • Player, which makes decisions on how to play, and
  • Game, which handles all other aspects of the game.

The Board

When initialized, the board randomly distributes the territories amongst the players.

In [2]:
import random
random.seed(42)
In [3]:
from board import Board
b = Board.create(n_players=4)

We can get a graphical representation of the board using plot_board():

In [4]:
b.plot_board()

We can easily manipulate the armies on the board:

In [5]:
b.set_owner(territory_id=0, player_id=5)
b.set_armies(territory_id=0, n=15)
b.plot_board()

The board is aware of the layout of the territories:

In [6]:
for territory_id, player_id, armies in b.neighbors(territory_id=0):
    print 'Territory', territory_id, 'owned by player', player_id, 'is occupied by', armies, 'armies'
Territory 6 owned by player 0 is occupied by 1 armies
Territory 15 owned by player 0 is occupied by 1 armies
Territory 21 owned by player 1 is occupied by 1 armies
Territory 35 owned by player 0 is occupied by 1 armies
Territory 36 owned by player 1 is occupied by 1 armies

And can handle attack moves:

In [7]:
b.attack(from_territory=0, to_territory=21, n_armies=3)
b.plot_board()

Missions

We can get all available missions using the missions function:

In [8]:
from missions import missions
all_missions = missions(n_players=4)

for m in all_missions:
    print m.description
conquer asia and south-america
conquer africa and asia
conquer africa and north-america
conquer north-america and oceania
conquer europe and south-america and an additional continent of choice
conquer europe and oceania and an additional continent of choice
conquer at least 24 territories
conquer at least 18 territories and have at least 2 armies on each territory
eliminate the red player
eliminate the blue player
eliminate the green player
eliminate the yellow player

Each mission is aware of the player it is assigned to:

In [9]:
mission = all_missions[0]
mission
Out[9]:
Mission("conquer asia and south-america", unassigned)
In [10]:
mission.assign_to(player_id=0)
mission
Out[10]:
Mission("conquer asia and south-america", assigned to red)

...and can evaluate whether the mission has been achieved yet:

In [11]:
mission.evaluate(board=b)
Out[11]:
False

There is a special case when a player's mission is to kill himself:

In [12]:
mission = all_missions[-1]
mission
Out[12]:
Mission("eliminate the yellow player", unassigned)
In [13]:
mission.assign_to(player_id=3)
mission
Out[13]:
Mission("fallback: conquer at least 24 territories", assigned to yellow)

Players

A player object is required to have four methods:

  • reinforce(),
  • attack(),
  • fortify(),
  • turn_in_cards()

Playing a game

Let's go through a whole game.

We'll use four RandomPlayers, which take a random decision at every step of their turn.

In [14]:
random.seed(42)
In [35]:
import game, player
risk_game = game.Game.create([player.RandomPlayer() for i in range(4)])
risk_game.plot()

Now the players may place armies until they each have 30 armies:

In [16]:
risk_game.initialize_single_army()
risk_game.plot()

Calling initialize_armies() will have them place all armies:

In [17]:
risk_game.initialize_armies()
risk_game.plot()

Now the first player may play his turn.

In [18]:
risk_game.reinforce(risk_game.current_player)
risk_game.plot()

Then the attack phase:

In [19]:
risk_game.attack(risk_game.current_player)
In [20]:
risk_game.attack(risk_game.current_player)
risk_game.plot()

And finally the fortification phase:

In [21]:
risk_game.fortify(risk_game.current_player)
risk_game.next_turn()
risk_game.plot()