Single-player browser deduction game with a pirate/comic-book theme. Six hidden creatures are scattered on a 4Γ7 grid; the player fires cannonballs until all beasts are found, shots run out, or (in ninja mode) hints lead the way.
- Vanilla JS, ES modules, no build step, no dependencies.
- Bun for the dev server and test runner.
- Web Audio API for procedural sound effects.
- Fonts: Bangers (headings) + Patrick Hand (body), loaded from Google Fonts.
bun run dev # serves at http://localhost:3000
bun test # run unit testsbattleship/
βββ index.html
βββ server.js # Bun static file server
βββ tests/
β βββ game.test.js # unit tests (bun:test)
βββ public/
βββ css/style.css
βββ js/
βββ game.js # pure game logic β board building, hints, constants
βββ main.js # UI layer β 5-screen state machine, DOM, events
βββ matrix.js # Matrix rain canvas (unused, kept for later)
βββ sound.js # Web Audio sound effects
Hard boundary: game.js is pure logic with zero DOM or browser API access. Keep it that way β it is what makes the unit tests fast and reliable.
- 4 rows Γ 7 columns = 28 cells total, indexed
0..27(flat:row * COLS + col) ROWS = 4,COLS = 7exported fromgame.jsβ never hardcode these values elsewhere- Column labels AβG, row labels 1β4
Six creatures are hidden per game β one per entry in CREATURES (Octo, Crab, Eel, Jelly, Shark, Puffer). Each has a name and color.
Placement depends on mode:
| Mode | Shots | Scatter | Hints | Placement style |
|---|---|---|---|---|
| n00b | 22 | false | false | Consecutive runs (horizontal or vertical groups) |
| ninja | 16 | true | true | 6 individual scattered cells (non-adjacent) |
| hacker | 12 | true | false | 6 individual scattered cells (non-adjacent) |
Non-adjacent constraint: no two creatures (from different runs/groups) may occupy 8-directionally adjacent cells.
Hints (ninja only): after each miss, Captain Blip whispers which column or row contains a hidden beast.
state.screen cycles through: home β levels β play β win / lose
- home β mascot + title + PLAY button + rule chips + sound toggle
- levels β three level cards (noob / ninja / hacker)
- play β HUD (mode, shots, beast pips, sound) + board + sidekick
- win / lose β end screen with stats and replay actions
Pure CSS/HTML pirate face, rendered by the blip(mood, size) function in main.js. Moods: happy (big smile), sad (frown), idle / point (small neutral curve). Uses .blip-bob CSS animation.
- Keep code simple and concise. Prefer direct expressions over abstractions.
- No comments unless the why is genuinely non-obvious.
- No TypeScript, no transpilation β plain ES2020+ that browsers understand natively.
- No defensive error handling for internal paths; validate only at real boundaries (user input, storage).
- Do not introduce helpers, wrappers, or indirection for things used once.
- Three similar lines is fine; extract only when a pattern repeats meaningfully.
- Tests live in
tests/game.test.jsand covergame.jsonly. - Use
bun:test(describe/it/expect). - Test pure logic. Do not mock the DOM or Web Audio; instead keep logic out of
main.jsso it never needs mocking. - Each new exported function in
game.jsmust have corresponding unit tests. - Property-based style is preferred for randomised functions β run the assertion 20β40 times in a loop.
All commits must follow Conventional Commits.
<type>[(<scope>)][!]: <subject>
- type β lowercase, one of:
feat,fix,chore,test,docs,style,refactor,perf,ci,build,revert - scope β optional, lowercase, e.g.
feat(game):orfix(ui): !β optional, marks a breaking change- subject β imperative mood, no period, max 72 chars total (including type and scope)
Enforcement: .githooks/commit-msg validates every commit locally. Activate once per clone with:
git config core.hooksPath .githooks- Do not add a framework, bundler, or package manager dependency.
- Do not touch difficulty constants or creature list without updating tests.
- Do not add sounds, animations, or visual effects to
game.js. - Do not add a timer β the game is shots-based only.