From 83bcc7f1544ce9f17052d79207bc173ba4c9c834 Mon Sep 17 00:00:00 2001 From: dwrz Date: Sun, 25 Oct 2026 02:51:49 +0000 Subject: [PATCH] Add minotaur cmd --- cmd/minotaur/main.go | 116 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 cmd/minotaur/main.go diff --git a/cmd/minotaur/main.go b/cmd/minotaur/main.go new file mode 100644 index 0000000..52ffca1 --- /dev/null +++ b/cmd/minotaur/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log/slog" + "os" + "os/signal" + "path/filepath" + "syscall" + "time" + + "code.chimeric.al/dwrz/minotaur/internal/minotaur" + "code.chimeric.al/dwrz/minotaur/pkg/terminal" +) + +var ( + height = flag.Int("h", 0, "height (default: terminal rows)") + width = flag.Int("w", 0, "width (default: terminal columns)") + tick = flag.Int("t", 250, "ms between minotaur movement") +) + +func main() { + flag.Parse() + + var log = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ + AddSource: true, + Level: slog.LevelDebug, + })) + + // Setup the main context. + // The context cancels on SIGINT or SIGTERM. + ctx, stop := signal.NotifyContext( + context.Background(), + syscall.SIGINT, + syscall.SIGTERM, + ) + defer stop() + + if err := run(ctx, log); err != nil { + log.Error("failed", slog.Any("error", err)) + os.Exit(1) + } +} + +func run(ctx context.Context, log *slog.Logger) error { + if *height < 0 { + return fmt.Errorf("invalid height: %d", *height) + } + if *width < 0 { + return fmt.Errorf("invalid width: %d", *width) + } + if *tick <= 0 { + return fmt.Errorf("invalid tick: %d", *tick) + } + + // Setup workspace and log file. + cdir, err := os.UserCacheDir() + if err != nil { + return fmt.Errorf( + "failed to determine user cache directory: %w", err, + ) + } + wdir := filepath.Join(cdir, "minotaur") + + if err := os.MkdirAll(wdir, os.ModeDir|0700); err != nil { + return fmt.Errorf("failed to create tmp dir: %w", err) + } + + f, err := os.Create(wdir + "/log") + if err != nil { + return fmt.Errorf("failed to create log file: %w", err) + } + defer f.Close() + + var gameLog = slog.New(slog.NewTextHandler(f, &slog.HandlerOptions{ + AddSource: true, + Level: slog.LevelDebug, + })) + + // Retrieve terminal info. + t, err := terminal.New(os.Stdin.Fd()) + if err != nil { + return fmt.Errorf("failed to get terminal attributes: %w", err) + } + size, err := t.Size() + if err != nil { + return fmt.Errorf("failed to get terminal size: %w", err) + } + if *height == 0 { + rows := int(size.Rows) + height = &rows + } + if *width == 0 { + cols := int(size.Columns) + width = &cols + } + + // Setup the game. + game, err := minotaur.New(minotaur.Parameters{ + Height: *height, + In: os.Stdin, + Log: gameLog, + Out: os.Stdout, + Terminal: t, + Tick: time.Duration(*tick) * time.Millisecond, + Width: *width, + }) + if err != nil { + return fmt.Errorf("failed to create game: %w", err) + } + + // Run the game. + return game.Run(ctx) +}