- commit
- 29af4f0757dca79265bedf3ffcabb4a752d4fa61
- parent
- 87f57c3c7a24fb0f825cbbcacd22e573828d3f04
- author
- Dami <dami.etoile@gmail.com>
| M | daily.go | 6 | +++++- |
| M | game/ | 7 | +++++++ |
| M | hashiwokakero/ | 6 | ++---- |
| M | hitori/ | 1 | + |
| M | hitori/ | 9 | +++++++-- |
| M | hitori/ | 5 | +---- |
| M | lightsout/ | 5 | +---- |
| M | model.go | 12 | ++++-------- |
| M | nonogram/ | 5 | +---- |
| M | store/ | 3 | ++- |
| M | takuzu/ | 5 | +---- |
@@ -69,11 +69,15 @@ func dailySeed(date time.Time) uint64 {
6969 // dailyRNG creates a deterministic RNG seeded from the given date.7070 func dailyRNG(date time.Time) *rand.Rand {7171 seed := dailySeed(date)72- return rand.New(rand.NewPCG(seed, seed))72+ return rand.New(rand.NewPCG(seed, ^seed))7373 }7474 7575 // dailyName generates the daily puzzle name in the format:7676 // "Daily Feb 14 26 - amber-falcon"77+//78+// NOTE: this must be called before dailyMode on the same RNG — the number79+// of draws here determines which mode is selected. Changing this function80+// will shift daily puzzles for all future dates.7781 func dailyName(date time.Time, rng *rand.Rand) string {7882 return "Daily " + date.Format("Jan _2 06") + " - " + namegen.GenerateSeeded(rng)7983 }@@ -17,6 +17,13 @@ var (
1717 solvedBadgeStyle = lipgloss.NewStyle().1818 Bold(true).1919 Foreground(lipgloss.AdaptiveColor{Light: "28", Dark: "78"})20+21+ // CursorSolvedStyle highlights the cursor position on a solved grid.22+ // Shared across all puzzle types.23+ CursorSolvedStyle = lipgloss.NewStyle().24+ Bold(true).25+ Foreground(lipgloss.AdaptiveColor{Light: "255", Dark: "235"}).26+ Background(lipgloss.AdaptiveColor{Light: "28", Dark: "28"})2027 )2128 2229 // TitleBarView renders a title bar with the game name, mode name, and optional solved badge.@@ -4,6 +4,7 @@ import (
44 "fmt"55 "strings"66 7+ "github.com/FelineStateMachine/puzzletea/game"78 "github.com/charmbracelet/lipgloss"89 )910 @@ -53,10 +54,7 @@ var (
5354 Background(colorCursorBg).5455 Bold(true)5556 56- islandCursorSolvedStyle = baseStyle.57- Foreground(lipgloss.AdaptiveColor{Dark: "255", Light: "235"}).58- Background(lipgloss.AdaptiveColor{Dark: "28", Light: "28"}).59- Bold(true)57+ islandCursorSolvedStyle = game.CursorSolvedStyle6058 6159 islandSelectedStyle = baseStyle.6260 Foreground(colorSelectedFg).@@ -52,5 +52,6 @@ func ImportModel(data []byte) (*Model, error) {
5252 }5353 m.initialMarks = newMarks(s.Size)5454 m.solved = m.checkSolved()55+ m.conflicts = computeConflicts(m.numbers, m.marks, m.size)5556 return m, nil5657 }@@ -17,6 +17,7 @@ type Model struct {
1717 numbers grid1818 marks [][]cellMark1919 initialMarks [][]cellMark20+ conflicts [][]bool2021 cursor game.Cursor2122 solved bool2223 keys KeyMap@@ -51,6 +52,7 @@ func New(mode HitoriMode, numbers grid) (game.Gamer, error) {
5152 modeTitle: mode.Title(),5253 }5354 m.solved = m.checkSolved()55+ m.conflicts = computeConflicts(m.numbers, m.marks, m.size)5456 return m, nil5557 }5658 @@ -65,6 +67,7 @@ func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {
6567 case game.ResetMsg:6668 m.marks = cloneMarks(m.initialMarks)6769 m.solved = m.checkSolved()70+ m.conflicts = computeConflicts(m.numbers, m.marks, m.size)6871 m.cursor = game.Cursor{}6972 case tea.KeyMsg:7073 switch {@@ -80,6 +83,7 @@ func (m Model) Update(msg tea.Msg) (game.Gamer, tea.Cmd) {
8083 if !m.solved {8184 m.marks[m.cursor.Y][m.cursor.X] = unmarked8285 m.solved = m.checkSolved()86+ m.conflicts = computeConflicts(m.numbers, m.marks, m.size)8387 }8488 default:8589 m.cursor.Move(m.keys.CursorKeyMap, msg, m.size-1, m.size-1)@@ -98,12 +102,12 @@ func (m *Model) toggleMark(mark cellMark) {
98102 m.marks[m.cursor.Y][m.cursor.X] = mark99103 }100104 m.solved = m.checkSolved()105+ m.conflicts = computeConflicts(m.numbers, m.marks, m.size)101106 }102107 103108 func (m Model) View() string {104109 title := game.TitleBarView("Hitori", m.modeTitle, m.solved)105- conflicts := computeConflicts(m.numbers, m.marks, m.size)106- grid := gridView(m.numbers, m.marks, m.cursor, m.solved, conflicts)110+ grid := gridView(m.numbers, m.marks, m.cursor, m.solved, m.conflicts)107111 status := statusBarView(m.showFullHelp)108112 return lipgloss.JoinVertical(lipgloss.Center, title, grid, status)109113 }@@ -120,6 +124,7 @@ func (m Model) IsSolved() bool {
120124 func (m Model) Reset() game.Gamer {121125 m.marks = cloneMarks(m.initialMarks)122126 m.solved = m.checkSolved()127+ m.conflicts = computeConflicts(m.numbers, m.marks, m.size)123128 m.cursor = game.Cursor{}124129 return m125130 }@@ -29,10 +29,7 @@ var (
2929 Foreground(lipgloss.AdaptiveColor{Light: "255", Dark: "235"}).3030 Background(lipgloss.AdaptiveColor{Light: "130", Dark: "214"})3131 32- cursorSolvedStyle = baseStyle.33- Bold(true).34- Foreground(lipgloss.AdaptiveColor{Light: "255", Dark: "235"}).35- Background(lipgloss.AdaptiveColor{Light: "28", Dark: "28"})32+ cursorSolvedStyle = game.CursorSolvedStyle3633 3734 conflictStyle = baseStyle.3835 Foreground(lipgloss.AdaptiveColor{Light: "160", Dark: "167"}).@@ -24,10 +24,7 @@ var (
2424 Foreground(lipgloss.AdaptiveColor{Light: "255", Dark: "235"}).2525 Background(lipgloss.AdaptiveColor{Light: "130", Dark: "173"})2626 27- cursorSolvedStyle = baseStyle.28- Bold(true).29- Foreground(lipgloss.AdaptiveColor{Light: "255", Dark: "235"}).30- Background(lipgloss.AdaptiveColor{Light: "28", Dark: "28"})27+ cursorSolvedStyle = game.CursorSolvedStyle3128 3229 solvedStyle = baseStyle.3330 Background(lipgloss.AdaptiveColor{Light: "151", Dark: "22"})@@ -306,7 +306,7 @@ func (m model) handleDailyPuzzle() (tea.Model, tea.Cmd) {
306306 log.Printf("failed to import daily game: %v", err)307307 return m, nil308308 }309- m.game = g.SetTitle(dailyTitle(rec.Name))309+ m.game = g.SetTitle(rec.Name)310310 m.game, _ = m.game.Update(game.HelpToggleMsg{Show: m.showFullHelp})311311 m.activeGameID = rec.ID312312 m.state = gameView@@ -330,12 +330,6 @@ func (m model) handleDailyPuzzle() (tea.Model, tea.Cmd) {
330330 return m, tea.Batch(m.spinner.Tick, spawnSeededCmd(spawner, rng))331331 }332332 333-// dailyTitle returns the display title for a daily game.334-// The name already starts with "Daily", so no extra prefix is needed.335-func dailyTitle(name string) string {336- return name337-}338-339333 func (m model) handleGameSelectEnter() (tea.Model, tea.Cmd) {340334 cat, ok := m.gameSelectList.SelectedItem().(game.Category)341335 if !ok {@@ -368,6 +362,8 @@ func spawnCmd(spawner game.Spawner) tea.Cmd {
368362 }369363 370364 // spawnSeededCmd returns a tea.Cmd that runs SpawnSeeded() off the main goroutine.365+// The caller must not use rng after this call — *rand.Rand is not goroutine-safe366+// and ownership is transferred to the spawned goroutine.371367 func spawnSeededCmd(spawner game.SeededSpawner, rng *rand.Rand) tea.Cmd {372368 return func() tea.Msg {373369 g, err := spawner.SpawnSeeded(rng)@@ -400,7 +396,7 @@ func (m model) handleSpawnComplete(msg game.SpawnCompleteMsg) (tea.Model, tea.Cm
400396 name = m.dailyName401397 gameType = m.dailyGameType402398 modeTitle = m.dailyModeTitle403- m.game = msg.Game.SetTitle(dailyTitle(name))399+ m.game = msg.Game.SetTitle(name)404400 } else {405401 name = generateUniqueName(m.store)406402 gameType = m.selectedCategory.Name@@ -28,10 +28,7 @@ var (
2828 Foreground(lipgloss.AdaptiveColor{Light: "255", Dark: "235"}).2929 Background(lipgloss.AdaptiveColor{Light: "130", Dark: "214"})3030 31- cursorSolvedStyle = baseStyle.32- Bold(true).33- Foreground(lipgloss.AdaptiveColor{Light: "255", Dark: "235"}).34- Background(lipgloss.AdaptiveColor{Light: "28", Dark: "28"})31+ cursorSolvedStyle = game.CursorSolvedStyle3532 3633 crosshairBG = lipgloss.AdaptiveColor{Light: "254", Dark: "237"}3734 crosshairFilledBG = lipgloss.AdaptiveColor{Light: "223", Dark: "100"}@@ -3,6 +3,7 @@ package store
33 44 import (55 "database/sql"6+ "errors"67 "fmt"78 "os"89 "path/filepath"@@ -195,7 +196,7 @@ func (s *Store) GetGameByName(name string) (*GameRecord, error) {
195196 &g.InitialState, &g.SaveState, &g.Status,196197 &g.CreatedAt, &g.UpdatedAt, &completedAt,197198 )198- if err == sql.ErrNoRows {199+ if errors.Is(err, sql.ErrNoRows) {199200 return nil, nil200201 }201202 if err != nil {@@ -25,10 +25,7 @@ var (
2525 Foreground(lipgloss.AdaptiveColor{Light: "255", Dark: "235"}).2626 Background(lipgloss.AdaptiveColor{Light: "130", Dark: "214"})2727 28- cursorSolvedStyle = baseStyle.29- Bold(true).30- Foreground(lipgloss.AdaptiveColor{Light: "255", Dark: "235"}).31- Background(lipgloss.AdaptiveColor{Light: "28", Dark: "28"})28+ cursorSolvedStyle = game.CursorSolvedStyle3229 3330 crosshairBG = lipgloss.AdaptiveColor{Light: "254", Dark: "237"}3431 solvedBG = lipgloss.AdaptiveColor{Light: "151", Dark: "22"}