- Add Reset() method to game.Gamer interface - Implement Reset in all game modes (nonogram, sudoku, takuzu, hashiwokakero, wordsearch, lightsout) - Add Ctrl+R keyboard handler in model.go - Update status bar help text in all games - Add initialGrid field to takuzu and lightsout (was missing) - Add TestReset for nonogram
- commit
- 76f8d6b969c045c0541bc405fe17d6bf462c2b6a
- parent
- 2e6d66b874ce29b9bde66b5de7eb28f050da0fe0
- author
- Dami <dami.etoile@gmail.com>
| M | game/ | 5 | ++++ |
| M | hashiwokakero/ | 10 | ++++++++ |
| M | hashiwokakero/ | 4 | ++-- |
| M | lightsout/ | 25 | +++++++++++++++++---- |
| M | lightsout/ | 2 | +- |
| M | model.go | 4 | +++ |
| M | nonogram/ | 10 | ++++++++ |
| M | nonogram/ | 36 | ++++++++++++++++++++++++++++++ |
| M | nonogram/ | 2 | +- |
| M | sudoku/ | 6 | +++++ |
| M | sudoku/ | 2 | +- |
| M | takuzu/ | 21 | +++++++++++++----- |
| M | takuzu/ | 2 | +- |
| M | wordsearch/ | 20 | +++++++++++++++++ |
| M | wordsearch/ | 2 | +- |
@@ -14,6 +14,7 @@ type Gamer interface {
1414 1515 GetSave() ([]byte, error)1616 IsSolved() bool17+ Reset() Gamer1718 SetTitle(string) Gamer1819 1920 Init() tea.Cmd@@ -67,6 +68,10 @@ func (c Category) FilterValue() string { return c.Name }
6768 // the full help display with Ctrl+H.6869 type HelpToggleMsg struct{ Show bool }6970 71+// ResetMsg is sent from the root model to games when the user wants to reset72+// the puzzle to its initial state (Ctrl+R).73+type ResetMsg struct{}74+7075 // Registry maps game type names to their import functions.7176 var Registry = map[string]func([]byte) (Gamer, error){}7277 @@ -144,6 +144,16 @@ func (m Model) IsSolved() bool {
144144 return m.puzzle.IsSolved()145145 }146146 147+func (m Model) Reset() game.Gamer {148+ m.puzzle.Bridges = nil149+ m.cursorIsland = 0150+ if len(m.puzzle.Islands) > 0 {151+ m.cursorIsland = m.puzzle.Islands[0].ID152+ }153+ m.selectedIsland = nil154+ return m155+}156+147157 func (m Model) GetDebugInfo() string {148158 solved := m.puzzle.IsSolved()149159 connected := m.puzzle.IsConnected()@@ -280,12 +280,12 @@ func bridgeVView(count int, solved bool) string {
280280 func statusBarView(selected, showFullHelp bool) string {281281 if selected {282282 if showFullHelp {283- return statusBarStyle.Render("arrows/wasd: build bridge enter/space/esc: cancel ctrl+n: menu ctrl+h: help")283+ return statusBarStyle.Render("arrows/wasd: build bridge enter/space/esc: cancel ctrl+n: menu ctrl+r: reset ctrl+h: help")284284 }285285 return statusBarStyle.Render("arrows/wasd: build bridge enter/space/esc: cancel")286286 }287287 if showFullHelp {288- return statusBarStyle.Render("arrows/wasd: move enter/space: select island ctrl+n: menu ctrl+h: help")288+ return statusBarStyle.Render("arrows/wasd: move enter/space: select island ctrl+n: menu ctrl+r: reset ctrl+h: help")289289 }290290 return statusBarStyle.Render("enter/space: select island")291291 }@@ -12,6 +12,7 @@ import (
1212 1313 type Model struct {1414 grid [][]bool15+ initialGrid [][]bool1516 width int1617 height int1718 cursor game.Cursor@@ -24,12 +25,18 @@ var _ game.Gamer = Model{}
2425 2526 func New(w, h int) (Model, error) {2627 grid := Generate(w, h)28+ initial := make([][]bool, h)29+ for y := range h {30+ initial[y] = make([]bool, w)31+ copy(initial[y], grid[y])32+ }2733 return Model{28- grid: grid,29- width: w,30- height: h,31- cursor: game.Cursor{X: w / 2, Y: h / 2},32- keys: DefaultKeyMap,34+ grid: grid,35+ initialGrid: initial,36+ width: w,37+ height: h,38+ cursor: game.Cursor{X: w / 2, Y: h / 2},39+ keys: DefaultKeyMap,3340 }, nil3441 }3542 @@ -74,6 +81,14 @@ func (m Model) IsSolved() bool {
7481 return IsSolved(m.grid)7582 }7683 84+func (m Model) Reset() game.Gamer {85+ for y := range m.height {86+ copy(m.grid[y], m.initialGrid[y])87+ }88+ m.cursor = game.Cursor{X: m.width / 2, Y: m.height / 2}89+ return m90+}91+7792 func (m Model) GetDebugInfo() string {7893 status := "In Progress"7994 if m.IsSolved() {@@ -83,7 +83,7 @@ func gridView(g [][]bool, c game.Cursor, solved bool) string {
8383 8484 func statusBarView(showFullHelp bool) string {8585 if showFullHelp {86- return statusBarStyle.Render("arrows/wasd: move enter/space: toggle ctrl+n: menu ctrl+h: help")86+ return statusBarStyle.Render("arrows/wasd: move enter/space: toggle ctrl+n: menu ctrl+r: reset ctrl+h: help")8787 }8888 return statusBarStyle.Render("enter/space: toggle")8989 }@@ -171,6 +171,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
171171 if m.state == gameView && m.game != nil {172172 m.game, _ = m.game.Update(game.HelpToggleMsg{Show: m.showFullHelp})173173 }174+ case tea.KeyCtrlR:175+ if m.state == gameView && m.game != nil {176+ m.game = m.game.Reset()177+ }174178 }175179 }176180 @@ -159,6 +159,16 @@ func (m Model) IsSolved() bool {
159159 return m.solved160160 }161161 162+func (m Model) Reset() game.Gamer {163+ g := newGrid(createEmptyState(m.height, m.width))164+ curr := generateTomography(g)165+ m.grid = g166+ m.currentHints = curr167+ m.solved = false168+ m.cursor = game.Cursor{}169+ return m170+}171+162172 func (m *Model) updateTile(r rune) {163173 m.grid[m.cursor.Y][m.cursor.X] = r164174 m.currentHints = generateTomography(m.grid)@@ -947,3 +947,39 @@ func TestSpawnPerformance(t *testing.T) {
947947 }948948 })949949 }950+951+func TestReset(t *testing.T) {952+ mode := NonogramMode{Width: 5, Height: 5}953+ hints := Hints{954+ rows: TomographyDefinition{{1}, {1}, {1}, {1}, {1}},955+ cols: TomographyDefinition{{1}, {1}, {1}, {1}, {1}},956+ }957+ m, err := New(mode, hints)958+ if err != nil {959+ t.Fatal(err)960+ }961+ model := m.(Model)962+963+ originalGrid := model.grid.String()964+965+ model.grid[0][0] = filledTile966+ model.grid[0][1] = filledTile967+968+ if model.grid.String() == originalGrid {969+ t.Fatal("grid should be modified before reset")970+ }971+972+ reset := model.Reset().(Model)973+974+ if reset.grid.String() != originalGrid {975+ t.Errorf("grid after reset = %q, want %q", reset.grid.String(), originalGrid)976+ }977+978+ if reset.cursor != (game.Cursor{}) {979+ t.Errorf("cursor after reset = %v, want zero cursor", reset.cursor)980+ }981+982+ if reset.solved {983+ t.Error("solved should be false after reset")984+ }985+}@@ -259,7 +259,7 @@ func tileView(val rune, isCursor, inCursorRow, inCursorCol, solved bool) string
259259 260260 func statusBarView(showFullHelp bool) string {261261 if showFullHelp {262- return statusBarStyle.Render("arrows/wasd: move z: fill x: mark bkspc: clear ctrl+n: menu ctrl+h: help")262+ return statusBarStyle.Render("arrows/wasd: move z: fill x: mark bkspc: clear ctrl+n: menu ctrl+r: reset ctrl+h: help")263263 }264264 return statusBarStyle.Render("z: fill x: mark bkspc: clear")265265 }@@ -80,6 +80,12 @@ func (m Model) IsSolved() bool {
8080 return m.isSolved()8181 }8282 83+// Reset implements game.Gamer.84+func (m Model) Reset() game.Gamer {85+ m.grid = newGrid(m.provided)86+ return m87+}88+8389 func (m *Model) updateCell(v int) {8490 if m.providedGrid[m.cursor.Y][m.cursor.X] {8591 return@@ -230,7 +230,7 @@ func computeConflicts(g grid) [gridSize][gridSize]bool {
230230 231231 func statusBarView(showFullHelp bool) string {232232 if showFullHelp {233- return statusBarStyle.Render("arrows/wasd: move 1-9: fill bkspc: clear ctrl+n: menu ctrl+h: help")233+ return statusBarStyle.Render("arrows/wasd: move 1-9: fill bkspc: clear ctrl+n: menu ctrl+r: reset ctrl+h: help")234234 }235235 return statusBarStyle.Render("1-9: fill bkspc: clear")236236 }@@ -14,6 +14,7 @@ import (
1414 type Model struct {1515 size int1616 grid grid17+ initialGrid grid1718 provided [][]bool1819 cursor game.Cursor1920 solved bool@@ -27,12 +28,13 @@ var _ game.Gamer = Model{}
2728 // New creates a new Takuzu game model.2829 func New(mode TakuzuMode, puzzle grid, provided [][]bool) (game.Gamer, error) {2930 m := Model{30- size: mode.Size,31- grid: puzzle,32- provided: provided,33- cursor: game.Cursor{X: 0, Y: 0},34- keys: DefaultKeyMap,35- modeTitle: mode.Title(),31+ size: mode.Size,32+ grid: puzzle,33+ initialGrid: puzzle.clone(),34+ provided: provided,35+ cursor: game.Cursor{X: 0, Y: 0},36+ keys: DefaultKeyMap,37+ modeTitle: mode.Title(),3638 }3739 m.solved = m.checkSolved()3840 return m, nil@@ -87,6 +89,13 @@ func (m Model) IsSolved() bool {
8789 return m.solved8890 }8991 92+func (m Model) Reset() game.Gamer {93+ m.grid = m.initialGrid.clone()94+ m.solved = m.checkSolved()95+ m.cursor = game.Cursor{}96+ return m97+}98+9099 func (m Model) checkSolved() bool {91100 // All cells must be filled.92101 for y := range m.size {@@ -144,7 +144,7 @@ func hBorderRow(w, cursorX int, left, right string, solved bool) string {
144144 145145 func statusBarView(showFullHelp bool) string {146146 if showFullHelp {147- return statusBarStyle.Render("arrows/wasd: move z: ● x: ○ bkspc: clear ctrl+n: menu ctrl+h: help")147+ return statusBarStyle.Render("arrows/wasd: move z: ● x: ○ bkspc: clear ctrl+n: menu ctrl+r: reset ctrl+h: help")148148 }149149 return statusBarStyle.Render("z: ● x: ○ bkspc: clear")150150 }@@ -187,6 +187,26 @@ func (m Model) IsSolved() bool {
187187 return m.solved188188 }189189 190+func (m Model) Reset() game.Gamer {191+ words := make([]Word, len(m.words))192+ for i, w := range m.words {193+ words[i] = Word{194+ Text: w.Text,195+ Start: w.Start,196+ End: w.End,197+ Direction: w.Direction,198+ Found: false,199+ }200+ }201+ m.words = words202+ m.foundCells = buildFoundCells(m.width, m.height, m.words)203+ m.solved = false204+ m.selection = noSelection205+ m.selectionStart = game.Cursor{}206+ m.cursor = game.Cursor{}207+ return m208+}209+190210 func (m Model) countFoundWords() int {191211 count := 0192212 for _, word := range m.words {@@ -94,7 +94,7 @@ func renderView(m Model) string {
9494 9595 func statusBarView(showFullHelp bool) string {9696 if showFullHelp {97- return statusBarStyle.Render("arrows/wasd: move enter/space: select esc: cancel ctrl+n: menu ctrl+h: help")97+ return statusBarStyle.Render("arrows/wasd: move enter/space: select esc: cancel ctrl+n: menu ctrl+r: reset ctrl+h: help")9898 }9999 return statusBarStyle.Render("enter/space: select esc: cancel")100100 }