- commit
- 783da5b07442cbdb3272983d566bd95a1a62f6b9
- parent
- 2564c95e4f50a98d993745fb2487a63be7a1ce09
- author
- Dami <dami.etoile@gmail.com>
| M | .github/ | 13 | +--- |
| M | CLAUDE.md | 103 | +++++++++++++++++++++++++++--- |
| M | README.md | 55 | ++++++++++++++-- |
@@ -24,8 +24,8 @@ jobs:
2424 with:2525 go-version: "1.24"2626 27- - name: Build binary28- run: go build -ldflags "-X main.version=${{ github.ref_name }}" -o puzzletea27+ - name: Install just28+ uses: extractions/setup-just@v22929 3030 - name: Install VHS and dependencies3131 run: |@@ -34,14 +34,7 @@ jobs:
3434 go install github.com/charmbracelet/vhs@latest3535 3636 - name: Generate GIFs37- run: |38- vhs vhs/menu.tape39- vhs vhs/nonogram.tape40- vhs vhs/sudoku.tape41- vhs vhs/takuzu.tape42- vhs vhs/wordsearch.tape43- vhs vhs/hashiwokakero.tape44- vhs vhs/lightsout.tape37+ run: just vhs4538 4639 - name: Create PR with updated GIFs4740 run: |@@ -41,6 +41,12 @@ just fmt # gofumpt -w .
4141 just tidy # go mod tidy4242 ```4343 44+Run a single package's tests:45+```bash46+go test ./nonogram/47+go test ./sudoku/ -run TestGenerateGrid48+```49+4450 ### VHS GIF Previews4551 ```bash4652 just vhs # generate all game preview GIFs (requires vhs + ffmpeg + ttyd)@@ -85,17 +91,30 @@ Save/load uses `game.Registry` — each package registers its import function vi
8591 8692 ### Adding a New Puzzle Type8793 94+#### Game package files (e.g. `mypuzzle/`)95+8896 1. Create a new package under the project root (e.g., `mypuzzle/`)8997 2. Define a mode struct embedding `game.BaseMode` that implements `game.Spawner`9098 3. Add compile-time checks: `var _ game.Mode = MyMode{}` and `var _ game.Spawner = MyMode{}`9199 4. Export a `Modes` variable (`[]list.Item`) listing available difficulties92100 5. Implement `game.Gamer` on a `Model` struct93101 6. Register the import function in an `init()`: `game.Register("My Puzzle", func(data []byte) (game.Gamer, error) { ... })`94-7. Add the category to `GameCategories` in `model.go` (maintain alphabetical order)95-8. Add aliases to `categoryAliases` in `resolve.go`96-9. Create a `README.md` in the package directory (see existing games for format)97-10. Create a VHS tape file in `vhs/<game>.tape` (see VHS Tape Files section)98-11. Add a one-line entry to "Current Implementations" below102+7. Write tests in `mypuzzle_test.go` (see Testing Conventions below)103+8. Create a `README.md` in the package directory (see Game README Format below)104+105+#### Main folder edits required106+107+9. **`model.go`**: Import the new package and add a `game.Category` entry to `GameCategories` (maintain alphabetical order):108+ - Add import: `"github.com/FelineStateMachine/puzzletea/mypuzzle"`109+ - Add entry: `game.Category{Name: "My Puzzle", Desc: "Short description.", Modes: mypuzzle.Modes}`110+ - The import triggers the package's `init()`, which registers the save/load function — no blank import needed.111+10. **`resolve.go`**: Add the canonical name and any CLI aliases to the `categoryAliases` map (lowercase, spaces not hyphens).112+11. **`CLAUDE.md`**: Add a one-line entry to "Current Implementations" below.113+114+#### VHS and CI115+116+12. Create a VHS tape file in `vhs/<game>.tape` (see VHS Tape Files section)117+13. **`justfile`**: Add the new tape to the `vhs` target (e.g. `vhs vhs/mypuzzle.tape`)99118 100119 Each package follows a consistent file structure:101120 - `Gamemode.go`: Mode struct, `NewMode()`, `Spawn()`, `Modes` var, `init()` with `game.Register()`@@ -154,6 +173,40 @@ Version is injected at build time via `-ldflags "-X main.version=..."`. The `ver
154173 155174 All Go code must pass `gofumpt` (strict superset of `gofmt`) and `golangci-lint` with the project's `.golangci.yml` config. Enabled linters: gofumpt, govet, errcheck, staticcheck, unused, gosimple, ineffassign, misspell. Run `just fmt` and `just lint` before committing.156175 176+### Testing Conventions177+178+Each game package has a `<game>_test.go` file. Tests follow these conventions:179+180+- **Table-driven tests** with subtests (`t.Run`) for all pure functions.181+- **Priority annotations** in section comments: `(P0)` = critical core logic, `(P1)` = important functionality (save/load, interface checks), `(P2)` = generators and less critical paths.182+- **Section separators**: `// --- FunctionName (P0) ---` above each test group.183+- **Save/load round-trip tests**: Create a model, call `GetSave()`, pass bytes to `ImportModel()`, and verify state is preserved.184+- **Compile-time interface checks** in tests: `var _ game.Gamer = Model{}` (or `&Model{}`).185+- **Edge cases**: Empty grids, 1x1 grids, maximum sizes, boundary conditions.186+- **Generator validation**: Verify generated puzzles satisfy game rules (e.g. solvability, uniqueness).187+188+Run `just test` before committing. All tests must pass in CI.189+190+### File Naming Convention191+192+Game packages use capital first letters for the three core files and lowercase for helpers:193+- **Capital**: `Gamemode.go`, `Model.go`, `Export.go`194+- **Lowercase**: `grid.go`, `keys.go`, `style.go`, `generator.go`, `<game>_test.go`195+196+### Game README Format197+198+Each game package contains a `README.md` with this structure:199+200+1. **Title** (`# Game Name`)201+2. **One-line description**202+3. **GIF preview** (``)203+4. **How to Play** — rules and mechanics204+5. **Controls** — table with Key and Action columns; include game-specific keys plus shared keys (`Ctrl+H`, `Ctrl+E`, `Ctrl+N`)205+6. **Modes** — table listing each mode with parameters and description206+7. **Quick Start** — `puzzletea new <game> <mode>` examples207+208+See `nonogram/README.md` for a complete example.209+157210 ### Color Convention158211 159212 All colors must use `lipgloss.AdaptiveColor{Light: "X", Dark: "Y"}` with ANSI 256 palette numbers. Avoid hex colors (`#rrggbb`) — they require true-color support and don't adapt to terminal background.@@ -163,11 +216,15 @@ All colors must use `lipgloss.AdaptiveColor{Light: "X", Dark: "Y"}` with ANSI 25
163216 Each game has a `.tape` file in `vhs/` that generates a GIF preview using [charmbracelet/vhs](https://github.com/charmbracelet/vhs). All tapes share a common preamble:164217 165218 ```tape166-Set FontSize 13167-Set Width 854168-Set Height 480169-Set Padding 40170-Set Theme "Catppuccin Mocha"219+Output vhs/<game>.gif220+221+Set Theme "catppuccin-mocha"222+Set FontSize 14223+Set Width 1200224+Set Height 800225+Set Margin 60226+Set MarginFill "#A85C40"227+Set WindowBar Colorful171228 172229 Env COLORTERM "truecolor"173230 Env CLICOLOR_FORCE "1"@@ -176,7 +233,31 @@ Env CI ""
176233 177234 The three `Env` lines are required for CI color support. `termenv` (used by lipgloss/bubbletea) checks `CI` env var and returns no-color when set — GitHub Actions sets `CI=true`. `COLORTERM=truecolor` sets the color profile, and `CLICOLOR_FORCE=1` is a fallback.178235 179-Each tape uses a `Hide` block to build the binary and clear the screen before `Show`ing the gameplay commands. Use `Ctrl+L` (not `clear`) to clear the screen in the hidden block.236+After the preamble, each tape follows this structure:237+238+```tape239+Hide240+Type "go build -o puzzletea"241+Enter242+Sleep 2s243+Ctrl+L244+Show245+246+Type "./puzzletea new <game> <mode>"247+Sleep 500ms248+Enter249+Sleep 2s250+251+# Demo gameplay actions...252+253+Sleep 3s254+```255+256+- Use a `Hide`/`Show` block to build the binary and clear the screen before showing gameplay.257+- Use `Ctrl+L` (not `clear`) to clear the screen in the hidden block.258+- Demonstrate core gameplay mechanics (filling cells, toggling, navigating, etc.).259+- End with `Sleep 3s` to let viewers see the final state.260+- Add the new tape to the `vhs` target in the `justfile`.180261 181262 GIFs are not tracked in git — they are generated by CI and committed via PR.182263 @@ -153,31 +153,70 @@ Arrow keys, WASD, and Vim bindings (hjkl) are supported for grid movement across
153153 154154 Games are automatically saved to `~/.puzzletea/history.db` (SQLite). Navigating away saves progress; quitting with `Ctrl+C` marks the game as abandoned. Completed games are preserved and can be revisited.155155 156-## Building156+## Building and Testing157157 158158 [just](https://github.com/casey/just) is used as the command runner:159159 160160 ```bash161161 just # build162162 just run # build and run163-just test # run tests163+just test # run tests (go test ./...)164164 just lint # run golangci-lint165165 just fmt # format with gofumpt166+just tidy # go mod tidy166167 just install # install to $GOPATH/bin167168 just clean # remove build artifacts169+just vhs # generate all VHS GIF previews168170 ```169171 172+Run a single package's tests:173+174+```bash175+go test ./nonogram/176+go test ./sudoku/ -run TestGenerateGrid177+```178+179+All code must pass `gofumpt` and `golangci-lint` before committing. CI runs both on every PR.180+170181 ## Adding a New Puzzle171182 172183 PuzzleTea uses a plugin architecture. To add a new puzzle type:173184 174-1. Create a package (e.g., `mypuzzle/`)175-2. Implement the `game.Gamer` interface on a Model struct176-3. Define modes embedding `game.BaseMode` that implement `game.Spawner`177-4. Register an import function in `init()` for save/load support178-5. Add the category to `GameCategories` in `model.go`185+### 1. Create the game package186+187+Create a directory (e.g., `mypuzzle/`) with these files:188+189+| File | Purpose |190+|------|---------|191+| `Gamemode.go` | Mode struct embedding `game.BaseMode`, `Spawn()`, `Modes` var, `init()` with `game.Register()` |192+| `Model.go` | `Model` struct implementing `game.Gamer` |193+| `Export.go` | `Save` struct, `GetSave()`, `ImportModel()` for persistence |194+| `keys.go` | Game-specific `KeyMap` struct |195+| `style.go` | lipgloss styling and rendering helpers |196+| `generator.go` | Puzzle generation logic (if applicable) |197+| `grid.go` | Grid type and serialization (for grid-based games) |198+| `mypuzzle_test.go` | Tests (table-driven, save/load round-trip, generator validation) |199+| `README.md` | Game docs: rules, controls table, modes table, quick start examples |200+201+### 2. Wire it into the main application202+203+These files in the project root must be edited:204+205+- **`model.go`**: Import the package and add a `game.Category` entry to `GameCategories` (maintain alphabetical order). The import triggers `init()`, which registers save/load — no blank import needed.206+- **`resolve.go`**: Add the canonical name and any CLI aliases to `categoryAliases`.207+208+### 3. Add a VHS preview209+210+- Create `vhs/<game>.tape` following the format in existing tapes.211+- Add the tape to the `vhs` target in the `justfile`.212+213+### 4. Verify214+215+```bash216+just fmt && just lint && just test217+```179218 180-See any existing game package (e.g., `nonogram/`) for the full pattern.219+See any existing game package (e.g., `nonogram/`) for the full pattern, and `CLAUDE.md` for detailed conventions.181220 182221 ## License183222