Introduction to Game Theory

A game refers to any interactive situation involving a group of “self-interested” agents, or players. The defining feature of a game is that the players are engaged in an “interdependent decision problem”.

Reading

  1. M. Osborne, Nash Equilibrium: Theory, Chapter from Introduction to Game Theory, Oxford University Press, 2002.

  2. D. Ross, Game Theory, Stanford Encyclopedia of Philosophy, 2019.

  3. E. Pacuit and O. Roy, Epistemic Foundations of Game Theory, Stanford Encyclopedia of Philosophy, 2012.

# make graphs look nice
import seaborn as sns
sns.set()
Copy to clipboard

The mathematical description of a game includes at least the following components:

  1. The players. In this entry, we only consider games with finitely many players. We use N to denote the set of players in a game.

  2. For each player iN, a finite set of feasible options (typically called actions or strategies).

  3. For each player iN, a preference over the possible outcomes of the game. The standard approach in game theory is to represent each player’s preference as a (von Neumann-Morgenstern) utility function that assigns a real number to each outcome of the game.

A game may represent other features of the strategic situation. For instance, some games represent multi-stage decision problems which may include simultaneous or stochastic moves. For simplicity, we start with games that involve players making a single decision simultaneously without stochastic moves.

A game in strategic form is a tuple N,(Si)iN,(ui)iN where:

  1. N is a finite non-empty set

  2. For each iN, Si is a finite non-empty set

  3. For each iN, ui:×iSiR is player i’s utility.

The elements of ×iNSi are the outcomes of the game and are called strategy profiles.

In most games, no single player has total control over which outcome will be realized at the end of the interaction. The outcome of a game depends on the decisions of all players.

The central analytic tool of classical game theory are solution concepts. A solution concept associates a set of outcomes (i.e., a set of strategy profiles) with each game (from some fixed class of games).

From a prescriptive point of view, a solution concept is a recommendation about what the players should do in a game, or about what outcomes can be expected assuming that the players choose rationally. From a predictive point of view, solution concepts describe what the players will actually do in a game.

Nash Equilibrium

Let G=N,(Si)iN,(ui)iN be a finite strategic game (each Si is finite and the set of players N is finite).

A strategy profile is an element ×iNSi

Given a strategy profile σ×iNSi, σi is an element of $S1×S2×Si1×Si+1×Sn$

A strategy profiel σ is a pure strategy Nash equilibrium provided that for all iN, for all aSi, $ui(σ)ui(a,σi)$

Pure Coordination Game

 

S1

S2

S1

(1,1)

(0,0)

S2

(0,0)

(1,1)

There are two pure strategy Nash equilibria: (S1,S1) and (S2,S2)

The basic intellectual premise, or working hypothesis, for rational players in this game seems to be the premise that some rule must be used if success is to exceed coincidence, and that the best rule to be found, whatever its rationalization, is consequently a rational rule. (T. Schelling, The Strategy of Conflict, pg. 283)

Hi-Lo Game

 

S1

S2

S1

(3,3)

(0,0)

S2

(0,0)

(1,1)

There are two pure strategy Nash equilibria: (S1,S1) and (S2,S2)

There are these two broad empirical facts about Hi-Lo games, people almost always choose [S1] and people with common knowledge of each other’s rationality think it is obviously rational to choose [S1].” (M. Bacharach, Beyond Individual Choice, p. 42)

Matching Pennies

 

S1

S2

S1

(1,1)

(1,1)

S2

(1,1)

(1,1)

There are no pure strategy Nash equilibria.

A mixed strategy for player i in a finite game N,(Si)iN,(ui)iN is a lottery on Si, i.e., a probability over player i’s strategies.

mixed-strat.jpg

The utilities for a mixed strategy profile (p,q) is:

Row:p(1q)pq(1p)(1q)+(1p)q
Col:p(1q)+pq+(1p)(1q)(1p)q)

We are reluctant to believe that our decisions are made at random. We prefer to be able to point to a reason for each action we take. Outside of Las Vegas we do not spin roulettes. (A. Rubinstein, Comments on the Interpretation of Game Theory, Econometrica 59, 909 - 924, 1991)

What does it mean to play a mixed strategy?

  • Randomize to confuse your opponent (e.g., matching pennies games)

  • Players randomize when they are uncertain about the other’s action (e.g., battle of the sexes game)

  • Mixed strategies are a concise description of what might happen in repeated play

  • Mixed strategies describe population dynamics: After selecting 2 agents from a population, a mixed strategy is the probability of getting an agent who will play one pure strategy or another.

 

S1

S2

S1

(1,1)

(1,1)

