Skip to content

Instantly share code, notes, and snippets.

@mark
Created October 10, 2013 03:54
Show Gist options
  • Save mark/6912816 to your computer and use it in GitHub Desktop.
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)
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