clone

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

chore/regenerate-vhs-gifs
puzzletea / session / session.go
session.go 3.5K
1package session23import (4 "fmt"5 "strconv"6 "strings"78 "github.com/FelineStateMachine/puzzletea/game"9 "github.com/FelineStateMachine/puzzletea/namegen"10 "github.com/FelineStateMachine/puzzletea/puzzle"11 "github.com/FelineStateMachine/puzzletea/registry"12 "github.com/FelineStateMachine/puzzletea/resolve"13 "github.com/FelineStateMachine/puzzletea/store"14)1516// NormalizeSeed keeps seeded names distinct from real dailies.17func NormalizeSeed(seed string) string {18 seed = strings.TrimSpace(seed)19 for _, reserved := range []string{"daily", "week"} {20 if strings.HasPrefix(strings.ToLower(seed), reserved) {21 return strings.ToLower(seed[:len(reserved)]) + seed[len(reserved):]22 }23 }24 return seed25}2627// SeededName derives the deterministic display name for a seeded puzzle.28func SeededName(seed string) string {29 nameRNG := resolve.RNGFromString("name:" + seed)30 return seed + " - " + namegen.GenerateSeeded(nameRNG)31}3233// SeededNameForGame derives the deterministic display name for a seeded34// puzzle locked to a specific game.35func SeededNameForGame(seed, gameType string) string {36 if strings.TrimSpace(gameType) == "" {37 return SeededName(seed)38 }3940 scope := game.NormalizeName(gameType)41 nameRNG := resolve.RNGFromString("name:" + seed + ":" + scope)42 return fmt.Sprintf("%s [%s] - %s",43 seed,44 gameType,45 namegen.GenerateSeeded(nameRNG),46 )47}4849// ImportRecord reconstructs a saved game and reapplies the record title.50func ImportRecord(rec *store.GameRecord) (game.Gamer, error) {51 if rec == nil {52 return nil, fmt.Errorf("nil game record")53 }5455 g, err := registry.Import(rec.GameType, []byte(rec.SaveState))56 if err != nil {57 return nil, err58 }59 return g.SetTitle(rec.Name), nil60}6162// CreateRecord persists the initial state for a newly created game.63func CreateRecord(64 s *store.Store,65 g game.Gamer,66 name string,67 gameType string,68 modeTitle string,69 run store.RunMetadata,70) (*store.GameRecord, error) {71 initialState, err := g.GetSave()72 if err != nil {73 return nil, fmt.Errorf("failed to get initial save: %w", err)74 }7576 rec := &store.GameRecord{77 Name: name,78 GameID: string(puzzle.CanonicalGameID(gameType)),79 GameType: gameType,80 ModeID: string(puzzle.CanonicalModeID(modeTitle)),81 Mode: modeTitle,82 InitialState: string(initialState),83 SaveState: string(initialState),84 Status: store.StatusNew,85 RunKind: run.Kind,86 RunDate: run.Date,87 WeekYear: run.WeekYear,88 WeekNumber: run.WeekNumber,89 WeekIndex: run.WeekIndex,90 SeedText: run.SeedText,91 }92 if err := s.CreateGame(rec); err != nil {93 return nil, fmt.Errorf("failed to create game record: %w", err)94 }95 return rec, nil96}9798// ResumeAbandonedDeterministicRecord marks an abandoned deterministic game99// as in-progress before resuming it.100func ResumeAbandonedDeterministicRecord(s *store.Store, rec *store.GameRecord) error {101 if rec == nil || rec.Status != store.StatusAbandoned {102 return nil103 }104 if err := s.UpdateStatus(rec.ID, store.StatusInProgress); err != nil {105 return fmt.Errorf("failed to mark deterministic game in progress: %w", err)106 }107 return nil108}109110// GenerateUniqueName generates a unique adjective-noun name, retrying if needed.111func GenerateUniqueName(s *store.Store) string {112 for range 100 {113 name := namegen.Generate()114 exists, err := s.NameExists(name)115 if err != nil || !exists {116 return name117 }118 }119 base := namegen.Generate()120 for i := 1; ; i++ {121 name := base + "-" + strconv.Itoa(i)122 exists, err := s.NameExists(name)123 if err != nil || !exists {124 return name125 }126 }127}