clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

style and ux

Dami committed df60dd8
commit
df60dd8244eeec9335b75c7db610d3b9e84d964b
parent
dcef8c16f7fb425d77c8d3a9159cf65cf07a676f
author
Dami <dami@Damis-MacBook-Air.local>
11 files changed, +243 -101
Mgame/gamer.go5+
Mhashiwokakero/Gamemode.go15++-
Mhashiwokakero/Model.go16++-
Mhashiwokakero/style.go163++++++++++++++++++++++--------
Mmodel.go14++-
Mnonogram/Model.go28+++--
Mnonogram/style.go39++++---
Msudoku/Model.go30+++--
Msudoku/style.go11+-
Mwordsearch/Model.go12++-
Mwordsearch/style.go11+-
M game/gamer.go +5 -0
M hashiwokakero/Gamemode.go +12 -3
M hashiwokakero/Model.go +12 -4
@@ -14,7 +14,8 @@ type Model struct {
1414 cursorIsland int // ID of island the cursor is on1515 selectedIsland *int // ID of island selected for bridge building, nil if none1616 keys KeyMap17- modeName string17+ modeTitle string18+ showFullHelp bool1819 }1920 2021 func New(mode HashiMode, puzzle Puzzle) game.Gamer {
@@ -26,7 +27,7 @@ func New(mode HashiMode, puzzle Puzzle) game.Gamer {
2627 puzzle: puzzle,2728 cursorIsland: cursorID,2829 keys: DefaultKeyMap,29- modeName: mode.Title(),30+ modeTitle: mode.Title(),3031 }3132 }3233
@@ -36,6 +37,8 @@ func (m Model) Init() tea.Cmd {
3637 3738 func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {3839 switch msg := msg.(type) {40+ case game.HelpToggleMsg:41+ m.showFullHelp = msg.Show3942 case tea.KeyMsg:4043 if m.selectedIsland != nil {4144 m = m.handleBridgeMode(msg)
@@ -121,14 +124,19 @@ func (m Model) cycleBridge(dx, dy int) Model {
121124 func (m Model) View() string {122125 solved := m.puzzle.IsSolved()123126 124- title := titleBarView(m.modeName, solved)127+ title := titleBarView(m.modeTitle, solved)125128 grid := gridView(m)126129 info := infoView(&m.puzzle)127- status := statusBarView(m.selectedIsland != nil)130+ status := statusBarView(m.selectedIsland != nil, m.showFullHelp)128131 129132 return lipgloss.JoinVertical(lipgloss.Left, title, grid, info, status)130133 }131134 135+func (m Model) SetTitle(t string) game.Gamer {136+ m.modeTitle = t137+ return m138+}139+132140 func (m Model) IsSolved() bool {133141 return m.puzzle.IsSolved()134142 }
M hashiwokakero/style.go +121 -42
@@ -10,48 +10,108 @@ import (
1010 1111 const cellWidth = 31212 13+// Color palette — cool ocean/slate theme14+const (15+ colorIslandFg = "#e0e6ed"16+ colorIslandBg = "#3a4555"17+ colorSatisfiedFg = "#7dcea0"18+ colorSatisfiedBg = "#1a3330"19+ colorOverFg = "#e07070"20+ colorOverBg = "#3a1a1e"21+ colorCursorFg = "#1a1e24"22+ colorCursorBg = "#5dade2"23+ colorSelectedFg = "#1a1e24"24+ colorSelectedBg = "#f5b041"25+ colorAdjacentBg = "#2e3a48"26+ colorBridge = "#5a7a8a"27+ colorBridgeSolved = "#7dcea0"28+ colorEmptyDot = "#2e3640"29+ colorEmptySolved = "#4a6a5a"30+ colorInfoSatisfied = "#7dcea0"31+ colorInfoText = "#8a9aaa"32+)33+1334 var (1435 baseStyle = lipgloss.NewStyle()1536 1637 islandDefaultStyle = baseStyle.17- Foreground(lipgloss.Color("#ffffff")).18- Background(lipgloss.Color("#4a4a4a")).38+ Foreground(lipgloss.Color(colorIslandFg)).39+ Background(lipgloss.Color(colorIslandBg)).1940 Bold(true)2041 2142 islandSatisfiedStyle = baseStyle.22- Foreground(lipgloss.Color("#00ff00")).23- Background(lipgloss.Color("#1a3a1a")).43+ Foreground(lipgloss.Color(colorSatisfiedFg)).44+ Background(lipgloss.Color(colorSatisfiedBg)).2445 Bold(true)2546 2647 islandOverStyle = baseStyle.27- Foreground(lipgloss.Color("#ff0000")).28- Background(lipgloss.Color("#3a1a1a")).48+ Foreground(lipgloss.Color(colorOverFg)).49+ Background(lipgloss.Color(colorOverBg)).2950 Bold(true)3051 3152 islandCursorStyle = baseStyle.32- Foreground(lipgloss.Color("#ffffff")).33- Background(lipgloss.Color("#ff00ff")).53+ Foreground(lipgloss.Color(colorCursorFg)).54+ Background(lipgloss.Color(colorCursorBg)).3455 Bold(true)3556 3657 islandSelectedStyle = baseStyle.37- Foreground(lipgloss.Color("#ffffff")).38- Background(lipgloss.Color("#ff8800")).58+ Foreground(lipgloss.Color(colorSelectedFg)).59+ Background(lipgloss.Color(colorSelectedBg)).60+ Bold(true)61+62+ islandAdjacentStyle = baseStyle.63+ Foreground(lipgloss.Color(colorIslandFg)).64+ Background(lipgloss.Color(colorAdjacentBg)).3965 Bold(true)4066 4167 bridgeHStyle = baseStyle.42- Foreground(lipgloss.Color("#666666"))68+ Foreground(lipgloss.Color(colorBridge))4369 4470 bridgeVStyle = baseStyle.45- Foreground(lipgloss.Color("#666666"))71+ Foreground(lipgloss.Color(colorBridge))72+73+ bridgeHSolvedStyle = baseStyle.74+ Foreground(lipgloss.Color(colorBridgeSolved))75+76+ bridgeVSolvedStyle = baseStyle.77+ Foreground(lipgloss.Color(colorBridgeSolved))4678 4779 emptyStyle = baseStyle.48- Foreground(lipgloss.Color("#333333"))80+ Foreground(lipgloss.Color(colorEmptyDot))4981 5082 statusBarStyle = lipgloss.NewStyle().51- Foreground(lipgloss.Color("#888888")).83+ Foreground(lipgloss.Color(colorInfoText)).5284 MarginTop(1)5385 )5486 87+// isHighlightedNeighbor returns true if islandID is directly connectable88+// to the cursor (or selected) island in any cardinal direction.89+func isHighlightedNeighbor(m Model, islandID int) bool {90+ sourceID := m.cursorIsland91+ if m.selectedIsland != nil {92+ sourceID = *m.selectedIsland93+ }94+ if sourceID == islandID {95+ return false96+ }97+ for _, dir := range [][2]int{{0, -1}, {0, 1}, {-1, 0}, {1, 0}} {98+ adj := m.puzzle.FindAdjacentIsland(sourceID, dir[0], dir[1])99+ if adj != nil && adj.ID == islandID {100+ return true101+ }102+ }103+ return false104+}105+106+// emptyCellView renders an empty grid cell as a subtle dot.107+func emptyCellView(solved bool) string {108+ s := emptyStyle109+ if solved {110+ s = s.Foreground(lipgloss.Color(colorEmptySolved))111+ }112+ return s.Width(cellWidth).AlignHorizontal(lipgloss.Center).Render("·")113+}114+55115 // gridView renders the puzzle using a display grid with gap cells between56116 // logical cells so that bridges between directly adjacent islands are visible.57117 // Display grid is (2*width-1) x (2*height-1): even coords are logical cells,
@@ -86,72 +146,72 @@ func displayCellView(m Model, dx, dy int, solved bool) string {
86146 case cellIsland:87147 return islandView(m, ci.IslandID, solved)88148 case cellBridgeH:89- return bridgeHView(ci.BridgeCount)149+ return bridgeHView(ci.BridgeCount, solved)90150 case cellBridgeV:91- return bridgeVView(ci.BridgeCount)151+ return bridgeVView(ci.BridgeCount, solved)92152 default:93- return emptyStyle.Width(cellWidth).Render(" ")153+ return emptyCellView(solved)94154 }95155 }96156 97157 if !evenX && evenY {98158 // Horizontal gap between (dx/2, dy/2) and (dx/2+1, dy/2)99159 lx, ly := dx/2, dy/2100- return hGapView(m, lx, ly)160+ return hGapView(m, lx, ly, solved)101161 }102162 103163 if evenX && !evenY {104164 // Vertical gap between (dx/2, dy/2) and (dx/2, dy/2+1)105165 lx, ly := dx/2, dy/2106- return vGapView(m, lx, ly)166+ return vGapView(m, lx, ly, solved)107167 }108168 109169 // Odd x and odd y — intersection of gaps, always empty110- return emptyStyle.Width(cellWidth).Render(" ")170+ return baseStyle.Width(cellWidth).Render(" ")111171 }112172 113173 // hGapView renders the horizontal gap between logical cell (lx,ly) and (lx+1,ly).114174 // Shows a bridge connector if both neighbors are islands connected by a bridge.115-func hGapView(m Model, lx, ly int) string {175+func hGapView(m Model, lx, ly int, solved bool) string {116176 left := m.puzzle.FindIslandAt(lx, ly)117177 right := m.puzzle.FindIslandAt(lx+1, ly)118178 if left != nil && right != nil {119179 if b := m.puzzle.GetBridge(left.ID, right.ID); b != nil {120- return bridgeHView(b.Count)180+ return bridgeHView(b.Count, solved)121181 }122182 }123183 // Also check if a bridge passes through this gap (non-adjacent islands)124184 ci := m.puzzle.CellContent(lx, ly)125185 if ci.Kind == cellBridgeH {126- return bridgeHView(ci.BridgeCount)186+ return bridgeHView(ci.BridgeCount, solved)127187 }128188 ciR := m.puzzle.CellContent(lx+1, ly)129189 if ciR.Kind == cellBridgeH {130- return bridgeHView(ciR.BridgeCount)190+ return bridgeHView(ciR.BridgeCount, solved)131191 }132- return emptyStyle.Width(cellWidth).Render(" ")192+ return baseStyle.Width(cellWidth).Render(" ")133193 }134194 135195 // vGapView renders the vertical gap between logical cell (lx,ly) and (lx,ly+1).136196 // Shows a bridge connector if both neighbors are islands connected by a bridge.137-func vGapView(m Model, lx, ly int) string {197+func vGapView(m Model, lx, ly int, solved bool) string {138198 top := m.puzzle.FindIslandAt(lx, ly)139199 bottom := m.puzzle.FindIslandAt(lx, ly+1)140200 if top != nil && bottom != nil {141201 if b := m.puzzle.GetBridge(top.ID, bottom.ID); b != nil {142- return bridgeVView(b.Count)202+ return bridgeVView(b.Count, solved)143203 }144204 }145205 // Also check if a bridge passes through this gap (non-adjacent islands)146206 ci := m.puzzle.CellContent(lx, ly)147207 if ci.Kind == cellBridgeV {148- return bridgeVView(ci.BridgeCount)208+ return bridgeVView(ci.BridgeCount, solved)149209 }150210 ciB := m.puzzle.CellContent(lx, ly+1)151211 if ciB.Kind == cellBridgeV {152- return bridgeVView(ciB.BridgeCount)212+ return bridgeVView(ciB.BridgeCount, solved)153213 }154- return emptyStyle.Width(cellWidth).Render(" ")214+ return baseStyle.Width(cellWidth).Render(" ")155215 }156216 157217 func islandView(m Model, islandID int, solved bool) string {
@@ -173,37 +233,53 @@ func islandView(m Model, islandID int, solved bool) string {
173233 style = islandSatisfiedStyle174234 } else if current > isl.Required {175235 style = islandOverStyle236+ } else if isHighlightedNeighbor(m, islandID) {237+ style = islandAdjacentStyle176238 }177239 178240 label := fmt.Sprintf("%d", isl.Required)179241 return style.Width(cellWidth).AlignHorizontal(lipgloss.Center).Render(label)180242 }181243 182-func bridgeHView(count int) string {244+func bridgeHView(count int, solved bool) string {183245 r := "───"184246 if count == 2 {185247 r = "═══"186248 }187- return bridgeHStyle.Width(cellWidth).AlignHorizontal(lipgloss.Center).Render(r)249+ s := bridgeHStyle250+ if solved {251+ s = bridgeHSolvedStyle252+ }253+ return s.Width(cellWidth).AlignHorizontal(lipgloss.Center).Render(r)188254 }189255 190-func bridgeVView(count int) string {256+func bridgeVView(count int, solved bool) string {191257 r := "│"192258 if count == 2 {193259 r = "║"194260 }195- return bridgeVStyle.Width(cellWidth).AlignHorizontal(lipgloss.Center).Render(r)261+ s := bridgeVStyle262+ if solved {263+ s = bridgeVSolvedStyle264+ }265+ return s.Width(cellWidth).AlignHorizontal(lipgloss.Center).Render(r)196266 }197267 198-func titleBarView(modeName string, solved bool) string {199- return game.TitleBarView("Hashiwokakero", modeName, solved)268+func titleBarView(modeTitle string, solved bool) string {269+ return game.TitleBarView("Hashiwokakero", modeTitle, solved)200270 }201271 202-func statusBarView(selected bool) string {272+func statusBarView(selected, showFullHelp bool) string {203273 if selected {204- return statusBarStyle.Render("arrows/wasd: build bridge enter/space/esc: deselect ctrl+n: menu ctrl+e: debug")274+ if showFullHelp {275+ return statusBarStyle.Render("arrows/wasd: build bridge enter/space/esc: deselect ctrl+n: menu ctrl+h: help")276+ }277+ return statusBarStyle.Render("arrows/wasd: build bridge enter/space/esc: deselect")205278 }206- return statusBarStyle.Render("arrows/wasd: move enter/space: select island ctrl+n: menu ctrl+e: debug")279+ if showFullHelp {280+ return statusBarStyle.Render("arrows/wasd: move enter/space: select island ctrl+n: menu ctrl+h: help")281+ }282+ return statusBarStyle.Render("enter/space: select island")207283 }208284 209285 func infoView(p *Puzzle) string {
@@ -215,9 +291,12 @@ func infoView(p *Puzzle) string {
215291 }216292 }217293 294+ satisfiedStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(colorInfoSatisfied))295+ infoStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(colorInfoText))296+218297 var sb strings.Builder219- sb.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("#888888")).Render(220- fmt.Sprintf("Islands: %d/%d satisfied Bridges: %d", satisfied, total, len(p.Bridges)),221- ))298+ sb.WriteString(infoStyle.Render("Islands: "))299+ sb.WriteString(satisfiedStyle.Render(fmt.Sprintf("%d", satisfied)))300+ sb.WriteString(infoStyle.Render(fmt.Sprintf("/%d satisfied Bridges: %d", total, len(p.Bridges))))222301 return sb.String()223302 }
M model.go +11 -3
@@ -72,6 +72,7 @@ type model struct {
7272 debug bool7373 debugRenderer *glamour.TermRenderer7474 debuginfo string75+ showFullHelp bool7576 7677 store *store.Store7778 activeGameID int64
@@ -150,7 +151,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
150151 if err != nil {151152 return m, nil152153 }153- m.game = g154+ name := generateUniqueName(m.store)155+ m.game = g.SetTitle(name)156+ m.game, _ = m.game.Update(game.HelpToggleMsg{Show: m.showFullHelp})154157 m.state = gameView155158 m.completionSaved = false156159
@@ -160,7 +163,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
160163 log.Printf("failed to get initial save: %v", err)161164 return m, nil162165 }163- name := generateUniqueName(m.store)164166 rec := &store.GameRecord{165167 Name: name,166168 GameType: m.selectedCategory.Name,
@@ -191,7 +193,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
191193 log.Printf("failed to import game: %v", err)192194 return m, nil193195 }194- m.game = g196+ m.game = g.SetTitle(rec.Name)197+ m.game, _ = m.game.Update(game.HelpToggleMsg{Show: m.showFullHelp})195198 m.activeGameID = rec.ID196199 m.state = gameView197200 m.completionSaved = rec.Status == store.StatusCompleted
@@ -211,6 +214,11 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
211214 return m, tea.Quit212215 case tea.KeyCtrlE:213216 m.debug = !m.debug217+ case tea.KeyCtrlH:218+ m.showFullHelp = !m.showFullHelp219+ if m.state == gameView && m.game != nil {220+ m.game, _ = m.game.Update(game.HelpToggleMsg{Show: m.showFullHelp})221+ }214222 }215223 }216224
M nonogram/Model.go +18 -10
@@ -23,7 +23,8 @@ type Model struct {
2323 cursor game.Cursor2424 grid grid2525 keys KeyMap26- modeName string26+ modeTitle string27+ showFullHelp bool2728 }2829 2930 func New(mode NonogramMode, hints Hints, save ...string) (game.Gamer, error) {
@@ -37,13 +38,13 @@ func New(mode NonogramMode, hints Hints, save ...string) (game.Gamer, error) {
3738 }3839 s := loadSave(createEmptyState(h, w), save...)3940 m := Model{40- width: w,41- height: h,42- rowHints: r,43- colHints: c,44- grid: newGrid(s),45- keys: DefaultKeyMap,46- modeName: mode.Title(),41+ width: w,42+ height: h,43+ rowHints: r,44+ colHints: c,45+ grid: newGrid(s),46+ keys: DefaultKeyMap,47+ modeTitle: mode.Title(),4748 }4849 4950 return m, nil
@@ -55,6 +56,8 @@ func (m Model) Init() tea.Cmd {
5556 5657 func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {5758 switch msg := msg.(type) {59+ case game.HelpToggleMsg:60+ m.showFullHelp = msg.Show5861 case tea.KeyMsg:5962 switch {6063 case key.Matches(msg, m.keys.FillTile):
@@ -77,12 +80,12 @@ func (m Model) View() string {
7780 7881 maxWidth, maxHeight := m.rowHints.RequiredLen()*cellWidth, m.colHints.RequiredLen()7982 80- title := nonoTitleBarView(m.modeName, solved)83+ title := nonoTitleBarView(m.modeTitle, solved)8184 g := gridView(m.grid, m.cursor, solved)8285 r := rowHintView(m.rowHints, maxWidth, curr.rows)8386 c := colHintView(m.colHints, maxHeight, curr.cols)8487 spacer := baseStyle.Width(maxWidth).Height(maxHeight).Render("")85- status := nonoStatusBarView(m.keys)88+ status := nonoStatusBarView(m.showFullHelp)8689 8790 s1 := lipgloss.JoinHorizontal(lipgloss.Bottom, spacer, c)8891 s2 := lipgloss.JoinHorizontal(lipgloss.Top, r, g)
@@ -167,6 +170,11 @@ func intSliceStr(s []int) string {
167170 return result168171 }169172 173+func (m Model) SetTitle(t string) game.Gamer {174+ m.modeTitle = t175+ return m176+}177+170178 func (m Model) IsSolved() bool {171179 curr := generateTomography(m.grid)172180 return curr.rows.equal(m.rowHints) && curr.cols.equal(m.colHints)
M nonogram/style.go +21 -18
@@ -12,29 +12,29 @@ var (
1212 baseStyle = lipgloss.NewStyle()1313 1414 filledStyle = baseStyle.15- Foreground(lipgloss.Color("#ffffff")).16- Background(lipgloss.Color("#4a4a4a"))15+ Foreground(lipgloss.Color("#ffd599")).16+ Background(lipgloss.Color("#3d2e1a"))1717 1818 markedStyle = baseStyle.19- Foreground(lipgloss.Color("#ff6b6b")).20- Background(lipgloss.Color("#2a1a1a"))19+ Foreground(lipgloss.Color("#c97b5a")).20+ Background(lipgloss.Color("#2a1f17"))2121 2222 emptyStyle = baseStyle.23- Foreground(lipgloss.Color("#333333")).24- Background(lipgloss.Color("#1a1a1a"))23+ Foreground(lipgloss.Color("#4a3f35")).24+ Background(lipgloss.Color("#1e1a16"))2525 2626 cursorStyle = baseStyle.2727 Bold(true).28- Foreground(lipgloss.Color("#ffffff")).29- Background(lipgloss.Color("#ff00ff"))28+ Foreground(lipgloss.Color("#1e1a16")).29+ Background(lipgloss.Color("#f0a84a"))3030 31- crosshairBG = lipgloss.Color("#252525")31+ crosshairBG = lipgloss.Color("#2a2520")3232 3333 hintStyle = baseStyle.34- Foreground(lipgloss.Color("#888888"))34+ Foreground(lipgloss.Color("#8a7e72"))3535 3636 hintSatisfiedStyle = baseStyle.37- Foreground(lipgloss.Color("#00ff00"))37+ Foreground(lipgloss.Color("#b8d46a"))3838 3939 nonoStatusBarStyle = lipgloss.NewStyle().4040 Foreground(lipgloss.Color("#888888")).
@@ -51,9 +51,9 @@ var (
5151 }5252 5353 renderRuneMap = map[rune]string{54- filledTile: "▐█",55- markedTile: "✕",56- emptyTile: " ",54+ filledTile: " ■ ",55+ markedTile: " ✕ ",56+ emptyTile: " · ",5757 }5858 )5959
@@ -165,12 +165,15 @@ func tileView(val rune, isCursor, inCursorRow, inCursorCol, solved bool) string
165165 return s.Width(cellWidth).AlignHorizontal(lipgloss.Center).Render(r)166166 }167167 168-func nonoTitleBarView(modeName string, solved bool) string {169- return game.TitleBarView("Nonogram", modeName, solved)168+func nonoTitleBarView(modeTitle string, solved bool) string {169+ return game.TitleBarView("Nonogram", modeTitle, solved)170170 }171171 172-func nonoStatusBarView(_ KeyMap) string {173- return nonoStatusBarStyle.Render("arrows/wasd: move z: fill x: mark bkspc: clear ctrl+n: menu ctrl+e: debug")172+func nonoStatusBarView(showFullHelp bool) string {173+ if showFullHelp {174+ return nonoStatusBarStyle.Render("arrows/wasd: move z: fill x: mark bkspc: clear ctrl+n: menu ctrl+h: help")175+ }176+ return nonoStatusBarStyle.Render("z: fill x: mark bkspc: clear")174177 }175178 176179 func intSliceEqual(a, b []int) bool {
M sudoku/Model.go +19 -11
@@ -14,21 +14,22 @@ import (
1414 const gridSize = 91515 1616 type Model struct {17- cursor game.Cursor18- grid grid19- provided []cell20- keys KeyMap21- modeName string17+ cursor game.Cursor18+ grid grid19+ provided []cell20+ keys KeyMap21+ modeTitle string22+ showFullHelp bool2223 }2324 2425 // New creates a new sudoku game using the provided cell values.2526 func New(mode SudokuMode, provided []cell, save ...string) (game.Gamer, error) {2627 g := loadSave(newGrid(provided), save...)2728 m := Model{28- grid: g,29- provided: provided,30- keys: DefaultKeyMap,31- modeName: mode.Title(),29+ grid: g,30+ provided: provided,31+ keys: DefaultKeyMap,32+ modeTitle: mode.Title(),3233 }3334 return m, nil3435 }
@@ -41,6 +42,8 @@ func (m Model) Init() tea.Cmd {
4142 // Update implements game.Gamer.4243 func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {4344 switch msg := msg.(type) {45+ case game.HelpToggleMsg:46+ m.showFullHelp = msg.Show4447 case tea.KeyMsg:4548 switch {4649 case key.Matches(msg, m.keys.FillValue):
@@ -56,6 +59,11 @@ func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {
5659 return m, nil5760 }5861 62+func (m Model) SetTitle(t string) game.Gamer {63+ m.modeTitle = t64+ return m65+}66+5967 // IsSolved implements game.Gamer.6068 func (m Model) IsSolved() bool {6169 return m.isSolved()
@@ -71,9 +79,9 @@ func (m *Model) updateCell(v int) {
7179 7280 // View implements game.Gamer.7381 func (m Model) View() string {74- title := titleBarView(m.modeName, m.isSolved())82+ title := titleBarView(m.modeTitle, m.isSolved())7583 grid := renderGrid(m)76- status := statusBarView()84+ status := statusBarView(m.showFullHelp)7785 7886 return lipgloss.JoinVertical(lipgloss.Left, title, grid, status)7987 }
M sudoku/style.go +7 -4
M wordsearch/Model.go +10 -2
M wordsearch/style.go +7 -4