diff --git a/.gitignore b/.gitignore index d3beee5..cf38b61 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ _testmain.go *.test *.prof +images/ +*.gif +.idea/ diff --git a/actors/ant.go b/actors/ant.go new file mode 100644 index 0000000..1f935c8 --- /dev/null +++ b/actors/ant.go @@ -0,0 +1,37 @@ +package actors + +import ( + "git.openprivacy.ca/sarah/microworlds/core" +) + + +type Ant struct { + SniffDistance int + Carrying bool + DropSize float32 +} + +func (a * Ant) Setup(env *core.Environment, t *core.Turtle) { + //t.SetXY(150,150) +} + +func (a*Ant) Run(env *core.Environment, t *core.Turtle) { + if a.Carrying == false { + if env.HasValue(t.Pos()) { + env.TakeValue(t.Pos()) + a.Carrying = true + a.DropSize = 100 + t.TurnAround() + } else { + t.Wiggle() + t.FollowGradient(env, a.SniffDistance, 5) + } + t.Step(env) + } else if a.Carrying == true { + a.DropSize -= 0.6 + t.Drop(env, a.DropSize) + t.Wiggle() + t.Step(env) + } +} + diff --git a/actors/slimemold.go b/actors/slimemold.go new file mode 100644 index 0000000..bfbb228 --- /dev/null +++ b/actors/slimemold.go @@ -0,0 +1,20 @@ +package actors + +import ( + "git.openprivacy.ca/sarah/microworlds/core" +) + +type SlimeMold struct { + SniffDistance int +} + +func(sm * SlimeMold) Setup(env *core.Environment, t *core.Turtle) { + // Do nothing +} + +func (sm * SlimeMold) Run(env *core.Environment, t *core.Turtle) { + t.Wiggle() + t.FollowGradient(env, sm.SniffDistance, 2) + t.Step(env) + t.Drop(env, 1) +} diff --git a/actors/woodchips.go b/actors/woodchips.go new file mode 100644 index 0000000..4026dd6 --- /dev/null +++ b/actors/woodchips.go @@ -0,0 +1,38 @@ +package actors + +import ( + "git.openprivacy.ca/sarah/microworlds/core" +) + +type WoodChips struct { + SniffDistance int + Carrying bool +} + +func (a * WoodChips) Setup(env *core.Environment, t *core.Turtle) { + //t.SetXY(150,150) +} + +func (a*WoodChips) Run(env *core.Environment, t *core.Turtle) { + if a.Carrying { + if env.HasValue(t.Pos()) { + for { + t.Wiggle() + t.Step(env) + if !env.HasValue(t.Pos()){ + env.PutValue(t.Pos()) + a.Carrying = false + break + } + } + } + } else { + if env.HasValue(t.Pos()) { + env.TakeValue(t.Pos()) + a.Carrying = true + t.TurnAround() + } + } + t.Wiggle() + t.Step(env) +} \ No newline at end of file diff --git a/core/actor.go b/core/actor.go new file mode 100644 index 0000000..52e4675 --- /dev/null +++ b/core/actor.go @@ -0,0 +1,6 @@ +package core + +type Actor interface { + Setup(*Environment, *Turtle) + Run(*Environment, *Turtle) +} diff --git a/core/environment.go b/core/environment.go new file mode 100644 index 0000000..330b6b5 --- /dev/null +++ b/core/environment.go @@ -0,0 +1,131 @@ +package core + +type Environment struct { + width,height int + state [][]float32 + pstate [][]float32 + value [][]bool + col [][]bool +} + +func (e *Environment) Width() int { + return e.width +} + + +func (e *Environment) Height() int { + return e.height +} + +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.col = make([][]bool, width) + for x := range env.col { + env.col[x] = make([]bool, height) + } + + env.value = make([][]bool, width) + for x := range env.value { + env.value[x] = make([]bool, height) + } + 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 + } + //log.Debugf("Marking: %d %d %f", x, y, e.state[x][y]) +} + + + +func (e Environment) Occupy(x,y int) { + e.col[x][y] = true +} + +func (e Environment) Check(x,y int) bool { + return e.col[x][y] +} + +func (e Environment) Leave(x,y int) { + e.col[x][y] = false +} + + +func (e Environment) HasValue(x,y int) bool { + return e.value[x][y] +} + +func (e Environment) PutValue(x,y int) { + e.value[x][y] = true +} + +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) normXY(x int, y int) (int,int) { + if x < 0 { + x = (e.width - 1) + } else if x >= e.width { + x = x % (e.width ) + } + + if y< 0 { + y = (e.height - 1) + } else if y >= e.height { + y = y % (e.height) + } + + return x,y +} + +func (e *Environment) Evaporate(rate float32) { + //log.Debugf("Evap") + + + e.pstate = make([][]float32, e.width) + for x := range e.pstate { + e.pstate[x] = make([]float32, e.height) + } + for x:=0;x as && as0 > as1{ + t.heading = h0 + } else if as1 > as && as1 > as0 { + t.heading = h1 + } +} + +func (t* Turtle) Step(env *Environment) bool { + dx := headings[t.heading][0] + dy := headings[t.heading][1] + + ox := t.xpos + oy := t.ypos + env.Leave(ox,oy) + t.xpos = (t.xpos + dx) % (env.width -1) + if t.xpos < 0 { + t.xpos = env.width-1 + } + t.ypos = (t.ypos + dy) % (env.height -1) + if t.ypos < 0 { + t.ypos = env.height-1 + } + + success := true + if env.Check(t.xpos,t.ypos) == true { + t.xpos = ox + t.ypos = oy + success = false + } + env.Occupy(t.xpos,t.ypos) + return success +} + +// Run the turtle program +func (t * Turtle) Run(env * Environment) { + //log.Debugf("Pos: %v %v", t.xpos, t.ypos) + t.actor.Run(env, t) +} diff --git a/core/turtle_test.go b/core/turtle_test.go new file mode 100644 index 0000000..5838b2e --- /dev/null +++ b/core/turtle_test.go @@ -0,0 +1,16 @@ +package core + +import ( + "testing" +) + +func TestTurtle_Wiggle(t *testing.T) { + turtle := NewTurtle(NewEnvironment(3,3,)) + counts := []int{0,0,0,0,0,0,0,0} + for i:=0;i<1000;i++ { + turtle.Wiggle() + //t.Logf("Heading %v", turtle.heading) + counts[turtle.heading]++ + } + t.Logf("Heading %v", counts) +} \ No newline at end of file diff --git a/graphics/graphics.go b/graphics/graphics.go new file mode 100644 index 0000000..31153bf --- /dev/null +++ b/graphics/graphics.go @@ -0,0 +1,80 @@ +package graphics + +import ( + "fmt" + "git.openprivacy.ca/sarah/microworlds/core" + "github.com/veandco/go-sdl2/sdl" + "math" + "os" + "strconv" +) + +type Graphics struct { + window *sdl.Window + renderer *sdl.Renderer + width, height int32 + t int +} + +func NewGraphics(width, height int32) *Graphics { + graphics := new(Graphics) + graphics.width = width + graphics.height= height + window, err := sdl.CreateWindow("Microworlds", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, + width, height, sdl.WINDOW_SHOWN) + if err != nil { + panic(err) + } + graphics.window = window + + surface,_ := window.GetSurface() + renderer, err := sdl.CreateSoftwareRenderer(surface) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create renderer: %s\n", err) + panic(err) + } + graphics.renderer = renderer + return graphics +} + +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}) + + + + for x:=0;x 0 { + col := uint8(amount*0x81) + if col > 0x81 { + col = 0x81 + } + g.renderer.SetDrawColor(col,0,col, uint8(255) ) + g.renderer.DrawPoint(int32(x), int32(y)) + } + + if env.HasValue(x,y) { + g.renderer.SetDrawColor(255,255,255, uint8(255) ) + g.renderer.DrawPoint(int32(x), int32(y)) + } + } + } + + + g.renderer.SetDrawColor(0xF3,0x81,0,0x00) + for _,t := range turtles { + x,y := t.Pos() + g.renderer.DrawPoint(int32(x),int32(y)) + t.Run(env) + } + env.Evaporate(0.95) + + g.renderer.Present() + g.window.UpdateSurface() + surface,_ := g.window.GetSurface() + surface.SaveBMP("./images/"+ strconv.Itoa(g.t)+".bmp") + g.t++; +} \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..09c2f59 --- /dev/null +++ b/main.go @@ -0,0 +1,122 @@ +package main + +import ( + "flag" + "git.openprivacy.ca/sarah/microworlds/actors" + "git.openprivacy.ca/sarah/microworlds/core" + "git.openprivacy.ca/sarah/microworlds/graphics" + "github.com/veandco/go-sdl2/sdl" + "log" + "math/rand" + "os" + "runtime/pprof" + "sync" + "time" +) + + +var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") + +var model = flag.String("model", "slime", "slimemold|swarm|woodchips") + +var width = flag.Int("width", 300, "width of environment") +var height = flag.Int("height", 300, "height of environment") +var numTurtles = flag.Int("numTurtles", 5000, "number of turtles") + + + +func main() { + + // We don't need real randomness + rand.Seed(time.Now().Unix()) + + flag.Parse() + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil { + panic(err) + } + defer sdl.Quit() + + + env := core.NewEnvironment(*width,*height) + turtles := make([]*core.Turtle,*numTurtles) + + + switch *model { + + case "swarm": + // Create 2 food blocks + for x:= 100;x<110;x++ { + for y:= 100;y<110;y++ { + env.PutValue(x,y) + } + } + for x:= 200;x<210;x++ { + for y:= 200;y<210;y++ { + env.PutValue(x,y) + } + } + for i:=0;i<*numTurtles;i++ { + turtles[i] = core.NewTurtle(env, &actors.Ant{SniffDistance:3, Carrying:false}) + } + case "woodchips": + for x := 0;x<*numTurtles;x++ { + env.PutValue(rand.Intn(*width), rand.Intn(*height)) + } + + for x:= 200;x<210;x++ { + for y:= 200;y<210;y++ { + env.PutValue(x,y) + } + } + for i:=0;i<*numTurtles;i++ { + turtles[i] = core.NewTurtle(env, &actors.WoodChips{SniffDistance:20, Carrying:false}) + } + case "slimemold": + for i:=0;i<*numTurtles;i++ { + turtles[i] = core.NewTurtle(env, &actors.SlimeMold{SniffDistance:20}) + } + 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{} + + wait.Add(1) + go func() { + for running { + g.Render(env, turtles) + } + wait.Done() + }() + + + wait.Add(1) + for running { + for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { + switch event.(type) { + case *sdl.QuitEvent: + running = false + break + } + } + } + wait.Done() + wait.Wait() +} +