From f3b380353557b00c1d1114f42d9beeef16fb2c49 Mon Sep 17 00:00:00 2001 From: Madison Rye Progress Date: Sat, 13 Sep 2025 20:58:36 -0700 Subject: [PATCH] Maintain history; use state --- main.go | 3 +-- model/model.go => model.go | 9 +++------ state/cursor.go | 8 ++++++++ state/field.go | 5 +++++ state/state.go | 29 ++++++++++++++++++++++++++--- state/state_test.go | 19 +++++++++++++++++++ model/tea.go => tea.go | 2 +- 7 files changed, 63 insertions(+), 12 deletions(-) rename model/model.go => model.go (69%) rename model/tea.go => tea.go (98%) diff --git a/main.go b/main.go index 1416f15..22fa5aa 100644 --- a/main.go +++ b/main.go @@ -4,12 +4,11 @@ import ( "fmt" "os" - "git.makyo.dev/makyo/gogogogogram/model" tea "github.com/charmbracelet/bubbletea" ) func main() { - p := tea.NewProgram(model.New(4, 4)) + p := tea.NewProgram(newModel(4, 4)) if _, err := p.Run(); err != nil { fmt.Printf("Ah drat — %v\n", err) os.Exit(1) diff --git a/model/model.go b/model.go similarity index 69% rename from model/model.go rename to model.go index 39eb0f3..137164a 100644 --- a/model/model.go +++ b/model.go @@ -1,4 +1,4 @@ -package model +package main import "git.makyo.dev/makyo/gogogogogram/state" @@ -9,22 +9,19 @@ type model struct { clears, score, factor, track int - cursor *state.Point - columnStates, rowStates [][]int columnsCorrect, rowsCorrect []bool history string } -func New(sectionSize, cellsPerSection int) model { +func newModel(sectionSize, cellsPerSection int) model { m := model{ fieldSize: sectionSize * cellsPerSection, sectionSize: sectionSize, cellsPerSection: cellsPerSection, - cursor: &state.Point{0, 0}, + state: state.New(sectionSize, cellsPerSection), } - m.state = state.New(sectionSize, cellsPerSection) return m } diff --git a/state/cursor.go b/state/cursor.go index 4cabec5..4b4624d 100644 --- a/state/cursor.go +++ b/state/cursor.go @@ -3,47 +3,55 @@ package state func (s *State) CursorCellUp() { if s.cursor.Y >= 1 { s.cursor.Y-- + s.history += "u" } } func (s *State) CursorCellDown() { if s.cursor.Y < s.size()-1 { s.cursor.Y++ + s.history += "d" } } func (s *State) CursorCellRight() { if s.cursor.X < s.size()-1 { s.cursor.X++ + s.history += "r" } } func (s *State) CursorCellLeft() { if s.cursor.X >= 1 { s.cursor.X-- + s.history += "l" } } func (s *State) CursorSectionUp() { if s.cursor.Y >= s.cellsPerSection { s.cursor.Y -= s.cellsPerSection + s.history += "U" } } func (s *State) CursorSectionDown() { if s.cursor.Y < s.size()-s.cellsPerSection { s.cursor.Y += s.cellsPerSection + s.history += "D" } } func (s *State) CursorSectionRight() { if s.cursor.X < s.size()-s.cellsPerSection { s.cursor.X += s.cellsPerSection + s.history += "R" } } func (s *State) CursorSectionLeft() { if s.cursor.X >= s.cellsPerSection { s.cursor.X -= s.cellsPerSection + s.history += "L" } } diff --git a/state/field.go b/state/field.go index 3e93f35..6f2b6e3 100644 --- a/state/field.go +++ b/state/field.go @@ -38,6 +38,11 @@ func (f *field) String() string { 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. func (f *field) state(p Point) bool { return f.cells[f.i(p)].state() diff --git a/state/state.go b/state/state.go index 0dd57af..b33af96 100644 --- a/state/state.go +++ b/state/state.go @@ -2,6 +2,7 @@ package state import ( "bytes" + "fmt" "math/rand" ) @@ -9,9 +10,18 @@ type Point struct { X, Y int } +func (p Point) String() string { + return fmt.Sprintf("(%d,%d)", p.X, p.Y) +} + type State struct { cursor *Point + clears, score, factor int + blackout []bool + + history string + sectionSize, cellsPerSection int cells, sections *field } @@ -24,11 +34,13 @@ func New(sectionSize, cellsPerSection int) *State { sections: newField(sectionSize), cursor: &Point{0, 0}, } + s.history = fmt.Sprintf("gogogogogram %d %d:\n", sectionSize, cellsPerSection) for x := 0; x < sectionSize; x++ { for y := 0; y < sectionSize; y++ { s.initSection(Point{x, y}) } } + // TODO reveal 1 row per section, 1 col per sections/2 return s } @@ -37,6 +49,11 @@ func (s *State) size() int { 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 { var buf bytes.Buffer for x := 0; x < s.sectionSize*s.cellsPerSection; x++ { @@ -68,14 +85,17 @@ func (s *State) String() string { } func (s *State) Mark() []bool { + s.history += "m" return s.mark(*s.cursor) } func (s *State) Flag() []bool { + s.history += "f" return s.flag(*s.cursor) } func (s *State) Clear() { + s.history += "c" s.clear(*s.cursor, true) } @@ -83,17 +103,20 @@ func (s *State) view(cursor []int) { } func (s *State) initSection(p Point) { + s.history += fmt.Sprintf("i%s", p) s.sections.clear(p, false) 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}, true) + currPoint := Point{x + startX, y + startY} + s.cells.clear(currPoint, true) if rand.Int()%2 == 0 { - s.cells.kill(Point{x + startX, y + startY}) + s.cells.kill(currPoint) } 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) diff --git a/state/state_test.go b/state/state_test.go index 9279823..de7ecc7 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -1,6 +1,7 @@ package state import ( + "regexp" "testing" . "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() { s.cells.vivify(Point{0, 0}) s.cells.vivify(Point{2, 0}) diff --git a/model/tea.go b/tea.go similarity index 98% rename from model/tea.go rename to tea.go index 6a1bd2b..0948f56 100644 --- a/model/tea.go +++ b/tea.go @@ -1,4 +1,4 @@ -package model +package main import tea "github.com/charmbracelet/bubbletea"