clone

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

chore/app-model-decomp
puzzletea / cmd / root.go
root.go 3.3K
1// Package cmd defines the CLI commands for PuzzleTea using Cobra.2package cmd34import (5 "fmt"6 "log"7 "strings"89 "github.com/FelineStateMachine/puzzletea/app"10 "github.com/FelineStateMachine/puzzletea/config"11 "github.com/FelineStateMachine/puzzletea/store"12 "github.com/FelineStateMachine/puzzletea/theme"1314 tea "charm.land/bubbletea/v2"15 "github.com/spf13/cobra"16)1718// Version is set at build time via ldflags:19//20// go build -ldflags "-X github.com/FelineStateMachine/puzzletea/cmd.Version=v1.0.0"21var Version = "dev"2223var (24 flagNew string25 flagContinue string26 flagTheme string27 flagConfigPath string28 // flagSetSeed is declared in new.go and shared across root and new commands.29)3031var (32 launchNewGameFn = launchNewGame33 launchSeededGameFn = launchSeededGame34 continueGameFn = continueGame35 openStoreFn = store.Open36)3738// RootCmd is the top-level Cobra command.39var RootCmd = &cobra.Command{40 Use: "puzzletea",41 Version: Version,42 Short: "A terminal-based puzzle game framework",43 Long: "PuzzleTea is a terminal-based puzzle game framework featuring Nonogram, Nurikabe, Sudoku, Word Search, Hashiwokakero, and Lights Out.",44 RunE: func(cmd *cobra.Command, args []string) error {45 cfg := loadActiveConfig()4647 if flagSetSeed != "" {48 if flagNew != "" || flagContinue != "" {49 return fmt.Errorf("--set-seed cannot be combined with --new or --continue")50 }51 return launchSeededGameFn(flagSetSeed, cfg)52 }53 if flagNew != "" {54 parts := strings.SplitN(flagNew, ":", 2)55 gameArg := parts[0]56 var modeArg string57 if len(parts) > 1 {58 modeArg = parts[1]59 }60 return launchNewGameFn(gameArg, modeArg, "", cfg)61 }62 if flagContinue != "" {63 return continueGameFn(flagContinue, cfg)64 }65 // Default: launch TUI menu.66 s, err := openStoreFn(cfg.DBPath)67 if err != nil {68 return err69 }70 defer s.Close()71 p := tea.NewProgram(app.InitialModel(s, cfg, activeConfigPath()))72 _, err = p.Run()73 return err74 },75}7677func init() {78 RootCmd.Flags().StringVar(&flagNew, "new", "", "start a new game (game:mode)")79 RootCmd.Flags().StringVar(&flagContinue, "continue", "", "resume a saved game by name")80 RootCmd.Flags().StringVar(&flagSetSeed, "set-seed", "", "seed string for deterministic puzzle selection and generation")81 RootCmd.PersistentFlags().StringVar(&flagConfigPath, "config", "", "path to config file (default: ~/.puzzletea/config.json)")82 RootCmd.PersistentFlags().StringVar(&flagTheme, "theme", "", "color theme name (overrides config)")8384 RootCmd.AddCommand(newCmd, continueCmd, listCmd, exportCmd, exportPDFCmd, testCmd)85}8687func loadActiveConfig() *config.Config {88 return loadConfig(activeConfigPath())89}9091func activeConfigPath() string {92 if flagConfigPath == "" {93 return config.DefaultPath()94 }95 return flagConfigPath96}9798// loadConfig reads the config file and applies the active theme. The --theme99// flag takes precedence over the persisted setting.100func loadConfig(configPath string) *config.Config {101 cfg, err := config.Load(configPath)102 if err != nil {103 log.Printf("warning: %v (using defaults)", err)104 cfg = config.Default()105 }106107 themeName := cfg.Theme108 if flagTheme != "" {109 themeName = flagTheme110 }111 applyTheme(themeName)112 return cfg113}114115func applyTheme(themeName string) {116 if err := theme.Apply(themeName); err != nil {117 log.Printf("warning: %v (using default theme)", err)118 }119}