Skip to content

Commit

Permalink
added chapter 7 code
Browse files Browse the repository at this point in the history
  • Loading branch information
sausheong committed Dec 19, 2011
1 parent 7649944 commit 22984d5
Show file tree
Hide file tree
Showing 31 changed files with 12,487 additions and 0 deletions.
3,001 changes: 3,001 additions & 0 deletions Chapter 7 - Money, sex and evolution/evolution/evolution.csv

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions Chapter 7 - Money, sex and evolution/evolution/evolution.r
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
library(ggplot2)
data <- read.table("evolution.csv", header=F, sep=",")
colnames(data) <- c('population','metabolism','vision_range')
pdf("evolution.pdf")
time = 1:nrow(data)
grid.newpage()
pushViewport(viewport(layout=grid.layout(1,2)))
vplayout <- function(x,y) {viewport(layout.pos.row=x, layout.pos.col=y)}
p <- qplot(time, metabolism, data=data, geom=c("point", "smooth"), main="Evolution in metabolism")
print(p, vp=vplayout(1,1))
p <- qplot(time, vision_range, data=data, geom=c("point", "smooth"), main="Evolution in vision range")
print(p, vp=vplayout(1,2))
dev.off()
24 changes: 24 additions & 0 deletions Chapter 7 - Money, sex and evolution/evolution/food.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Food
attr_reader :quantity, :position

def initialize(slot, p)
@position = p
@slot = slot
@quantity = rand(20) + 10
end

def eat(much)
@quantity -= much
end

def draw
@slot.oval :left => @position[0], :top => @position[1], :radius => quantity, :center => true
end

def tick
if @quantity <= 0
$food.delete self
end
draw
end
end
27 changes: 27 additions & 0 deletions Chapter 7 - Money, sex and evolution/evolution/parameters.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FPS = 6
ROID_SIZE = 6
WORLD = {:xmax => ROID_SIZE * 100, :ymax => ROID_SIZE * 100} # boundary of the world
POPULATION_SIZE = 50
FOOD_COUNT = 30
OBSTACLE_SIZE = 30
MAGIC_NUMBER = 10 # the number of roids it will monitor

SEPARATION_RADIUS = ROID_SIZE * 2 # steer to avoid crowding of flockmates
ALIGNMENT_RADIUS = ROID_SIZE * 35 # steer towards average heading of flockmates
COHESION_RADIUS = ROID_SIZE * 35 # steer to move toward average position of flockmates

SEPARATION_ADJUSTMENT = 10 # how far away should roids stay from each other (small further away)
ALIGNMENT_ADJUSTMENT = 8 # how aligned are the roids with each other (smaller more aligned)
COHESION_ADJUSTMENT = 100 # how cohesive the roids are with each other (smaller more cohesive)
CENTER_RADIUS = ROID_SIZE * 10 # radius of how close to the center it stays
MAX_ROID_SPEED = 20

END_OF_THE_WORLD = 3000
MAX_LIFESPAN = 100
MAX_ENERGY = 100
CHILDBEARING_AGE = 20..50
CHILDBEARING_ENERGY_LEVEL = 15
CHILDBEARING_ENERGY_SAP = 0.8

MAX_METABOLISM = 4.0
MAX_VISION_RANGE = ROID_SIZE * 10.0
199 changes: 199 additions & 0 deletions Chapter 7 - Money, sex and evolution/evolution/roid.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
class Roid
attr_reader :velocity, :position, :energy, :uid, :sex, :lifespan, :age, :metabolism, :vision_range

def initialize(slot, p, v, id)
@velocity = v # assume v is a Vector with X velocity and Y velocity as elements
@position = p # assume p is a Vector with X and Y as elements
@slot = slot
@energy = rand(MAX_ENERGY)
@uid = id
@sex = rand(2) == 1 ? :male : :female
@lifespan = rand(MAX_LIFESPAN)
@age = 0
@metabolism = rand(MAX_METABOLISM*10.0)/10.0
@vision_range = rand(MAX_VISION_RANGE*10.0)/10.0
end

def distance_from(roid)
distance_from_point(roid.position)
end

def distance_from_point(vector)
x = self.position[0] - vector[0]
y = self.position[1] - vector[1]
Math.sqrt(x*x + y*y)
end

def nearby?(threshold, roid)
return false if roid === self
(distance_from(roid) < threshold) and within_fov?(roid)
end

def within_fov?(roid)
v1 = self.velocity - self.position
v2 = roid.position - self.position
cos_angle = v1.inner_product(v2)/(v1.r*v2.r)
Math.acos(cos_angle) < 0.75 * Math::PI
end

def draw
size = ROID_SIZE * @energy.to_f/50.0
size = 10 if size > 10
o = @slot.oval :left => @position[0], :top => @position[1], :radius => size, :center => true
o.fill = @slot.lightblue if @sex == :male
@slot.line @position[0], @position[1], @position[0] - @velocity[0], @position[1] - @velocity[1]
end

def move
@delta = Vector[0,0]
%w(separate align cohere muffle hungry).each do |action|
self.send action
end
@velocity += @delta
@position += @velocity
fallthrough and draw
end

def separate
distance = Vector[0,0]
r = $roids.sort {|a,b| self.distance_from(a) <=> self.distance_from(b)}
roids = r.first(MAGIC_NUMBER)
roids.each do |roid|
if nearby?(SEPARATION_RADIUS, roid)
distance += self.position - roid.position
end
end
@delta += distance
end

# roids should look out for roids near it and then fly towards the center of where the rest are flying
def align
alignment = Vector[0,0]
r = $roids.sort {|a,b| self.distance_from(a) <=> self.distance_from(b)}
roids = r.first(MAGIC_NUMBER)
roids.each do |roid|
alignment += roid.velocity
end
alignment /= MAGIC_NUMBER
@delta += alignment/ALIGNMENT_ADJUSTMENT
end

