What the game is
Paws in Peril is a board game for 2 to 4 players that Christian Renfro is making. Each player is an animal on a board of hex tiles around a desert, with 10 HP, two lives and a hand of item cards. A turn is 5 action points: move, loot the tile you stand on, buy a card, raid a player next to you, or invade their home. Looting a home or a mansion means a dice fight with the monsters that live there; beat them and the building is yours, and the monster's card joins you with its one ability. Every home or mansion you own is a victory point, and the first to 4 wins (3 in a two-player Explorer game), or the last animal with lives left. Chests give item cards, forests give barricades, and 66 different item cards bend the rules on your turn, in a fight, or at any moment.
Rules
The V4 rulebook, a draft tuned over thousands of bot games. The engine below plays exactly this.
Loading the rulebook…
Characters
The 44 monsters. Each one guards a home or a mansion; beat it and its card is yours, with its ability as a buff from your next turn. HP is the number on the card.
Items
66 item cards, 78 in the deck. Playing one is free. On your turn cards are played outside a fight while you have AP, In Combat cards during a fight you are in, Anytime cards at any moment.
Tiles
Flat-top hexes. The Beginner board is the desert with 18 tiles in two rings around it; on the Explorer board each player places their own tiles as the game goes.
Numbers
Decks
Monster HP
| HP | Monsters | Share |
|---|
Item timing
| Played | Cards | Copies |
|---|
Balance under V4
Smart bots, 3,000 seeded games per board, 2 to 4 players rotating (TUNING.md on the lab branch).
| Board | Rounds, average | Ends on VP | Leader after round 3 wins | First player wins | Raids a game | Stalls |
|---|---|---|---|---|---|---|
| Beginner | 11.4 | 86% | 62% | 39% | 1.3 | 2% |
| Explorer | 14.1 | 75% | 56% | 36% | 2.2 | 3% |
- Knockouts score nothing in V4, so the target dropped from 5 VP to 4; at 5, more than a third of Beginner games ended by elimination instead of points.
- The early leader still wins too often with 2 players (79%); with 4 it is fair (44%). No edge for the leader's homes is the one catch-up rule that helped.
- Reckless players stall on the Explorer board; a video game should end at round 20 with the most VP winning.
- Six item cards have no rule yet (marked on their cards above); the engine plays them as no effect.
Downloads
Paths inside the zip match the site under /paws/: assets/art/<id>.webp is /paws/assets/art/<id>.webp. manifest.json lists every file with its size, pixel size and what it is.
| File | What it is | Size |
|---|---|---|
| paws-in-peril-assets.zip | All the art (161 pictures) with the data, the engine, the bots and the rulebook, in one folder | |
| data/game.json | The V4 rule constants: AP per turn, HP, lives, VP targets, costs, dice, board | |
| data/monsters.json | The 44 monsters: id, name, HP, ability, portrait and card paths | |
| data/items.json | The 66 item cards: copies, timing, effect, notes, image path | |
| data/tiles.json | The 5 tile types: count in print, what looting does, VP | |
| engine.js | The reference rules engine: every rule and card, plain JavaScript, no dependencies | |
| bot.js | The greedy bot and the fight odds it uses | |
| smartbot.js | The smart bot the balance numbers come from | |
| rulebook.md | The V4 rulebook (draft), Markdown | |
| manifest.json | Every file above and every picture, with its size and what it is |
The engine, in short
engine.js is a plain ES module that runs in any modern browser and in Node 18 or later. It is the tested implementation the playable game uses: state in, action in, new state out.
import { createGame, legalActions, apply, actor } from './engine.js';
const monsters = await (await fetch('/paws/kit/data/monsters.json')).json();
const items = await (await fetch('/paws/kit/data/items.json')).json();
let state = createGame({ monsters, items, players: 3, board: 'beginner', seed: 42 });
const moves = legalActions(state); // every action any player may take right now
state = apply(state, moves[0]); // pure: returns a new state, throws on an illegal action
- createGame(options):
players2 to 4 (or an array of{ name, color }),board'beginner'or'explorer',mode'ffa'or'teams',seed. Rules default to V4. - legalActions(state): plain objects such as
{ type: 'move', player, q, r },loot,buyItem,raid,invade,playItem,useAbility, the fight stepscombatRoll,combatContinue,combatApply, and the choicesdiscard,chooseDefender,takeCard,keepCard,placeTile.actor(state)is the player the game is waiting on. - apply(state, action): never mutates its input.
state.lastEventslists what just happened (each has atypeand a readablemsg): animate from those.state.overholds the winner. - State is plain JSON, so it saves, diffs and travels over a socket. Dice come from a seeded generator inside the state.
- Bots:
nextSmartStep(state, isBot)fromsmartbot.jsreturns the next{ pid, action }for the seatsisBot(pid)says are bots. Keep the three files in one folder.
Make it a video game
A concept a developer or an AI coding agent can build from, using only what is on this page.
The pitch
Paws in Peril is a turn-based tactics game for 2 to 4 players on a small hex map. Each player is an animal with 10 HP, two lives and a hand of item cards, racing to claim homes and mansions by beating the monsters inside them in quick dice fights. Every monster you beat joins your side with one ability, so each game builds a different little crew. A game is about 11 rounds, on one screen passed around the couch or online with friends, against bots that play like careful people. It looks like the cards: hot orange into purple, paint-splatter backgrounds, chunky round type.
Platform and engine
- A web game in TypeScript, built with Vite, drawn with Phaser 3. It runs from a link on phones, tablets and desktops, installs as a PWA, and can be wrapped with Capacitor for the app stores later.
- The rules core is
engine.js, unchanged. The game drawsstate, offers whatlegalActions(state)allows, and callsapplywith the player's pick. No rule is written twice, and every rule the tests cover stays covered. - If you prefer HTML for menus and cards, use PixiJS for the board only and keep the rest in the DOM. Godot 4 suits a native or console build, but it means porting the engine; start on the web and port once the design is settled.
One turn, the core loop
- Start. The player gets 5 AP (plus monster buffs); starting on the desert heals 2 HP; poison ticks. The camera centres on their animal.
- Act. Tap a lit neighbouring tile to move (1 AP). Buttons for the current tile: Loot, Buy a card, Raid, Invade, Barricade. Item cards in the hand light up when they can be played (free).
- Fight. Looting a home or mansion, invading or raiding opens the fight: both sides roll 4 dice, the two highest count, the gap is damage. Between rolls, In Combat and Anytime cards can be played.
- Resolve. A beaten monster's card flies into the player's crew and their colour goes on the building; a knockout costs a life and sends the animal back to the desert.
- End. Unused AP is lost. The next player's turn begins, or the victory screen when someone reaches the VP target.
Screens
| Screen | What is on it |
|---|---|
| Title | The logo over the card-back art, Play, How to play (the rulebook), Settings (sound, motion, speed of bots). |
| Lobby | 2 to 4 seats, each human or bot with a colour; Beginner or Explorer board; free for all or teams; for online play, a room code to share. |
| Board | The hex map in the middle; a panel per player (HP out of 12, lives, AP pips, VP, their monster cards); the action bar and the hand at the bottom; a short event feed. |
| Fight | An overlay: the player on the left, the monster's portrait and card on the right, a tray of 4 dice each with the two that count lifted, HP bars, damage numbers, the playable cards, and a Roll or Continue button. |
| Card hand | A fan of item cards; tap to zoom to the full card art, play it if it is legal now; a discard picker when the hand goes over 5. Also the pickers for choosing a defender, taking a card after a knockout and keeping a monster. |
| End | The winner, everyone's VP and crew, rounds played, fights won, and Rematch. |
How the assets map to the game
| Kit file | In the game |
|---|---|
assets/tiles/*.webp | The hex map. Each is 400 × 346, a flat-top hex, so a tile of radius R sits at x = 1.5 R q, y = √3 R (r + q/2) from the engine's axial q, r. Tint or stamp a tile in the owner's colour; show a barricade and a looted brick on top. |
assets/art/*.webp | Monster portraits, 480 square: the monster in a fight, the crew row on a player panel, the token on a building it defends. |
assets/cards/*.webp | The printed monster cards, 600 × 840: the zoomed view and the "you won this card" moment. card-back.webp is the monster deck pile. |
assets/items/*.webp | The item cards, 600 × 818: the hand, the card being played, the discard pile. _back.webp is the item deck and the other players' hands. |
data/*.json | All names, numbers and card text; game.json sizes the HUD (AP pips, the HP bar out of 12, lives). |
| Not in the kit | The players' own animals. Use tokens in the engine's colours (red, blue, green, yellow) with a shape each, or draw four animals in the cards' style. The four V4 cards (Grenade, Snare, Poison Dart, Smoke Bomb) have stand-in art. |
Multiplayer
- Hot-seat first. One device passed around. Hands are secret, so between turns show a "Pass to Blue" screen that hides the hand until the next player taps.
- Online, with the server in charge. A small Node server imports
engine.js, keeps the one true state per room, and applies the actions clients send (applyalready refuses illegal ones). Each client gets its own view: other players' hands as counts, the decks as sizes, and never the random seed, which would let a client predict dice. WebSocket per room, a short room code to join, and a rejoin that sends the latest view. - Reactions. Anytime cards can be played on other players' turns.
legalActionslists them for every player who may act, so the client shows them as a prompt with a few seconds' timer rather than stopping the game.
AI opponents
smartbot.js is the opponent to ship. It prices every option in victory points: a home is worth 1 VP (more when it wins the game), a monster card is worth its buff and its value as a defender, and dying costs a life and the cards you drop. It takes a fight only when the chance of winning times the gain beats the chance of losing times the cost, with the chance from a few hundred simulated fights, and never takes a hopeless one. It raids when the cards it expects to grab outweigh the HP it risks, throws grenades at clusters or the leader, and keeps its best defender. It is deterministic, and at a table split between the two bots it wins about two games in three against the greedy one under the tuned rules.
- Difficulty: Easy is the greedy bot (
bot.js), Normal the smart bot, Hard the smart bot that also sees the top of the monster deck. - Run bots in a Web Worker so the fight simulations never stall a frame, and wait half a second between bot actions so people can follow.
Art and audio
- Palette: hot orange
#FF9A2Eand ember#EA6A1Finto deep purple#1C0E24and#28143A, with orange dots as the motif, as on this page and the cards. - Type: Lilita One for titles, numbers and buttons; Figtree for card text and menus.
- Look: storybook animals with oversized weapons over paint-splatter skies; the board is the painted top-down tiles; UI is chunky rounded pills with a solid drop shadow that presses in.
- Motion: cards deal in with a tilt, dice tumble and the two that count lift, damage pops as a number, a claimed building gets a stamp in the owner's colour. A reduced-motion setting swaps all of it for fades.
- Audio: a warm folk-adventure loop with hand percussion, a tenser layer during fights; wooden dice on felt, a card flick, a heavy stamp on a claim, a soft growl scaled to the monster's HP. No voice needed.
First milestone
A playable slice: hot-seat on the Beginner board, one human against one smart bot, start to finish.
- A Vite + TypeScript + Phaser project that loads the kit's data and
engine.jsas they are. - The board from
state.board.tileswith the tile art and a token per player; the tiles you can move to lit fromlegalActions. - Player panels (HP, lives, AP, VP, crew) and the action bar: move, loot, buy, end turn.
- The fight overlay for monster fights, stepping through
combatRoll,combatContinueandcombatApply. - The hand: item cards that can be played when
legalActionsincludes them; the discard picker. - The end screen from
state.over.
Done when a game plays from the lobby to a winner on a desktop and on a phone, with no rule logic outside engine.js. Next: raids, invasions and the other pickers; the Explorer board and teams; sound; then online play.
Prompt for an AI coding agent
Build a web video game of the board game Paws in Peril. Everything you need is at https://renfrovibes.com/paws/kit/.
Fetch and read these before writing any code:
- https://renfrovibes.com/paws/kit/rulebook.md (the rules, V4 draft)
- https://renfrovibes.com/paws/kit/data/game.json (the rule constants)
- https://renfrovibes.com/paws/kit/data/monsters.json, data/items.json, data/tiles.json (the content)
- https://renfrovibes.com/paws/kit/engine.js (the rules engine: a plain ES module, no dependencies, every rule and card)
- https://renfrovibes.com/paws/kit/bot.js and smartbot.js (the bots; they import ./engine.js and ./bot.js, keep all three in one folder)
- https://renfrovibes.com/paws/kit/manifest.json (every picture with its path, size in bytes and pixels, and what it is)
Then download https://renfrovibes.com/paws/kit/paws-in-peril-assets.zip, which holds all of the above and all the art, and unzip its paws-in-peril folder into the project's public folder.
Stack: TypeScript, Vite, Phaser 3. Keep engine.js, bot.js and smartbot.js byte for byte. The game never implements a rule itself.
How the engine works:
- createGame({ monsters, items, players: 2 to 4, board: 'beginner' or 'explorer', mode: 'ffa' or 'teams', seed }) returns the game state as plain JSON. Rules default to V4.
- legalActions(state) returns every action any player may take now, as plain objects ({ type: 'move', player, q, r }, loot, buyItem, raid, invade, placeBarricade, playItem, useAbility, endTurn, combatRoll, combatContinue, combatApply, discard, chooseDefender, takeCard, doneTaking, keepCard, placeTile). actor(state) is the player the game waits on.
- apply(state, action) returns a new state and throws on an illegal action. state.lastEvents lists what just happened, each with a type and a readable msg; animate from these. state.over is set when the game ends.
- The board uses axial hex coordinates (q, r) with the desert at 0,0. Tiles are flat-top hexes, assets/tiles/*.webp at 400 x 346.
- nextSmartStep(state, isBot) from smartbot.js returns { pid, action } for the next bot move.
Build this first (hot-seat on one screen):
1. A title screen and a setup screen: 2 to 4 seats, each human or bot, the Beginner board.
2. The board: every tile in state.board.tiles drawn with its tile art, a coloured token per player (red, blue, green, yellow), and the tiles the current player can move to highlighted from legalActions.
3. A panel per player: HP out of 12, lives, AP pips, VP, and the monster cards they own (assets/cards/<id>.webp).
4. The current player's actions: tap a tile to move; buttons for loot, buy, raid, invade, barricade and end turn, shown only when legalActions has them. The hand of item cards (assets/items/<id>.webp), playable when legalActions includes them.
5. A fight overlay: both sides (monster portraits from assets/art/<id>.webp), 4 dice each with the two highest marked, damage, HP bars; step through combatRoll, combatContinue and combatApply.
6. A picker for every choice the engine waits on: discard, chooseDefender, takeCard, keepCard, placeTile.
7. An end screen from state.over.
Bot seats act through nextSmartStep with half a second between moves; run it in a Web Worker if it stalls a frame.
Done when a game with one human and one smart bot plays from setup to a winner on a desktop and on a phone, with no rule logic outside engine.js. Next: the Explorer board, teams, sound, then online play with engine.js on a server that owns the state and hides other players' hands.
Look: hot orange (#FF9A2E, #EA6A1F) into deep purple (#1C0E24, #28143A), orange dots, Lilita One for headings and numbers, Figtree for text, rounded chunky buttons. No emoji.