Finished go/minesweeper

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

48
go/minesweeper/README.md Normal file
View File

@ -0,0 +1,48 @@
# Minesweeper
Add the numbers to a minesweeper board.
Minesweeper is a popular game where the user has to find the mines using
numeric hints that indicate how many mines are directly adjacent
(horizontally, vertically, diagonally) to a square.
In this exercise you have to create some code that counts the number of
mines adjacent to a square and transforms boards like this (where `*`
indicates a mine):
+-----+
| * * |
| * |
| * |
| |
+-----+
into this:
+-----+
|1*3*1|
|13*31|
| 2*2 |
| 111 |
+-----+
## 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).
## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

112
go/minesweeper/board.go Normal file
View File

@ -0,0 +1,112 @@
package minesweeper
import (
"bytes"
"errors"
"strconv"
)
const testVersion = 1
type Board [][]byte
// Count fills in the number of mines in adjacent squares on the board
func (b Board) Count() error {
if !b.isValid() {
return errors.New("Invalid Board")
}
for i := range b {
for j := range b[i] {
switch b[i][j] {
case '+', '-', '|', '*':
continue
case ' ':
num := b.countAdjacentMines(j, i)
if num != 0 {
b[i][j] = []byte(strconv.Itoa(num))[0]
}
default:
return errors.New("Invalid Board")
}
}
}
return nil
}
// countAdjacentMines returns the number of mines adjacent to x,y
// or -1 if x,y is invalid
func (b Board) countAdjacentMines(x, y int) int {
var numMines int
if b.isMine(x-1, y-1) {
numMines++
}
if b.isMine(x, y-1) {
numMines++
}
if b.isMine(x+1, y-1) {
numMines++
}
if b.isMine(x-1, y) {
numMines++
}
if b.isMine(x, y) {
numMines++
}
if b.isMine(x+1, y) {
numMines++
}
if b.isMine(x-1, y+1) {
numMines++
}
if b.isMine(x, y+1) {
numMines++
}
if b.isMine(x+1, y+1) {
numMines++
}
return numMines
}
func (b Board) isMine(x, y int) bool {
if x < 0 || y < 0 || x > len(b[0])-1 || y > len(b)-1 {
return false
}
return b[y][x] == '*'
}
func (b Board) isValid() bool {
if len(b) <= 0 {
// no size
return false
}
for i := range b {
if len(b[i]) != len(b[0]) {
// All rows must be same length
return false
}
for j := range b[i] {
// Check top & bottom row for border
if i == 0 || i == len(b)-1 {
// Check corners for border
if j == 0 || j == len(b[i])-1 {
if b[i][j] != '+' {
return false
}
} else if b[i][j] != '-' {
return false
}
} else {
if j == 0 || j == len(b[i])-1 {
if b[i][j] != '|' {
return false
}
}
}
}
}
return true
}
func (b Board) String() string {
return "\n" + string(bytes.Join(b, []byte{'\n'}))
}

View File

@ -0,0 +1,198 @@
package minesweeper
import (
"bytes"
"testing"
)
const targetTestVersion = 1
var validBoards = []string{
// zero size board
`
++
++`,
// empty board
`
+---+
| |
| |
| |
+---+`,
// board full of mines
`
+---+
|***|
|***|
|***|
+---+`,
// surrounded
`
+---+
|***|
|*8*|
|***|
+---+`,
// horizontal line
`
+-----+
|1*2*1|
+-----+`,
// vertical line
`
+-+
|1|
|*|
|2|
|*|
|1|
+-+`,
// cross
`
+-----+
| 2*2 |
|25*52|
|*****|
|25*52|
| 2*2 |
+-----+`,
// Large 6x6 board with 8 bombs
`
+------+
|1*22*1|
|12*322|
| 123*2|
|112*4*|
|1*22*2|
|111111|
+------+`,
// Large 5x5 board with 7 bombs
`
+-----+
|1*2*1|
|11322|
| 12*2|
|12*4*|
|1*3*2|
+-----+`,
// Small 1x5 board with 2 bombs
`
+-+
|*|
|2|
|*|
|1|
| |
+-+`,
// 1x1 square with 1 bomb
`
+-+
|*|
+-+`,
// 2x2 square with 4 bombs
`
+--+
|**|
|**|
+--+`,
// 5x5 square with 2 bombs
`
+-----+
| 111|
| 1*1|
| 111|
|111 |
|1*1 |
+-----+`,
}
var badBoards = []string{
// Unaligned
`
+-+
| |
|* |
| |
+-+`,
// incomplete border
`
+-----+
* * |
+-- --+`,
// Unknown characters
`
+-----+
|X * |
+-----+`,
}
// constructor. take board as a string, return Board with digits cleared.
func clear(s string) Board {
b := []byte(s)
for i, c := range b {
if c >= '0' && c <= '9' {
b[i] = ' '
}
}
return bytes.Split(b, []byte{'\n'})[1:]
}
func TestTestVersion(t *testing.T) {
if testVersion != targetTestVersion {
t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
}
}
func TestValid(t *testing.T) {
for _, ref := range validBoards {
b := clear(ref)
t.Log(b)
if err := b.Count(); err != nil {
var _ error = err
t.Fatalf("Count() returned %q, want nil.", err)
}
if res := b.String(); res != ref {
t.Fatalf("Count() result: %s,\n want:%s.", res, ref)
}
}
}
func TestBad(t *testing.T) {
for _, ref := range badBoards {
b := clear(ref)
t.Log(b)
if b.Count() == nil {
t.Fatal("Count() returned nil, want error.")
}
}
}
func BenchmarkCount(b *testing.B) {
m := clear(`
+------+
|1*22*1|
|12*322|
| 123*2|
|112*4*|
|1*22*2|
|111111|
+------+`)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.Count()
}
}

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)
}