clone

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

split cmd module into seperate files

Dami committed c5a1dc3
commit
c5a1dc342dc191619d0d8e6cac289190f9370175
parent
61d11a76db3d153f8b4060c96cede68099f3eca7
author
Dami <dami.etoile@gmail.com>
5 files changed, +261 -226
Dcmd/cmd.go226------------------------------
Acmd/continue.go59++++++++
Acmd/list.go58++++++++
Acmd/new.go82+++++++++++
Acmd/root.go62++++++++
D cmd/cmd.go +0 -226
@@ -1,226 +0,0 @@
1-// Package cmd defines the CLI commands for PuzzleTea using Cobra.2-package cmd3-4-import (5- "fmt"6- "log"7- "os"8- "strings"9- "text/tabwriter"10-11- "github.com/FelineStateMachine/puzzletea/app"12- "github.com/FelineStateMachine/puzzletea/game"13- "github.com/FelineStateMachine/puzzletea/resolve"14- "github.com/FelineStateMachine/puzzletea/stats"15- "github.com/FelineStateMachine/puzzletea/store"16-17- tea "charm.land/bubbletea/v2"18- "github.com/spf13/cobra"19-)20-21-// Version is set at build time via ldflags:22-//23-// go build -ldflags "-X github.com/FelineStateMachine/puzzletea/cmd.Version=v1.0.0"24-var Version = "dev"25-26-var (27- flagNew string28- flagContinue string29-)30-31-// RootCmd is the top-level Cobra command.32-var RootCmd = &cobra.Command{33- Use: "puzzletea",34- Version: Version,35- Short: "A terminal-based puzzle game framework",36- Long: "PuzzleTea is a terminal-based puzzle game framework featuring Nonogram, Sudoku, Word Search, Hashiwokakero, and Lights Out.",37- RunE: func(cmd *cobra.Command, args []string) error {38- if flagNew != "" {39- parts := strings.SplitN(flagNew, ":", 2)40- gameArg := parts[0]41- var modeArg string42- if len(parts) > 1 {43- modeArg = parts[1]44- }45- return launchNewGame(gameArg, modeArg)46- }47- if flagContinue != "" {48- return continueGame(flagContinue)49- }50- // Default: launch TUI menu.51- s, err := store.Open(store.DefaultDBPath())52- if err != nil {53- return err54- }55- defer s.Close()56- stats.InitModeXP(app.GameCategories)57- p := tea.NewProgram(app.InitialModel(s))58- _, err = p.Run()59- return err60- },61-}62-63-var newCmd = &cobra.Command{64- Use: "new <game> [mode]",65- Short: "Start a new puzzle game",66- Long: fmt.Sprintf("Start a new puzzle game, optionally specifying the difficulty mode.\n\nAvailable games:\n %s",67- strings.Join(resolve.CategoryNames(app.GameCategories), "\n ")),68- Args: cobra.RangeArgs(1, 2),69- RunE: func(cmd *cobra.Command, args []string) error {70- gameArg := args[0]71- var modeArg string72- if len(args) > 1 {73- modeArg = args[1]74- }75- return launchNewGame(gameArg, modeArg)76- },77-}78-79-var continueCmd = &cobra.Command{80- Use: "continue <name>",81- Short: "Resume a saved game by name",82- Long: "Resume a previously saved game using its unique name.\nUse 'puzzletea list' to see available saved games.",83- Args: cobra.ExactArgs(1),84- RunE: func(cmd *cobra.Command, args []string) error {85- return continueGame(args[0])86- },87-}88-89-var listAll bool90-91-var listCmd = &cobra.Command{92- Use: "list",93- Short: "List saved games",94- Long: "Display a table of saved games. Use --all to include abandoned games.",95- RunE: func(cmd *cobra.Command, args []string) error {96- s, err := store.Open(store.DefaultDBPath())97- if err != nil {98- return err99- }100- defer s.Close()101-102- var games []store.GameRecord103- if listAll {104- games, err = s.ListAllGames()105- } else {106- games, err = s.ListGames()107- }108- if err != nil {109- return err110- }111-112- if len(games) == 0 {113- fmt.Println("No saved games.")114- return nil115- }116-117- w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)118- fmt.Fprintln(w, "NAME\tGAME\tMODE\tSTATUS\tLAST UPDATED")119- for _, g := range games {120- fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",121- g.Name,122- g.GameType,123- g.Mode,124- g.Status,125- g.UpdatedAt.Local().Format("Jan 02 15:04"),126- )127- }128- return w.Flush()129- },130-}131-132-func init() {133- RootCmd.Flags().StringVar(&flagNew, "new", "", "start a new game (game:mode)")134- RootCmd.Flags().StringVar(&flagContinue, "continue", "", "resume a saved game by name")135-136- listCmd.Flags().BoolVar(&listAll, "all", false, "include abandoned games")137-138- RootCmd.AddCommand(newCmd, continueCmd, listCmd)139-}140-141-// launchNewGame resolves the game/mode, spawns a new game, and launches the TUI.142-func launchNewGame(gameArg, modeArg string) error {143- cat, err := resolve.Category(gameArg, app.GameCategories)144- if err != nil {145- return err146- }147-148- spawner, modeTitle, err := resolve.Mode(cat, modeArg)149- if err != nil {150- return err151- }152-153- g, err := spawner.Spawn()154- if err != nil {155- return fmt.Errorf("failed to spawn game: %w", err)156- }157-158- s, err := store.Open(store.DefaultDBPath())159- if err != nil {160- return err161- }162- defer s.Close()163-164- stats.InitModeXP(app.GameCategories)165-166- name := app.GenerateUniqueName(s)167- g = g.SetTitle(name)168-169- initialState, err := g.GetSave()170- if err != nil {171- return fmt.Errorf("failed to get initial save: %w", err)172- }173-174- rec := &store.GameRecord{175- Name: name,176- GameType: cat.Name,177- Mode: modeTitle,178- InitialState: string(initialState),179- SaveState: string(initialState),180- Status: store.StatusNew,181- }182- if err := s.CreateGame(rec); err != nil {183- log.Printf("failed to create game record: %v", err)184- }185-186- m := app.InitialModelWithGame(s, g, rec.ID, false)187- p := tea.NewProgram(m)188- _, err = p.Run()189- return err190-}191-192-// continueGame looks up a saved game by name and launches the TUI.193-func continueGame(name string) error {194- s, err := store.Open(store.DefaultDBPath())195- if err != nil {196- return err197- }198- defer s.Close()199-200- stats.InitModeXP(app.GameCategories)201-202- rec, err := s.GetGameByName(name)203- if err != nil {204- return err205- }206- if rec == nil {207- return fmt.Errorf("no saved game found with name %q\nRun 'puzzletea list' to see available games.", name)208- }209-210- importFn, ok := game.Registry[rec.GameType]211- if !ok {212- return fmt.Errorf("unknown game type %q in save data", rec.GameType)213- }214-215- g, err := importFn([]byte(rec.SaveState))216- if err != nil {217- return fmt.Errorf("failed to import game: %w", err)218- }219- g = g.SetTitle(rec.Name)220-221- completed := rec.Status == store.StatusCompleted222- m := app.InitialModelWithGame(s, g, rec.ID, completed)223- p := tea.NewProgram(m)224- _, err = p.Run()225- return err226-}
A cmd/continue.go +59 -0
@@ -0,0 +1,59 @@
1+package cmd2+3+import (4+ "fmt"5+6+ "github.com/FelineStateMachine/puzzletea/app"7+ "github.com/FelineStateMachine/puzzletea/game"8+ "github.com/FelineStateMachine/puzzletea/stats"9+ "github.com/FelineStateMachine/puzzletea/store"10+11+ tea "charm.land/bubbletea/v2"12+ "github.com/spf13/cobra"13+)14+15+var continueCmd = &cobra.Command{16+ Use: "continue <name>",17+ Short: "Resume a saved game by name",18+ Long: "Resume a previously saved game using its unique name.\nUse 'puzzletea list' to see available saved games.",19+ Args: cobra.ExactArgs(1),20+ RunE: func(cmd *cobra.Command, args []string) error {21+ return continueGame(args[0])22+ },23+}24+25+// continueGame looks up a saved game by name and launches the TUI.26+func continueGame(name string) error {27+ s, err := store.Open(store.DefaultDBPath())28+ if err != nil {29+ return err30+ }31+ defer s.Close()32+33+ stats.InitModeXP(app.GameCategories)34+35+ rec, err := s.GetGameByName(name)36+ if err != nil {37+ return err38+ }39+ if rec == nil {40+ return fmt.Errorf("no saved game found with name %q\nRun 'puzzletea list' to see available games.", name)41+ }42+43+ importFn, ok := game.Registry[rec.GameType]44+ if !ok {45+ return fmt.Errorf("unknown game type %q in save data", rec.GameType)46+ }47+48+ g, err := importFn([]byte(rec.SaveState))49+ if err != nil {50+ return fmt.Errorf("failed to import game: %w", err)51+ }52+ g = g.SetTitle(rec.Name)53+54+ completed := rec.Status == store.StatusCompleted55+ m := app.InitialModelWithGame(s, g, rec.ID, completed)56+ p := tea.NewProgram(m)57+ _, err = p.Run()58+ return err59+}
A cmd/list.go +58 -0
A cmd/new.go +82 -0
@@ -0,0 +1,82 @@
1+package cmd2+3+import (4+ "fmt"5+ "log"6+ "strings"7+8+ "github.com/FelineStateMachine/puzzletea/app"9+ "github.com/FelineStateMachine/puzzletea/resolve"10+ "github.com/FelineStateMachine/puzzletea/stats"11+ "github.com/FelineStateMachine/puzzletea/store"12+13+ tea "charm.land/bubbletea/v2"14+ "github.com/spf13/cobra"15+)16+17+var newCmd = &cobra.Command{18+ Use: "new <game> [mode]",19+ Short: "Start a new puzzle game",20+ Long: fmt.Sprintf("Start a new puzzle game, optionally specifying the difficulty mode.\n\nAvailable games:\n %s",21+ strings.Join(resolve.CategoryNames(app.GameCategories), "\n ")),22+ Args: cobra.RangeArgs(1, 2),23+ RunE: func(cmd *cobra.Command, args []string) error {24+ gameArg := args[0]25+ var modeArg string26+ if len(args) > 1 {27+ modeArg = args[1]28+ }29+ return launchNewGame(gameArg, modeArg)30+ },31+}32+33+// launchNewGame resolves the game/mode, spawns a new game, and launches the TUI.34+func launchNewGame(gameArg, modeArg string) error {35+ cat, err := resolve.Category(gameArg, app.GameCategories)36+ if err != nil {37+ return err38+ }39+40+ spawner, modeTitle, err := resolve.Mode(cat, modeArg)41+ if err != nil {42+ return err43+ }44+45+ g, err := spawner.Spawn()46+ if err != nil {47+ return fmt.Errorf("failed to spawn game: %w", err)48+ }49+50+ s, err := store.Open(store.DefaultDBPath())51+ if err != nil {52+ return err53+ }54+ defer s.Close()55+56+ stats.InitModeXP(app.GameCategories)57+58+ name := app.GenerateUniqueName(s)59+ g = g.SetTitle(name)60+61+ initialState, err := g.GetSave()62+ if err != nil {63+ return fmt.Errorf("failed to get initial save: %w", err)64+ }65+66+ rec := &store.GameRecord{67+ Name: name,68+ GameType: cat.Name,69+ Mode: modeTitle,70+ InitialState: string(initialState),71+ SaveState: string(initialState),72+ Status: store.StatusNew,73+ }74+ if err := s.CreateGame(rec); err != nil {75+ log.Printf("failed to create game record: %v", err)76+ }77+78+ m := app.InitialModelWithGame(s, g, rec.ID, false)79+ p := tea.NewProgram(m)80+ _, err = p.Run()81+ return err82+}
A cmd/root.go +62 -0
@@ -0,0 +1,62 @@
1+// Package cmd defines the CLI commands for PuzzleTea using Cobra.2+package cmd3+4+import (5+ "strings"6+7+ "github.com/FelineStateMachine/puzzletea/app"8+ "github.com/FelineStateMachine/puzzletea/stats"9+ "github.com/FelineStateMachine/puzzletea/store"10+11+ tea "charm.land/bubbletea/v2"12+ "github.com/spf13/cobra"13+)14+15+// Version is set at build time via ldflags:16+//17+// go build -ldflags "-X github.com/FelineStateMachine/puzzletea/cmd.Version=v1.0.0"18+var Version = "dev"19+20+var (21+ flagNew string22+ flagContinue string23+)24+25+// RootCmd is the top-level Cobra command.26+var RootCmd = &cobra.Command{27+ Use: "puzzletea",28+ Version: Version,29+ Short: "A terminal-based puzzle game framework",30+ Long: "PuzzleTea is a terminal-based puzzle game framework featuring Nonogram, Sudoku, Word Search, Hashiwokakero, and Lights Out.",31+ RunE: func(cmd *cobra.Command, args []string) error {32+ if flagNew != "" {33+ parts := strings.SplitN(flagNew, ":", 2)34+ gameArg := parts[0]35+ var modeArg string36+ if len(parts) > 1 {37+ modeArg = parts[1]38+ }39+ return launchNewGame(gameArg, modeArg)40+ }41+ if flagContinue != "" {42+ return continueGame(flagContinue)43+ }44+ // Default: launch TUI menu.45+ s, err := store.Open(store.DefaultDBPath())46+ if err != nil {47+ return err48+ }49+ defer s.Close()50+ stats.InitModeXP(app.GameCategories)51+ p := tea.NewProgram(app.InitialModel(s))52+ _, err = p.Run()53+ return err54+ },55+}56+57+func init() {58+ RootCmd.Flags().StringVar(&flagNew, "new", "", "start a new game (game:mode)")59+ RootCmd.Flags().StringVar(&flagContinue, "continue", "", "resume a saved game by name")60+61+ RootCmd.AddCommand(newCmd, continueCmd, listCmd)62+}