


gridrover.go
package mainimport ("fmt""image""log""math/rand""sync""time")func main() {gridSize := image.Point{X: 20, Y: 10}grid := NewMarsGrid(gridSize)rover := make([]*RoverDriver, 5)for i := range rover {rover[i] = startDriver(fmt.Sprint("rover", i), grid)}time.Sleep(10 * time.Second)}func startDriver(name string, grid *MarsGrid) *RoverDriver {var o *Occupier// Try a random point; continue until we've found one that's// not currently occupied.for o == nil {startPoint := image.Point{X: rand.Intn(grid.Size().X), Y: rand.Intn(grid.Size().Y)}o = grid.Occupy(startPoint)}return NewRoverDriver(name, o)}// RoverDriver drives a rover around the surface of Mars.type RoverDriver struct {commandc chan commandoccupier *Occupiername string}// NewRoverDriver starts a new RoverDriver and returns it.func NewRoverDriver(name string, occupier *Occupier) *RoverDriver {r := &RoverDriver{commandc: make(chan command),occupier: occupier,name: name,}go r.drive()return r}type command intconst (right command = 0left command = 1)// drive is responsible for driving the rover. It// is expected to be started in a goroutine.func (r *RoverDriver) drive() {log.Printf("%s initial position %v", r.name, r.occupier.Pos())direction := image.Point{X: 1, Y: 0}updateInterval := 250 * time.MillisecondnextMove := time.After(updateInterval)for {select {case c := <-r.commandc:switch c {case right:direction = image.Point{X: -direction.Y,Y: direction.X,}case left:direction = image.Point{X: direction.Y,Y: -direction.X,}}log.Printf("%s new direction %v", r.name, direction)case <-nextMove:nextMove = time.After(updateInterval)newPos := r.occupier.Pos().Add(direction)if r.occupier.MoveTo(newPos) {log.Printf("%s moved to %v", r.name, newPos)// Successfully moved to new position.break}log.Printf("%s blocked trying to move from %v to %v", r.name, r.occupier.Pos(), newPos)// Pick one of the other directions randomly.// Next time round, we'll try to move in the new// direction.dir := rand.Intn(3) + 1for i := 0; i < dir; i++ {direction = image.Point{X: -direction.Y,Y: direction.X,}}log.Printf("%s new random direction %v", r.name, direction)}}}// Left turns the rover left (90° anticlockwise).func (r *RoverDriver) Left() {r.commandc <- left}// Right turns the rover right (90° clockwise).func (r *RoverDriver) Right() {r.commandc <- right}// MarsGrid represents a grid of some of the surface// of Mars. It may be used concurrently by different// goroutines.type MarsGrid struct {bounds image.Rectanglemu sync.Mutexcells [][]cell}// SensorData holds information about whats in// a point in the grid.type SensorData struct {Life int}type cell struct {groundData SensorDataoccupier *Occupier}// NewMarsGrid returns a new MarsGrid of the// given size.func NewMarsGrid(size image.Point) *MarsGrid {grid := &MarsGrid{bounds: image.Rectangle{Max: size,},cells: make([][]cell, size.Y),}for i := range grid.cells {grid.cells[i] = make([]cell, size.X)}return grid}// Size returns the size of the grid.func (g *MarsGrid) Size() image.Point {return g.bounds.Max}// Occupy occupies a cell at the given point in the grid. It// returns nil if the point is already or the point is outside// the grid. Otherwise it returns a value that can be used// to move to different places on the grid.func (g *MarsGrid) Occupy(p image.Point) *Occupier {g.mu.Lock()defer g.mu.Unlock()cell := g.cell(p)if cell == nil || cell.occupier != nil {return nil}cell.occupier = &Occupier{grid: g,pos: p,}return cell.occupier}func (g *MarsGrid) cell(p image.Point) *cell {if !p.In(g.bounds) {return nil}return &g.cells[p.Y][p.X]}// Occupier represents an occupied cell in the grid.type Occupier struct {grid *MarsGridpos image.Point}// MoveTo moves the occupier to a different cell in the grid.// It reports whether the move was successful// It might fail because it was trying to move outside// the grid or because the cell it's trying to move into// is occupied. If it fails, the occupier remains in the same place.func (o *Occupier) MoveTo(p image.Point) bool {o.grid.mu.Lock()defer o.grid.mu.Unlock()newCell := o.grid.cell(p)if newCell == nil || newCell.occupier != nil {return false}o.grid.cell(o.pos).occupier = nilnewCell.occupier = oo.pos = preturn true}// Sense returns sensory data from the current cell.func (o *Occupier) Sense() SensorData {o.grid.mu.Lock()defer o.grid.mu.Unlock()return o.grid.cell(o.pos).groundData}// Pos returns the current grid position of the occupier.func (o *Occupier) Pos() image.Point {return o.pos}
lifeonmars.go
package mainimport ("fmt""image""log""math/rand""sync""time")func main() {marsToEarth := make(chan []Message)go earthReceiver(marsToEarth)gridSize := image.Point{X: 20, Y: 10}grid := NewMarsGrid(gridSize)rover := make([]*RoverDriver, 5)for i := range rover {rover[i] = startDriver(fmt.Sprint("rover", i), grid, marsToEarth)}time.Sleep(60 * time.Second)}// Message holds a message as sent from Mars to Earth.type Message struct {Pos image.PointLifeSigns intRover string}const (// The length of a Mars day.dayLength = 24 * time.Second// The length of time per day during which// messages can be transmitted from a rover to Earth.receiveTimePerDay = 2 * time.Second)// earthReceiver receives messages sent from Mars.// As connectivity is limited, it only receives messages// for some time every Mars day.func earthReceiver(msgc chan []Message) {for {time.Sleep(dayLength - receiveTimePerDay)receiveMarsMessages(msgc)}}// receiveMarsMessages receives messages sent from Mars// for the given duration.func receiveMarsMessages(msgc chan []Message) {finished := time.After(receiveTimePerDay)for {select {case <-finished:returncase ms := <-msgc:for _, m := range ms {log.Printf("earth received report of life sign level %d from %s at %v", m.LifeSigns, m.Rover, m.Pos)}}}}func startDriver(name string, grid *MarsGrid, marsToEarth chan []Message) *RoverDriver {var o *Occupier// Try a random point; continue until we've found one that's// not currently occupied.for o == nil {startPoint := image.Point{X: rand.Intn(grid.Size().X), Y: rand.Intn(grid.Size().Y)}o = grid.Occupy(startPoint)}return NewRoverDriver(name, o, marsToEarth)}// Radio represents a radio transmitter that can send// message to Earth.type Radio struct {fromRover chan Message}// SendToEarth sends a message to Earth. It always// succeeds immediately - the actual message// may be buffered and actually transmitted later.func (r *Radio) SendToEarth(m Message) {r.fromRover <- m}// NewRadio returns a new Radio instance that sends// messages on the toEarth channel.func NewRadio(toEarth chan []Message) *Radio {r := &Radio{fromRover: make(chan Message),}go r.run(toEarth)return r}// run buffers messages sent by a rover until they// can be sent to Earth.func (r *Radio) run(toEarth chan []Message) {var buffered []Messagefor {toEarth1 := toEarthif len(buffered) == 0 {toEarth1 = nil}select {case m := <-r.fromRover:buffered = append(buffered, m)case toEarth1 <- buffered:buffered = nil}}}// RoverDriver drives a rover around the surface of Mars.type RoverDriver struct {commandc chan commandoccupier *Occupiername stringradio *Radio}// NewRoverDriver starts a new RoverDriver and returns it.func NewRoverDriver(name string,occupier *Occupier,marsToEarth chan []Message,) *RoverDriver {r := &RoverDriver{commandc: make(chan command),occupier: occupier,name: name,radio: NewRadio(marsToEarth),}go r.drive()return r}type command intconst (right command = 0left command = 1)// drive is responsible for driving the rover. It// is expected to be started in a goroutine.func (r *RoverDriver) drive() {log.Printf("%s initial position %v", r.name, r.occupier.Pos())direction := image.Point{X: 1, Y: 0}updateInterval := 250 * time.MillisecondnextMove := time.After(updateInterval)for {select {case c := <-r.commandc:switch c {case right:direction = image.Point{X: -direction.Y,Y: direction.X,}case left:direction = image.Point{X: direction.Y,Y: -direction.X,}}log.Printf("%s new direction %v", r.name, direction)case <-nextMove:nextMove = time.After(updateInterval)newPos := r.occupier.Pos().Add(direction)if r.occupier.MoveTo(newPos) {log.Printf("%s moved to %v", r.name, newPos)r.checkForLife()break}log.Printf("%s blocked trying to move from %v to %v", r.name, r.occupier.Pos(), newPos)// Pick one of the other directions randomly.// Next time round, we'll try to move in the new// direction.dir := rand.Intn(3) + 1for i := 0; i < dir; i++ {direction = image.Point{X: -direction.Y,Y: direction.X,}}log.Printf("%s new random direction %v", r.name, direction)}}}func (r *RoverDriver) checkForLife() {// Successfully moved to new position.sensorData := r.occupier.Sense()if sensorData.LifeSigns < 900 {return}r.radio.SendToEarth(Message{Pos: r.occupier.Pos(),LifeSigns: sensorData.LifeSigns,Rover: r.name,})}// Left turns the rover left (90° counterclockwise).func (r *RoverDriver) Left() {r.commandc <- left}// Right turns the rover right (90° clockwise).func (r *RoverDriver) Right() {r.commandc <- right}// MarsGrid represents a grid of some of the surface// of Mars. It may be used concurrently by different// goroutines.type MarsGrid struct {bounds image.Rectanglemu sync.Mutexcells [][]cell}// SensorData holds information about what's in// a point in the grid.type SensorData struct {LifeSigns int}type cell struct {groundData SensorDataoccupier *Occupier}// NewMarsGrid returns a new MarsGrid of the// given size.func NewMarsGrid(size image.Point) *MarsGrid {grid := &MarsGrid{bounds: image.Rectangle{Max: size,},cells: make([][]cell, size.Y),}for y := range grid.cells {grid.cells[y] = make([]cell, size.X)for x := range grid.cells[y] {cell := &grid.cells[y][x]cell.groundData.LifeSigns = rand.Intn(1000)}}return grid}// Size returns a Point representing the size of the grid.func (g *MarsGrid) Size() image.Point {return g.bounds.Max}// Occupy occupies a cell at the given point in the grid. It// returns nil if the point is already occupied or the point is outside// the grid. Otherwise it returns a value that can be used// to move to different places on the grid.func (g *MarsGrid) Occupy(p image.Point) *Occupier {g.mu.Lock()defer g.mu.Unlock()cell := g.cell(p)if cell == nil || cell.occupier != nil {return nil}cell.occupier = &Occupier{grid: g,pos: p,}return cell.occupier}func (g *MarsGrid) cell(p image.Point) *cell {if !p.In(g.bounds) {return nil}return &g.cells[p.Y][p.X]}// Occupier represents an occupied cell in the grid.type Occupier struct {grid *MarsGridpos image.Point}// MoveTo moves the occupier to a different cell in the grid.// It reports whether the move was successful// It might fail because it was trying to move outside// the grid or because the cell it's trying to move into// is occupied. If it fails, the occupier remains in the same place.func (o *Occupier) MoveTo(p image.Point) bool {o.grid.mu.Lock()defer o.grid.mu.Unlock()newCell := o.grid.cell(p)if newCell == nil || newCell.occupier != nil {return false}o.grid.cell(o.pos).occupier = nilnewCell.occupier = oo.pos = preturn true}// Sense returns sensory data from the current cell.func (o *Occupier) Sense() SensorData {o.grid.mu.Lock()defer o.grid.mu.Unlock()return o.grid.cell(o.pos).groundData}// Pos returns the current grid position of the occupier.func (o *Occupier) Pos() image.Point {return o.pos}
marsgrid.go
package mainimport ("image""sync")func main() {}// MarsGrid represents a grid of some of the surface// of Mars. It may be used concurrently by different// goroutines.type MarsGrid struct {bounds image.Rectanglemu sync.Mutexcells [][]cell}// cell holds information about a given cell in// the grid.type cell struct {occupier *Occupier}// NewMarsGrid returns a new MarsGrid of the// given size.func NewMarsGrid(size image.Point) *MarsGrid {grid := &MarsGrid{bounds: image.Rectangle{Max: size,},cells: make([][]cell, size.Y),}for i := range grid.cells {grid.cells[i] = make([]cell, size.X)}return grid}// Size returns the size of the grid.func (g *MarsGrid) Size() image.Point {return g.bounds.Max}// Occupy occupies a cell at the given point in the grid. It// returns nil if the point is already or the point is outside// the grid. Otherwise it returns a value that can be used// to move to different places on the grid.func (g *MarsGrid) Occupy(p image.Point) *Occupier {g.mu.Lock()defer g.mu.Unlock()cell := g.cell(p)if cell == nil || cell.occupier != nil {return nil}cell.occupier = &Occupier{grid: g,pos: p,}return cell.occupier}// cell returns a pointer to the cell at the// given position, or nil if it's outside// the grid.func (g *MarsGrid) cell(p image.Point) *cell {if !p.In(g.bounds) {return nil}return &g.cells[p.Y][p.X]}// Occupier represents an occupied cell in the grid.type Occupier struct {grid *MarsGridpos image.Point}// MoveTo moves the occupier to a different cell in the grid.// It reports whether the move was successful// It might fail because it was trying to move outside// the grid or because the cell it's trying to move into// is occupied. If it fails, the occupier remains in the same place.func (o *Occupier) MoveTo(p image.Point) bool {o.grid.mu.Lock()defer o.grid.mu.Unlock()newCell := o.grid.cell(p)if newCell == nil || newCell.occupier != nil {return false}o.grid.cell(o.pos).occupier = nilnewCell.occupier = oo.pos = preturn true}// Pos returns the current grid position of the occupier.func (o *Occupier) Pos() image.Point {return o.pos}
