Reorganize state
This commit is contained in:
104
state/cell.go
Normal file
104
state/cell.go
Normal file
@ -0,0 +1,104 @@
|
||||
package state
|
||||
|
||||
const (
|
||||
statebit = 1 // the current state of the cell (for field)
|
||||
flagbit = statebit << 1 // whether or not the cell has been flagged as empty (for field)
|
||||
markbit = statebit << 2 // whether or not the cell has been flagged as full (for field)
|
||||
correctbit = statebit << 3 // whether or not the cell is correct (for field, section)
|
||||
completebit = statebit << 4 // whether or not the cell is complete (for section)
|
||||
)
|
||||
|
||||
// Cells {{{
|
||||
|
||||
// cell represents a single entry in the field managing various states.
|
||||
type cell byte
|
||||
|
||||
// state returns whether the cell is alive or dead.
|
||||
func (c cell) state() bool {
|
||||
return (c & statebit) != 0
|
||||
}
|
||||
|
||||
// correct returns whether or not the cell has been guessed correctly, or the section is made up of entirely correct guesses.
|
||||
func (c cell) correct() bool {
|
||||
return (c & correctbit) != 0
|
||||
}
|
||||
|
||||
// complete marks the section as complete (that is, all sections in its row/column are correct.
|
||||
func (c cell) complete() bool {
|
||||
return (c & completebit) != 0
|
||||
}
|
||||
|
||||
// flagged returns whether or not the cell is suspected to be dead.
|
||||
func (c cell) flagged() bool {
|
||||
return (c & flagbit) != 0
|
||||
}
|
||||
|
||||
// marked returns whether or not the cell is suspected to be alive.
|
||||
func (c cell) marked() bool {
|
||||
return (c & markbit) != 0
|
||||
}
|
||||
|
||||
// vivify sets the state of the cell to alive.
|
||||
func (c cell) vivify() cell {
|
||||
c = c | statebit
|
||||
if c.marked() {
|
||||
return c.setCorrect(true)
|
||||
} else {
|
||||
return c.setCorrect(false)
|
||||
}
|
||||
}
|
||||
|
||||
// kill sets the state of the cell to dead.
|
||||
func (c cell) kill() cell {
|
||||
c = c &^ statebit
|
||||
if c.flagged() {
|
||||
return c.setCorrect(true)
|
||||
} else {
|
||||
return c.setCorrect(false)
|
||||
}
|
||||
}
|
||||
|
||||
// setCorrect marks the cell as being guessed correctly, or the section being made up of entirely correct guesses.
|
||||
func (c cell) setCorrect(to bool) cell {
|
||||
if to {
|
||||
return c | correctbit
|
||||
} else {
|
||||
return c &^ correctbit
|
||||
}
|
||||
}
|
||||
|
||||
// setComplete marks the section as complete (that is, all sections in its row/column are correct).
|
||||
func (c cell) setComplete(to bool) cell {
|
||||
if to {
|
||||
return c | completebit
|
||||
} else {
|
||||
return c &^ completebit
|
||||
}
|
||||
}
|
||||
|
||||
// mark marks a bit as suspected alive.
|
||||
func (c cell) mark() cell {
|
||||
c |= markbit
|
||||
c = c &^ flagbit
|
||||
if c.state() {
|
||||
return c.setCorrect(true)
|
||||
} else {
|
||||
return c.setCorrect(false)
|
||||
}
|
||||
}
|
||||
|
||||
// flag marks a bit as suspected dead.
|
||||
func (c cell) flag() cell {
|
||||
c |= flagbit
|
||||
c = c &^ markbit
|
||||
if c.state() {
|
||||
return c.setCorrect(false)
|
||||
} else {
|
||||
return c.setCorrect(true)
|
||||
}
|
||||
}
|
||||
|
||||
// clear clears all bits except for the status.
|
||||
func (c cell) clear() cell {
|
||||
return c &^ (markbit | flagbit | correctbit | completebit)
|
||||
}
|
||||
101
state/cell_test.go
Normal file
101
state/cell_test.go
Normal file
@ -0,0 +1,101 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestCell(t *testing.T) {
|
||||
Convey("Given a cell", t, func() {
|
||||
var c cell
|
||||
|
||||
Convey("When managing state", func() {
|
||||
So(c.state(), ShouldBeFalse)
|
||||
|
||||
Convey("You can vivify and kill the cell", func() {
|
||||
So(c.state(), ShouldBeFalse)
|
||||
c = c.vivify()
|
||||
So(c.state(), ShouldBeTrue)
|
||||
c = c.kill()
|
||||
So(c.state(), ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When managing flags", func() {
|
||||
|
||||
Convey("You can mark a cell as assumed alive", func() {
|
||||
So(c.marked(), ShouldBeFalse)
|
||||
c = c.mark()
|
||||
So(c.marked(), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("You can mark a cell as assumed dead", func() {
|
||||
So(c.flagged(), ShouldBeFalse)
|
||||
c = c.flag()
|
||||
So(c.flagged(), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("You can mark/unmark a cell as correct", func() {
|
||||
So(c.correct(), ShouldBeFalse)
|
||||
c = c.setCorrect(true)
|
||||
So(c.correct(), ShouldBeTrue)
|
||||
c = c.setCorrect(false)
|
||||
So(c.correct(), ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("You can mark/unmark a cell as complete", func() {
|
||||
So(c.complete(), ShouldBeFalse)
|
||||
c = c.setComplete(true)
|
||||
So(c.complete(), ShouldBeTrue)
|
||||
c = c.setComplete(false)
|
||||
So(c.complete(), ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Marking a cell unflags it and vice versa", func() {
|
||||
c = c.mark()
|
||||
So(c.marked(), ShouldBeTrue)
|
||||
So(c.flagged(), ShouldBeFalse)
|
||||
c = c.flag()
|
||||
So(c.marked(), ShouldBeFalse)
|
||||
So(c.flagged(), ShouldBeTrue)
|
||||
c = c.mark()
|
||||
So(c.marked(), ShouldBeTrue)
|
||||
So(c.flagged(), ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Marking/flagging a cell manages its correctness", func() {
|
||||
c = c.vivify()
|
||||
So(c.correct(), ShouldBeFalse)
|
||||
c = c.mark()
|
||||
So(c.correct(), ShouldBeTrue)
|
||||
c = c.kill()
|
||||
So(c.correct(), ShouldBeFalse)
|
||||
|
||||
c = c.vivify()
|
||||
c = c.flag()
|
||||
So(c.correct(), ShouldBeFalse)
|
||||
c = c.kill()
|
||||
So(c.correct(), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("You can clear all flags (but leave the state)", func() {
|
||||
c = c.vivify()
|
||||
c = c.mark()
|
||||
c = c.setCorrect(true)
|
||||
c = c.setComplete(true)
|
||||
So(c.state(), ShouldBeTrue)
|
||||
So(c.marked(), ShouldBeTrue)
|
||||
So(c.flagged(), ShouldBeFalse)
|
||||
So(c.correct(), ShouldBeTrue)
|
||||
So(c.complete(), ShouldBeTrue)
|
||||
c = c.clear()
|
||||
So(c.state(), ShouldBeTrue)
|
||||
So(c.marked(), ShouldBeFalse)
|
||||
So(c.flagged(), ShouldBeFalse)
|
||||
So(c.correct(), ShouldBeFalse)
|
||||
So(c.complete(), ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
49
state/cursor.go
Normal file
49
state/cursor.go
Normal file
@ -0,0 +1,49 @@
|
||||
package state
|
||||
|
||||
func (s *State) CursorCellUp() {
|
||||
if s.cursor.Y >= 1 {
|
||||
s.cursor.Y--
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) CursorCellDown() {
|
||||
if s.cursor.Y < s.size()-1 {
|
||||
s.cursor.Y++
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) CursorCellRight() {
|
||||
if s.cursor.X < s.size()-1 {
|
||||
s.cursor.X++
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) CursorCellLeft() {
|
||||
if s.cursor.X >= 1 {
|
||||
s.cursor.X--
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) CursorSectionUp() {
|
||||
if s.cursor.Y >= s.cellsPerSection {
|
||||
s.cursor.Y -= s.cellsPerSection
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) CursorSectionDown() {
|
||||
if s.cursor.Y < s.size()-s.cellsPerSection {
|
||||
s.cursor.Y += s.cellsPerSection
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) CursorSectionRight() {
|
||||
if s.cursor.X < s.size()-s.cellsPerSection {
|
||||
s.cursor.X += s.cellsPerSection
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) CursorSectionLeft() {
|
||||
if s.cursor.X >= s.cellsPerSection {
|
||||
s.cursor.X -= s.cellsPerSection
|
||||
}
|
||||
}
|
||||
107
state/cursor_test.go
Normal file
107
state/cursor_test.go
Normal file
@ -0,0 +1,107 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestCursor(t *testing.T) {
|
||||
Convey("Given a cursor", t, func() {
|
||||
s := New(4, 4)
|
||||
So(*s.cursor, ShouldResemble, Point{0, 0})
|
||||
|
||||
Convey("When moving cell to cell", func() {
|
||||
|
||||
Convey("You can move down", func() {
|
||||
s.CursorCellDown()
|
||||
So(*s.cursor, ShouldResemble, Point{0, 1})
|
||||
})
|
||||
|
||||
Convey("You can move up", func() {
|
||||
s.CursorCellDown()
|
||||
s.CursorCellUp()
|
||||
So(*s.cursor, ShouldResemble, Point{0, 0})
|
||||
})
|
||||
|
||||
Convey("You can move right", func() {
|
||||
s.CursorCellRight()
|
||||
So(*s.cursor, ShouldResemble, Point{1, 0})
|
||||
})
|
||||
|
||||
Convey("You can move left", func() {
|
||||
s.CursorCellRight()
|
||||
s.CursorCellLeft()
|
||||
So(*s.cursor, ShouldResemble, Point{0, 0})
|
||||
})
|
||||
|
||||
Convey("You can't move up beyond the top", func() {
|
||||
s.CursorCellUp()
|
||||
So(*s.cursor, ShouldResemble, Point{0, 0})
|
||||
})
|
||||
|
||||
Convey("You can't move left beyond the edge", func() {
|
||||
s.CursorCellLeft()
|
||||
So(*s.cursor, ShouldResemble, Point{0, 0})
|
||||
})
|
||||
|
||||
Convey("You can't move down below the bottom", func() {
|
||||
s.cursor = &Point{15, 15}
|
||||
s.CursorCellDown()
|
||||
So(*s.cursor, ShouldResemble, Point{15, 15})
|
||||
})
|
||||
|
||||
Convey("You can't move right beyond the edge", func() {
|
||||
s.cursor = &Point{15, 15}
|
||||
s.CursorCellRight()
|
||||
So(*s.cursor, ShouldResemble, Point{15, 15})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When moving section to section", func() {
|
||||
Convey("You can move down", func() {
|
||||
s.CursorSectionDown()
|
||||
So(*s.cursor, ShouldResemble, Point{0, 4})
|
||||
})
|
||||
|
||||
Convey("You can move up", func() {
|
||||
s.CursorSectionDown()
|
||||
s.CursorSectionUp()
|
||||
So(*s.cursor, ShouldResemble, Point{0, 0})
|
||||
})
|
||||
|
||||
Convey("You can move right", func() {
|
||||
s.CursorSectionRight()
|
||||
So(*s.cursor, ShouldResemble, Point{4, 0})
|
||||
})
|
||||
|
||||
Convey("You can move left", func() {
|
||||
s.CursorSectionRight()
|
||||
s.CursorSectionLeft()
|
||||
So(*s.cursor, ShouldResemble, Point{0, 0})
|
||||
})
|
||||
|
||||
Convey("You can't move up beyond the top", func() {
|
||||
s.CursorCellUp()
|
||||
So(*s.cursor, ShouldResemble, Point{0, 0})
|
||||
})
|
||||
|
||||
Convey("You can't move left beyond the edge", func() {
|
||||
s.CursorCellLeft()
|
||||
So(*s.cursor, ShouldResemble, Point{0, 0})
|
||||
})
|
||||
|
||||
Convey("You can't move down below the bottom", func() {
|
||||
s.cursor = &Point{15, 15}
|
||||
s.CursorSectionDown()
|
||||
So(*s.cursor, ShouldResemble, Point{15, 15})
|
||||
})
|
||||
|
||||
Convey("You can't move right beyond the edge", func() {
|
||||
s.cursor = &Point{15, 15}
|
||||
s.CursorSectionRight()
|
||||
So(*s.cursor, ShouldResemble, Point{15, 15})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
99
state/field.go
Normal file
99
state/field.go
Normal file
@ -0,0 +1,99 @@
|
||||
package state
|
||||
|
||||
import "math"
|
||||
|
||||
// field represents a square field of cells.
|
||||
type field struct {
|
||||
cells []cell
|
||||
size int
|
||||
}
|
||||
|
||||
// newField returns a field of cells, all unset.
|
||||
func newField(size int) *field {
|
||||
return &field{
|
||||
cells: make([]cell, size*size),
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
// fieldFromBytes returns a field of cells given a bytearray; it assumes that the bytearray is a square.
|
||||
func fieldFromBytes(repr []byte) *field {
|
||||
f := &field{
|
||||
cells: make([]cell, len(repr)),
|
||||
size: int(math.Sqrt(float64(len(repr)))),
|
||||
}
|
||||
for i := range repr {
|
||||
f.cells[i] = cell(repr[i])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// i is a utility function for translating a point to an array index
|
||||
func (f *field) i(p Point) int {
|
||||
return p.Y*f.size + p.X
|
||||
}
|
||||
|
||||
// String returns a string representation of the field.
|
||||
func (f *field) String() string {
|
||||
return string(f.cells)
|
||||
}
|
||||
|
||||
// state returns whether or not the cell is alive.
|
||||
func (f *field) state(p Point) bool {
|
||||
return f.cells[f.i(p)].state()
|
||||
}
|
||||
|
||||
// correct returns whether or not the guess for the cell's state is correct, or whether or not the section is made up of all complete guesses.
|
||||
func (f *field) correct(p Point) bool {
|
||||
return f.cells[f.i(p)].correct()
|
||||
}
|
||||
|
||||
// complete returns whether or not the current section is complete (that is, all sections in its row/column are correct).
|
||||
func (f *field) complete(p Point) bool {
|
||||
return f.cells[f.i(p)].complete()
|
||||
}
|
||||
|
||||
// flagged returns whether or not the cell is suspected of being dead.
|
||||
func (f *field) flagged(p Point) bool {
|
||||
return f.cells[f.i(p)].flagged()
|
||||
}
|
||||
|
||||
// marked returns whether or not the cell is suspected of being alive.
|
||||
func (f *field) marked(p Point) bool {
|
||||
return f.cells[f.i(p)].marked()
|
||||
}
|
||||
|
||||
// vivify sets the cell to alive.
|
||||
func (f *field) vivify(p Point) {
|
||||
f.cells[f.i(p)] = f.cells[f.i(p)].vivify()
|
||||
}
|
||||
|
||||
// kill sets the cell to dead.
|
||||
func (f *field) kill(p Point) {
|
||||
f.cells[f.i(p)] = f.cells[f.i(p)].kill()
|
||||
}
|
||||
|
||||
// setCorrect sets the whether or not the guess at the cell's state is correct
|
||||
func (f *field) setCorrect(p Point, to bool) {
|
||||
f.cells[f.i(p)] = f.cells[f.i(p)].setCorrect(to)
|
||||
}
|
||||
|
||||
// setComplete sets whether or not the section is complete (that is, all of its rows/columns are correct).
|
||||
func (f *field) setComplete(p Point, to bool) {
|
||||
f.cells[f.i(p)] = f.cells[f.i(p)].setComplete(to)
|
||||
}
|
||||
|
||||
// mark marks the cell as suspected alive.
|
||||
func (f *field) mark(p Point) {
|
||||
f.cells[f.i(p)] = f.cells[f.i(p)].mark()
|
||||
}
|
||||
|
||||
// flag marks the cell as suspected dead.
|
||||
func (f *field) flag(p Point) {
|
||||
f.cells[f.i(p)] = f.cells[f.i(p)].flag()
|
||||
}
|
||||
|
||||
// clear clears all but the cell's state.
|
||||
func (f *field) clear(p Point) {
|
||||
f.cells[f.i(p)] = f.cells[f.i(p)].clear()
|
||||
}
|
||||
54
state/field_test.go
Normal file
54
state/field_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestField(t *testing.T) {
|
||||
Convey("Given a field of cells", t, func() {
|
||||
p := Point{0, 0}
|
||||
f := newField(2)
|
||||
So(f.size, ShouldEqual, 2)
|
||||
So(len(f.cells), ShouldEqual, 4)
|
||||
|
||||
Convey("You can load from a bytearray", func() {
|
||||
f := fieldFromBytes([]byte("\x00\x00\x00\x00"))
|
||||
So(f.size, ShouldEqual, 2)
|
||||
So(len(f.cells), ShouldEqual, 4)
|
||||
})
|
||||
|
||||
Convey("You can get an index from a point", func() {
|
||||
So(f.i(Point{1, 1}), ShouldEqual, 3)
|
||||
})
|
||||
|
||||
Convey("You can get a string representation of the field", func() {
|
||||
So(f.String(), ShouldEqual, "\x00\x00\x00\x00")
|
||||
})
|
||||
|
||||
Convey("You can manage the state of a cell in the field", func() {
|
||||
So(f.state(p), ShouldBeFalse)
|
||||
f.vivify(p)
|
||||
So(f.state(p), ShouldBeTrue)
|
||||
f.kill(p)
|
||||
So(f.state(p), ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("You can manage the flags of a cell", func() {
|
||||
f.setCorrect(p, true)
|
||||
So(f.correct(p), ShouldBeTrue)
|
||||
f.setComplete(p, true)
|
||||
So(f.complete(p), ShouldBeTrue)
|
||||
f.mark(p)
|
||||
So(f.marked(p), ShouldBeTrue)
|
||||
f.flag(p)
|
||||
So(f.flagged(p), ShouldBeTrue)
|
||||
f.clear(p)
|
||||
So(f.correct(p), ShouldBeFalse)
|
||||
So(f.complete(p), ShouldBeFalse)
|
||||
So(f.marked(p), ShouldBeFalse)
|
||||
So(f.flagged(p), ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
}
|
||||
174
state/state.go
Normal file
174
state/state.go
Normal file
@ -0,0 +1,174 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
type Point struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
type State struct {
|
||||
cursor *Point
|
||||
|
||||
sectionSize, cellsPerSection int
|
||||
cells, sections *field
|
||||
}
|
||||
|
||||
func New(sectionSize, cellsPerSection int) *State {
|
||||
s := &State{
|
||||
sectionSize: sectionSize,
|
||||
cellsPerSection: cellsPerSection,
|
||||
cells: newField(sectionSize * cellsPerSection),
|
||||
sections: newField(sectionSize),
|
||||
cursor: &Point{0, 0},
|
||||
}
|
||||
for x := 0; x < sectionSize; x++ {
|
||||
for y := 0; y < sectionSize; y++ {
|
||||
s.initSection(Point{x, y})
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// size is a utility method to keep cursor code clean.
|
||||
func (s *State) size() int {
|
||||
return s.sectionSize * s.cellsPerSection
|
||||
}
|
||||
|
||||
func (s *State) String() string {
|
||||
var buf bytes.Buffer
|
||||
for x := 0; x < s.sectionSize*s.cellsPerSection; x++ {
|
||||
for y := 0; y < s.sectionSize*s.cellsPerSection; y++ {
|
||||
p := Point{x, y}
|
||||
if s.cells.correct(p) {
|
||||
if s.cells.state(p) {
|
||||
buf.WriteString("O")
|
||||
} else {
|
||||
buf.WriteString("X")
|
||||
}
|
||||
} else {
|
||||
if s.cells.marked(p) {
|
||||
buf.WriteString("o")
|
||||
} else if s.cells.flagged(p) {
|
||||
buf.WriteString("x")
|
||||
} else {
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (s *State) Mark() []bool {
|
||||
return s.mark(*s.cursor)
|
||||
}
|
||||
|
||||
func (s *State) Flag() []bool {
|
||||
return s.flag(*s.cursor)
|
||||
}
|
||||
|
||||
func (s *State) Clear() {
|
||||
s.clear(*s.cursor)
|
||||
}
|
||||
|
||||
func (s *State) view(cursor []int) {
|
||||
}
|
||||
|
||||
func (s *State) initSection(p Point) {
|
||||
s.sections.clear(p)
|
||||
startX := p.X * s.cellsPerSection
|
||||
startY := p.Y * s.cellsPerSection
|
||||
for x := 0; x < s.cellsPerSection; x++ {
|
||||
for y := 0; y < s.cellsPerSection; y++ {
|
||||
s.cells.clear(Point{x + startX, y + startY})
|
||||
if rand.Int()%2 == 0 {
|
||||
s.cells.kill(Point{x + startX, y + startY})
|
||||
} else {
|
||||
s.cells.vivify(Point{x + startX, y + startY})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) mark(p Point) []bool {
|
||||
s.cells.mark(p)
|
||||
return s.update(p)
|
||||
}
|
||||
|
||||
func (s *State) flag(p Point) []bool {
|
||||
s.cells.flag(p)
|
||||
return s.update(p)
|
||||
}
|
||||
|
||||
func (s *State) clear(p Point) {
|
||||
s.cells.clear(p)
|
||||
s.update(p)
|
||||
}
|
||||
|
||||
func (s *State) sectionCorrect(p Point) bool {
|
||||
sectionCorrect := true
|
||||
for x := 0; x < s.cellsPerSection; x++ {
|
||||
for y := 0; y < s.cellsPerSection; y++ {
|
||||
sectionCorrect = sectionCorrect && s.cells.correct(Point{p.X + x, p.Y + y})
|
||||
}
|
||||
}
|
||||
return sectionCorrect
|
||||
}
|
||||
|
||||
func (s *State) updateCompletedSections() {
|
||||
for x := 0; x < s.sectionSize; x++ {
|
||||
for y := 0; y < s.sectionSize; y++ {
|
||||
complete := true
|
||||
for i := 0; i < s.sectionSize; i++ {
|
||||
complete = complete &&
|
||||
s.sections.correct(Point{x, i}) &&
|
||||
s.sections.correct(Point{i, y})
|
||||
}
|
||||
s.sections.setComplete(Point{x, y}, complete)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) clearValidCompletedSections() []bool {
|
||||
cleared := make([]bool, s.sections.size*s.sections.size)
|
||||
for x := 0; x < s.sectionSize; x++ {
|
||||
for y := 0; y < s.sectionSize; y++ {
|
||||
clearable := s.sections.complete(Point{x, y})
|
||||
s.sections.complete(Point{x + 1%s.sectionSize, y})
|
||||
s.sections.complete(Point{x, y + 1%s.sectionSize})
|
||||
s.sections.complete(Point{x + 1%s.sectionSize, y + 1%s.sectionSize})
|
||||
if clearable {
|
||||
cleared[y*s.sectionSize+x] = true
|
||||
cleared[y*s.sectionSize+(x+1%s.sectionSize)] = true
|
||||
cleared[(y*s.sectionSize+s.sectionSize%len(cleared))+x] = true
|
||||
cleared[(y*s.sectionSize+s.sectionSize%len(cleared))+(x+1%s.sectionSize)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, v := range cleared {
|
||||
if v {
|
||||
s.initSection(Point{i % s.sectionSize, i / s.sectionSize})
|
||||
}
|
||||
}
|
||||
return cleared
|
||||
}
|
||||
|
||||
func (s *State) update(p Point) []bool {
|
||||
sectionPoint := Point{p.X / s.cellsPerSection, p.Y / s.cellsPerSection}
|
||||
|
||||
if s.cells.correct(p) && s.sectionCorrect(sectionPoint) {
|
||||
s.sections.setCorrect(sectionPoint, true)
|
||||
s.updateCompletedSections()
|
||||
return s.clearValidCompletedSections()
|
||||
}
|
||||
s.sections.setCorrect(sectionPoint, false)
|
||||
for i := 0; i < s.sectionSize; i++ {
|
||||
s.sections.setComplete(Point{sectionPoint.X, i}, false)
|
||||
s.sections.setComplete(Point{i, sectionPoint.Y}, false)
|
||||
}
|
||||
return make([]bool, s.sections.size*s.sections.size)
|
||||
}
|
||||
43
state/state_test.go
Normal file
43
state/state_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestState(t *testing.T) {
|
||||
Convey("Given a game state", t, func() {
|
||||
s := New(2, 2)
|
||||
|
||||
Convey("You can get the size", func() {
|
||||
So(s.size(), ShouldEqual, 4)
|
||||
})
|
||||
|
||||
Convey("You can render a simple string of the board", func() {
|
||||
s.cells.vivify(Point{0, 0})
|
||||
s.cells.kill(Point{0, 1})
|
||||
s.cells.vivify(Point{1, 0})
|
||||
s.cells.kill(Point{1, 1})
|
||||
|
||||
Convey("It shows correct guesses", func() {
|
||||
s.Mark()
|
||||
s.flag(Point{0, 1})
|
||||
So(s.String(), ShouldEqual, "OX \n \n \n \n")
|
||||
})
|
||||
|
||||
Convey("It shows incorrect guesses", func() {
|
||||
s.Flag()
|
||||
s.mark(Point{0, 1})
|
||||
So(s.String(), ShouldEqual, "xo \n \n \n \n")
|
||||
})
|
||||
|
||||
Convey("It shows cleared guesses as empty", func() {
|
||||
s.Flag()
|
||||
s.mark(Point{0, 1})
|
||||
s.Clear()
|
||||
So(s.String(), ShouldEqual, " o \n \n \n \n")
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user