From 3ba02dfb02c054a38bba0b92eb6a8f6eb3e1625e Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Sat, 16 Nov 2019 19:00:03 -0800 Subject: [PATCH] Plotting, Slime Mazes, Evolution(First Cuts) --- core/environment.go | 32 +- core/turtle.go | 51 ++- experiments/evolution/soccer.go | 361 +++++++++++++++++ experiments/experiment.go | 42 +- experiments/flocking/main.go | 20 +- experiments/fractal/fractal.go | 14 +- experiments/lightning-bugs/lightning-bugs.go | 137 +++++++ .../predatorprey/{main.go => predprey.go} | 73 +++- experiments/slimemold/main.go | 7 +- experiments/slimemold/plasmoidiam/main.go | 79 +--- experiments/swarm/main.go | 370 ++++++++++++++---- go.mod | 11 +- go.sum | 15 + graphics/graphics.go | 8 +- graphics/plotting.go | 71 ++++ models/neoplasmodium.go | 85 ++++ models/plasmodium.go | 82 ++++ 17 files changed, 1255 insertions(+), 203 deletions(-) create mode 100644 experiments/evolution/soccer.go create mode 100644 experiments/lightning-bugs/lightning-bugs.go rename experiments/predatorprey/{main.go => predprey.go} (77%) create mode 100644 graphics/plotting.go create mode 100644 models/neoplasmodium.go create mode 100644 models/plasmodium.go diff --git a/core/environment.go b/core/environment.go index 3d14d06..6543cce 100644 --- a/core/environment.go +++ b/core/environment.go @@ -1,17 +1,47 @@ package core +import ( + "github.com/foolusion/quadtree" +) + type Environment struct { width, height int state map[string][][]float32 value [][]bool col [][]*Turtle - Step int + Step int + quadtree *quadtree.QuadTree } func (e *Environment) Width() int { return e.width } +func (e *Environment) GetNearestNeighbours(bounding *quadtree.AABB, num int) (turtles []*Turtle) { + points := e.quadtree.SearchArea(bounding) + // fmt.Printf("Found X neighbours %v\n",len(points)) + for _, point := range points { + if len(turtles) < num+1 { + x, y := point.X, point.Y + + if e.Get(int(x), int(y)) != nil { // WHY DOES THIS HAPPEN?!?! + turtles = append(turtles, e.Get(int(x), int(y))) + } + } + } + return +} + +func (e *Environment) InsertIntoQuadTree(turtle *Turtle) { + e.quadtree.Insert(quadtree.NewXY(float64(turtle.xpos), float64(turtle.ypos))) +} + +func (e *Environment) ResetQuadtree() { + center := quadtree.NewXY(150, 150) + area := quadtree.NewAABB(*center, *center) + e.quadtree = quadtree.New(*area, 10) +} + func (e *Environment) Height() int { return e.height } diff --git a/core/turtle.go b/core/turtle.go index e53c298..edb81a2 100644 --- a/core/turtle.go +++ b/core/turtle.go @@ -34,7 +34,7 @@ func (t *Turtle) Heading() int { func NewTurtle(env *Environment, actor Actor) *Turtle { - for i := 0; i < 5; i++ { + for i := 0; i < 10; i++ { turtle := new(Turtle) turtle.width = env.width turtle.height = env.height @@ -42,7 +42,7 @@ func NewTurtle(env *Environment, actor Actor) *Turtle { turtle.ypos = rand.Intn(env.height) turtle.actor = actor turtle.atts = make(map[string]string) - if env.Check(turtle.xpos, turtle.ypos) == false { + if env.Check(turtle.xpos, turtle.ypos) == false && env.HasValue(turtle.xpos, turtle.ypos) == false { turtle.setRandomHeading() actor.Setup(env, turtle) env.Occupy(turtle, turtle.xpos, turtle.ypos) @@ -106,10 +106,9 @@ func (t *Turtle) SetAttribute(name, val string) { } func (t *Turtle) SetHeading(heading int) { - t.heading =heading + t.heading = heading } - func (t *Turtle) TurnAround() { t.heading = (t.heading + 4) % 8 } @@ -256,6 +255,42 @@ func (t *Turtle) FollowGradient(env *Environment, distance int, threshold float3 } } +func (t *Turtle) RejectGradient(env *Environment, distance int, pheromone string) { + + h0 := t.heading - 1 + if h0 < 0 { + h0 = 7 + } + + dx0 := headings[h0][0] * distance + dy0 := headings[h0][1] * distance + + x0 := (t.xpos + dx0) + y0 := (t.ypos + dy0) + + dx := headings[t.heading][0] * distance + dy := headings[t.heading][1] * distance + + x := (t.xpos + dx) + y := (t.ypos + dy) + + h1 := (t.heading + 1) % 8 + dx1 := headings[h1][0] * distance + dy1 := headings[h1][1] * distance + + x1 := (t.xpos + dx1) + y1 := (t.ypos + dy1) + + as0 := env.SniffNormalized(x0, y0, pheromone) + as := env.SniffNormalized(x, y, pheromone) + as1 := env.SniffNormalized(x1, y1, pheromone) + + if as0 > as && as0 > as1 { + t.heading = h0 + } else if as1 > as && as1 > as0 { + t.heading = h1 + } +} func (t *Turtle) AvoidAverageGradient(env *Environment, distance int, threshold float32, pheromone string) { @@ -287,10 +322,10 @@ func (t *Turtle) AvoidAverageGradient(env *Environment, distance int, threshold as := env.SniffNormalized(x, y, pheromone) as1 := env.SniffNormalized(x1, y1, pheromone) - avg := float64((1 * as0) + (2*as) + (3 *as1) / (as0+as+as1)) + avg := float64((1 * as0) + (2 * as) + (3*as1)/(as0+as+as1)) heading := math.Round(avg) - if heading < 1 && as0 > threshold{ + if heading < 1 && as0 > threshold { t.heading = h1 } else if heading > 2 && as1 > threshold { t.heading = h0 @@ -327,10 +362,10 @@ func (t *Turtle) FollowAverageGradient(env *Environment, distance int, threshold as := env.SniffNormalized(x, y, pheromone) as1 := env.SniffNormalized(x1, y1, pheromone) - avg := float64((1 * as0) + (2*as) + (3 *as1) / (as0+as+as1)) + avg := float64((1 * as0) + (2 * as) + (3*as1)/(as0+as+as1)) heading := math.Round(avg) - if heading < 1 && as0 > threshold{ + if heading < 1 && as0 > threshold { t.heading = h0 } else if heading > 2 && as1 > threshold { t.heading = h1 diff --git a/experiments/evolution/soccer.go b/experiments/evolution/soccer.go new file mode 100644 index 0000000..e3ae1f8 --- /dev/null +++ b/experiments/evolution/soccer.go @@ -0,0 +1,361 @@ +package main + +import ( + "fmt" + "git.openprivacy.ca/sarah/microworlds/core" + "git.openprivacy.ca/sarah/microworlds/experiments" + "image/color" + "math/rand" + "strconv" +) + +type Player struct { + dna [40]int + Team int +} + +func (player *Player) Setup(env *core.Environment, t *core.Turtle) { + for i := 0; i < len(player.dna); i++ { + player.dna[i] = rand.Intn(NumGenes) + } + t.SetAttribute("team", strconv.Itoa(player.Team)) + if player.Team == 1 { + t.SetColor(color.RGBA{0xFF, 0x00, 0x00, 0xFF}) + } else { + t.SetColor(color.RGBA{0x00, 0x00, 0xFF, 0xFF}) + } + +} + +const ( + Wiggle = iota + FollowMyTeam + FollowOtherTeam + FollowBall + Drop + RetreatMyTeam + RetreatOtherTeam + RetreatBall + TurnAround + HeadTowardsOurGoal + HeadTowardsOtherGoal +) + +var NumGenes = 11 + +func (player *Player) Run(env *core.Environment, t *core.Turtle) { + + MyTeam := strconv.Itoa(player.Team) + OtherTeam := "1" + if player.Team == 1 { + OtherTeam = "2" + } + + ahead := t.Check(env) + if ahead != nil { + ball, ok := ahead.GetActor().(*Ball) + if ok { + ahead.SetHeading(t.Heading()) + if rand.Intn(5) == 0 { + ahead.Wiggle() + } + ahead.Step(env) + ball.Check(env, ahead) + } + } + t.Step(env) + + for i := 0; i < len(player.dna); i++ { + switch player.dna[i] { + case TurnAround: + t.TurnAround() + case Wiggle: + t.Wiggle() + case FollowMyTeam: + if i+2 < len(player.dna) { + t.FollowGradient(env, player.dna[i+1], float32(player.dna[i+2]), MyTeam) + i += 2 + } + case FollowOtherTeam: + if i+2 < len(player.dna) { + t.FollowGradient(env, player.dna[i+1], float32(player.dna[i+2]), OtherTeam) + i += 2 + } + case FollowBall: + t.FollowGradient(env, 1, 0, "ball") + case Drop: + if i+1 < len(player.dna) { + t.Drop(env, float32(player.dna[i+1]), strconv.Itoa(player.Team)) + } + case RetreatMyTeam: + if i+2 < len(player.dna) { + t.AvoidAverageGradient(env, player.dna[i+1], float32(player.dna[i+2]), MyTeam) + i += 2 + } + case RetreatOtherTeam: + if i+2 < len(player.dna) { + t.AvoidAverageGradient(env, player.dna[i+1], float32(player.dna[i+2]), OtherTeam) + i += 2 + } + case RetreatBall: + if i+2 < len(player.dna) { + t.AvoidAverageGradient(env, player.dna[i+1], float32(player.dna[i+2]), "ball") + i += 2 + } + case HeadTowardsOurGoal: + x, _ := t.Pos() + if player.Team == 1 { + if x > 1 { + t.SetHeading(7) + } + } else { + if x < 299 { + t.SetHeading(3) + } + } + case HeadTowardsOtherGoal: + x, _ := t.Pos() + if player.Team == 2 { + if x > 1 { + t.SetHeading(7) + } + } else { + if x < 299 { + t.SetHeading(3) + } + } + } + } +} + +func (player *Player) Mutate() { + mutatePoint := rand.Intn(len(player.dna)) + fmt.Printf("\t\t Mutating %v at %v\n", player.Team, mutatePoint) + player.dna[mutatePoint] = rand.Intn(NumGenes) +} + +func (player *Player) Clone(parentA *Player, parentB *Player) { + fmt.Printf("\t\tCrossing Over team %v with:%v %v\n", player.Team, parentA.dna, parentB.dna) + crossoverPoint := rand.Intn(len(player.dna)) + copy(player.dna[0:crossoverPoint], parentA.dna[0:crossoverPoint]) + copy(player.dna[crossoverPoint:], parentB.dna[crossoverPoint:]) +} + +type Ball struct { + Team1Score int + Team2Score int +} + +func (b *Ball) Setup(env *core.Environment, t *core.Turtle) { + t.SetXY(50, 50) + t.SetColor(color.RGBA{0xFF, 0xFF, 0xFF, 0xFF}) +} + +func (b *Ball) Run(env *core.Environment, t *core.Turtle) { + b.Check(env, t) + t.Drop(env, 10, "ball") +} + +func (b *Ball) Check(env *core.Environment, t *core.Turtle) { + x, _ := t.Pos() + if x > 98 { + fmt.Printf("\t\tTeam 1 Scored!!! %v\n", env.Step) + b.Reset(env, t) + b.Team1Score++ + } else if x < 1 { + fmt.Printf("\t\tTeam 2 Scored!!! %v\n", env.Step) + b.Reset(env, t) + b.Team2Score++ + } +} + +func (b *Ball) Reset(env *core.Environment, t *core.Turtle) { + x, y := t.Pos() + t.SetHeading(rand.Intn(8)) + env.Leave(x, y) + t.SetXY(50, 50) + env.Occupy(t, 50, 50) +} + +func main() { + ball := new(Ball) + experiment := new(experiments.Experiment) + experiment.InitializeExperiment() + experiment.InitNTurtles(func() core.Actor { + player := new(Player) + player.Team = 1 + return player + }, 25) + experiment.InitNTurtles(func() core.Actor { + player := new(Player) + player.Team = 2 + return player + }, 25) + experiment.InitNTurtles(func() core.Actor { + return ball + }, 1) + experiment.InitPheromone("1", color.RGBA{0xFF, 0x00, 0x00, 0x00}) + experiment.InitPheromone("2", color.RGBA{0x00, 0x00, 0xFF, 0x00}) + experiment.InitPheromone("ball", color.RGBA{0xff, 0xff, 0xff, 0xff}) + + // x := []float64{-2} + // team1 := []float64{0} + // team2 := []float64{1} + + // var graph chart.Chart + /**experiment.AddPlot("Goals Scored", func(environment *core.Environment, turtles []*core.Turtle) *chart.Chart { + x = append(x, float64(environment.Step)) + team1 = append(team1, float64(ball.Team1Score)) + team2 = append(team2, float64(ball.Team2Score)) + + if environment.Step % 10 == 0 { + + graph = chart.Chart{ + Background: chart.Style{ + Padding: chart.Box{ + Top: 50, + }, + }, + XAxis: chart.XAxis{Name: "Time Step", NameStyle: chart.Style{Show: true}, Style: chart.Style{Show: true, TextRotationDegrees: 90}, ValueFormatter: func(v interface{}) string { + return fmt.Sprintf("%d", int(v.(float64))) + },}, + YAxis: chart.YAxis{Name: "Goals Scored", NameStyle: chart.Style{Show: true}, Style: chart.Style{Show: true}, ValueFormatter: func(v interface{}) string { + return fmt.Sprintf("%d", int(v.(float64))) + },}, + Series: []chart.Series{ + chart.ContinuousSeries{ + XValues: x, + YValues: team1, + }, + chart.ContinuousSeries{ + XValues: x, + YValues: team2, + }, + }, + } + } + return &graph + })*/ + + gamelengths := []int{} + gamewins := []int{} + gamescores := []int{} + lastGameStart := 0 + + experiment.OnStep = func(environment *core.Environment, turtles []*core.Turtle, i int) { + + environment.EvaporateAndDiffuse(0.95, "ball") + environment.EvaporateAndDiffuse(0.95, "1") + environment.EvaporateAndDiffuse(0.95, "2") + + if environment.Step-lastGameStart > 4000 { + fmt.Printf("Time!\n") + + if ball.Team1Score == ball.Team2Score { + fmt.Printf("Draw!\n") + gamelengths = append(gamelengths, environment.Step-lastGameStart) + gamewins = append(gamewins, 0) + gamescores = append(gamescores, ball.Team1Score, ball.Team2Score) + lastGameStart = environment.Step + + ball.Team2Score = 0 + ball.Team1Score = 0 + + red, blue := GetTeams(turtles) + for _, r := range red { + player := r.GetActor().(*Player) + player.Mutate() + player.Mutate() + player.Mutate() + player.Mutate() + player.Mutate() + } + for _, b := range blue { + player := b.GetActor().(*Player) + player.Mutate() + player.Mutate() + player.Mutate() + player.Mutate() + player.Mutate() + + } + for i, lengths := range gamelengths { + fmt.Printf("Generation: %v, %v time steps Team %v Won (%v - %v)\n", i, lengths, gamewins[i], gamescores[i*2], gamescores[(i*2)+1]) + } + } else { + ball.Team1Score++ + ball.Team2Score++ + } + } + + if ball.Team1Score >= 3 && ball.Team2Score < 3 { + fmt.Printf("Team 1 Win") + red, blue := GetTeams(turtles) + for _, b := range blue { + player := b.GetActor().(*Player) + parentA := rand.Intn(len(red)) + parentB := rand.Intn(len(red)) + player.Clone(red[parentA].GetActor().(*Player), red[parentB].GetActor().(*Player)) + player.Mutate() + } + adjust := 0 + if environment.Step-lastGameStart > 4000 { + adjust = (environment.Step - lastGameStart) - 4000 + } + + gamelengths = append(gamelengths, environment.Step-lastGameStart) + gamewins = append(gamewins, 1) + gamescores = append(gamescores, ball.Team1Score-adjust, ball.Team2Score-adjust) + lastGameStart = environment.Step + + for i, lengths := range gamelengths { + fmt.Printf("Generation: %v, %v time steps Team %v Won (%v - %v)\n", i, lengths, gamewins[i], gamescores[i*2], gamescores[(i*2)+1]) + } + + ball.Team2Score = 0 + ball.Team1Score = 0 + + } else if ball.Team2Score >= 3 && ball.Team1Score < 3 { + fmt.Printf("Team 2 Win") + red, blue := GetTeams(turtles) + for _, r := range red { + player := r.GetActor().(*Player) + parentA := rand.Intn(len(blue)) + parentB := rand.Intn(len(blue)) + player.Clone(blue[parentA].GetActor().(*Player), blue[parentB].GetActor().(*Player)) + player.Mutate() + } + + adjust := 0 + if environment.Step-lastGameStart > 4000 { + adjust = (environment.Step - lastGameStart) - 4000 + } + + gamelengths = append(gamelengths, environment.Step-lastGameStart) + gamewins = append(gamewins, 2) + gamescores = append(gamescores, ball.Team1Score-adjust, ball.Team2Score-adjust) + lastGameStart = environment.Step + + ball.Team2Score = 0 + ball.Team1Score = 0 + + for i, lengths := range gamelengths { + fmt.Printf("Generation: %v, %v time steps Team %v Won (%v - %v)\n", i, lengths, gamewins[i], gamescores[i*2], gamescores[(i*2)+1]) + } + } + + } + + experiment.Run() +} + +func GetTeams(turtles []*core.Turtle) (redTeam, blueTeam []*core.Turtle) { + for _, turtle := range turtles { + if turtle.GetAttribute("team") == "1" { + redTeam = append(redTeam, turtle) + } else if turtle.GetAttribute("team") == "2" { + blueTeam = append(blueTeam, turtle) + } + } + return +} diff --git a/experiments/experiment.go b/experiments/experiment.go index 2ee5036..88041cb 100644 --- a/experiments/experiment.go +++ b/experiments/experiment.go @@ -5,6 +5,7 @@ import ( "git.openprivacy.ca/sarah/microworlds/core" "git.openprivacy.ca/sarah/microworlds/graphics" "github.com/veandco/go-sdl2/sdl" + "github.com/wcharczuk/go-chart" "image/color" "log" "math/rand" @@ -27,6 +28,8 @@ type Experiment struct { initializedTurtles int pheromones []string OnStep func(*core.Environment, []*core.Turtle, int) + plots []*graphics.Plot + running bool } func (e *Experiment) InitializeExperiment() { @@ -40,7 +43,6 @@ func (e *Experiment) InitializeExperiment() { log.Fatal(err) } pprof.StartCPUProfile(f) - defer pprof.StopCPUProfile() } if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil { @@ -89,43 +91,65 @@ func (e *Experiment) InitTurtles(f func() core.Actor) { e.InitNTurtles(f, (*numTurtles)) } +func (e *Experiment) AddPlot(title string, plotfunc func(environment *core.Environment, turtles []*core.Turtle) *chart.Chart) { + plot := graphics.NewPlot(title, int32(*width), int32(*height), int32(*pxsize)) + plot.GeneratePlot = plotfunc + e.plots = append(e.plots, plot) +} + +func (e *Experiment) Stop() { + e.running = false +} + +func (e *Experiment) Restart() { + e.running = false +} + func (e *Experiment) Run() { wait := sync.WaitGroup{} - running := true + e.running = true wait.Add(1) + e.env.ResetQuadtree() go func() { step := 0 - for running { + for e.running { e.env.Step = step - e.graphics.Render(e.env, e.turtles) - e.OnStep(e.env, e.turtles, step) + for _, plot := range e.plots { + plot.Render(e.env, e.turtles) + } + newTurtles := make([]*core.Turtle, 0) - deleted := 0 + for _, t := range e.turtles { + t.Run(e.env) if t.GetAttribute("status") != "dead" { newTurtles = append(newTurtles, t) } else { e.env.Leave(t.Pos()) // Dead turtles occupy no space - deleted++ } } e.turtles = newTurtles + e.env.ResetQuadtree() + for _, t := range e.turtles { + e.env.InsertIntoQuadTree(t) + } + step++ } wait.Done() }() wait.Add(1) - for running { + for e.running { for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { switch event.(type) { case *sdl.QuitEvent: - running = false + e.running = false break } } diff --git a/experiments/flocking/main.go b/experiments/flocking/main.go index 9919b1d..ad1a4b7 100644 --- a/experiments/flocking/main.go +++ b/experiments/flocking/main.go @@ -1,16 +1,15 @@ package main import ( -"flag" -"git.openprivacy.ca/sarah/microworlds/core" -"git.openprivacy.ca/sarah/microworlds/experiments" -"image/color" + "flag" + "git.openprivacy.ca/sarah/microworlds/core" + "git.openprivacy.ca/sarah/microworlds/experiments" + "image/color" ) var sniffDistance = flag.Int("sniffDistance", 3, "the distance a turtle can detect pheromone levels from") type Bird struct { - } func (sm *Bird) Setup(env *core.Environment, t *core.Turtle) { @@ -20,22 +19,22 @@ func (sm *Bird) Setup(env *core.Environment, t *core.Turtle) { func (sm *Bird) Run(env *core.Environment, t *core.Turtle) { //t.Wiggle() - if t.AmountAll(env,1,"bird") > 1 { + if t.AmountAll(env, 1, "bird") > 1 { //t.Wiggle() t.AvoidAverageGradient(env, 1, 2.5, "bird") //t.Wiggle() t.Step(env) t.Drop(env, 2, "bird") - } else if t.AmountAll(env,1,"bird") > 2 { + } else if t.AmountAll(env, 1, "bird") > 2 { t.AvoidAverageGradient(env, 1, 1, "bird") t.Wiggle() t.Step(env) t.Drop(env, 1, "bird") } else { // t.TurnAround() - // t.Wiggle() - // t.Wiggle() - // t.Wiggle() + // t.Wiggle() + // t.Wiggle() + // t.Wiggle() t.FollowAverageGradient(env, 5, 1, "bird") t.FollowAverageGradient(env, 4, 2, "bird") t.FollowAverageGradient(env, 3, 3, "bird") @@ -60,4 +59,3 @@ func main() { } experiment.Run() } - diff --git a/experiments/fractal/fractal.go b/experiments/fractal/fractal.go index 6c871a1..8765726 100644 --- a/experiments/fractal/fractal.go +++ b/experiments/fractal/fractal.go @@ -5,16 +5,16 @@ import ( "git.openprivacy.ca/sarah/microworlds/core" "git.openprivacy.ca/sarah/microworlds/experiments" "image/color" + "math" ) var sniffDistance = flag.Int("sniffDistance", 3, "the distance a turtle can detect pheromone levels from") var defensiveDecentralization = flag.Bool("defensiveDecentralization", false, "if true, slime molds will break up if the concentration is too great") - type SlimeMold struct { SniffDistance int - Age int - Stuck bool + Age int + Stuck bool } func (sm *SlimeMold) Setup(env *core.Environment, t *core.Turtle) { @@ -22,12 +22,12 @@ func (sm *SlimeMold) Setup(env *core.Environment, t *core.Turtle) { } func (sm *SlimeMold) Run(env *core.Environment, t *core.Turtle) { - frac := (float64(sm.Age)/float64(env.Step+1)) + frac := float64(sm.Age) / math.Min(float64(env.Step+1), 255) col := uint8(256 * frac) if env.Step < 100 { - t.SetColor(color.RGBA{col,0,col/2, col}) + t.SetColor(color.RGBA{col, 0, col / 2, 0xf2}) } else { - t.SetColor(color.RGBA{col,0,col, col}) + t.SetColor(color.RGBA{col, 0, col, col}) } if sm.Stuck == false { sm.Age++ @@ -49,7 +49,7 @@ func (sm *SlimeMold) Run(env *core.Environment, t *core.Turtle) { func main() { experiment := new(experiments.Experiment) experiment.InitializeExperiment() - n:=0 + n := 0 experiment.InitTurtles(func() core.Actor { n++ sm := new(SlimeMold) diff --git a/experiments/lightning-bugs/lightning-bugs.go b/experiments/lightning-bugs/lightning-bugs.go new file mode 100644 index 0000000..c81fabe --- /dev/null +++ b/experiments/lightning-bugs/lightning-bugs.go @@ -0,0 +1,137 @@ +package main + +import ( + "flag" + "fmt" + "git.openprivacy.ca/sarah/microworlds/core" + "git.openprivacy.ca/sarah/microworlds/experiments" + "github.com/foolusion/quadtree" + "github.com/wcharczuk/go-chart" + "image/color" + "math/rand" + "os" + "os/signal" + "runtime/pprof" +) + +var sniffDistance = flag.Int("sniffDistance", 5, "the distance a turtle can detect pheromone levels from") + +type LightningBug struct { + Timer int + HasReset bool + Num int + Threshold int +} + +func (sm *LightningBug) Setup(env *core.Environment, t *core.Turtle) { + // Do nothing + sm.Threshold = 35 + sm.Timer = rand.Intn(sm.Threshold) + +} + +var searchtree = quadtree.XY{50, 50} + +func (sm *LightningBug) Run(env *core.Environment, t *core.Turtle) { + t.Wiggle() + + if sm.Timer > 3 { + t.Step(env) + } + + t.SetColor(color.RGBA{0x00, 0x1f, 0x00, 0xff}) + if sm.Timer <= 3 { + t.SetColor(color.RGBA{0xff, 0xff, 0x00, 0xff}) + } + + sm.Timer++ + + if sm.Timer > 10 { + x, y := t.Pos() + center := quadtree.NewXY(float64(x), float64(y)) + neighbours := env.GetNearestNeighbours(quadtree.NewAABB(*center, searchtree), 3) + + for _, n := range neighbours { + if n.GetActor().(*LightningBug).Num != sm.Num && n.GetActor().(*LightningBug).Timer == 0 { + sm.Timer = 0 + } + //sm.HasReset = true + //t.SetColor(color.RGBA{0x00,0x1f,0xff,0xff}) + } + } + + if sm.Timer > sm.Threshold { + sm.Timer = 0 + sm.HasReset = false + } + +} + +func main() { + experiment := new(experiments.Experiment) + experiment.InitializeExperiment() + num := 0 + experiment.InitTurtles(func() core.Actor { + sm := new(LightningBug) + sm.Num = num + num++ + return sm + }) + + x := []float64{} + y := []float64{} + + experiment.AddPlot("Flashing Bugs", func(environment *core.Environment, turtles []*core.Turtle) *chart.Chart { + numLight := 0.0 + for _, t := range turtles { + lb := t.GetActor().(*LightningBug) + if lb.Timer == 0 { + numLight++ + } + } + + x = append(x, float64(environment.Step)) + y = append(y, numLight) + + graph := chart.Chart{ + Width: 300, + Height: 300, + Background: chart.Style{ + Padding: chart.Box{ + Top: 50, + }, + }, + XAxis: chart.XAxis{Name: "Time Step", NameStyle: chart.Style{Show: true}, Style: chart.Style{Show: true, TextRotationDegrees: 90}, ValueFormatter: func(v interface{}) string { + return fmt.Sprintf("%d", int(v.(float64))) + }}, + YAxis: chart.YAxis{Name: "Number of Flashing Lightning Bugs", NameStyle: chart.Style{Show: true}, Style: chart.Style{Show: true}, ValueFormatter: func(v interface{}) string { + return fmt.Sprintf("%d", int(v.(float64))) + }}, + Series: []chart.Series{ + chart.ContinuousSeries{ + XValues: x, + YValues: y, + }, + }, + } + return &graph + }) + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func() { + for sig := range c { + fmt.Printf("Got Signal %v", sig) + pprof.StopCPUProfile() + os.Exit(0) + } + }() + + experiment.OnStep = func(environment *core.Environment, turtles []*core.Turtle, i int) { + environment.Diffuse("light") + environment.Diffuse("light") + environment.Evaporate(0.99, "light") + } + experiment.InitPheromone("light", color.RGBA{0xff, 0xff, 0x00, 0x00}) + experiment.Run() +} diff --git a/experiments/predatorprey/main.go b/experiments/predatorprey/predprey.go similarity index 77% rename from experiments/predatorprey/main.go rename to experiments/predatorprey/predprey.go index 702f3c4..2594653 100644 --- a/experiments/predatorprey/main.go +++ b/experiments/predatorprey/predprey.go @@ -5,6 +5,7 @@ import ( "fmt" "git.openprivacy.ca/sarah/microworlds/core" "git.openprivacy.ca/sarah/microworlds/experiments" + "github.com/wcharczuk/go-chart" "image/color" "math" "math/rand" @@ -109,28 +110,25 @@ func main() { sm := new(Prey) return sm }, *numPrey) - experiment.InitPheromone("scent", color.RGBA{0x80, 0xFF, 0x00, 0x00}) - fmt.Printf("%v, %v,%v\n", "time step", "number of prey", "number of predators") - experiment.OnStep = func(env *core.Environment, turtles []*core.Turtle, step int) { - alive := 0 - predalive := 0 - env.EvaporateAndDiffuse(0.95, "scent") - // Grow Grass - x := rand.Intn(env.Width()) - y := rand.Intn(env.Height()) - env.PutValue(x, y) + x := []float64{} + pred := []float64{} + prey := []float64{} + + experiment.AddPlot("Predators vs. Prey", func(environment *core.Environment, turtles []*core.Turtle) *chart.Chart { + preyalive := 0 + predalive := 0 for _, turtle := range turtles { if turtle.GetAttribute("type") == "prey" && turtle.GetAttribute("status") != "dead" { - alive++ + preyalive++ prey := turtle.GetActor().(*Prey) if prey.Steps > (*preyReproductiveAge) && prey.Energy > (*preyReproductionEnergy) && float64(rand.Intn(100)) < (100*(*preyReproductionProbability)) { experiment.InitNTurtles(func() core.Actor { sm := new(Prey) return sm }, 1) - alive++ + preyalive++ } } if turtle.GetAttribute("type") == "predator" && turtle.GetAttribute("status") != "dead" { @@ -148,8 +146,57 @@ func main() { } } } + + x = append(x, float64(environment.Step)) + prey = append(prey, float64(preyalive)) + pred = append(pred, float64(predalive)) + + preypop := chart.ContinuousSeries{ + Name: "Prey Population Size", + XValues: x, + YValues: prey, + } + + predpop := chart.ContinuousSeries{ + Name: "Predator Population Size", + XValues: x, + YValues: pred, + } + + graph := chart.Chart{ + Width: 300, + Height: 300, + + Background: chart.Style{ + Padding: chart.Box{ + Top: 50, + }, + }, + XAxis: chart.XAxis{Name: "Time Step", NameStyle: chart.Style{Show: true}, Style: chart.Style{Show: true}, ValueFormatter: func(v interface{}) string { + return fmt.Sprintf("%d", int(v.(float64))) + }}, + YAxis: chart.YAxis{Name: "Population", NameStyle: chart.Style{Show: true}, Style: chart.Style{Show: true}, ValueFormatter: func(v interface{}) string { + return fmt.Sprintf("%d", int(v.(float64))) + }}, + Series: []chart.Series{ + preypop, + predpop, + }, + } + return &graph + }) + + experiment.InitPheromone("scent", color.RGBA{0x80, 0xFF, 0x00, 0x00}) + fmt.Printf("%v, %v,%v\n", "time step", "number of prey", "number of predators") + experiment.OnStep = func(env *core.Environment, turtles []*core.Turtle, step int) { + env.EvaporateAndDiffuse(0.95, "scent") + // Grow Grass + x := rand.Intn(env.Width()) + y := rand.Intn(env.Height()) + env.PutValue(x, y) + //if step == 0 { - fmt.Printf("%v,%v,%v\n", step, alive, predalive) + //fmt.Printf("%v,%v,%v\n", step, alive, predalive) //} } experiment.Run() diff --git a/experiments/slimemold/main.go b/experiments/slimemold/main.go index 2ae5b9d..c711280 100644 --- a/experiments/slimemold/main.go +++ b/experiments/slimemold/main.go @@ -10,20 +10,21 @@ import ( var sniffDistance = flag.Int("sniffDistance", 3, "the distance a turtle can detect pheromone levels from") var defensiveDecentralization = flag.Bool("defensiveDecentralization", false, "if true, slime molds will break up if the concentration is too great") - type SlimeMold struct { - SniffDistance int + SniffDistance int + StartX, StartY int } func (sm *SlimeMold) Setup(env *core.Environment, t *core.Turtle) { t.SetColor(color.RGBA{100, 255, 10, 0}) + } func (sm *SlimeMold) Run(env *core.Environment, t *core.Turtle) { t.Wiggle() if *defensiveDecentralization == false { t.FollowGradient(env, sm.SniffDistance, 2, "trail") - } else if t.Amount(env,sm.SniffDistance,"trail") < 5.2 { + } else if t.Amount(env, sm.SniffDistance, "trail") < 5.2 { t.FollowGradient(env, sm.SniffDistance, 2, "trail") } else { //t.FollowGradient(env, sm.SniffDistance, 2, "trail") diff --git a/experiments/slimemold/plasmoidiam/main.go b/experiments/slimemold/plasmoidiam/main.go index c93eb9b..394657e 100644 --- a/experiments/slimemold/plasmoidiam/main.go +++ b/experiments/slimemold/plasmoidiam/main.go @@ -3,81 +3,10 @@ package main import ( "git.openprivacy.ca/sarah/microworlds/core" "git.openprivacy.ca/sarah/microworlds/experiments" + "git.openprivacy.ca/sarah/microworlds/models" "image/color" ) -type SlimeMold struct { - Num int -} - -func (sm *SlimeMold) Setup(env *core.Environment, t *core.Turtle) { - t.SetColor(color.RGBA{100, 255, 10, 0}) - t.SetXY(150+(sm.Num%100), 150+(sm.Num/100)) -} - -func (sm *SlimeMold) CheckNeighbours(env *core.Environment, ox, oy int) int { - neighbours := 0 - if env.Check(ox-1, oy-1) { - neighbours++ - } - if env.Check(ox, oy-1) { - neighbours++ - } - if env.Check(ox+1, oy-1) { - neighbours++ - } - if env.Check(ox-1, oy) { - neighbours++ - } - if env.Check(ox+1, oy) { - neighbours++ - } - - if env.Check(ox-1, oy+1) { - neighbours++ - } - if env.Check(ox, oy+1) { - neighbours++ - } - if env.Check(ox+1, oy+1) { - neighbours++ - } - return neighbours -} - -func (sm *SlimeMold) Run(env *core.Environment, t *core.Turtle) { - - // Move around the world, if there are too many slimes around us turn around, otherwise follow slimes and food. - t.Wiggle() - if t.Amount(env, 1, "slime") > 10 { - t.TurnAround() - } else { - t.FollowGradient(env, 1, 1, "slime") - } - t.FollowGradient(env, 1, .1, "food") - - // If we have no neighbours we pretend we found some food so the others can find us. - if t.Step(env) { - ox, oy := t.Pos() - if sm.CheckNeighbours(env, ox, oy) > 0 || env.HasValue(t.Pos()) { - t.Drop(env, .1, "slime") - } else { - t.Drop(env, 10, "food") - } - } else { - // We are on top of other slime, add some more randomness - t.Wiggle() - t.Wiggle() - t.Wiggle() - } - - // We've found food, let's drop some chemical to tell others - if env.HasValue(t.Pos()) { - env.TakeValue(t.Pos()) - t.Drop(env, 256, "food") - } -} - func main() { experiment := new(experiments.Experiment) experiment.InitializeExperiment() @@ -108,14 +37,14 @@ func main() { }) experiment.InitNTurtles(func() core.Actor { - sm := new(SlimeMold) + sm := new(models.SlimeMold) sm.Num = num num++ return sm - }, 3000) + }, 10000) experiment.InitPheromone("slime", color.RGBA{0x80, 0xFF, 0x00, 0x00}) - experiment.InitPheromone("food", color.RGBA{0x80, 0xFF, 0x00, 0x00}) + experiment.InitPheromone("food", color.RGBA{0xff, 0x00, 0x00, 0x00}) experiment.OnStep = func(environment *core.Environment, turtles []*core.Turtle, i int) { environment.EvaporateAndDiffuse(0.99, "slime") environment.EvaporateAndDiffuse(0.99, "food") diff --git a/experiments/swarm/main.go b/experiments/swarm/main.go index 7a29399..76da1bf 100644 --- a/experiments/swarm/main.go +++ b/experiments/swarm/main.go @@ -4,97 +4,290 @@ import ( "flag" "git.openprivacy.ca/sarah/microworlds/core" "git.openprivacy.ca/sarah/microworlds/experiments" + "git.openprivacy.ca/sarah/microworlds/models" "image/color" + "math" ) -var sniffDistance = flag.Int("sniffDistance", 30, "the distance an ant can detect pheromone levels from") +var sniffDistance = flag.Int("sniffDistance", 15, "the distance an ant can detect pheromone levels from") var dropSize = flag.Float64("dropSize", 1.0, "the amount of pheromone an ants drops") type Wall struct { } func (a *Wall) Setup(env *core.Environment, t *core.Turtle) { - t.SetColor(color.RGBA{0xff, 0, 0, 0xff}) + t.SetColor(color.RGBA{0x4b, 0x35, 0x57, 0xff}) } func (a *Wall) Run(env *core.Environment, t *core.Turtle) { } -type Ant struct { - SniffDistance int - Carrying bool - DropSize float64 - Num int - Heading int +type SlimeMold struct { + Num int + StartX, StartY int + Energy float64 + Count float32 +} + +func (sm *SlimeMold) Setup(env *core.Environment, t *core.Turtle) { + t.SetColor(color.RGBA{0x1f, 0xff, 0x00, 0xff}) + //t.SetXY(sm.StartX, sm.StartY) + sm.Energy = 0 +} + +func (sm *SlimeMold) CheckNeighbours(env *core.Environment, ox, oy int) int { + neighbours := 0 + if env.Check(ox-1, oy-1) { + neighbours++ + } + if env.Check(ox, oy-1) { + neighbours++ + } + if env.Check(ox+1, oy-1) { + neighbours++ + } + if env.Check(ox-1, oy) { + neighbours++ + } + if env.Check(ox+1, oy) { + neighbours++ + } + + if env.Check(ox-1, oy+1) { + neighbours++ + } + if env.Check(ox, oy+1) { + neighbours++ + } + if env.Check(ox+1, oy+1) { + neighbours++ + } + return neighbours +} + +func (sm *SlimeMold) Run(env *core.Environment, t *core.Turtle) { + neighbour := t.Check(env) + if neighbour != nil { + n, ok := neighbour.GetActor().(*SlimeMold) + if ok && n.Energy > sm.Energy { + sm.Energy = (sm.Energy + n.Energy) * .5 + n.Energy = (sm.Energy + n.Energy) * .5 + } + } + + // Move around the world, if there are too many slimes around us turn around, otherwise follow slimes and food. + t.Wiggle() + + if t.Amount(env, 1, "slime") > 10 { + t.FollowGradient(env, 1, 10, "slime") + t.TurnAround() + } + t.FollowGradient(env, 1, .1, "food") + // If we have no neighbours we pretend we found some food so the others can find us. + t.Step(env) + ox, oy := t.Pos() + neighbours := sm.CheckNeighbours(env, ox, oy) + if neighbours > 2 { + sm.Count = 0 + t.Drop(env, .1, "slime") + if neighbours >= 7 { + t.Wiggle() + t.Wiggle() + t.Wiggle() + t.Drop(env, 1, "slime") + } + } else { + sm.Count += 0.1 + t.Drop(env, float32(math.Pow(2, float64(sm.Count))), "food") + } + + // We've found food, let's drop some chemical to tell others + if env.HasValue(t.Pos()) { + //env.TakeValue(t.Pos()) + //env.TakeValue(t.Pos()) + t.Drop(env, 255, "food") + sm.Energy = 256 + } + + if sm.Energy > 0 { + sm.Energy -= 2 + if sm.Energy == 0 { + //t.SetAttribute("status","dead") + } + } + t.Drop(env, float32(sm.Energy), "food") + t.SetColor(color.RGBA{100, uint8(sm.Energy / 256), 10, 0}) + } var W = 1 +var maze0 = [][]int{ + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, 0, 0, 3, 0, 3, 0, 3, 0, 0, 0, 3, 0, 3, W}, + {W, 0, W, W, W, W, W, 3, W, 3, W, 0, W, 0, W}, + {W, 0, W, 0, 3, 0, 3, 0, W, 0, W, 0, W, 3, W}, + {W, 3, W, 3, W, W, W, W, W, 3, W, 3, W, 0, W}, + {W, 0, W, 0, W, 0, 3, 0, 3, 0, W, 0, W, 3, W}, + {W, 3, W, 3, W, 0, W, W, W, 3, W, 3, W, 0, W}, + {2, 2, 3, 0, W, 3, 0, 0, W, 0, W, W, W, 0, W}, + {2, 2, W, 0, W, W, 0, W, W, 3, W, 3, W, 3, W}, + {2, 2, W, 0, W, 0, 3, W, 0, 0, W, 0, W, 0, W}, + {2, 2, W, 3, W, W, 0, W, 3, W, W, 3, 0, 3, W}, + {2, 2, W, 0, W, 0, 3, W, 0, 3, 0, 0, W, 0, W}, + {2, 2, W, 3, W, 3, W, W, W, W, W, 3, W, 3, W}, + {2, 2, W, 0, W, 0, 0, W, 2, 2, 2, 2, W, 0, W}, + {W, W, W, W, W, W, W, W, 2, 2, 2, 2, W, W, W}, +} + +var maze2 = [][]int{ + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, W, W, W, W, W, 2, 2, 2, W, W, W, W, W, W}, + {W, W, W, W, W, W, W, 2, W, W, W, W, W, W, W}, + {W, W, W, W, W, W, W, 2, W, W, W, W, W, W, W}, + {W, W, W, W, W, W, W, 2, W, W, W, W, W, W, W}, + {W, W, W, W, W, W, W, 2, W, W, W, W, W, W, W}, + {W, W, W, W, W, W, 3, 3, 3, W, W, W, W, W, W}, + {W, W, W, W, W, W, 3, 3, 3, W, W, W, W, W, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, +} + +var logo = [][]int{ + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, 2, 2, 2, 2, 2, W, 2, W, 2, 2, W, 2, 2, W}, + {W, 2, W, 2, W, 2, W, 2, W, 2, W, W, 2, W, W}, + {W, 3, W, 2, W, 2, W, 3, W, 2, 3, W, 3, W, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, 3, 2, 2, W, 3, W, W, W, 2, W, 3, 2, 2, W}, + {W, 2, W, 2, W, 2, W, 2, W, 2, W, 2, W, 2, W}, + {W, 2, 2, 2, W, 2, 2, 2, 2, 2, W, 2, 2, 2, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, 2, 2, W, 2, W, W, 2, 2, W, W, 2, 5, W, W}, + {W, 2, W, W, 2, W, W, 2, 4, 5, W, 2, W, W, W}, + {W, 3, W, W, 2, 3, W, 3, 2, W, 4, 3, W, W, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, +} + var maze = [][]int{ {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, - {W, 0, W, W, W, W, W, 0, W, 0, W, 0, W, 0, W}, - {W, 0, W, 0, 0, 0, 0, 0, W, 0, W, 0, W, 0, W}, - {W, 0, W, 0, W, W, W, W, W, 0, W, 0, W, 0, W}, - {W, 0, W, 0, W, 0, 0, 0, 0, 0, W, 0, W, 0, W}, - {W, 0, W, 0, W, 0, W, W, W, 0, W, 0, W, 0, W}, - {W, 0, 0, 0, W, 0, 0, 0, W, 0, W, W, W, 0, W}, - {W, 0, W, 0, W, W, 0, W, W, 0, W, 0, W, 0, W}, - {W, 0, W, 0, W, 0, 0, W, 0, 0, W, 0, W, 0, W}, - {W, 0, W, 0, W, W, 0, W, 0, W, W, 0, 0, 0, W}, - {W, 2, W, 0, W, 0, 0, W, 0, 0, 0, 0, W, 0, W}, - {W, 2, W, 0, W, 0, W, W, W, W, W, 0, W, 0, W}, - {W, 2, W, 0, W, 0, 0, W, 0, 0, 0, 0, W, 2, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, } -func (a *Ant) Setup(env *core.Environment, t *core.Turtle) { - //t.SetColor(color.RGBA{0x67,0xf5,0x10,0xff}) - - //t.SetXY(40+(a.Num %70),50+(a.Num/50)) +var smallmaze = [][]int{ + {W, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, W}, + {W, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, W}, + {W, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, 2, 2, W}, + {W, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, W, W, W, W}, + {W, 2, W, 2, 2, W, W, W, W, W, 2, 2, W, 0, W}, + {W, 2, W, W, 2, W, 3, 3, 3, W, 2, 2, W, 0, W}, + {W, 2, W, 2, 2, 2, 3, 0, 3, W, W, 0, W, 0, W}, + {W, 2, W, W, W, W, 3, 0, 3, W, 0, 0, W, 0, W}, + {W, 2, 2, 2, 2, W, 3, 3, 3, W, 0, 0, 0, 0, W}, + {W, 2, 2, 2, 2, W, W, W, W, W, 0, 0, 0, 0, W}, + {W, 2, 2, 2, W, W, W, W, W, W, W, W, W, W, W}, + {W, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, W}, + {W, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, W}, + {W, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, W}, } -func (a *Ant) Run(env *core.Environment, t *core.Turtle) { - a.Heading = t.Heading() - if env.HasValue(t.Pos()) || a.Carrying { - t.Drop(env, 255, "slime") - a.Carrying = true - } else { - n := t.Check(env) - if n != nil { - ant,ok := n.GetActor().(*Ant) - if ok { - if ant.Carrying { - a.DropSize += 10 - t.SetHeading(a.Heading) - } else if ant.DropSize > 20{ - t.SetHeading(a.Heading) - a.DropSize += 5 - } else if ant.DropSize > 1 { - t.Wiggle() - a.DropSize += 5 - } - } - } +var steiner2 = [][]int{ + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 0, W}, + {W, 0, 0, 0, 2, 2, 0, 0, 0, 0, 2, 2, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, +} +var mazetest = [][]int{ + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, W, W, W, W, W, 0, W, 0, W, 0, 0, W}, + {W, 0, 0, 0, 0, W, 0, W, 0, W, 0, W, 0, 0, W}, + {W, 0, 0, 0, 0, W, 3, W, 2, 2, 0, W, 0, 0, W}, + {W, 0, 0, 0, 0, W, 0, 0, 2, 2, 2, 2, 2, 2, W}, + {W, 0, 0, 0, 0, 2, 2, W, 2, 2, 0, 0, 0, 2, W}, + {W, 0, 0, 0, 0, 2, 2, W, 0, 0, 0, 0, 0, 2, W}, + {W, 0, 0, 0, 0, 2, 2, W, 0, 0, 0, 0, 0, 2, W}, + {W, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 2, W}, + {W, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, W}, + {W, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, W}, + {W, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, W}, + {W, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, +} - } +var mold = [][]int{ + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 2, 2, 0, 0, 0, 2, 2, 0, 0, W}, + {W, 0, 0, 0, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, +} - if a.DropSize < 10 { - t.SetColor(color.RGBA{0, 0xf5, 0x10, 0x1f}) - t.FollowGradient(env, a.SniffDistance, 20, "slime") - t.Wiggle() - t.Step(env) - } - - if a.DropSize > 1 { - a.DropSize-- - t.SetColor(color.RGBA{0xff,0xf5,0, 0x1f}) - t.Drop(env, float32(a.DropSize), "slime") - } +var utrap = [][]int{ + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 2, W, 0, 2, 0, W, 2, 0, 0, 0, W}, + {W, 0, 0, 0, 0, W, 0, 0, 0, W, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 2, W, W, W, W, W, 2, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 2, 0, 3, 0, 0, 2, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W}, + {W, W, W, W, W, W, W, W, W, W, W, W, W, W, W}, } func main() { + + maze = maze0 + experiment := new(experiments.Experiment) experiment.InitializeExperiment() t := new(core.Turtle) @@ -109,25 +302,62 @@ func main() { environment.Occupy(t, x, y) } else if maze[y/20][x/20] == 2 { environment.PutValue(x, y) + } else if maze[y/20][x/20] == 4 { + if x%20 > 10 { + environment.PutValue(x, y) + } else { + environment.Occupy(t, x, y) + } + } else if maze[y/20][x/20] == 5 { + if x%20 < 10 { + environment.PutValue(x, y) + } else { + environment.Occupy(t, x, y) + } } /// } } } }) - i := 0 - experiment.InitTurtles(func() core.Actor { - sm := new(Ant) - sm.SniffDistance = *sniffDistance - sm.DropSize = 0 - sm.Num = i - i++ - return sm - }) + for x := 0; x < 300; x++ { + for y := 0; y < 300; y++ { + if maze[y/20][x/20] == 3 { + i := 0 + experiment.InitNTurtles(func() core.Actor { + sm := new(models.NeoSlimeMold) + //sm.SniffDistance = *sniffDistance + sm.StartX, sm.StartY = x, y + sm.Num = i + i++ + return sm + }, 1) + } + } + } - experiment.InitPheromone("slime", color.RGBA{0xff, 0xfF, 0x00, 0xff}) + experiment.InitNTurtles(func() core.Actor { + sm := new(models.NeoSlimeMold) + //sm.SniffDistance = *sniffDistance + return sm + }, 0) + + experiment.InitPheromone("slime", color.RGBA{0x00, 0xfF, 0x00, 0xff}) + experiment.InitPheromone("food", color.RGBA{0xff, 0xff, 0x00, 0xff}) experiment.OnStep = func(environment *core.Environment, turtles []*core.Turtle, i int) { - environment.EvaporateAndDiffuse(0.5, "slime") + environment.EvaporateAndDiffuse(0.99, "slime") + environment.EvaporateAndDiffuse(0.99, "food") + + for x := 0; x < 300; x++ { + for y := 0; y < 300; y++ { + if maze[y/20][x/20] == 2 && environment.HasValue(x, y) { + //environment.Mark("food",x,y,256) + } + } + } + + // fmt.Printf("%d: Turtles: %v\n", environment.Step, len(turtles)) + } experiment.Run() } diff --git a/go.mod b/go.mod index 761a439..6018026 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,13 @@ module git.openprivacy.ca/sarah/microworlds go 1.13 -require github.com/veandco/go-sdl2 v0.3.3 +require ( + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/asim/quadtree v0.0.0-20190907063054-ae2e556e6bb4 + github.com/foolusion/quadtree v0.0.0-20140826014210-88d124c993be + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/nickng/bibtex v1.0.1 // indirect + github.com/veandco/go-sdl2 v0.3.3 + github.com/wcharczuk/go-chart v2.0.1+incompatible + golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect +) diff --git a/go.sum b/go.sum index 2369cc0..1911e7e 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,21 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/asim/quadtree v0.0.0-20190907063054-ae2e556e6bb4 h1:ilm6YOZRb8ZA/ulKbNcL+AC/fbpkM3dRZ9kWXhBwx8U= +github.com/asim/quadtree v0.0.0-20190907063054-ae2e556e6bb4/go.mod h1:K2M5YTG4dpIXvuERd53snM5THchZczdy+k9gH02Shww= +github.com/foolusion/quadtree v0.0.0-20140826014210-88d124c993be h1:fLLJtjJ2bdBHXLPtRsDVdnVEr9zBmw9be/WF751O0Gg= +github.com/foolusion/quadtree v0.0.0-20140826014210-88d124c993be/go.mod h1:Gk+3NkBZRly4/GnVRU95Wwv+JmmyoVGmdcEDwA5Wm1M= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/nickng/bibtex v1.0.1 h1:Uop3DVOdQdrTamXfxr65f9KyHrd4RhttXwHi1BY6Wk0= +github.com/nickng/bibtex v1.0.1/go.mod h1:0qHZj8RRrLaGXyPoF9odM3M1EX1HnWiwACyR3wgGf8U= github.com/veandco/go-sdl2 v0.3.0 h1:IWYkHMp8V3v37NsKjszln8FFnX2+ab0538J371t+rss= github.com/veandco/go-sdl2 v0.3.0/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg= github.com/veandco/go-sdl2 v0.3.1 h1:WMQ72+BeCj2o/zOO/wsB4MMpqHr1Y67m6QNFxp053ug= github.com/veandco/go-sdl2 v0.3.1/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg= github.com/veandco/go-sdl2 v0.3.3 h1:4/TirgB2MQ7oww3pM3Yfgf1YbChMlAQAmiCPe5koK0I= github.com/veandco/go-sdl2 v0.3.3/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg= +github.com/wcharczuk/go-chart v2.0.1+incompatible h1:0pz39ZAycJFF7ju/1mepnk26RLVLBCWz1STcD3doU0A= +github.com/wcharczuk/go-chart v2.0.1+incompatible/go.mod h1:PF5tmL4EIx/7Wf+hEkpCqYi5He4u90sw+0+6FhrryuE= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/graphics/graphics.go b/graphics/graphics.go index ccbef58..a8a015e 100644 --- a/graphics/graphics.go +++ b/graphics/graphics.go @@ -37,7 +37,6 @@ func NewGraphics(width, height, pxsize int32) *Graphics { } graphics.colorMap = make(map[string][4]uint8) - graphics.renderer = renderer return graphics } @@ -51,7 +50,7 @@ func (g *Graphics) ColorPheromone(name string, color [4]uint8) { } func (g *Graphics) Render(env *core.Environment, turtles []*core.Turtle) { - g.renderer.SetDrawColor(0x00, 0x00, 0x00, 0x00) + g.renderer.SetDrawColor(0x00, 0x00, 0x0, 0xff) g.renderer.FillRect(&sdl.Rect{X: 0, Y: 0, W: g.width * g.pxsize, H: g.width * g.pxsize}) for x := 0; x < int(g.width); x++ { @@ -84,8 +83,8 @@ func (g *Graphics) Render(env *core.Environment, turtles []*core.Turtle) { g.DrawTileColor(int32(x), int32(y)) } - g.renderer.SetDrawColor(255, 0, 0, uint8(255)) - if env.Get(x,y) != nil { + g.renderer.SetDrawColor(0x4b, 0x35, 0x57, uint8(255)) + if env.Get(x, y) != nil { g.DrawTileColor(int32(x), int32(y)) } } @@ -99,7 +98,6 @@ func (g *Graphics) Render(env *core.Environment, turtles []*core.Turtle) { col := t.GetColor() g.renderer.SetDrawColor(col.R, col.G, col.B, col.A) g.DrawTileColor(int32(x), int32(y)) - t.Run(env) } g.renderer.Present() diff --git a/graphics/plotting.go b/graphics/plotting.go new file mode 100644 index 0000000..0a404d2 --- /dev/null +++ b/graphics/plotting.go @@ -0,0 +1,71 @@ +package graphics + +import ( + "bytes" + "fmt" + "git.openprivacy.ca/sarah/microworlds/core" + "github.com/veandco/go-sdl2/sdl" + "github.com/wcharczuk/go-chart" + "image" + "log" + "os" +) + +type Plot struct { + Graphics + GeneratePlot func(*core.Environment, []*core.Turtle) *chart.Chart +} + +func NewPlot(title string, width, height, pxsize int32) *Plot { + graphics := new(Plot) + graphics.width = width + graphics.height = height + window, err := sdl.CreateWindow(title, sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, + width, height, sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE) + 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.colorMap = make(map[string][4]uint8) + graphics.renderer = renderer + return graphics +} + +func (p *Plot) Render(env *core.Environment, turtles []*core.Turtle) { + graph := p.GeneratePlot(env, turtles) + + // graph.Elements = []chart.Renderable{chart.Legend(graph)} + w, h := p.window.GetSize() + surface, _ := p.window.GetSurface() + renderer, _ := sdl.CreateSoftwareRenderer(surface) + p.renderer = renderer + graph.Width = int(w) + graph.Height = int(h) + if env.Step > 2 { + buffer := bytes.NewBuffer([]byte{}) + err := graph.Render(chart.PNG, buffer) + if err != nil { + log.Fatal(err) + } + + graph, _, _ := image.Decode(buffer) + + for x := 0; x < int(w); x++ { + for y := 0; y < int(h); y++ { + col := graph.At(x, y) + r, g, b, a := col.RGBA() + p.renderer.SetDrawColor(uint8(r), uint8(g), uint8(b), uint8(a)) + p.renderer.DrawPoint(int32(x), int32(y)) + } + } + } + p.window.UpdateSurface() +} diff --git a/models/neoplasmodium.go b/models/neoplasmodium.go new file mode 100644 index 0000000..ec75bbe --- /dev/null +++ b/models/neoplasmodium.go @@ -0,0 +1,85 @@ +package models + +import ( + "git.openprivacy.ca/sarah/microworlds/core" + "image/color" +) + +type NeoSlimeMold struct { + Num int + StartX, StartY int + N int +} + +func (sm *NeoSlimeMold) Setup(env *core.Environment, t *core.Turtle) { + t.SetColor(color.RGBA{100, 255, 10, 0}) + if sm.StartX != 0 && sm.StartY != 0 { + t.SetXY(sm.StartX, sm.StartY) + } +} + +func (sm *NeoSlimeMold) CheckNeighbours(env *core.Environment, ox, oy int) int { + neighbours := 0 + if env.Check(ox-1, oy-1) { + neighbours++ + } + if env.Check(ox, oy-1) { + neighbours++ + } + if env.Check(ox+1, oy-1) { + neighbours++ + } + if env.Check(ox-1, oy) { + neighbours++ + } + if env.Check(ox+1, oy) { + neighbours++ + } + + if env.Check(ox-1, oy+1) { + neighbours++ + } + if env.Check(ox, oy+1) { + neighbours++ + } + if env.Check(ox+1, oy+1) { + neighbours++ + } + return neighbours +} + +func (sm *NeoSlimeMold) Run(env *core.Environment, t *core.Turtle) { + + // Move around the world, if there are too many slimes around us turn around, otherwise follow slimes and food. + t.Wiggle() + if sm.N == 0 { + t.FollowGradient(env, 1, .1, "food") + } + t.AvoidAverageGradient(env, 1, 1, "slime") + + // If we have no neighbours we pretend we found some food so the others can find us. + if t.Step(env) { + ox, oy := t.Pos() + if sm.CheckNeighbours(env, ox, oy) > 3 { + t.Drop(env, .1, "slime") + } else { + t.Drop(env, .1, "food") + } + } else { + // We are on top of other slime, add some more randomness + t.Wiggle() + t.Wiggle() + t.Wiggle() + } + + // We've found food, let's drop some chemical to tell others + if env.HasValue(t.Pos()) && sm.N == 0 { + //env.TakeValue(t.Pos()) + sm.N = 100 + t.Drop(env, 20, "food") + } + + if sm.N > 0 { + sm.N-- + } +} diff --git a/models/plasmodium.go b/models/plasmodium.go new file mode 100644 index 0000000..9d3ec4c --- /dev/null +++ b/models/plasmodium.go @@ -0,0 +1,82 @@ +package models + +import ( + "git.openprivacy.ca/sarah/microworlds/core" + "image/color" +) + +type SlimeMold struct { + Num int + StartX, StartY int + N int +} + +func (sm *SlimeMold) Setup(env *core.Environment, t *core.Turtle) { + t.SetColor(color.RGBA{100, 255, 10, 0}) + if sm.StartX != 0 && sm.StartY != 0 { + t.SetXY(sm.StartX, sm.StartY) + } +} + +func (sm *SlimeMold) CheckNeighbours(env *core.Environment, ox, oy int) int { + neighbours := 0 + if env.Check(ox-1, oy-1) { + neighbours++ + } + if env.Check(ox, oy-1) { + neighbours++ + } + if env.Check(ox+1, oy-1) { + neighbours++ + } + if env.Check(ox-1, oy) { + neighbours++ + } + if env.Check(ox+1, oy) { + neighbours++ + } + + if env.Check(ox-1, oy+1) { + neighbours++ + } + if env.Check(ox, oy+1) { + neighbours++ + } + if env.Check(ox+1, oy+1) { + neighbours++ + } + return neighbours +} + +func (sm *SlimeMold) Run(env *core.Environment, t *core.Turtle) { + + // Move around the world, if there are too many slimes around us turn around, otherwise follow slimes and food. + t.Wiggle() + if t.Amount(env, 1, "slime") > 10 { + t.TurnAround() + } else { + t.FollowGradient(env, 1, 1, "slime") + } + t.FollowGradient(env, 1, .1, "food") + + // If we have no neighbours we pretend we found some food so the others can find us. + if t.Step(env) { + ox, oy := t.Pos() + if sm.CheckNeighbours(env, ox, oy) > 3 && !env.HasValue(t.Pos()) { + t.Drop(env, .1, "slime") + } else { + t.Drop(env, 1, "food") + } + } else { + // We are on top of other slime, add some more randomness + t.Wiggle() + t.Wiggle() + t.Wiggle() + } + + // We've found food, let's drop some chemical to tell others + if env.HasValue(t.Pos()) { + env.TakeValue(t.Pos()) + t.Drop(env, 10, "food") + } +}