S2

(1,1)

(1,1)

There is one mixed strategy Nash equilibria: ((1/2:S1,1/2:S2),(1/2:S1,1/2:S2)).

Battle of the Sexes

 

S1

S2

S1

(2,1)

(0,0)

S2

(0,0)

(1,2)

There are two pure strategy Nash equilibrium (S1,S1) and (S2,S2) (and one mixed strategy Nash equilibrium).

Stag Hunt

 

S1

S2

S1

(3,3)

(0,2)

S2

(2,0)

(1,1)

There are two pure strategy Nash equilibrium (S1,S1) and (S2,S2). While (S1,S1) Pareto dominates (S1,S1), but (S2,S2) is ‘less risky’.

The problem of instituting, or improving, the social contract can be thought of as the problem of moving from riskless hunt hare equilibrium to the risky but rewarding stag hunt equilibrium. (B. Skyrms, Stag Hunt and the Evolution of Social Structure, p. 9)

Prisoner’s Dilemma

 

S1

S2

S1

(3,3)

(0,4)

S2

(4,0)

(1,1)

There is one Nash equilibrium (S2,S2). The non-equilibrium (S1,S1) Pareto-dominates (S2,S2).

Game theorists think it just plain wrong to claim that the Prisoners’ Dilemma embodies the essence of the problem of human cooperation. On the contrary, it represents a situation in which the dice are as loaded against the emergence of cooperation as they could possibly be. If the great game of life played by the human species were the Prisoner’s Dilemma, we wouldn’t have evolved as social animals!….No paradox of rationality exists. Rational players don’t cooperate in the Prisoners’ Dilemma, because the conditions necessary for rational cooperation are absent in this game. (K. Binmore, Natural Justice, p. 63)

Game Theory in Python

Nashpy

import nashpy as nash
import numpy as np

#  Coordination Game
A = np.array([[1, 0], [0, 1]])
B = np.array([[1, 0], [0, 1]])
coord = nash.Game(A, B)

print(coord)

print([1,2])
print(np.array([1,2]))
Copy to clipboard
Bi matrix game with payoff matrices:

Row player:
[[1 0]
 [0 1]]

Column player:
[[1 0]
 [0 1]]
[1, 2]
[1 2]
Copy to clipboard
sigma1_r = [1, 0]
sigma1_c = [0, 1]
print("The utilities are ", coord[sigma1_r, sigma1_c])

sigma2_r = [1 / 2, 1 / 2]
sigma2_c = [1 / 2, 1 / 2]
print("The utilities are ", coord[sigma2_r, sigma2_c])

eqs = coord.support_enumeration()
print("The Nash equilibria are:")
for ne in eqs:
    print("\t", ne)
Copy to clipboard
The utilities are  [0 0]
The utilities are  [0.5 0.5]
The Nash equilibria are:
	 (array([1., 0.]), array([1., 0.]))
	 (array([0., 1.]), array([0., 1.]))
	 (array([0.5, 0.5]), array([0.5, 0.5]))
Copy to clipboard
# Hi-Lo 

A = np.array([[3, 0], [0, 1]])
B = np.array([[3, 0], [0, 1]])
hilo = nash.Game(A, B)
eqs = hilo.support_enumeration()
print("The Nash equilibria for Hi-Lo are:")
for ne in eqs:
    print("\t", ne)
Copy to clipboard
The Nash equilibria for Hi-Lo are:
	 (array([1., 0.]), array([1., 0.]))
	 (array([0., 1.]), array([0., 1.]))
	 (array([0.25, 0.75]), array([0.25, 0.75]))
Copy to clipboard
# Battle of the Sexes

A = np.array([[2, 0], [0, 1]])
B = np.array([[1, 0], [0, 2]])
bos = nash.Game(A, B)
eqs = bos.support_enumeration()
print("The Nash equilibria for Battle of the Sexes are:")
for ne in eqs:
    print("\t", ne)
Copy to clipboard
The Nash equilibria for Battle of the Sexes are:
	 (array([1., 0.]), array([1., 0.]))
	 (array([0., 1.]), array([0., 1.]))
	 (array([0.66666667, 0.33333333]), array([0.33333333, 0.66666667]))
Copy to clipboard
# Stag Hunt

A = np.array([[3, 0], [2, 1]])
B = np.array([[3, 2], [0, 1]])
sh = nash.Game(A, B)

eqs = sh.support_enumeration()
print("The Nash equilibria for the Stag Hunt are:")
for ne in eqs:
    print("\t", ne)
