- commit
- c5a1dc342dc191619d0d8e6cac289190f9370175
- parent
- 61d11a76db3d153f8b4060c96cede68099f3eca7
- author
- Dami <dami.etoile@gmail.com>
| D | cmd/ | 226 | ------------------------------ |
| A | cmd/ | 59 | ++++++++ |
| A | cmd/ | 58 | ++++++++ |
| A | cmd/ | 82 | +++++++++++ |
| A | cmd/ | 62 | ++++++++ |
@@ -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-}@@ -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+}@@ -0,0 +1,58 @@
1+package cmd2+3+import (4+ "fmt"5+ "os"6+ "text/tabwriter"7+8+ "github.com/FelineStateMachine/puzzletea/store"9+10+ "github.com/spf13/cobra"11+)12+13+var listAll bool14+15+var listCmd = &cobra.Command{16+ Use: "list",17+ Short: "List saved games",18+ Long: "Display a table of saved games. Use --all to include abandoned games.",19+ RunE: func(cmd *cobra.Command, args []string) error {20+ s, err := store.Open(store.DefaultDBPath())21+ if err != nil {22+ return err23+ }24+ defer s.Close()25+26+ var games []store.GameRecord27+ if listAll {28+ games, err = s.ListAllGames()29+ } else {30+ games, err = s.ListGames()31+ }32+ if err != nil {33+ return err34+ }35+36+ if len(games) == 0 {37+ fmt.Println("No saved games.")38+ return nil39+ }40+41+ w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)42+ fmt.Fprintln(w, "NAME\tGAME\tMODE\tSTATUS\tLAST UPDATED")43+ for _, g := range games {44+ fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",45+ g.Name,46+ g.GameType,47+ g.Mode,48+ g.Status,49+ g.UpdatedAt.Local().Format("Jan 02 15:04"),50+ )51+ }52+ return w.Flush()53+ },54+}55+56+func init() {57+ listCmd.Flags().BoolVar(&listAll, "all", false, "include abandoned games")58+}@@ -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+}@@ -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+}