# roids should stick to each other
def cohere
average_position = Vector[0,0]
r = $roids.sort {|a,b| self.distance_from(a) <=> self.distance_from(b)}
roids = r.first(MAGIC_NUMBER)
roids.each do |roid|
average_position += roid.position
end
average_position /= MAGIC_NUMBER
@delta += (average_position - @position)/COHESION_ADJUSTMENT
end

# get the roids to move around the center of the displayed world
def center
@delta -= (@position - Vector[WORLD[:xmax]/2, WORLD[:ymax]/2]) / CENTER_RADIUS
end

# muffle the speed of the roid to dampen the swing
# swing causes the roid to move too quickly out of range to be affected by the rules
def muffle
if @velocity.r > MAX_ROID_SPEED
@velocity /= @velocity.r
@velocity *= MAX_ROID_SPEED
end
end

def center
@delta -= (@position - Vector[WORLD[:xmax]/2, WORLD[:ymax]/2]) / CENTER_RADIUS
end

def fallthrough
x = case
when @position[0] < 0 then WORLD[:xmax] + @position[0]
when @position[0] > WORLD[:xmax] then WORLD[:xmax] - @position[0]
else @position[0]
end
y = case
when @position[1] < 0 then WORLD[:ymax] + @position[1]
when @position[1] > WORLD[:ymax] then WORLD[:ymax] - @position[1]
else @position[1]
end

@position = Vector[x,y]
end

# get attracted to food
def hungry
$food.each do |food|
if distance_from_point(food.position) < (food.quantity + @vision_range)
@delta -= self.position - food.position
end
if distance_from_point(food.position) <= food.quantity + 15
eat food
end
end
end

def reduce_energy_from_childbirth
@energy = @energy * CHILDBEARING_ENERGY_SAP
end

# consume the food and replenish energy with it
def eat(food)
food.eat 1
@energy += @metabolism
end

# lose energy at every tick
def lose_energy
@energy -= 1
end

def grow_older
@age += 1
end

def inherit(crossover)
@metabolism = crossover[0]
@vision_range = crossover[1]
end

def procreate
if attractive and @sex == :female
# check for potential nearby mates
r = $roids.sort {|a,b| self.distance_from(a) <=> self.distance_from(b)}
roids = r.first(MAGIC_NUMBER)
roids.each do |roid|
if roid.attractive and roid.sex == :male
baby = Roid.new(@slot, @position, @velocity, 1001)
crossovers = [[@metabolism, @vision_range],
[@metabolism,roid.vision_range],
[roid.metabolism, @vision_range],
[roid.metabolism,roid.vision_range]]
baby.inherit crossovers[rand(4)]
$roids << baby
reduce_energy_from_childbirth
roid.reduce_energy_from_childbirth
end

end
end
end

def attractive
CHILDBEARING_AGE.include? @age and @energy > CHILDBEARING_ENERGY_LEVEL
end

# called at every tick of time
def tick
move
lose_energy
grow_older
procreate
if @energy <= 0 or @age > @lifespan
$roids.delete self
end
end

end
Empty file.
82 changes: 82 additions & 0 deletions Chapter 7 - Money, sex and evolution/evolution/utopia.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
require 'matrix'
require 'csv'
require 'enc/trans/transdb'
require './parameters'
require './roid'
require './food'

class Vector
def /(x)
if (x != 0)
Vector[self[0]/x.to_f,self[1]/x.to_f]
else
self
end
end
end

def random_location
Vector[rand(WORLD[:xmax]),rand(WORLD[:ymax])]
end

def populate
POPULATION_SIZE.times do |i|
random_velocity = Vector[rand(11)-5,rand(11)-5]
$roids << Roid.new(self, random_location, random_velocity, i)
end
end

def scatter_food
FOOD_COUNT.times do
$food << Food.new(self, random_location)
end
end

def randomly_scatter_food(probability)
if (0..probability).include?(rand(100))
$food << Food.new(self, random_location)
end
end

def write(data)
CSV.open('data.csv', 'w') do |csv|
data.each do |row|
csv << row
end
end
end

Shoes.app(:title => 'Utopia', :width => WORLD[:xmax], :height => WORLD[:ymax]) do
background ghostwhite
stroke slategray

$roids = []
$food = []
data = []
populate
scatter_food

time = END_OF_THE_WORLD

animate(FPS) do
randomly_scatter_food 30
clear do
fill yellowgreen
$food.each do |food| food.tick; end
fill gainsboro
$roids.each do |roid|
roid.tick
end
mean_metabolism = $roids.inject(0.0){ |sum, el| sum + el.metabolism}.to_f / $roids.size
mean_vision_range = $roids.inject(0.0){ |sum, el| sum + el.vision_range}.to_f / $roids.size
data << [$roids.size, mean_metabolism.round(2), mean_vision_range.round(2)]
para "countdown: #{time}"
para "population: #{$roids.size}"
para "metabolism: #{mean_metabolism.round(2)}"
para "vision range: #{mean_vision_range.round(2)}"
end

time -= 1
close & write(data) if time < 0 or $roids.size <= 0
end
end
24 changes: 24 additions & 0 deletions Chapter 7 - Money, sex and evolution/inheritance/food.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Food
attr_reader :quantity, :position

def initialize(slot, p)
@position = p
@slot = slot
@quantity = rand(20) + 10
end

def eat(much)
@quantity -= much
end

def draw
@slot.oval :left => @position[0], :top => @position[1], :radius => quantity, :center => true
end

def tick
if @quantity <= 0
$food.delete self
end
draw
end
end
Loading

0 comments on commit 22984d5

Please sign in to comment.