Copy to clipboard
The Nash equilibria for the Stag Hunt are:
	 (array([1., 0.]), array([1., 0.]))
	 (array([0., 1.]), array([0., 1.]))
	 (array([0.5, 0.5]), array([0.5, 0.5]))
Copy to clipboard
# Prisoner's Dilemma 

A = np.array([[3, 0], [4, 1]])
B = np.array([[3, 4], [0, 1]])
pd = nash.Game(A, B)

eqs = pd.support_enumeration()
print("The Nash equilibria  for the Prisoner's Dilemma are:")
for ne in eqs:
    print("\t", ne)
Copy to clipboard
The Nash equilibria  for the Prisoner's Dilemma are:
	 (array([0., 1.]), array([0., 1.]))
Copy to clipboard

Axelrod

Axelrod is a Python package to study repeated play of the Prisoner’s Dilemma:

 

S1

S2

S1

(3,3)

(0,4)

S2

(4,0)

(1,1)

There are different ways to repeat play of PD:

  1. Two players repeatedly playing a PD

  2. Multiple players repeatedly playing PD against each other

  3. Multiple players repeatedly playing PD against their neighbors

Running a Match

In a Match, two player repeatedly play a PD.

import axelrod as axl

players = (axl.Cooperator(), axl.Alternator())
match = axl.Match(players, 5)

print("Match Play: ", match.play())
print("Match Scores: ", match.scores())
print("Final Scores: ", match.final_score())
print("Final Scores Per Turn: ", match.final_score_per_turn())
print("Winner: ", match.winner())
print("Cooperation: ", match.cooperation())
print("Normalized Coperation: ", match.normalised_cooperation()) 
Copy to clipboard
Match Play:  [(C, C), (C, D), (C, C), (C, D), (C, C)]
Match Scores:  [(3, 3), (0, 5), (3, 3), (0, 5), (3, 3)]
Final Scores:  (9, 19)
Final Scores Per Turn:  (1.8, 3.8)
Winner:  Alternator
Cooperation:  (5, 3)
Normalized Coperation:  (1.0, 0.6)
Copy to clipboard
players = (axl.Cooperator(),  axl.Random())
match = axl.Match(players=players, turns=10, noise=0.0)
match.play()  

print("Match Scores: ", match.scores())
print("Final Scores: ", match.final_score())
print("Final Scores Per Turn: ", match.final_score_per_turn())
print("Winner: ", match.winner())
print("Cooperation: ", match.cooperation())
print("Normalized Coperation: ", match.normalised_cooperation()) 
Copy to clipboard
Match Scores:  [(3, 3), (0, 5), (0, 5), (3, 3), (3, 3), (3, 3), (0, 5), (3, 3), (3, 3), (3, 3)]
Final Scores:  (21, 36)
Final Scores Per Turn:  (2.1, 3.6)
Winner:  Random: 0.5
Cooperation:  (10, 7)
Normalized Coperation:  (1.0, 0.7)
Copy to clipboard

Running a Tournament

In a tournament, each player plays every other player.

import pprint

players = [axl.Cooperator(), axl.Defector(),
           axl.TitForTat(), axl.Grudger()]
