diff --git a/core/environment.go b/core/environment.go index dad7562..55294df 100644 --- a/core/environment.go +++ b/core/environment.go @@ -4,7 +4,7 @@ type Environment struct { width, height int state map[string][][]float32 value [][]bool - col [][]bool + col [][]*Turtle } func (e *Environment) Width() int { @@ -29,9 +29,9 @@ func NewEnvironment(width int, height int) *Environment { env.height = height env.state = make(map[string][][]float32) - env.col = make([][]bool, width) + env.col = make([][]*Turtle, width) for x := range env.col { - env.col[x] = make([]bool, height) + env.col[x] = make([]*Turtle, height) } env.value = make([][]bool, width) @@ -54,16 +54,20 @@ func (e *Environment) Mark(pheromone string, x, y int, amount float32) { } } -func (e Environment) Occupy(x, y int) { - e.col[x][y] = true +func (e Environment) Occupy(turtle *Turtle, x, y int) { + e.col[x][y] = turtle } -func (e Environment) Check(x, y int) bool { +func (e Environment) Get(x, y int) *Turtle { return e.col[x][y] } +func (e Environment) Check(x, y int) bool { + return e.col[x][y] != nil +} + func (e Environment) Leave(x, y int) { - e.col[x][y] = false + e.col[x][y] = nil } func (e Environment) HasValue(x, y int) bool { diff --git a/core/turtle.go b/core/turtle.go index 2df397c..62bce86 100644 --- a/core/turtle.go +++ b/core/turtle.go @@ -1,6 +1,7 @@ package core import ( + "image/color" "math/rand" ) @@ -9,6 +10,8 @@ type Turtle struct { heading int actor Actor width, height int + col color.RGBA + atts map[string]string } type NilActor struct { @@ -20,22 +23,36 @@ func (NilActor) Setup(*Environment, *Turtle) { func (NilActor) Run(*Environment, *Turtle) { } +func (t *Turtle) GetActor() Actor { + return t.actor +} + func NewTurtle(env *Environment, actor Actor) *Turtle { - for { + + for i := 0; i < 5; i++ { turtle := new(Turtle) turtle.width = env.width turtle.height = env.height turtle.xpos = rand.Intn(env.width) turtle.ypos = rand.Intn(env.height) turtle.actor = actor + turtle.atts = make(map[string]string) if env.Check(turtle.xpos, turtle.ypos) == false { actor.Setup(env, turtle) - env.Occupy(turtle.xpos, turtle.ypos) + env.Occupy(turtle, turtle.xpos, turtle.ypos) turtle.setRandomHeading() return turtle } } + return nil +} +func (t *Turtle) GetColor() color.RGBA { + return t.col +} + +func (t *Turtle) SetColor(col color.RGBA) { + t.col = col } func (t *Turtle) Pos() (int, int) { @@ -75,6 +92,14 @@ func (t *Turtle) Wiggle() { t.heading = h } +func (t *Turtle) GetAttribute(name string) string { + return t.atts[name] +} + +func (t *Turtle) SetAttribute(name, val string) { + t.atts[name] = val +} + func (t *Turtle) TurnAround() { t.heading = (t.heading + 4) % 8 } @@ -206,6 +231,22 @@ func (t *Turtle) FollowGradient(env *Environment, distance int, threshold float3 } } +func (t *Turtle) Check(env *Environment) *Turtle { + dx := headings[t.heading][0] + dy := headings[t.heading][1] + + xpos := (t.xpos + dx) % (env.width) + if xpos < 0 { + xpos = env.width - 1 + } + ypos := (t.ypos + dy) % (env.height) + if ypos < 0 { + ypos = env.height - 1 + } + + return env.Get(xpos, ypos) +} + func (t *Turtle) Step(env *Environment) bool { dx := headings[t.heading][0] dy := headings[t.heading][1] @@ -228,7 +269,7 @@ func (t *Turtle) Step(env *Environment) bool { t.ypos = oy success = false } - env.Occupy(t.xpos, t.ypos) + env.Occupy(t, t.xpos, t.ypos) return success } diff --git a/experiments/experiment.go b/experiments/experiment.go index 7505aff..6e85a79 100644 --- a/experiments/experiment.go +++ b/experiments/experiment.go @@ -25,6 +25,7 @@ type Experiment struct { turtles []*core.Turtle graphics *graphics.Graphics initializedTurtles int + OnStep func(*core.Environment, []*core.Turtle, int) } func (e *Experiment) InitializeExperiment() { @@ -46,7 +47,7 @@ func (e *Experiment) InitializeExperiment() { } e.env = core.NewEnvironment(*width, *height) - e.turtles = make([]*core.Turtle, *numTurtles) + e.turtles = make([]*core.Turtle, 0) e.graphics = graphics.NewGraphics(int32(*width), int32(*height), int32(*pxsize)) } @@ -65,10 +66,15 @@ func (e *Experiment) InitEnvironment(f func(environment *core.Environment)) { } func (e *Experiment) InitNTurtles(f func() core.Actor, num int) { + numSuccess := 0 for i := e.initializedTurtles; i < e.initializedTurtles+num; i++ { - e.turtles[i] = core.NewTurtle(e.env, f()) + t := core.NewTurtle(e.env, f()) + if t != nil { + e.turtles = append(e.turtles, t) + numSuccess++ + } } - e.initializedTurtles += num + e.initializedTurtles += numSuccess } func (e *Experiment) InitTurtles(f func() core.Actor) { @@ -81,8 +87,24 @@ func (e *Experiment) Run() { running := true wait.Add(1) go func() { + step := 0 for running { e.graphics.Render(e.env, e.turtles) + e.OnStep(e.env, e.turtles, step) + + newTurtles := make([]*core.Turtle, 0) + deleted := 0 + for _, t := range e.turtles { + if t.GetAttribute("status") != "dead" { + newTurtles = append(newTurtles, t) + } else { + e.env.Leave(t.Pos()) // Dead turtles occupy no space + deleted++ + } + } + + e.turtles = newTurtles + step++ } wait.Done() }() diff --git a/experiments/predatorprey/main.go b/experiments/predatorprey/main.go new file mode 100644 index 0000000..80f5300 --- /dev/null +++ b/experiments/predatorprey/main.go @@ -0,0 +1,142 @@ +package main + +import ( + "flag" + "fmt" + "git.openprivacy.ca/sarah/microworlds/core" + "git.openprivacy.ca/sarah/microworlds/experiments" + "image/color" + "math" + "math/rand" +) + +var numPrey = flag.Int("numPrey", 600, "the number of prey") +var numPred = flag.Int("numPred", 60, "the number of predators") + +type Predator struct { + Steps int + Energy int +} + +func (sm *Predator) Setup(env *core.Environment, t *core.Turtle) { + // Do nothing + t.SetAttribute("type", "predator") + t.SetColor(color.RGBA{255, 200, 0, 0}) + sm.Energy = 50 +} + +func (sm *Predator) Run(env *core.Environment, t *core.Turtle) { + sm.Steps++ + + if sm.Steps == 30 { + t.SetAttribute("status", "dead") + return + } + sm.Energy-- + if sm.Energy == 0 { + t.SetAttribute("status", "dead") + return + } + t.Wiggle() + prey := t.Check(env) + if prey != nil { + if prey.GetAttribute("type") == "prey" && prey.GetAttribute("status") != "dead" { + prey.SetAttribute("status", "dead") + sm.Energy = int(math.Max(float64(sm.Energy)+10, 120)) + } + } + t.FollowGradient(env, 1, 3, "scent") + t.Step(env) +} + +type Prey struct { + Steps int + Energy int +} + +func (sm *Prey) Setup(env *core.Environment, t *core.Turtle) { + // Do nothing + t.SetAttribute("type", "prey") + t.SetColor(color.RGBA{100, 0, 100, 0}) + sm.Steps = 0 + sm.Energy = 25 +} + +func (sm *Prey) Run(env *core.Environment, t *core.Turtle) { + sm.Steps++ + sm.Energy-- + + if sm.Steps >= 20 || sm.Energy == 0 { + t.SetAttribute("status", "dead") + return + } + t.Wiggle() + t.Drop(env, 1, "scent") + t.Step(env) + if env.HasValue(t.Pos()) { + env.TakeValue(t.Pos()) + sm.Energy += 1 + } +} + +func main() { + experiment := new(experiments.Experiment) + experiment.InitializeExperiment() + experiment.InitEnvironment(func(env *core.Environment) { + for i := 0; i < 600; i++ { + x := rand.Intn(env.Width()) + y := rand.Intn(env.Height()) + env.PutValue(x, y) + } + }) + experiment.InitNTurtles(func() core.Actor { + sm := new(Predator) + return sm + }, *numPred) + experiment.InitNTurtles(func() core.Actor { + sm := new(Prey) + return sm + }, *numPrey) + experiment.InitPheromone("scent", color.RGBA{0x80, 0xFF, 0x00, 0x00}) + experiment.OnStep = func(env *core.Environment, turtles []*core.Turtle, step int) { + alive := 0 + predalive := 0 + + // Grow Grass + x := rand.Intn(env.Width()) + y := rand.Intn(env.Height()) + env.PutValue(x, y) + + for _, turtle := range turtles { + if turtle.GetAttribute("type") == "prey" && turtle.GetAttribute("status") != "dead" { + alive++ + prey := turtle.GetActor().(*Prey) + if prey.Steps > 10 && prey.Energy > 5 && rand.Intn(5) == 1 { + experiment.InitNTurtles(func() core.Actor { + sm := new(Prey) + return sm + }, 1) + alive++ + } + } + if turtle.GetAttribute("type") == "predator" && turtle.GetAttribute("status") != "dead" { + predalive++ + + if turtle.GetAttribute("type") == "predator" { + pred := turtle.GetActor().(*Predator) + if pred.Energy >= 80 && rand.Intn(16) == 1 { + experiment.InitNTurtles(func() core.Actor { + sm := new(Predator) + return sm + }, 1) + predalive++ + } + } + } + } + if step%10 == 0 { + fmt.Printf("Prey Alive: %v | Pred Alive: %v \n", alive, predalive) + } + } + experiment.Run() +} diff --git a/graphics/graphics.go b/graphics/graphics.go index e190f6f..ca3f462 100644 --- a/graphics/graphics.go +++ b/graphics/graphics.go @@ -7,7 +7,7 @@ import ( "math" "os" "strconv" - //"strconv" + // "strconv" ) type Graphics struct { @@ -87,16 +87,20 @@ func (g *Graphics) Render(env *core.Environment, turtles []*core.Turtle) { } } - g.renderer.SetDrawColor(0xF3, 0x81, 0, 0x00) for _, t := range turtles { + if t.GetAttribute("status") == "dead" { + continue + } x, y := t.Pos() + col := t.GetColor() + g.renderer.SetDrawColor(col.R, col.G, col.B, col.A) g.DrawTileColor(int32(x), int32(y)) t.Run(env) } // TODO: Move this into an environment specification for name := range g.colorMap { - env.Evaporate(0.99, name) + env.Evaporate(0.95, name) } g.renderer.Present()