package main import ( "bytes" "math" "math/rand" "strconv" ) // A World is the hex-grid world the simulation takes place in. type World struct { width, height int agents []Agent } // Random returns a random location in the world. func (w *World) Random() Hex { return Hex{rand.Intn(w.height), rand.Intn(w.width)} } // RandomBorder returns a random location on the border of the world. func (w *World) RandomBorder() Hex { perimeter := (w.width+w.height)*2 - 4 r := rand.Intn(perimeter) if r < w.width { // top edge ([0, 0] to [0, width - 1] return Hex{0, r} } r = r - w.width if r < w.height-1 { // right edge (1, width - 1] to [height - 1, width - 1]) return Hex{r + 1, w.width - 1} } r = r - (w.height - 1) if r < w.width-1 { // bottom edge ([height - 1, 0] to [height - 1, width - 2] return Hex{w.height - 1, r} } // left egde ([1, 0] to [height - 1, 0] r = r - (w.width - 1) return Hex{r + 1, 0} } // Update updates all the agents in the world, initiates interactions between them, // removes dead agents, and adds newly spawned agents. func (w *World) Update() { // update all the agents for _, a := range w.agents { a.Update() } // check for interactions between all pairs of agents for _, a1 := range w.agents { for _, a2 := range w.agents { if a1 != a2 { r1, c1 := a1.Position() r2, c2 := a2.Position() // interact if in same location // (a2.ActOn(a1) happens when outer loop // gets to a2) if r1 == r2 && c1 == c2 { a1.ActOn(a2) } } } } // remove dead agents and spawn new ones next := make([]Agent, 0, 10) for _, a := range w.agents { if a.Alive() { next = append(next, a) s := a.Spawn() if s != nil { next = append(next, s) } } } w.agents = next } // String returns a printable representation of the world. func (w *World) String() string { // make a 2-D array (slice of slices) to hold output characters, // initialized to all '.' agentMap := make([][]byte, w.height) for i := 0; i < len(agentMap); i++ { agentMap[i] = make([]byte, w.width) for j := 0; j < len(agentMap[i]); j++ { agentMap[i][j] = '.' } } // add each agent to the 2-D array for _, a := range w.agents { r, c := a.Position() if r >= 0 && r < w.height && c >= 0 && c < w.width { agentMap[r][c] = a.String()[0] } } // format 2-D array as hexes var buf bytes.Buffer for r := 0; r < len(agentMap); r++ { // odd rows offset by 1 character if r%2 == 1 { buf.WriteString(" ") } // output each row with a space between hexes for c := 0; c < len(agentMap[r]); c++ { buf.WriteByte(agentMap[r][c]) buf.WriteString(" ") } buf.WriteString("\n") } // output number of agents at the end (so we can see that agents // are dying when they should) buf.WriteString(strconv.Itoa(len(w.agents))) buf.WriteString("\n") // convert buffer to string return buf.String() } // NewWorld creates a new world with one predator, one food source, and one prey. func NewWorld(w int) *World { // initialize world with width, height (computed so square), and empty // slice to hold agents world := World{w, int(float64(w) * 2 / math.Sqrt(3)), make([]Agent, 0, 10)} // add a predator, food, and prey to the world world.agents = append(world.agents, NewPredator(&world)) world.agents = append(world.agents, NewFood(&world)) world.agents = append(world.agents, NewPrey(&world)) // return pointer to the world (compiler automatically determines // that world has to be on heap to escape this function) return &world }