tournament = axl.Tournament(players)
results = tournament.play(progress_bar=False)
print("Ranked players: ", results.ranked_names)
print("Normalized Scores: ", results.normalised_scores  )
print("Wins: ", results.wins)
print("payoff matrix")
pprint.pprint(results.payoff_matrix);
Copy to clipboard
Ranked players:  ['Defector', 'Tit For Tat', 'Grudger', 'Cooperator']
Normalized Scores:  [[2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0], [2.3466666666666662, 2.3466666666666662, 2.3466666666666662, 2.3466666666666662, 2.3466666666666662, 2.3466666666666662, 2.3466666666666662, 2.3466666666666662, 2.3466666666666662, 2.3466666666666662], [2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666], [2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666, 2.3316666666666666]]
Wins:  [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [3, 3, 3, 3, 3, 3, 3, 3, 3, 3], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
payoff matrix
[[3.0, 0.0, 3.0, 3.0],
 [5.0, 1.0, 1.02, 1.02],
 [3.0, 0.9949999999999999, 3.0, 3.0],
 [3.0, 0.9949999999999999, 3.0, 3.0]]
Copy to clipboard
plot = axl.Plot(results)
plot.boxplot();
plot.winplot();
Copy to clipboard
../_images/02a-intro-game-theory_29_0.png ../_images/02a-intro-game-theory_29_1.png
players = [axl.Cooperator(), axl.Defector(),
           axl.TitForTat(), axl.Grudger(), axl.Random()]

tournament = axl.Tournament(players)
results = tournament.play(progress_bar=False)
print(results.ranked_names)
plot = axl.Plot(results)
plot.boxplot();
Copy to clipboard
['Grudger', 'Defector', 'Tit For Tat', 'Cooperator', 'Random: 0.5']
Copy to clipboard
../_images/02a-intro-game-theory_30_1.png
p = plot.winplot()
Copy to clipboard
../_images/02a-intro-game-theory_31_0.png
plot.payoff();
Copy to clipboard
../_images/02a-intro-game-theory_32_0.png

Axelrod’s Tournament

In 1980, Robert Axelrod (a political scientist) invited submissions to a computer tournament version of an iterated prisoners dilemma (“Effective Choice in the Prisoner’s Dilemma”).

  • 15 strategies submitted.

  • Round robin tournament with 200 stages including a 16th player who played randomly.

  • The winner (average score) was in fact a very simple strategy: Tit For Tat. This strategy starts by cooperating and then repeats the opponents previous move.

The fact that Tit For Tat won garnered a lot of (still ongoing) research. For an overview o of how to use axelrod to reproduce this first tournament, see http://axelrod.readthedocs.io/en/stable/reference/overview_of_strategies.html#axelrod-s-first-tournament.

axelrod_first_tournament = [s() for s in axl.axelrod_first_strategies]

number_of_strategies = len(axelrod_first_tournament)
for player in axelrod_first_tournament:
    print(player)
Copy to clipboard
Tit For Tat
First by Tideman and Chieruzzi: (D, D)
First by Nydegger
First by Grofman
First by Shubik
First by Stein and Rapoport: 0.05: (D, D)
Grudger
First by Davis: 10
First by Graaskamp: 0.05
First by Downing
First by Feld: 1.0, 0.5, 200
First by Joss: 0.9
First by Tullock
First by Anonymous
Random: 0.5
Copy to clipboard
tournament = axl.Tournament(
    players=axelrod_first_tournament,
    turns=200,
    repetitions=5,
)
results = tournament.play(progress_bar=False)
for name in results.ranked_names:
    print(name)
Copy to clipboard
First by Stein and Rapoport: 0.05: (D, D)
First by Shubik
First by Grofman
Tit For Tat
First by Tideman and Chieruzzi: (D, D)
First by Nydegger
Grudger
First by Davis: 10
First by Graaskamp: 0.05
First by Downing
First by Feld: 1.0, 0.5, 200
First by Joss: 0.9
First by Tullock
First by Anonymous
Random: 0.5
Copy to clipboard
plot = axl.Plot(results)
plot.boxplot();
Copy to clipboard
../_images/02a-intro-game-theory_36_0.png

There are over 200 strategies implemented in axelrod (see https://axelrod.readthedocs.io/en/stable/reference/all_strategies.html). Including some recent strategies that have done quite well in tournaments:

Press, William H. and Freeman J. Dyson (2012), Iterated prisoner’s dilemma contains strategies that dominate any evolutionary opponent. Proceedings of the National Academy of Sciences, 109, 10409–10413.

players = [ axl.ZDExtort2(), axl.ZDSet2(), axl.TitForTat()]
tournament = axl.Tournament(
    players=players,
    turns=200,
    repetitions=5,
)
results = tournament.play(progress_bar=False)
for name in results.ranked_names:
    print(name)
    
Copy to clipboard
ZD-SET-2: 0.25, 0.0, 2
Tit For Tat
ZD-Extort-2: 0.1111111111111111, 0.5
Copy to clipboard

Moran Process

Given an initial population of players, the population is iterated in rounds consisting of:

  1. matches played between each pair of players, with the cumulative total scores recorded.

  2. a player is chosen to reproduce proportional to the player’s score in the round.

  3. a player is chosen at random to be replaced.

players = [axl.Cooperator(),
           axl.Cooperator(),axl.Cooperator(),axl.Cooperator(), axl.Cooperator(), 
           axl.Cooperator(), 
           axl.Defector(), axl.Random()]
mp = axl.MoranProcess(players, turns=100)
mp.play()
print("Winning strategy: ", mp.winning_strategy_name)
mp.populations_plot();
Copy to clipboard
Winning strategy:  Defector
Copy to clipboard
../_images/02a-intro-game-theory_40_1.png

Moran Process with Mutation

players = [axl.Cooperator(), axl.Defector(),
           axl.TitForTat(), axl.Grudger()]
mp = axl.MoranProcess(players, turns=100, mutation_rate=0.1)

for _ in mp:
    if len(mp.population_distribution()) == 1:
        break

mp.populations_plot();
Copy to clipboard
../_images/02a-intro-game-theory_42_0.png