Maintain history; use state
This commit is contained in:
3
main.go
3
main.go
@ -4,12 +4,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.makyo.dev/makyo/gogogogogram/model"
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
p := tea.NewProgram(model.New(4, 4))
|
p := tea.NewProgram(newModel(4, 4))
|
||||||
if _, err := p.Run(); err != nil {
|
if _, err := p.Run(); err != nil {
|
||||||
fmt.Printf("Ah drat — %v\n", err)
|
fmt.Printf("Ah drat — %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package model
|
package main
|
||||||
|
|
||||||
import "git.makyo.dev/makyo/gogogogogram/state"
|
import "git.makyo.dev/makyo/gogogogogram/state"
|
||||||
|
|
||||||
@ -9,22 +9,19 @@ type model struct {
|
|||||||
|
|
||||||
clears, score, factor, track int
|
clears, score, factor, track int
|
||||||
|
|
||||||
cursor *state.Point
|
|
||||||
|
|
||||||
columnStates, rowStates [][]int
|
columnStates, rowStates [][]int
|
||||||
columnsCorrect, rowsCorrect []bool
|
columnsCorrect, rowsCorrect []bool
|
||||||
|
|
||||||
history string
|
history string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(sectionSize, cellsPerSection int) model {
|
func newModel(sectionSize, cellsPerSection int) model {
|
||||||
m := model{
|
m := model{
|
||||||
fieldSize: sectionSize * cellsPerSection,
|
fieldSize: sectionSize * cellsPerSection,
|
||||||
sectionSize: sectionSize,
|
sectionSize: sectionSize,
|
||||||
cellsPerSection: cellsPerSection,
|
cellsPerSection: cellsPerSection,
|
||||||
cursor: &state.Point{0, 0},
|
state: state.New(sectionSize, cellsPerSection),
|
||||||
}
|
}
|
||||||
m.state = state.New(sectionSize, cellsPerSection)
|
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@ -3,47 +3,55 @@ package state
|
|||||||
func (s *State) CursorCellUp() {
|
func (s *State) CursorCellUp() {
|
||||||
if s.cursor.Y >= 1 {
|
if s.cursor.Y >= 1 {
|
||||||
s.cursor.Y--
|
s.cursor.Y--
|
||||||
|
s.history += "u"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) CursorCellDown() {
|
func (s *State) CursorCellDown() {
|
||||||
if s.cursor.Y < s.size()-1 {
|
if s.cursor.Y < s.size()-1 {
|
||||||
s.cursor.Y++
|
s.cursor.Y++
|
||||||
|
s.history += "d"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) CursorCellRight() {
|
func (s *State) CursorCellRight() {
|
||||||
if s.cursor.X < s.size()-1 {
|
if s.cursor.X < s.size()-1 {
|
||||||
s.cursor.X++
|
s.cursor.X++
|
||||||
|
s.history += "r"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) CursorCellLeft() {
|
func (s *State) CursorCellLeft() {
|
||||||
if s.cursor.X >= 1 {
|
if s.cursor.X >= 1 {
|
||||||
s.cursor.X--
|
s.cursor.X--
|
||||||
|
s.history += "l"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) CursorSectionUp() {
|
func (s *State) CursorSectionUp() {
|
||||||
if s.cursor.Y >= s.cellsPerSection {
|
if s.cursor.Y >= s.cellsPerSection {
|
||||||
s.cursor.Y -= s.cellsPerSection
|
s.cursor.Y -= s.cellsPerSection
|
||||||
|
s.history += "U"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) CursorSectionDown() {
|
func (s *State) CursorSectionDown() {
|
||||||
if s.cursor.Y < s.size()-s.cellsPerSection {
|
if s.cursor.Y < s.size()-s.cellsPerSection {
|
||||||
s.cursor.Y += s.cellsPerSection
|
s.cursor.Y += s.cellsPerSection
|
||||||
|
s.history += "D"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) CursorSectionRight() {
|
func (s *State) CursorSectionRight() {
|
||||||
if s.cursor.X < s.size()-s.cellsPerSection {
|
if s.cursor.X < s.size()-s.cellsPerSection {
|
||||||
s.cursor.X += s.cellsPerSection
|
s.cursor.X += s.cellsPerSection
|
||||||
|
s.history += "R"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) CursorSectionLeft() {
|
func (s *State) CursorSectionLeft() {
|
||||||
if s.cursor.X >= s.cellsPerSection {
|
if s.cursor.X >= s.cellsPerSection {
|
||||||
s.cursor.X -= s.cellsPerSection
|
s.cursor.X -= s.cellsPerSection
|
||||||
|
s.history += "L"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,6 +38,11 @@ func (f *field) String() string {
|
|||||||
return string(f.cells)
|
return string(f.cells)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cell returns the raw cell (a byte) at the given point.
|
||||||
|
func (f *field) cell(p Point) cell {
|
||||||
|
return f.cells[f.i(p)]
|
||||||
|
}
|
||||||
|
|
||||||
// state returns whether or not the cell is alive.
|
// state returns whether or not the cell is alive.
|
||||||
func (f *field) state(p Point) bool {
|
func (f *field) state(p Point) bool {
|
||||||
return f.cells[f.i(p)].state()
|
return f.cells[f.i(p)].state()
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package state
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -9,9 +10,18 @@ type Point struct {
|
|||||||
X, Y int
|
X, Y int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Point) String() string {
|
||||||
|
return fmt.Sprintf("(%d,%d)", p.X, p.Y)
|
||||||
|
}
|
||||||
|
|
||||||
type State struct {
|
type State struct {
|
||||||
cursor *Point
|
cursor *Point
|
||||||
|
|
||||||
|
clears, score, factor int
|
||||||
|
blackout []bool
|
||||||
|
|
||||||
|
history string
|
||||||
|
|
||||||
sectionSize, cellsPerSection int
|
sectionSize, cellsPerSection int
|
||||||
cells, sections *field
|
cells, sections *field
|
||||||
}
|
}
|
||||||
@ -24,11 +34,13 @@ func New(sectionSize, cellsPerSection int) *State {
|
|||||||
sections: newField(sectionSize),
|
sections: newField(sectionSize),
|
||||||
cursor: &Point{0, 0},
|
cursor: &Point{0, 0},
|
||||||
}
|
}
|
||||||
|
s.history = fmt.Sprintf("gogogogogram %d %d:\n", sectionSize, cellsPerSection)
|
||||||
for x := 0; x < sectionSize; x++ {
|
for x := 0; x < sectionSize; x++ {
|
||||||
for y := 0; y < sectionSize; y++ {
|
for y := 0; y < sectionSize; y++ {
|
||||||
s.initSection(Point{x, y})
|
s.initSection(Point{x, y})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO reveal 1 row per section, 1 col per sections/2
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,6 +49,11 @@ func (s *State) size() int {
|
|||||||
return s.sectionSize * s.cellsPerSection
|
return s.sectionSize * s.cellsPerSection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// History returns the gameplay history for replays
|
||||||
|
func (s *State) History() string {
|
||||||
|
return s.history
|
||||||
|
}
|
||||||
|
|
||||||
func (s *State) String() string {
|
func (s *State) String() string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
for x := 0; x < s.sectionSize*s.cellsPerSection; x++ {
|
for x := 0; x < s.sectionSize*s.cellsPerSection; x++ {
|
||||||
@ -68,14 +85,17 @@ func (s *State) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) Mark() []bool {
|
func (s *State) Mark() []bool {
|
||||||
|
s.history += "m"
|
||||||
return s.mark(*s.cursor)
|
return s.mark(*s.cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) Flag() []bool {
|
func (s *State) Flag() []bool {
|
||||||
|
s.history += "f"
|
||||||
return s.flag(*s.cursor)
|
return s.flag(*s.cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) Clear() {
|
func (s *State) Clear() {
|
||||||
|
s.history += "c"
|
||||||
s.clear(*s.cursor, true)
|
s.clear(*s.cursor, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,17 +103,20 @@ func (s *State) view(cursor []int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) initSection(p Point) {
|
func (s *State) initSection(p Point) {
|
||||||
|
s.history += fmt.Sprintf("i%s", p)
|
||||||
s.sections.clear(p, false)
|
s.sections.clear(p, false)
|
||||||
startX := p.X * s.cellsPerSection
|
startX := p.X * s.cellsPerSection
|
||||||
startY := p.Y * s.cellsPerSection
|
startY := p.Y * s.cellsPerSection
|
||||||
for x := 0; x < s.cellsPerSection; x++ {
|
for x := 0; x < s.cellsPerSection; x++ {
|
||||||
for y := 0; y < s.cellsPerSection; y++ {
|
for y := 0; y < s.cellsPerSection; y++ {
|
||||||
s.cells.clear(Point{x + startX, y + startY}, true)
|
currPoint := Point{x + startX, y + startY}
|
||||||
|
s.cells.clear(currPoint, true)
|
||||||
if rand.Int()%2 == 0 {
|
if rand.Int()%2 == 0 {
|
||||||
s.cells.kill(Point{x + startX, y + startY})
|
s.cells.kill(currPoint)
|
||||||
} else {
|
} else {
|
||||||
s.cells.vivify(Point{x + startX, y + startY})
|
s.cells.vivify(currPoint)
|
||||||
}
|
}
|
||||||
|
s.history += fmt.Sprintf("%c", byte(s.cells.cell(currPoint)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.update(p)
|
s.update(p)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
@ -48,6 +49,24 @@ func TestState(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("You can maintain a history of all actions", func() {
|
||||||
|
s.Flag()
|
||||||
|
s.Mark()
|
||||||
|
s.Clear()
|
||||||
|
s.CursorSectionRight()
|
||||||
|
s.CursorSectionLeft()
|
||||||
|
s.CursorSectionDown()
|
||||||
|
s.CursorSectionUp()
|
||||||
|
s.CursorCellRight()
|
||||||
|
s.CursorCellLeft()
|
||||||
|
s.CursorCellDown()
|
||||||
|
s.CursorCellUp()
|
||||||
|
|
||||||
|
matches, err := regexp.Match("gogogogogram 2 2:\n(i\\(\\d,\\d\\)[\b\x01]{4}){4}fmcRLDUrldu", []byte(s.History()))
|
||||||
|
So(matches, ShouldBeTrue)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
Convey("You can complete sections and clear portions of the board", func() {
|
Convey("You can complete sections and clear portions of the board", func() {
|
||||||
s.cells.vivify(Point{0, 0})
|
s.cells.vivify(Point{0, 0})
|
||||||
s.cells.vivify(Point{2, 0})
|
s.cells.vivify(Point{2, 0})
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package model
|
package main
|
||||||
|
|
||||||
import tea "github.com/charmbracelet/bubbletea"
|
import tea "github.com/charmbracelet/bubbletea"
|
||||||
|
|
||||||
Reference in New Issue
Block a user