Last active
December 26, 2015 22:59
-
-
Save joshnuss/7226604 to your computer and use it in GitHub Desktop.
WIP Prisoner's Dilemma Simulation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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