diff --git a/actors/ant.go b/actors/ant.go index 3f68dff..5e4dda7 100644 --- a/actors/ant.go +++ b/actors/ant.go @@ -23,12 +23,12 @@ func (a *Ant) Run(env *core.Environment, t *core.Turtle) { t.TurnAround() } else { t.Wiggle() - t.FollowGradient(env, a.SniffDistance, 5) + t.FollowGradient(env, a.SniffDistance, 5, "food") } t.Step(env) } else if a.Carrying == true { a.DropSize -= 0.6 - t.Drop(env, a.DropSize) + t.Drop(env, a.DropSize, "food") t.Wiggle() t.Step(env) } diff --git a/actors/slimemold.go b/actors/slimemold.go index 13a86e5..7903ba3 100644 --- a/actors/slimemold.go +++ b/actors/slimemold.go @@ -14,7 +14,7 @@ func (sm *SlimeMold) Setup(env *core.Environment, t *core.Turtle) { func (sm *SlimeMold) Run(env *core.Environment, t *core.Turtle) { t.Wiggle() - t.FollowGradient(env, sm.SniffDistance, 2) + t.FollowGradient(env, sm.SniffDistance, 2, "trail") t.Step(env) - t.Drop(env, 1) + t.Drop(env, 1, "trail") } diff --git a/core/environment.go b/core/environment.go index 676eefd..dad7562 100644 --- a/core/environment.go +++ b/core/environment.go @@ -2,8 +2,7 @@ package core type Environment struct { width, height int - state [][]float32 - pstate [][]float32 + state map[string][][]float32 value [][]bool col [][]bool } @@ -16,14 +15,20 @@ func (e *Environment) Height() int { return e.height } +func (e *Environment) InitPheromone(name string) { + state := make([][]float32, e.width) + for x := range state { + state[x] = make([]float32, e.height) + } + e.state[name] = state +} + func NewEnvironment(width int, height int) *Environment { env := new(Environment) env.width = width env.height = height - env.state = make([][]float32, width) - for x := range env.state { - env.state[x] = make([]float32, height) - } + env.state = make(map[string][][]float32) + env.col = make([][]bool, width) for x := range env.col { env.col[x] = make([]bool, height) @@ -36,10 +41,16 @@ func NewEnvironment(width int, height int) *Environment { return env } -func (e *Environment) Mark(x, y int, amount float32) { - e.state[x][y] = e.state[x][y] + amount - if e.state[x][y] > 255 { - e.state[x][y] = 255 +func (e *Environment) Mark(pheromone string, x, y int, amount float32) { + + _, exists := e.state[pheromone] + if !exists { + e.InitPheromone(pheromone) + } + + e.state[pheromone][x][y] = e.state[pheromone][x][y] + amount + if e.state[pheromone][x][y] > 255 { + e.state[pheromone][x][y] = 255 } } @@ -67,8 +78,8 @@ func (e Environment) TakeValue(x, y int) { e.value[x][y] = false } -func (e Environment) Sniff(x, y int) float32 { - return e.state[x][y] +func (e Environment) Sniff(pheromone string, x, y int) float32 { + return e.state[pheromone][x][y] } func (e *Environment) normXY(x int, y int) (int, int) { @@ -87,37 +98,46 @@ func (e *Environment) normXY(x int, y int) (int, int) { return x, y } -func (e *Environment) Evaporate(rate float32) { +func (e *Environment) Evaporate(rate float32, pheromone string) { //log.Debugf("Evap") - e.pstate = make([][]float32, e.width) - for x := range e.pstate { - e.pstate[x] = make([]float32, e.height) + _, exists := e.state[pheromone] + if !exists { + e.InitPheromone(pheromone) + } + + pheromoneprev := pheromone + "prev" + + e.state[pheromoneprev] = make([][]float32, e.width) + for x := range e.state[pheromoneprev] { + e.state[pheromoneprev][x] = make([]float32, e.height) } for x := 0; x < e.width; x++ { for y := 0; y < e.height; y++ { - e.pstate[x][y] = e.state[x][y] * rate + e.state[pheromoneprev][x][y] = e.state[pheromone][x][y] * rate } } + pheromoneMap := e.state[pheromoneprev] for x := 0; x < e.width; x++ { for y := 0; y < e.height; y++ { - amount := e.pstate[x][y] + amount := e.state[pheromoneprev][x][y] - totalAmount := amount + e.NormalizeSniff(x-1, y-1) + e.NormalizeSniff(x, y-1) + e.NormalizeSniff(x+1, y-1) + e.NormalizeSniff(x-1, y) + e.NormalizeSniff(x+1, y) - totalAmount += e.NormalizeSniff(x-1, y+1) + e.NormalizeSniff(x, y+1) + e.NormalizeSniff(x+1, y+1) + totalAmount := amount + e.sniffNormalized(x-1, y-1, pheromoneMap) + e.sniffNormalized(x, y-1, pheromoneMap) + e.sniffNormalized(x+1, y-1, pheromoneMap) + e.sniffNormalized(x-1, y, pheromoneMap) + e.sniffNormalized(x+1, y, pheromoneMap) + totalAmount += e.sniffNormalized(x-1, y+1, pheromoneMap) + e.sniffNormalized(x, y+1, pheromoneMap) + e.sniffNormalized(x+1, y+1, pheromoneMap) - e.state[x][y] = totalAmount / 9 + e.state[pheromone][x][y] = totalAmount / 9 } } } -func (e *Environment) SniffNormalized(x int, y int) float32 { +// Internal optimaization to avoid slow map access +func (e *Environment) sniffNormalized(x int, y int, pheromonemap [][]float32) float32 { x, y = e.normXY(x, y) - return e.state[x][y] + return pheromonemap[x][y] } -func (e *Environment) NormalizeSniff(x int, y int) float32 { +func (e *Environment) SniffNormalized(x int, y int, pheromone string) float32 { x, y = e.normXY(x, y) - return e.pstate[x][y] + return e.state[pheromone][x][y] } diff --git a/core/environment_test.go b/core/environment_test.go index b3d5ecd..a1d8f24 100644 --- a/core/environment_test.go +++ b/core/environment_test.go @@ -6,19 +6,19 @@ import ( func TestEnvironment_Mark(t *testing.T) { env := NewEnvironment(3, 3) - env.Mark(1, 1, 9) - env.Evaporate(0.9) + env.Mark("test", 1, 1, 9) + env.Evaporate(0.9, "test") for x := 0; x < 3; x++ { for y := 0; y < 3; y++ { - t.Logf("mark(%d,%d) = %f", x, y, env.Sniff(x, y)) + t.Logf("mark(%d,%d) = %f", x, y, env.Sniff("test", x, y)) } } t.Logf("\n") - env.Evaporate(0.9) + env.Evaporate(0.9, "test") for x := 0; x < 3; x++ { for y := 0; y < 3; y++ { - t.Logf("mark(%d,%d) = %f", x, y, env.Sniff(x, y)) + t.Logf("mark(%d,%d) = %f", x, y, env.Sniff("test", x, y)) } } } diff --git a/core/turtle.go b/core/turtle.go index 8702b1b..5d3bf16 100644 --- a/core/turtle.go +++ b/core/turtle.go @@ -63,11 +63,11 @@ func (t *Turtle) TurnAround() { t.heading = (t.heading + 4) % 8 } -func (t *Turtle) Drop(env *Environment, amount float32) { - env.Mark(t.xpos, t.ypos, amount) +func (t *Turtle) Drop(env *Environment, amount float32, pheromone string) { + env.Mark(pheromone, t.xpos, t.ypos, amount) } -func (t *Turtle) FollowGradient(env *Environment, distance int, threshold float32) { +func (t *Turtle) FollowGradient(env *Environment, distance int, threshold float32, pheromone string) { h0 := t.heading - 1 if h0 < 0 { @@ -93,15 +93,15 @@ func (t *Turtle) FollowGradient(env *Environment, distance int, threshold float3 x1 := (t.xpos + dx1) y1 := (t.ypos + dy1) - as0 := env.SniffNormalized(x0, y0) + as0 := env.SniffNormalized(x0, y0, pheromone) if as0 < threshold { as0 = 0 } - as := env.SniffNormalized(x, y) + as := env.SniffNormalized(x, y, pheromone) if as < threshold { as = 0 } - as1 := env.SniffNormalized(x1, y1) + as1 := env.SniffNormalized(x1, y1, pheromone) if as1 < threshold { as1 = 0 } diff --git a/graphics/graphics.go b/graphics/graphics.go index b2a826e..9173c76 100644 --- a/graphics/graphics.go +++ b/graphics/graphics.go @@ -14,6 +14,7 @@ type Graphics struct { renderer *sdl.Renderer width, height int32 t int + colorMap map[string][4]uint8 } func NewGraphics(width, height int32) *Graphics { @@ -33,25 +34,30 @@ func NewGraphics(width, height int32) *Graphics { fmt.Fprintf(os.Stderr, "Failed to create renderer: %s\n", err) panic(err) } + + graphics.colorMap = make(map[string][4]uint8) + graphics.renderer = renderer return graphics } +func (g *Graphics) ColorPheromone(name string, color [4]uint8) { + g.colorMap[name] = color +} + func (g *Graphics) Render(env *core.Environment, turtles []*core.Turtle) { g.renderer.SetDrawColor(0x00, 0x00, 0x00, 0x00) - g.renderer.FillRect(&sdl.Rect{0, 0, 600, 600}) + g.renderer.FillRect(&sdl.Rect{X: 0, Y: 0, W: 600, H: 600}) for x := 0; x < int(g.width); x++ { for y := 0; y < int(g.height); y++ { - amount := math.Min(float64(env.Sniff(x, y)), 255) + for name, color := range g.colorMap { + amount := math.Min(float64(env.Sniff(name, x, y)), 255) - if amount > 0 { - col := uint8(amount * 0x81) - if col > 0x81 { - col = 0x81 + if amount > 0 { + g.renderer.SetDrawColor(color[0]*uint8(amount), color[1]*uint8(amount), color[2]*uint8(amount), uint8(255)) + g.renderer.DrawPoint(int32(x), int32(y)) } - g.renderer.SetDrawColor(col, 0, col, uint8(255)) - g.renderer.DrawPoint(int32(x), int32(y)) } if env.HasValue(x, y) { @@ -67,7 +73,11 @@ func (g *Graphics) Render(env *core.Environment, turtles []*core.Turtle) { g.renderer.DrawPoint(int32(x), int32(y)) t.Run(env) } - env.Evaporate(0.95) + + // TODO: Move this into an environment specification + for name := range g.colorMap { + env.Evaporate(0.95, name) + } g.renderer.Present() g.window.UpdateSurface() diff --git a/main.go b/main.go index 1e1412e..8439c43 100644 --- a/main.go +++ b/main.go @@ -44,6 +44,7 @@ func main() { env := core.NewEnvironment(*width, *height) turtles := make([]*core.Turtle, *numTurtles) + g := graphics.NewGraphics(int32(*width), int32(*height)) switch *model { @@ -62,6 +63,8 @@ func main() { for i := 0; i < *numTurtles; i++ { turtles[i] = core.NewTurtle(env, &actors.Ant{SniffDistance: 3, Carrying: false}) } + env.InitPheromone("food") + g.ColorPheromone("food", [4]uint8{0x81, 0x81, 0x12, 0x00}) case "woodchips": for x := 0; x < *numTurtles; x++ { env.PutValue(rand.Intn(*width), rand.Intn(*height)) @@ -79,14 +82,15 @@ func main() { for i := 0; i < *numTurtles; i++ { turtles[i] = core.NewTurtle(env, &actors.SlimeMold{SniffDistance: 5}) } + env.InitPheromone("trail") + g.ColorPheromone("trail", [4]uint8{0x81, 0, 0x81, 0x00}) + default: for i := 0; i < *numTurtles; i++ { turtles[i] = core.NewTurtle(env, &actors.SlimeMold{SniffDistance: 20}) } } - g := graphics.NewGraphics(int32(*width), int32(*height)) - running := true wait := sync.WaitGroup{}