1) Getting started with Gambit#
In this tutorial, we’ll demo the basic features of the Gambit library for game theory, using the PyGambit Python package.
This includes creating a Game object and using it to set up a strategic (normal) form game, the Prisoner’s Dilemma, one of the most famous games in game theory.
We’ll then use Gambit’s built-in functions to analyze the game and find its Nash equilibria.
The Prisoner’s Dilemma
The Prisoner’s Dilemma is a classic example in game theory that illustrates why two rational individuals who cannot communicate might not cooperate, even if it appears that it is in their best interest to do so. After being caught by the police for committing a crime, the two prisoners are separately offered a deal:
If both stay silent (cooperate), they get light sentences.
If one defects (betrays the other) while the other stays silent, the defector goes free and the silent one gets a heavy sentence.
If both defect, they both get moderate sentences.
Creating a strategic form game#
Let’s start by importing PyGambit and creating a game object. Since Prisoner’s Dilemma is a strategic form game, it can be created in a tabular fashion with Game.new_table.
To do this, we need to know the number of players, which in Prisoner’s Dilemma is 2, and the number of strategies for each player, which is in both cases is 2 (Cooperate and Defect). We’ll define a list as long as the number of players, specifying the number of strategies for each player to pass into the Game.new_table function.
[1]:
import pygambit as gbt
n_strategies = [2, 2]
g = gbt.Game.new_table(n_strategies, title="Prisoner's Dilemma")
type(g)
[1]:
pygambit.gambit.Game
Now let’s name the players and each of their possible strategies, in both cases “Cooperate” and “Defect”.
Note: it’s not necessary to specify labels for players and strategies when defining a game, however doing so makes the game easier to understand and work with.
[2]:
g.players[0].label = "Tom"
g.players[0].strategies[0].label = "Cooperate"
g.players[0].strategies[1].label = "Defect"
g.players[1].label = "Jerry"
g.players[1].strategies[0].label = "Cooperate"
g.players[1].strategies[1].label = "Defect"
Now let’s assign payoffs for each of the game’s possible outcomes, based on the standard payoffs for the Prisoner’s Dilemma:
Both players cooperate and receive the lightest sentence:
(-1, -1)Tom cooperates, but Jerry defects (betrays Tom):
(0, -3)Tom defects, Jerry cooperates:
(-3, 0)Both defect:
(-2, -2)
[3]:
# Both cooperate
g["Cooperate", "Cooperate"]["Tom"] = -1
g["Cooperate", "Cooperate"]["Jerry"] = -1
# Tom cooperates, Jerry defects
g["Cooperate", "Defect"]["Tom"] = -3
g["Cooperate", "Defect"]["Jerry"] = 0
# Tom defects, Jerry cooperates
g["Defect", "Cooperate"]["Tom"] = 0
g["Defect", "Cooperate"]["Jerry"] = -3
# Both defect
g["Defect", "Defect"]["Tom"] = -2
g["Defect", "Defect"]["Jerry"] = -2
[4]:
# View the payout matrix
g
[4]:
Prisoner's Dilemma
| Cooperate | Defect | |
| Cooperate | -1,-1 | -3,0 |
| Defect | 0,-3 | -2,-2 |
The payout matrix structure shows what in Game Theory is described as the “strategic form” (also “normal form”) representation of a game.
The matrix presents the players’ strategies and their expected payoff following their played strategies.
The strategic form assumes players choose their strategies simultaneously, and the outcome depends on the combination.
Creating games from arrays#
The most direct way to create a strategic form game is via Game.from_arrays().
This function takes one n-dimensional array per player, where n is the number of players in the game.
The arrays can be any object that can be indexed like an n-times-nested Python list; so, for example, numpy arrays can be used directly.
To create a two-player symmetric game, we can simply transpose the payoff matrix for the second player before passing to Game.from_arrays().
[5]:
import numpy as np
player1_payoffs = np.array([[-1, -3], [0, -2]])
player2_payoffs = np.transpose(player1_payoffs)
g1 = gbt.Game.from_arrays(
player1_payoffs,
player2_payoffs,
title="Another Prisoner's Dilemma"
)
g1
[5]:
Another Prisoner's Dilemma
| 1 | 2 | |
| 1 | -1,-1 | -3,0 |
| 2 | 0,-3 | -2,-2 |
You can retrieve the players’ payoff tables from a game object using the Game.to_arrays() method, which produces a list of numpy arrays representing the payoffs for each player.
The optional parameter dtype controls the data type of the payoffs in the generated arrays.
[6]:
tom_payoffs, jerry_payoffs = g.to_arrays(
# dtype=float
)
print(tom_payoffs[0][0])
print(type(tom_payoffs[0][0]))
-1
<class 'pygambit.gambit.Rational'>
Computing the Nash equilibria in one line of code#
We can use Gambit to compute the Nash equilibria for our Prisoner’s Dilemma game in a single line of code; a Nash equilibrium tells us the strategies that players can adopt to maximize their payoffs, given the setup of the game.
For a two-player normal form game, let’s use enumpure_solve to search for a pure-strategy Nash equilibria. The returned object will be a NashComputationResult.
[7]:
result = gbt.nash.enumpure_solve(g)
type(result)
[7]:
pygambit.nash.NashComputationResult
Let’s inspect our result further to see how many equilibria were found.
[8]:
len(result.equilibria)
[8]:
1
For a given equilibria, we can then look at the “mixed strategy profile”, which maps each strategy in a game to the corresponding probability with which that strategy is played.
[9]:
msp = result.equilibria[0]
msp
[9]:
[10]:
type(msp)
[10]:
pygambit.gambit.MixedStrategyProfileRational
The mixed strategy profile can show us the expected payoffs for each player when playing the strategies as specified by an equilibrium.
The profile [[0,1],[0,1]] indicates that both players’ strategy is to play “Cooperate” with probability 0 and “Defect” with probability 1:
[11]:
for player in g.players:
print(f"{player.label} plays the equilibrium strategy:")
print(f"Probability of cooperating: {msp[player.label]['Cooperate']}")
print(f"Probability of defecting: {msp[player.label]['Defect']}")
print(f"Payoff: {msp.payoff(player.label)}")
print()
Tom plays the equilibrium strategy:
Probability of cooperating: 0
Probability of defecting: 1
Payoff: -2
Jerry plays the equilibrium strategy:
Probability of cooperating: 0
Probability of defecting: 1
Payoff: -2
The equilibrium shows that both players are playing their dominant strategy, which is to defect. This is because defecting is the best response to the other player’s strategy, regardless of what that strategy is.
Saving and reading strategic form games to and from file#
You can use Gambit to save games to, and read from files. The specific format depends on whether the game is normal or extensive form.
Here we’ll save the Prisoner’s Dilemma (normal form) to the .nfg format.
[12]:
g.to_nfg("games/prisoners_dilemma.nfg")
You can easily restore the game object from file like so:
[13]:
restored_game = gbt.read_nfg("games/prisoners_dilemma.nfg")
type(restored_game)
[13]:
pygambit.gambit.Game
