Created
October 10, 2013 03:54
-
-
Save mark/6912816 to your computer and use it in GitHub Desktop.
Simulate an ecosystem of the ultimatum game (http://en.wikipedia.org/wiki/Ultimatum_game)
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
class Array | |
def random | |
self[ rand(length) ] | |
end | |
def weighted_random(weights=nil) | |
return weighted_random(map {|n| n.send(weights)}) if weights.is_a? Symbol | |
weights ||= Array.new(length, 1.0) | |
total = weights.inject(0.0) {|t,w| t+w} | |
point = rand * total | |
zip(weights).each do |n,w| | |
return n if w >= point | |
point -= w | |
end | |
end | |
end | |
class Person | |
attr_reader :generosity, :greed | |
attr_accessor :money, :trials | |
def initialize(world, generosity, greed) | |
@world = world | |
@generosity = generosity | |
@greed = greed | |
@money = 0 | |
@trials = 0 | |
end | |
def self.random(world) | |
new world, rand(world.max_offer+1), rand(world.max_offer+1) | |
end | |
def child | |
Person.new @world, mutate_param(generosity), mutate_param(greed) | |
end | |
def mutate_param(param) | |
if rand < @world.mutation_rate | |
# new_param = param + (rand < 0.5 ? 1 : -1) | |
# new_param = @world.max_offer if new_param > @world.max_offer | |
# new_param = 0 if new_param < 0 | |
# new_param | |
rand(@world.max_offer + 1) | |
else | |
param | |
end | |
end | |
def average_money | |
@money * 100 / @trials | |
end | |
def to_s | |
"(#{ generosity.to_s.rjust(2) }, #{ greed.to_s.rjust(2) })" | |
end | |
end | |
class Ultimatum | |
attr_reader :offer | |
def initialize(offer) | |
@offer = offer | |
end | |
def play(player_1, player_2) | |
player_1.trials += 1 | |
player_2.trials += 1 | |
offer_accepted = player_1.generosity >= player_2.greed | |
if offer_accepted | |
player_2.money += player_1.generosity | |
player_1.money += offer - player_1.generosity | |
end | |
return offer_accepted | |
end | |
end | |
class World | |
MAX_TRIALS = 10_000_000 | |
MUTATION_RATE = 0.05 | |
attr_reader :mutation_rate | |
def initialize(ultimatum, max_trials = MAX_TRIALS, mutation_rate = MUTATION_RATE, max_population = nil) | |
@ultimatum = ultimatum | |
@max_trials = max_trials | |
@mutation_rate = mutation_rate | |
@max_population = max_population | |
@generation = 0 | |
end | |
def next_generation | |
if @people | |
generate_child_generation | |
else | |
generate_empty_generation | |
end | |
@generation += 1 | |
end | |
def generate_empty_generation | |
@people = [] | |
@max_population.times { @people << Person.random(self) } | |
# (0..max_offer).each do |generosity| | |
# (0..max_offer).each do |greed| | |
# @people << Person.new(self, generosity, greed) | |
# end | |
# end | |
end | |
def max_offer | |
@ultimatum.offer | |
end | |
def accepted_percentage | |
@accepted_count * 100 / @trial_count | |
end | |
def generate_child_generation | |
new_generation = [] | |
@people.length.times do | |
new_generation << @people.weighted_random(:average_money).child | |
end | |
@people = new_generation | |
end | |
def run(generations, trials = @max_trials) | |
generations.times do | |
next_generation | |
run_generation(trials) | |
display | |
end | |
end | |
def run_generation(trials = @max_trials) | |
@trial_count = 0 | |
@accepted_count = 0 | |
trials.times do | |
person_1 = @people.random | |
person_2 = @people.random | |
accepted = @ultimatum.play(person_1, person_2) | |
@trial_count += 1 | |
@accepted_count += 1 if accepted | |
end | |
end | |
def display | |
header = "GENERATION #{ @generation } // #{ accepted_percentage }% accepted" | |
system("clear") | |
puts header | |
# puts "=" * header.length | |
# @people.sort_by(&:average_money).reverse.each { |p| puts "#{ p } ==> #{ p.average_money }" } | |
counts = Array.new(max_offer+1) { Array.new(max_offer+1) { 0 } } | |
@people.each { |p| counts[p.generosity][p.greed] += 1 } | |
(max_offer+1).times do |generosity| | |
puts "+---" * (max_offer+1) + "+" | |
(max_offer+1).times do |greed| | |
print "|" | |
if counts[generosity][greed] > 0 | |
print counts[generosity][greed].to_s.rjust(3) | |
else | |
print " " | |
end | |
end | |
puts "|" | |
end | |
puts "+---" * (max_offer+1) + "+" | |
puts | |
end | |
end | |
MAX_POPULATION = 1_000 | |
MAX_OFFER = 20 | |
GENERATIONS = 1_000 | |
MUTATION_RATE = 0.05 | |
MAX_TRIALS = 100_000 | |
@game = Ultimatum.new(MAX_OFFER) | |
@world = World.new(@game, MAX_TRIALS, MUTATION_RATE, MAX_POPULATION) | |
@world.run GENERATIONS |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment