Skip to content

Instantly share code, notes, and snippets.

@joshnuss
Last active December 26, 2015 22:59
Show Gist options
  • Save joshnuss/7226604 to your computer and use it in GitHub Desktop.
Save joshnuss/7226604 to your computer and use it in GitHub Desktop.
WIP Prisoner's Dilemma Simulation
# Prisoner's Dilemma
defmodule Scoreboard do
use GenServer.Behaviour
def start do
:gen_server.start_link({:local, :scoreboard}, __MODULE__, [], [])
end
def init do
{:ok, []}
end
def handle_call(:clear, _from, _config) do
# todo
end
def handle_cast({:score, strategies, generation, results}, scoreboard) do
scores = tally(results)
log_results(strategies, generation, results, scores)
{:noreply, scoreboard}
end
def handle_call(:report, _from, scoreboard) do
# todo
end
# when both cooperate, both get 3 points
def tally({:cooperate, :cooperate}), do: {3,3}
# when one cooperates and other defects, defector gets 4 points, cooperater gets 1 point
def tally({:cooperate, :defect}), do: {1,4}
def tally({:defect, :cooperate}), do: {4,1}
# when both defect, both get 1 point
def tally({:defect, :defect}), do: {1,1}
def log_results({strategy1, strategy2}, generation, {result1, result2}, {score1, score2}) do
IO.puts "#{generation+1} #{inspect strategy1}: #{result1} #{score1}, #{inspect strategy2}: #{result2} #{score2}"
end
def score(strategies, generation, results) do
:gen_server.cast(:scoreboard, {:score, strategies, generation, results})
end
def clear, do: :gen_server.call(:scoreboard, :clear)
def report, do: :gen_server.call(:scoreboard, :report)
end
defmodule Dilemma do
use GenServer.Behaviour
defrecord Config, strategies: nil, generations: 0, plays: 0
def start(strategies, generations) do
config = Config.new(strategies: strategies, generations: generations)
:gen_server.start_link(__MODULE__, [config], [])
end
def init([config=Config[]]) do
{:ok, config}
end
def handle_cast(:move, config) do
next_move(config)
{:noreply, config}
end
def next_move(config), do: next_move(config, 0, [], [])
# dont do anything when the number of generations requested in the config matches the current generation, cause that means we're done
def next_move(Config[generations: generations], generations, _, _), do: nil
# run a single generation
def next_move(config=Config[strategies: {strategy1, strategy2}], generation, player1_history, player2_history) do
# ask each strategy what it wants to do, based on previous history
result1 = strategy1.move(player1_history)
result2 = strategy2.move(player2_history)
# notify the scoreboard of what just happened
Scoreboard.score(config.strategies, generation, {result1, result2})
# continue on to next generation
next_move(config, generation+1, [{result1, result2} | player1_history],
[{result2, result1} | player2_history])
end
end
#### STRATEGIES
defmodule BlindOptimist do
# regardless of history, will always cooperate
def move(_history), do: :cooperate
end
defmodule Evil do
# regardless of history, will always defect
def move(_history), do: :defect
end
defmodule TitForTat do
# if history is empty, we start by cooperating
def move([]), do: :cooperate
# check the other players action, and copy it
def move([{_me, opponent}]), do: opponent
def move([{_me, opponent} | _rest]), do: opponent
end
defmodule TitForTwo do
# if history is empty, we start by cooperating
def move([]), do: :cooperate
# check if the other player has cooperated twice, otherwise defect
def move(history) do
moves = Enum.take(history, 2)
case moves do
[{_, :cooperate}, {_, :cooperate}] -> :cooperate
_ -> :defect
end
end
end
defmodule TwoForTat do
# if history is empty, we start by cooperating
def move([]), do: :cooperate
# check if the other player has cooperated at least once in the past 2 turns, otherwise defect
def move(history) do
moves = Enum.take(history, 2)
case moves do
[{_, :cooperate}, _ ] -> :cooperate
[_ , {_, :cooperate}] -> :cooperate
_ -> :defect
end
end
end
defmodule Random do
# regardless of history, return a random result
def move(_history) do
rand = :random.uniform(4)
if rem(rand, 2) == 0 do
:cooperate
else
:defect
end
end
end
# build a list of all strategies
strategies = [TitForTat, TitForTwo, TwoForTat, BlindOptimist, Evil, Random]
Scoreboard.start
# itereate thru each strategy, and create a dilemma where each strategy gets
# to play against another (including itself) for 100,000 generations
Enum.each strategies, fn strategy1 ->
Enum.each strategies, fn strategy2 ->
{:ok, pid} = Dilemma.start({strategy1, strategy2}, 100000)
:gen_server.cast(pid, :move)
end
end
:timer.send_after 100000, :foo
receive do n -> IO.inspect(n) end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment