Finished go/minesweeper

This commit is contained in:
2017-08-24 13:50:10 -05:00
parent 612b12d1e0
commit ce5ea92f79
8 changed files with 895 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
# Robot Simulator
Write a robot simulator.
A robot factory's test facility needs a program to verify robot movements.
The robots have three possible movements:
- turn right
- turn left
- advance
Robots are placed on a hypothetical infinite grid, facing a particular
direction (north, east, south, or west) at a set of {x,y} coordinates,
e.g., {3,8}, with coordinates increasing to the north and east.
The robot then receives a number of instructions, at which point the
testing facility verifies the robot's new position, and in which
direction it is pointing.
- The letter-string "RAALAL" means:
- Turn right
- Advance twice
- Turn left
- Advance once
- Turn left yet again
- Say a robot starts at {7, 3} facing north. Then running this stream
of instructions should leave it at {9, 4} facing west.
## Running the tests
To run the tests run the command `go test` from within the exercise directory.
If the test suite contains benchmarks, you can run these with the `-bench`
flag:
go test -bench .
Keep in mind that each reviewer will run benchmarks on a different machine, with
different specs, so the results from these benchmark tests may vary.
## Further information
For more detailed information about the Go track, including how to get help if
you're having trouble, please visit the exercism.io [Go language page](http://exercism.io/languages/go/about).
## Source
Inspired by an interview question at a famous company.
## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

View File

@@ -0,0 +1,32 @@
package robot
import "fmt"
// definitions used in step 1
var Step1Robot struct {
X, Y int
Dir
}
type Dir int
var _ fmt.Stringer = Dir(1729)
// additional definitions used in step 2
type Command byte // valid values are 'R', 'L', 'A'
type RU int
type Pos struct{ Easting, Northing RU }
type Rect struct{ Min, Max Pos }
type Step2Robot struct {
Dir
Pos
}
// additional definition used in step 3
type Step3Robot struct {
Name string
Step2Robot
}

View File

@@ -0,0 +1,89 @@
// +build step2 !step1,!step3
package robot
import "testing"
// For step 1 you implement robot movements, but it's not much of a simulation.
// For example where in the source code is "the robot"? Where is "the grid"?
// Where are the computations that turn robot actions into grid positions,
// in the robot or in the grid? The physical world is different.
//
// Step 2 introduces a "room." It seems a small addition, but we'll make
// big changes to clarify the rolls of "room", "robot", and "test program"
// and begin to clarify the physics of the simulation. You will define Room
// and Robot as functions which the test program "brings into existence" by
// launching them as goroutines. Information moves between test program,
// robot, and room over Go channels.
//
// Think of Room as a "physics engine," something that models and simulates
// a physical room with walls and a robot. It should somehow model the
// coordinate space of the room, the location of the robot and the walls,
// and ensure for example that the robot doesn't walk through walls.
// We want Robot to be an agent that performs actions, but we want Room to
// maintain a coherent truth.
//
// Step 2 API:
//
// StartRobot(chan Command, chan Action)
// Room(extent Rect, robot Step2Robot, act chan Action, rep chan Step2Robot)
//
// You get to define Action; see defs.go for other definitions.
//
// The test program creates the channels and starts both Room and Robot.
// The test program then sends commands to Robot. When it is done sending
// commands, it closes the command channel. Robot must accept commands and
// inform Room of actions it is attempting. When it senses the command channel
// closing, it must shut down itself. The room must interpret the physical
// consequences of the robot actions. When it senses the robot shutting down,
// it sends a final report back to the test program, telling the robot's final
// position and direction.
var test2 = []struct {
Command
Step2Robot
}{
0: {' ', Step2Robot{N, Pos{1, 1}}}, // no command, this is the start DirAt
1: {'A', Step2Robot{N, Pos{1, 2}}},
2: {'R', Step2Robot{E, Pos{1, 2}}},
3: {'A', Step2Robot{E, Pos{2, 2}}},
4: {'L', Step2Robot{N, Pos{2, 2}}},
5: {'L', Step2Robot{W, Pos{2, 2}}},
6: {'L', Step2Robot{S, Pos{2, 2}}},
7: {'A', Step2Robot{S, Pos{2, 1}}},
8: {'R', Step2Robot{W, Pos{2, 1}}},
9: {'A', Step2Robot{W, Pos{1, 1}}},
10: {'A', Step2Robot{W, Pos{1, 1}}}, // bump W wall
11: {'L', Step2Robot{S, Pos{1, 1}}},
12: {'A', Step2Robot{S, Pos{1, 1}}}, // bump S wall
13: {'L', Step2Robot{E, Pos{1, 1}}},
14: {'A', Step2Robot{E, Pos{2, 1}}},
15: {'A', Step2Robot{E, Pos{2, 1}}}, // bump E wall
16: {'L', Step2Robot{N, Pos{2, 1}}},
17: {'A', Step2Robot{N, Pos{2, 2}}},
18: {'A', Step2Robot{N, Pos{2, 2}}}, // bump N wall
}
func TestStep2(t *testing.T) {
// run incrementally longer tests
for i := 1; i <= len(test2); i++ {
cmd := make(chan Command)
act := make(chan Action)
rep := make(chan Step2Robot)
go StartRobot(cmd, act)
go Room(Rect{Pos{1, 1}, Pos{2, 2}}, test2[0].Step2Robot, act, rep)
for j := 1; j < i; j++ {
cmd <- test2[j].Command
}
close(cmd)
da := <-rep
last := i - 1
want := test2[last].Step2Robot
if da.Pos != want.Pos {
t.Fatalf("Command #%d, Pos = %v, want %v", last, da.Pos, want.Pos)
}
if da.Dir != want.Dir {
t.Fatalf("Command #%d, Dir = %v, want %v", last, da.Dir, want.Dir)
}
}
}

View File

@@ -0,0 +1,293 @@
// +build step3 !step1,!step2
package robot
import "testing"
// Step 3 has three major changes:
//
// * Robots run scripts rather than respond to individual commands.
// * A log channel allows robots and the room to log messages.
// * The room allows multiple robots to exist and operate concurrently.
//
// Step 3 API:
//
// StartRobot3(name, script string, action chan Action3, log chan string)
// Room3(extent Rect, robots []Step3Robot, action chan Action3, report chan []Step3Robot, log chan string)
//
// Again, you define Action3.
//
// For the final position report sent from StartRobot3, you can return the same slice
// received from the robots channel, just with updated positions and directions.
//
// Messages must be sent on the log channel for
// * A robot without a name
// * Duplicate robot names
// * Robots placed at the same place
// * A robot placed outside of the room
// * An undefined command in a script
// * An action from an unknown robot
// * A robot attempting to advance into a wall
// * A robot attempting to advance into another robot
var testOneRobot = []struct {
cmd byte
Step2Robot
nMsg int
}{
// (test2 data with message counts added)
{' ', Step2Robot{N, Pos{1, 1}}, 0},
{'A', Step2Robot{N, Pos{1, 2}}, 0},
{'R', Step2Robot{E, Pos{1, 2}}, 0},
{'A', Step2Robot{E, Pos{2, 2}}, 0},
{'L', Step2Robot{N, Pos{2, 2}}, 0},
{'L', Step2Robot{W, Pos{2, 2}}, 0},
{'L', Step2Robot{S, Pos{2, 2}}, 0},
{'A', Step2Robot{S, Pos{2, 1}}, 0},
{'R', Step2Robot{W, Pos{2, 1}}, 0},
{'A', Step2Robot{W, Pos{1, 1}}, 0},
{'A', Step2Robot{W, Pos{1, 1}}, 1}, // bump W wall
{'L', Step2Robot{S, Pos{1, 1}}, 1},
{'A', Step2Robot{S, Pos{1, 1}}, 2}, // bump S wall
{'L', Step2Robot{E, Pos{1, 1}}, 2},
{'A', Step2Robot{E, Pos{2, 1}}, 2},
{'A', Step2Robot{E, Pos{2, 1}}, 3}, // bump E wall
{'L', Step2Robot{N, Pos{2, 1}}, 3},
{'A', Step2Robot{N, Pos{2, 2}}, 3},
{'A', Step2Robot{N, Pos{2, 2}}, 4}, // bump N wall
}
func logMon(log chan string, nMsg chan int, t *testing.T) {
n := 0
for msg := range log {
t.Log("Sim:", msg)
n++
}
nMsg <- n
}
// Same tests as step 2; single robot, but expect log messages on wall bumps.
func TestOneStep3(t *testing.T) {
for i := 1; i <= len(testOneRobot); i++ {
log := make(chan string)
nMsg := make(chan int)
go logMon(log, nMsg, t)
act := make(chan Action3)
rep := make(chan []Step3Robot)
go Room3(
Rect{Pos{1, 1}, Pos{2, 2}},
[]Step3Robot{{"Robbie", testOneRobot[0].Step2Robot}},
act, rep, log)
scr := ""
for j := 1; j < i; j++ {
scr += string(testOneRobot[j].cmd)
}
t.Logf("Script %q", scr)
go StartRobot3("Robbie", scr, act, log)
pls := <-rep
lastTest := testOneRobot[i-1]
if len(pls) != 1 {
t.Fatalf("Got report on %d robots, want 1.", len(pls))
}
pl := pls[0]
if pl.Name != "Robbie" {
t.Fatalf(`Got report for robot %q, want report for "Robbie".`,
pl.Name)
}
da := pl.Step2Robot
want := lastTest.Step2Robot
if da.Pos != want.Pos {
t.Fatalf("Script %q, Pos = %v, want %v", scr, da.Pos, want.Pos)
}
if da.Dir != want.Dir {
t.Fatalf("Script %q, Dir = %v, want %v", scr, da.Dir, want.Dir)
}
close(log)
if n := <-nMsg; n != lastTest.nMsg {
t.Errorf("%d sim messages logged, want %d.", n, lastTest.nMsg)
}
}
}
func TestNoName(t *testing.T) {
log := make(chan string)
nMsg := make(chan int)
go logMon(log, nMsg, t)
act := make(chan Action3)
rep := make(chan []Step3Robot)
go Room3(
Rect{Pos{1, 1}, Pos{1, 1}},
[]Step3Robot{{"", Step2Robot{N, Pos{1, 1}}}},
act, rep, log)
go StartRobot3("", "", act, log)
<-rep
close(log)
if n := <-nMsg; n != 1 {
t.Fatalf("Got %d messages, want 1.", n)
}
}
func TestSameName(t *testing.T) {
log := make(chan string)
nMsg := make(chan int)
go logMon(log, nMsg, t)
act := make(chan Action3)
rep := make(chan []Step3Robot)
go Room3(
Rect{Pos{1, 1}, Pos{2, 1}},
[]Step3Robot{
{"Daryl", Step2Robot{N, Pos{1, 1}}},
{"Daryl", Step2Robot{N, Pos{2, 1}}},
},
act, rep, log)
go StartRobot3("Daryl", "", act, log)
go StartRobot3("Daryl", "", act, log)
<-rep
close(log)
if n := <-nMsg; n != 1 {
t.Fatalf("Got %d messages, want 1.", n)
}
}
func TestSamePosition(t *testing.T) {
log := make(chan string)
nMsg := make(chan int)
go logMon(log, nMsg, t)
act := make(chan Action3)
rep := make(chan []Step3Robot)
go Room3(
Rect{Pos{1, 1}, Pos{100, 100}},
[]Step3Robot{
{"Matter", Step2Robot{N, Pos{23, 47}}},
{"Antimatter", Step2Robot{N, Pos{23, 47}}},
},
act, rep, log)
go StartRobot3("Matter", "", act, log)
go StartRobot3("Antimatter", "", act, log)
<-rep
close(log)
if n := <-nMsg; n != 1 {
t.Fatalf("Got %d messages, want 1.", n)
}
}
func TestOutsideRoom(t *testing.T) {
log := make(chan string)
nMsg := make(chan int)
go logMon(log, nMsg, t)
act := make(chan Action3)
rep := make(chan []Step3Robot)
go Room3(
Rect{Pos{1, 1}, Pos{1, 1}},
[]Step3Robot{{"Elvis", Step2Robot{N, Pos{2, 3}}}},
act, rep, log)
go StartRobot3("Elvis", "", act, log)
<-rep
close(log)
if n := <-nMsg; n != 1 {
t.Fatalf("Got %d messages, want 1.", n)
}
}
func TestBadCommand(t *testing.T) {
log := make(chan string)
nMsg := make(chan int)
go logMon(log, nMsg, t)
act := make(chan Action3)
rep := make(chan []Step3Robot)
go Room3(
Rect{Pos{0, 0}, Pos{0, 99}},
[]Step3Robot{{"Vgr", Step2Robot{N, Pos{0, 99}}}},
act, rep, log)
go StartRobot3("Vgr", "RET", act, log)
<-rep
close(log)
if n := <-nMsg; n != 1 {
t.Fatalf("Got %d messages, want 1.", n)
}
}
func TestBadRobot(t *testing.T) {
log := make(chan string)
nMsg := make(chan int)
go logMon(log, nMsg, t)
act := make(chan Action3)
rep := make(chan []Step3Robot)
go Room3(
Rect{Pos{0, 0}, Pos{0, 0}},
[]Step3Robot{{"Data", Step2Robot{N, Pos{0, 0}}}},
act, rep, log)
go StartRobot3("Lore", "A", act, log)
<-rep
close(log)
if n := <-nMsg; n != 1 {
t.Fatalf("Got %d messages, want 1.", n)
}
}
func TestThree(t *testing.T) { // no bumping
log := make(chan string)
nMsg := make(chan int)
go logMon(log, nMsg, t)
act := make(chan Action3)
go StartRobot3("clutz", "LAAARALA", act, log)
go StartRobot3("sphero", "RRAAAAALA", act, log)
go StartRobot3("roomba", "LAAARRRALLLL", act, log)
rep := make(chan []Step3Robot)
go Room3(
Rect{Pos{-10, -10}, Pos{15, 10}},
[]Step3Robot{
{"clutz", Step2Robot{N, Pos{0, 0}}},
{"sphero", Step2Robot{E, Pos{2, -7}}},
{"roomba", Step2Robot{S, Pos{8, 4}}},
},
act, rep, log)
pls := <-rep
if len(pls) != 3 {
t.Fatalf("Got report on %d robots, want 3.", len(pls))
}
exp:
for _, exp := range []Step3Robot{
{"clutz", Step2Robot{W, Pos{-4, 1}}},
{"sphero", Step2Robot{S, Pos{-3, -8}}},
{"roomba", Step2Robot{N, Pos{11, 5}}},
} {
for _, pl := range pls {
if pl.Name != exp.Name {
continue
}
if pl.Step2Robot.Pos != exp.Step2Robot.Pos {
t.Fatalf("%s at %v, want %v",
pl.Name, pl.Step2Robot.Pos, exp.Step2Robot.Pos)
}
if pl.Step2Robot.Dir != exp.Step2Robot.Dir {
t.Fatalf("%s facing %v, want %v",
pl.Name, pl.Step2Robot.Dir, exp.Step2Robot.Dir)
}
continue exp
}
t.Fatalf("Missing %s", exp.Name)
}
}
func TestBattle(t *testing.T) {
log := make(chan string)
nMsg := make(chan int)
go logMon(log, nMsg, t)
act := make(chan Action3)
go StartRobot3("Toro", "AAAAAAA", act, log)
go StartRobot3("Phere", "AAAAAAA", act, log)
rep := make(chan []Step3Robot)
go Room3(
Rect{Pos{1, 1}, Pos{9, 9}},
[]Step3Robot{
{"Toro", Step2Robot{E, Pos{1, 5}}},
{"Phere", Step2Robot{W, Pos{9, 5}}},
},
act, rep, log)
<-rep
close(log)
if n := <-nMsg; n != 7 {
t.Fatalf("Got %d messages, want 7.", n)
}
}

View File

@@ -0,0 +1,71 @@
// +build step1 !step2,!step3
package robot
// Tests are separated into 3 steps.
//
// Run all tests with `go test` or run specific tests with the -tags option.
// Examples,
//
// go test # run all tests
// go test -tags step1 # run just step 1 tests.
// go test -tags 'step1 step2' # run step1 and step2 tests
//
// This source file contains step 1 tests only. For other tests see
// robot_simulator_step2_test.go and robot_simulator_step3_test.go.
//
// You are given the source file defs.go which defines a number of things
// the test program requires. It is organized into three sections by step.
//
// To complete step 1 you will define Right, Left, Advance, N, S, E, W,
// and Dir.String. Complete step 1 before moving on to step 2.
import (
"runtime"
"testing"
)
const targetTestVersion = 3
func TestTestVersion(t *testing.T) {
if testVersion != targetTestVersion {
t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
}
}
func TestStep1(t *testing.T) {
want := func(x, y int, dir Dir) {
_, _, line, _ := runtime.Caller(1)
if Step1Robot.X != x || Step1Robot.Y != y {
t.Fatalf("(from line %d) robot at = %d, %d. Want %d, %d.",
line, Step1Robot.X, Step1Robot.Y, x, y)
}
if Step1Robot.Dir != dir {
t.Fatalf("(from line %d) robot facing %v, want %v.",
line, Step1Robot.Dir, dir)
}
}
want(0, 0, N)
Advance()
want(0, 1, N)
Right()
want(0, 1, E)
Advance()
want(1, 1, E)
Left()
want(1, 1, N)
Left()
Left()
Advance()
want(1, 0, S)
Right()
Advance()
want(0, 0, W)
}