(EDIT: something changed in the code so the description given in this post may be not 100% accurate. Some comments in the source code can give more info about)
(github code: https://github.com/tonyx/PrisonerDilemmaFsharp)
A quick look at the prisoner dilemma description in wikipedia.
This code is about:
defining the possible strategies
defining the player types
creating an iterated prisoner dilemma tournament
playing a tournament.
computing the scores in a tournament.
Let's start with the description and the code:
1) Each move can be: Cooperates, or Defect
2) the result of a single game between two players is this record
Let's also define the mutual score of a jointmove.
3) given a JointMove, we get a score determined by both the players moves:
To determine the JointScore, given a JointMove we have the following function
I want to have a version of the previous function that allows me to "parametrize" the scores, and chack the P>R>T>S rule at compile time
In the "iterated" version, two players play repeatedly. each player remembers the history of the jointMoves and can use it to decide the next move.
So a player adopts a Strategy, which is a function from jointMoves list to a Move:
type Strategy = JointMove list -> Move
(EDIT: strategy changed ad follows:
type Strategy = Move list -> Move list -≷ Move)
We may also want to label a strategy with a name, so we define a type of record called StrategyInfo:
type StrategyInfo = {Name: string; Strategy:Strategy}
We want to give to each player a name and a StrategyInfo (which means a strategy with a name):
type Player = {Name: string; StrategyInfo: StrategyInfo}
Any game, at a certain state, involves two players, and a list of jointMove that they made:
type Game = { Player1:Player; Player2:Player; JointmoveHistory: JointMove list }
A "tick" is the joint play of both player in a game. The result will be the same game with their joint moves added to the history of moves:
A function for "ticking" a game n time will be useful:
Now, let's try some simple cases, not before defining the naive strategy, the cooperator:
let (cooperatorStrategy:Strategy) =
fun _ ->
Cooperate
We want two create a cooperatorstrategyInfo, and two cooperators players, which use the same strategyInfo:
let cooperatorStrategyInfo = {Name="cooperatorStrategy";Strategy=cooperatorStrategy}
let cooperator1 = {Name = "cooperator1";StrategyInfo= cooperatorStrategyInfo}
let cooperator2 = {Name = "cooperator2";StrategyInfo= cooperatorStrategyInfo}
let's define a new game:
let aGame = {Player1=cooperator1;Player2=cooperator2;JointmoveHistory=[]}
let's tick it one time:
game |> tick
the result is a game with a one item in the history of moves.
let't "3 ticks" the game, using the nTicks function:
nTick game 3
the resulting game has a history of moves longest 3.
We can't do so much with a single game.
We need a way to define a tournament, which is based on a list of games, and a number representing the "ticks for game"
(edit July 11 - 2019: in the new version I dropped the tornment type, and always use the games, by playing a game for n "ticks" using the function playGameNTimes)
type Tournment = {Games:Game list; TicksForGame: int}
What the previous code actually means is, described in a procedural way:
for each player, get any player which is different from the current player, and create a game of them. The result will be "flattened" by the List(@) [] operator.
The resulting operation will be the game, and the function will return a structure given by the games and the ticks for each game.
This will ensure that for each pair of players there will be a game with it as the first and second player (and vice-versa).
To play a tournament, we just have to tick each game:
Given a game, we may want to get the total score of the game (given by cumulating all the scores):
For a list of games, we want to get all the players and score for game, and put them in triplets (player1, player2, jointScore):
We want to compute the score in a set of games for a single player.
And we want also the scores for all the players.
(it looks redundant having a way to re-extract players that we already used from the beginning, but for now I found easier to get back players from the games, also because in future "evolutionary" versions of the game players may mutate).
Now suppose that we will start a tournment between four players:
a cooperator, a defector, a random, and a titForTat
I define a strategyInfo for each of them:
let cooperatorStrategyInfo = {Name="cooperatorStrategy";Strategy=cooperatorStrategy}
let defectorStrategyInfo = {Name="defectorStrategy"; Strategy=defectorStrategy}
let randomStrategyInfo = {Name="randomStrategy";Strategy=randomStrategy}
let titForTatStrategyInfo = {Name="titForTatStrategy";Strategy=titForTatStrategy}
To define a single player, like a cooperator, we may say:
let cooperatorPlayer = {Name="cooperator";StrategyInfo=cooperatorStrategyInfo}
But we rather want to have a chunk of cooperators, sharing the same strategy, so we have the following function:
Now suppose we want to make a tornment between 5 cooperators, 5 titForTat, 5 defectors, and 5 randoms:
let cooperatorPlayers = makeNPlayersByStrategyInfo cooperatorStrategyInfo 5
let titForTatPlayers = makeNPlayersByStrategyInfo titForTatStrategyInfo 5
let randomPlayers = makeNPlayersByStrategyInfo randomStrategyInfo 5
let defectorPlayers = makeNPlayersByStrategyInfo defectorStrategyInfo 5
We put all players in a single list:
let allPlayers = cooperatorPlayers@titForTatPlayers@randomPlayers@defectorPlayers
we create a tournment, making all players play against each other, with 10 ticks for game:
let tournment = makeTournment allPlayers 10
we run the tournment:
let playedGames = playTournment tournment
Now we can see all the scores, sorted from the lowest:
sortedPlayersScore playedGames;;
this is the sketch of the result, showing that in this kind of tournament the defector is generally stronger.
This is just a particular case, where the defectors win as long as there are many cooperators to spoil. When there are many defectors (or free riders) they will perform lower because a game between defectors will end up in a lower total outcome, compared to, say cooperator vs cooperator (or titForTat vs titForTat).
Analyzing the prisoner dilemma theory is beyond the goal of this post.
val it : (Player * int) list =
[({Name = "cooperatorStrategy_4";
StrategyInfo = {Name = "cooperatorStrategy";
Strategy =
({Name = "cooperatorStrategy_2";
StrategyInfo = {Name = "cooperatorStrategy";
Strategy =
({Name = "cooperatorStrategy_0";
StrategyInfo = {Name = "cooperatorStrategy";
Strategy =
...
Strategy =
({Name = "defectorStrategy_3";
StrategyInfo = {Name = "defectorStrategy";
Strategy =
({Name = "defectorStrategy_4";
StrategyInfo = {Name = "defectorStrategy";
Strategy =
({Name = "defectorStrategy_0";
StrategyInfo = {Name = "defectorStrategy";
Strategy =
({Name = "defectorStrategy_2";
StrategyInfo = {Name = "defectorStrategy";
Strategy =
Future development:
- implementing evolution
- implementing real time chart visualization
No comments:
Post a Comment