Reorganize state

This commit is contained in:
Madison Rye Progress
2025-08-26 22:26:08 -07:00
parent b87631cb77
commit 29bd181e4f
13 changed files with 747 additions and 191 deletions

View File

@ -1,49 +0,0 @@
package model
func (m model) cursorCellUp() {
if m.cursor.y >= 1 {
m.cursor.y--
}
}
func (m model) cursorCellDown() {
if m.cursor.y < m.fieldSize-1 {
m.cursor.y++
}
}
func (m model) cursorCellRight() {
if m.cursor.x < m.fieldSize-1 {
m.cursor.x++
}
}
func (m model) cursorCellLeft() {
if m.cursor.x >= 1 {
m.cursor.x--
}
}
func (m model) cursorSectionUp() {
if m.cursor.y >= m.cellsPerSection {
m.cursor.y -= m.cellsPerSection
}
}
func (m model) cursorSectionDown() {
if m.cursor.y < m.fieldSize-m.cellsPerSection {
m.cursor.y += m.cellsPerSection
}
}
func (m model) cursorSectionRight() {
if m.cursor.x < m.fieldSize-m.cellsPerSection {
m.cursor.x += m.cellsPerSection
}
}
func (m model) cursorSectionLeft() {
if m.cursor.x >= m.cellsPerSection {
m.cursor.x -= m.cellsPerSection
}
}

View File

@ -1,107 +0,0 @@
package model
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestCursor(t *testing.T) {
Convey("Given a cursor", t, func() {
m := New(4, 4)
So(*m.cursor, ShouldResemble, point{0, 0})
Convey("When moving cell to cell", func() {
Convey("You can move down", func() {
m.cursorCellDown()
So(*m.cursor, ShouldResemble, point{0, 1})
})
Convey("You can move up", func() {
m.cursorCellDown()
m.cursorCellUp()
So(*m.cursor, ShouldResemble, point{0, 0})
})
Convey("You can move right", func() {
m.cursorCellRight()
So(*m.cursor, ShouldResemble, point{1, 0})
})
Convey("You can move left", func() {
m.cursorCellRight()
m.cursorCellLeft()
So(*m.cursor, ShouldResemble, point{0, 0})
})
Convey("You can't move up beyond the top", func() {
m.cursorCellUp()
So(*m.cursor, ShouldResemble, point{0, 0})
})
Convey("You can't move left beyond the edge", func() {
m.cursorCellLeft()
So(*m.cursor, ShouldResemble, point{0, 0})
})
Convey("You can't move down below the bottom", func() {
m.cursor = &point{15, 15}
m.cursorCellDown()
So(*m.cursor, ShouldResemble, point{15, 15})
})
Convey("You can't move right beyond the edge", func() {
m.cursor = &point{15, 15}
m.cursorCellRight()
So(*m.cursor, ShouldResemble, point{15, 15})
})
})
Convey("When moving section to section", func() {
Convey("You can move down", func() {
m.cursorSectionDown()
So(*m.cursor, ShouldResemble, point{0, 4})
})
Convey("You can move up", func() {
m.cursorSectionDown()
m.cursorSectionUp()
So(*m.cursor, ShouldResemble, point{0, 0})
})
Convey("You can move right", func() {
m.cursorSectionRight()
So(*m.cursor, ShouldResemble, point{4, 0})
})
Convey("You can move left", func() {
m.cursorSectionRight()
m.cursorSectionLeft()
So(*m.cursor, ShouldResemble, point{0, 0})
})
Convey("You can't move up beyond the top", func() {
m.cursorCellUp()
So(*m.cursor, ShouldResemble, point{0, 0})
})
Convey("You can't move left beyond the edge", func() {
m.cursorCellLeft()
So(*m.cursor, ShouldResemble, point{0, 0})
})
Convey("You can't move down below the bottom", func() {
m.cursor = &point{15, 15}
m.cursorSectionDown()
So(*m.cursor, ShouldResemble, point{15, 15})
})
Convey("You can't move right beyond the edge", func() {
m.cursor = &point{15, 15}
m.cursorSectionRight()
So(*m.cursor, ShouldResemble, point{15, 15})
})
})
})
}

View File

@ -1,17 +1,15 @@
package model
type point struct {
x, y int
}
import "git.makyo.dev/makyo/gogogogogram/state"
type model struct {
fieldSize, sectionSize, cellsPerSection int
state *state
state *state.State
clears, score, factor, track int
cursor *point
cursor *state.Point
columnStates, rowStates [][]int
columnsCorrect, rowsCorrect []bool
@ -24,9 +22,9 @@ func New(sectionSize, cellsPerSection int) model {
fieldSize: sectionSize * cellsPerSection,
sectionSize: sectionSize,
cellsPerSection: cellsPerSection,
cursor: &point{0, 0},
cursor: &state.Point{0, 0},
}
m.state = newState(sectionSize, cellsPerSection)
m.state = state.New(sectionSize, cellsPerSection)
return m
}

View File

@ -19,39 +19,39 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Movement by cell
case "up", "w":
m.cursorCellUp()
m.state.CursorCellUp()
case "down", "s":
m.cursorCellDown()
m.state.CursorCellDown()
case "right", "d":
m.cursorCellRight()
m.state.CursorCellRight()
case "left", "a":
m.cursorCellLeft()
m.state.CursorCellLeft()
// Movement by section
case "ctrl+up", "ctrl+w", "shift+up", "shift+w":
m.cursorSectionUp()
m.state.CursorSectionUp()
case "ctrl+down", "ctrl+s", "shift+down", "shift+s":
m.cursorSectionDown()
m.state.CursorSectionDown()
case "ctrl+right", "ctrl+d", "shift+right", "shift+d":
m.cursorSectionRight()
m.state.CursorSectionRight()
case "ctrl+left", "ctrl+a", "shift+left", "shift+a":
m.cursorSectionRight()
m.state.CursorSectionRight()
// Marking/flagging
case " ", "enter":
m.state.mark(*m.cursor)
m.state.Mark()
case "x":
m.state.flag(*m.cursor)
m.state.Flag()
case "delete", "backspace":
m.state.clear(*m.cursor)
m.state.Clear()
}
}

View File

@ -1,17 +0,0 @@
package model
func (m model) update() model {
// Update correctness/sections
// Check for complete sections, which are those where the row and column are both correct
// Check for clears, which are at least 2x2
// Clear and bump scores
// Check for blackout
// Update row/column states/correctness
return m
}

104
state/cell.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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")
})
})
})
}