clone

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

feat/app-decomposition
puzzletea / daily / daily.go
daily.go 2.0K
1package daily23import (4 "hash/fnv"5 "math/rand/v2"6 "time"78 "github.com/FelineStateMachine/puzzletea/game"9 "github.com/FelineStateMachine/puzzletea/namegen"10 "github.com/FelineStateMachine/puzzletea/registry"11 "github.com/FelineStateMachine/puzzletea/schedule"12)1314type Entry = schedule.Entry1516// eligibleModes is the flattened pool built from each package's DailyModes.17var eligibleModes = buildEligibleModes()1819func buildEligibleModes() []Entry {20 return schedule.BuildEligibleModes(registry.DailyEntries())21}2223// Seed returns a deterministic uint64 seed derived from the date.24func Seed(date time.Time) uint64 {25 dateStr := date.Format("2006-01-02")26 h := fnv.New64a()27 h.Write([]byte(dateStr))28 return h.Sum64()29}3031// RNG creates a deterministic RNG seeded from the given date.32func RNG(date time.Time) *rand.Rand {33 seed := Seed(date)34 return rand.New(rand.NewPCG(seed, ^seed))35}3637// Name generates the daily puzzle name in the format:38// "Daily Feb 14 26 - amber-falcon"39//40// Name uses its own sub-RNG derived from the date so that changes to41// the namegen word lists cannot affect mode selection or puzzle generation.42func Name(date time.Time) string {43 h := fnv.New64a()44 h.Write([]byte("name:"))45 h.Write([]byte(date.Format("2006-01-02")))46 seed := h.Sum64()47 nameRNG := rand.New(rand.NewPCG(seed, ^seed))48 return "Daily " + date.Format("Jan _2 06") + " - " + namegen.GenerateSeeded(nameRNG)49}5051// Mode selects the daily mode from the eligible pool using rendezvous52// hashing (highest random weight). Each entry is scored by hashing the53// date together with its (GameType, Mode) pair; the highest score wins.54//55// This is resilient to changes in the pool: adding or removing an entry56// only affects dates where the changed entry would have been the winner.57func Mode(date time.Time) (game.SeededSpawner, string, string) {58 if len(eligibleModes) == 0 {59 return nil, "", ""60 }6162 dateStr := date.Format("2006-01-02")63 best, found := schedule.SelectBySeed(dateStr, eligibleModes)64 if !found {65 return nil, "", ""66 }67 return best.Spawner, best.GameType, best.Mode68}