clone

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

fix daily algorithm. update help and menu tape files.

Dami committed 50495ad
commit
50495ad15465aa27199b6e671c2c3ba9f76b20ce
parent
f3b28a82f0cf39badc1579bbfff5db05d9165572
author
Dami <dami.etoile@gmail.com>
3 files changed, +57 -21
Mdaily.go69++++++++++++++++++++++--------
Mvhs/help.tape2+-
Mvhs/menu.tape7++-
M daily.go +51 -18
@@ -1,35 +1,68 @@
11 package main22 33 import (4- "fmt"4+ "hash/fnv"55 "math/rand/v2"66 "time"77 88 "github.com/FelineStateMachine/puzzletea/game"9+ "github.com/FelineStateMachine/puzzletea/hashiwokakero"10+ "github.com/FelineStateMachine/puzzletea/hitori"11+ "github.com/FelineStateMachine/puzzletea/lightsout"912 "github.com/FelineStateMachine/puzzletea/namegen"13+ "github.com/FelineStateMachine/puzzletea/nonogram"14+ "github.com/FelineStateMachine/puzzletea/sudoku"15+ "github.com/FelineStateMachine/puzzletea/takuzu"16+ "github.com/FelineStateMachine/puzzletea/wordsearch"1017 )1118 12-// dailyRNG returns a deterministic RNG seeded from the given date.13-// The same calendar day always produces the same seed, ensuring14-// reproducible daily puzzles regardless of the time of day.15-func dailyRNG(today time.Time) *rand.Rand {16- y, m, d := today.Date()17- seed := uint64(y)*10000 + uint64(m)*100 + uint64(d)19+// dailyEntry pairs a SeededSpawner with metadata for the eligible daily pool.20+type dailyEntry struct {21+ spawner game.SeededSpawner22+ gameType string23+ mode string24+}25+26+// eligibleDailyModes is the curated pool of quick (<10 min) modes for daily rotation.27+var eligibleDailyModes = []dailyEntry{28+ {nonogram.NewMode("Standard", "10x10 grid, ~67% filled. Classic size, dense hints.", 10, 10, 0.67), "Nonogram", "Standard"},29+ {nonogram.NewMode("Classic", "10x10 grid, ~52% filled. The typical nonogram experience.", 10, 10, 0.52), "Nonogram", "Classic"},30+ {sudoku.NewMode("Easy", "38\u201344 clues. Naked Singles.", 38), "Sudoku", "Easy"},31+ {sudoku.NewMode("Medium", "32\u201337 clues. Hidden Pairs / Pointing.", 32), "Sudoku", "Medium"},32+ {takuzu.NewMode("Medium", "8\u00d78 grid, ~40% clues. Larger grid, moderate deduction.", 8, 0.40), "Takuzu", "Medium"},33+ {takuzu.NewMode("Tricky", "10\u00d710 grid, ~38% clues. Uniqueness rule needed.", 10, 0.38), "Takuzu", "Tricky"},34+ {hashiwokakero.NewMode("Easy 9x9", "9x9 grid with 12-16 islands.", 9, 9, 12, 16), "Hashiwokakero", "Easy 9x9"},35+ {hashiwokakero.NewMode("Medium 7x7", "7x7 grid with 12-15 islands.", 7, 7, 12, 15), "Hashiwokakero", "Medium 7x7"},36+ {hitori.NewMode("Easy", "6\u00d76 grid, straightforward logic.", 6, 0.32), "Hitori", "Easy"},37+ {hitori.NewMode("Medium", "8\u00d78 grid, moderate challenge.", 8, 0.30), "Hitori", "Medium"},38+ {lightsout.NewMode("Medium", "5x5 grid", 5, 5), "Lights Out", "Medium"},39+ {lightsout.NewMode("Hard", "7x7 grid", 7, 7), "Lights Out", "Hard"},40+ {wordsearch.NewMode("Easy 10x10", "Find 6 words in a 10x10 grid.", 10, 10, 6, 3, 5, []wordsearch.Direction{wordsearch.Right, wordsearch.Down, wordsearch.DownRight}), "Word Search", "Easy 10x10"},41+}42+43+// dailySeed returns a deterministic int64 seed derived from the date.44+func dailySeed(date time.Time) uint64 {45+ dateStr := date.Format("2006-01-02")46+ h := fnv.New64a()47+ h.Write([]byte(dateStr))48+ return h.Sum64()49+}50+51+// dailyRNG creates a deterministic RNG seeded from the given date.52+func dailyRNG(date time.Time) *rand.Rand {53+ seed := dailySeed(date)1854 return rand.New(rand.NewPCG(seed, seed))1955 }2056 21-// dailyName builds the unique identifier for today's daily puzzle.22-// Format: "Daily <Mon DD YY> <adjective-noun>"23-func dailyName(today time.Time, rng *rand.Rand) string {24- datePart := today.Format("Jan _2 06")25- namePart := namegen.GenerateSeeded(rng)26- return fmt.Sprintf("Daily %s %s", datePart, namePart)57+// dailyName generates the daily puzzle name in the format:58+// "Daily Feb 14 26 - amber-falcon"59+func dailyName(date time.Time, rng *rand.Rand) string {60+ return "Daily " + date.Format("Jan _2 06") + " - " + namegen.GenerateSeeded(rng)2761 }2862 29-// dailyMode picks a random game category and mode using the provided RNG.30-// Returns the SeededSpawner, the canonical game type name, and the mode title.63+// dailyMode selects the daily mode from the eligible pool using the seeded RNG.64+// Returns the spawner, game type name, and mode title.3165 func dailyMode(rng *rand.Rand) (game.SeededSpawner, string, string) {32- cat := GameCategories[rng.IntN(len(GameCategories))].(game.Category)33- mode := cat.Modes[rng.IntN(len(cat.Modes))].(game.Mode)34- return mode.(game.SeededSpawner), cat.Name, mode.Title()66+ entry := eligibleDailyModes[rng.IntN(len(eligibleDailyModes))]67+ return entry.spawner, entry.gameType, entry.mode3568 }
M vhs/help.tape +1 -1
M vhs/menu.tape +5 -2