From b87631cb77299af0c6ff8eb0aceabcdcd5f30430 Mon Sep 17 00:00:00 2001 From: Madison Rye Progress Date: Tue, 26 Aug 2025 16:06:39 -0700 Subject: [PATCH] Switch to better state machine --- main.go | 7 +--- model/cursor.go | 56 ++++++++++++--------------- model/cursor_test.go | 92 ++++++++++++++++++++------------------------ model/guessing.go | 36 ----------------- model/model.go | 37 ++++++++++-------- model/section.go | 30 --------------- model/tea.go | 28 +++++++------- model/update.go | 86 +---------------------------------------- 8 files changed, 103 insertions(+), 269 deletions(-) delete mode 100644 model/guessing.go delete mode 100644 model/section.go diff --git a/main.go b/main.go index e5490a1..1416f15 100644 --- a/main.go +++ b/main.go @@ -9,12 +9,7 @@ import ( ) func main() { - m, err := model.New(4, 4) - if err != nil { - fmt.Printf("Ah, drat — %v\n", err) - os.Exit(1) - } - p := tea.NewProgram(m) + p := tea.NewProgram(model.New(4, 4)) if _, err := p.Run(); err != nil { fmt.Printf("Ah drat — %v\n", err) os.Exit(1) diff --git a/model/cursor.go b/model/cursor.go index a886bed..3248e8b 100644 --- a/model/cursor.go +++ b/model/cursor.go @@ -1,57 +1,49 @@ package model -func (m Model) CursorCellUp() Model { - if m.cursor-m.size >= 0 { - m.cursor -= m.size +func (m model) cursorCellUp() { + if m.cursor.y >= 1 { + m.cursor.y-- } - return m } -func (m Model) CursorCellDown() Model { - if m.cursor+m.size < m.size*m.size { - m.cursor += m.size +func (m model) cursorCellDown() { + if m.cursor.y < m.fieldSize-1 { + m.cursor.y++ } - return m } -func (m Model) CursorCellRight() Model { - if m.cursor%m.size < m.size-1 { - m.cursor++ +func (m model) cursorCellRight() { + if m.cursor.x < m.fieldSize-1 { + m.cursor.x++ } - return m } -func (m Model) CursorCellLeft() Model { - if m.cursor%m.size != 0 { - m.cursor-- +func (m model) cursorCellLeft() { + if m.cursor.x >= 1 { + m.cursor.x-- } - return m } -func (m Model) CursorSectionUp() Model { - if m.cursor >= m.size*m.perSection { - m.cursor -= m.size * m.perSection +func (m model) cursorSectionUp() { + if m.cursor.y >= m.cellsPerSection { + m.cursor.y -= m.cellsPerSection } - return m } -func (m Model) CursorSectionDown() Model { - if m.cursor < m.size*m.perSection*(m.section-1) { - m.cursor += m.size * m.perSection +func (m model) cursorSectionDown() { + if m.cursor.y < m.fieldSize-m.cellsPerSection { + m.cursor.y += m.cellsPerSection } - return m } -func (m Model) CursorSectionRight() Model { - if m.cursor%m.perSection < m.section-1 { - m.cursor += m.section +func (m model) cursorSectionRight() { + if m.cursor.x < m.fieldSize-m.cellsPerSection { + m.cursor.x += m.cellsPerSection } - return m } -func (m Model) CursorSectionLeft() Model { - if m.cursor%m.perSection >= 0 { - m.cursor -= m.section +func (m model) cursorSectionLeft() { + if m.cursor.x >= m.cellsPerSection { + m.cursor.x -= m.cellsPerSection } - return m } diff --git a/model/cursor_test.go b/model/cursor_test.go index 117cab1..b5df1d0 100644 --- a/model/cursor_test.go +++ b/model/cursor_test.go @@ -8,107 +8,99 @@ import ( 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() { - m, err := New(4, 4) - So(err, ShouldBeNil) - So(m.cursor, ShouldEqual, 0) Convey("You can move down", func() { - m = m.CursorCellDown() - So(m.cursor, ShouldEqual, 16) + m.cursorCellDown() + So(*m.cursor, ShouldResemble, point{0, 1}) }) Convey("You can move up", func() { - m = m.CursorCellDown() - m = m.CursorCellUp() - So(m.cursor, ShouldEqual, 0) + m.cursorCellDown() + m.cursorCellUp() + So(*m.cursor, ShouldResemble, point{0, 0}) }) Convey("You can move right", func() { - m = m.CursorCellRight() - So(m.cursor, ShouldEqual, 1) + m.cursorCellRight() + So(*m.cursor, ShouldResemble, point{1, 0}) }) Convey("You can move left", func() { - m = m.CursorCellRight() - m = m.CursorCellLeft() - So(m.cursor, ShouldEqual, 0) + m.cursorCellRight() + m.cursorCellLeft() + So(*m.cursor, ShouldResemble, point{0, 0}) }) Convey("You can't move up beyond the top", func() { - m = m.CursorCellUp() - So(m.cursor, ShouldEqual, 0) + m.cursorCellUp() + So(*m.cursor, ShouldResemble, point{0, 0}) }) Convey("You can't move left beyond the edge", func() { - m = m.CursorCellLeft() - So(m.cursor, ShouldEqual, 0) + m.cursorCellLeft() + So(*m.cursor, ShouldResemble, point{0, 0}) }) - bottomRight := (m.size * m.size) - 1 - m.cursor = bottomRight - Convey("You can't move down below the bottom", func() { - m.cursor = bottomRight - m = m.CursorCellDown() - So(m.cursor, ShouldEqual, bottomRight) + 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 = bottomRight - m = m.CursorCellRight() - So(m.cursor, ShouldEqual, bottomRight) + m.cursor = &point{15, 15} + m.cursorCellRight() + So(*m.cursor, ShouldResemble, point{15, 15}) }) }) Convey("When moving section to section", func() { - m, err := New(4, 4) - So(err, ShouldBeNil) Convey("You can move down", func() { - m = m.CursorSectionDown() - So(m.cursor, ShouldEqual, 64) + m.cursorSectionDown() + So(*m.cursor, ShouldResemble, point{0, 4}) }) Convey("You can move up", func() { - m = m.CursorSectionDown() - m = m.CursorSectionUp() - So(m.cursor, ShouldEqual, 0) + m.cursorSectionDown() + m.cursorSectionUp() + So(*m.cursor, ShouldResemble, point{0, 0}) }) Convey("You can move right", func() { - m = m.CursorSectionRight() - So(m.cursor, ShouldEqual, 4) + m.cursorSectionRight() + So(*m.cursor, ShouldResemble, point{4, 0}) }) Convey("You can move left", func() { - m = m.CursorSectionRight() - m = m.CursorSectionLeft() - So(m.cursor, ShouldEqual, 0) + m.cursorSectionRight() + m.cursorSectionLeft() + So(*m.cursor, ShouldResemble, point{0, 0}) }) Convey("You can't move up beyond the top", func() { - m = m.CursorCellUp() - So(m.cursor, ShouldEqual, 0) + m.cursorCellUp() + So(*m.cursor, ShouldResemble, point{0, 0}) }) Convey("You can't move left beyond the edge", func() { - m = m.CursorCellLeft() - So(m.cursor, ShouldEqual, 0) + m.cursorCellLeft() + So(*m.cursor, ShouldResemble, point{0, 0}) }) - bottomRight := (m.size * m.size) - 1 - Convey("You can't move down below the bottom", func() { - m.cursor = bottomRight - m = m.CursorSectionDown() - So(m.cursor, ShouldEqual, bottomRight) + 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 = bottomRight - m = m.CursorSectionRight() - So(m.cursor, ShouldEqual, bottomRight) + m.cursor = &point{15, 15} + m.cursorSectionRight() + So(*m.cursor, ShouldResemble, point{15, 15}) }) }) }) diff --git a/model/guessing.go b/model/guessing.go deleted file mode 100644 index 52919b3..0000000 --- a/model/guessing.go +++ /dev/null @@ -1,36 +0,0 @@ -package model - -import "fmt" - -func (m Model) Mark() Model { - if m.marks&m.cursor != 0 { - m.marks |= m.cursor - m.flags = m.flags &^ m.cursor - } else { - m.marks = m.marks &^ m.cursor - } - m.history = append(m.history, fmt.Sprintf("m%d", m.cursor)) - m = m.update() - return m -} - -func (m Model) Flag() Model { - if m.flags&m.cursor != 0 { - m.flags |= m.cursor - m.marks = m.marks &^ m.cursor - } else { - m.flags = m.flags &^ m.cursor - } - m.history = append(m.history, fmt.Sprintf("f%d", m.cursor)) - m = m.update() - return m -} - -func (m Model) ClearGuess() Model { - m.marks = m.marks &^ m.cursor - m.flags = m.flags &^ m.cursor - m.correct = m.correct &^ m.cursor - m.history = append(m.history, fmt.Sprintf("c%d", m.cursor)) - m = m.update() - return m -} diff --git a/model/model.go b/model/model.go index 1f02c02..5135493 100644 --- a/model/model.go +++ b/model/model.go @@ -1,29 +1,32 @@ package model -type Model struct { - size, section, perSection int - field, view int - marks, flags int - correct, sections, completed int +type point struct { + x, y int +} + +type model struct { + fieldSize, sectionSize, cellsPerSection int + + state *state + clears, score, factor, track int - cursor int + + cursor *point columnStates, rowStates [][]int columnsCorrect, rowsCorrect []bool - history []string + history string } -func New(section, perSection int) (Model, error) { - m := Model{ - size: section * perSection, - section: section, - perSection: perSection, +func New(sectionSize, cellsPerSection int) model { + m := model{ + fieldSize: sectionSize * cellsPerSection, + sectionSize: sectionSize, + cellsPerSection: cellsPerSection, + cursor: &point{0, 0}, } + m.state = newState(sectionSize, cellsPerSection) - for i := 0; i < section*section; i++ { - m = m.randomizeSection(i) - } - - return m, nil + return m } diff --git a/model/section.go b/model/section.go deleted file mode 100644 index 6131870..0000000 --- a/model/section.go +++ /dev/null @@ -1,30 +0,0 @@ -package model - -import "math/rand" - -func (m Model) randomizeSection(s int) Model { - m = m.clearSection(s) - for y := 0; y < m.section; y++ { - for x := 0; x < m.section; x++ { - cell := ((s/m.section)*m.size + y) + ((s%m.size)*m.section + x) - if rand.Int()%2 == 1 { - m.field |= cell - } - } - } - return m -} - -func (m Model) clearSection(s int) Model { - m.sections = m.sections &^ s - for y := 0; y < m.section; y++ { - for x := 0; x < m.section; x++ { - cell := ((s/m.section)*m.size + y) + ((s%m.size)*m.section + x) - m.field = m.field &^ cell - m.marks = m.marks &^ cell - m.flags = m.flags &^ cell - m.correct = m.correct &^ cell - } - } - return m -} diff --git a/model/tea.go b/model/tea.go index b2aabd8..be3a960 100644 --- a/model/tea.go +++ b/model/tea.go @@ -2,11 +2,11 @@ package model import tea "github.com/charmbracelet/bubbletea" -func (m Model) Init() tea.Cmd { +func (m model) Init() tea.Cmd { return nil } -func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: @@ -19,45 +19,45 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Movement by cell case "up", "w": - m = m.CursorCellUp() + m.cursorCellUp() case "down", "s": - m = m.CursorCellDown() + m.cursorCellDown() case "right", "d": - m = m.CursorCellRight() + m.cursorCellRight() case "left", "a": - m = m.CursorCellLeft() + m.cursorCellLeft() // Movement by section case "ctrl+up", "ctrl+w", "shift+up", "shift+w": - m = m.CursorSectionUp() + m.cursorSectionUp() case "ctrl+down", "ctrl+s", "shift+down", "shift+s": - m = m.CursorSectionDown() + m.cursorSectionDown() case "ctrl+right", "ctrl+d", "shift+right", "shift+d": - m = m.CursorSectionRight() + m.cursorSectionRight() case "ctrl+left", "ctrl+a", "shift+left", "shift+a": - m = m.CursorSectionRight() + m.cursorSectionRight() // Marking/flagging case " ", "enter": - m = m.Mark() + m.state.mark(*m.cursor) case "x": - m = m.Flag() + m.state.flag(*m.cursor) case "delete", "backspace": - m = m.ClearGuess() + m.state.clear(*m.cursor) } } return m, nil } -func (m Model) View() string { +func (m model) View() string { return "" } diff --git a/model/update.go b/model/update.go index a2edef1..ea3eaa4 100644 --- a/model/update.go +++ b/model/update.go @@ -1,99 +1,17 @@ package model -func (m Model) update() Model { - // Reset sections to all true - m.sections = m.section*m.section - 1 - +func (m model) update() model { // Update correctness/sections - for i := 0; i < m.size*m.size; i++ { - cell := m.field & i - marked := m.marks & i - flagged := m.flags & i - - // Update the correctness of each cell - m.correct |= (cell & marked) | (flagged &^ cell) - - // Update the correctness of each section - cellSection := ((i / (m.section * m.size)) * m.section) + (i/m.perSection)%m.section - m.sections &= m.correct & cellSection - } // Check for complete sections, which are those where the row and column are both correct - completedIndices := map[int]bool{} - for i := 0; i < m.section*m.section; i++ { - if m.sections&i == 0 { - continue - } - mask := 0 - x := i % m.section - y := i / m.section - for j := 0; j < m.section; j++ { - mask |= 1< 0 { - m.score += clearedIncrease * (m.factor + 1) - m.clears++ - } // Check for blackout - if m.completed == m.section*m.section-1 { - m.completed = 0 - m.factor++ - } // Update row/column states/correctness - for x := 0; x < m.size; x++ { - rowCorrect := true - columnCorrect := true - for y := 0; y < m.size; y++ { - cellCorrect := m.correct&y*m.size+x != 0 - rowCorrect = rowCorrect && cellCorrect - columnCorrect = columnCorrect && cellCorrect - } - } return m }