A push-your-luck dice game. Each round you roll a pool of dice, stack up bonuses, and try to beat a rising target score. Clear a round and you pick one of three randomly-rolled rewards to make your dice stronger for the next.
yarn install
yarn dev # start the dev server
yarn build # type-check + production build
yarn test # run the vitest suite- You start with 6 dice (a random mix of d6/d8/d10/d12).
- Each round gives you up to 5 rolls. Press Space (or Roll Die) to roll every die at once.
- Your score for the round accumulates across rolls. Reach the target score before your rolls run out to clear the round.
- Run out of rolls below the target and you lose.
Each roll is scored as:
- Subtotal — the sum of all numeric die faces.
- Die bonuses / penalties — rolling a die's highest face adds a bonus;
rolling its lowest face applies a penalty (see
maxBonus/minPenaltyinsrc/die.ts). - Combo bonuses — matching faces and runs, scaled by the matched value
(so a pair of 8s beats a pair of 2s, and upgrading dice grows your combos):
- Pair
value×3, Triplevalue×6, Quadvalue×8, Five-of-a-kindvalue×10, Six-of-a-kindvalue×16 - Straight (five consecutive values) — highest value in the run
×6
- Pair
- Stickers — additions are applied first, then multipliers, which scale the
whole total. Multipliers stack additively (
1 + Σ(factor − 1)), so a lone x3 triples but x3 + x4 gives x6, not x12 — several landing at once can't explode the score.
The full pipeline lives in Game.calculate (src/game.ts).
The target score for each round is computed by targetForRound in
src/progression.ts. Rather than a flat geometric jump, the ratio between
consecutive rounds decays from 1.38 toward 1, so the curve climbs steeply
early then eases off. The constants were tuned by simulation so targets grow at
roughly the pace rewards add power — runs last and reward choices matter,
instead of every game hitting a wall around round 7.
round: 1 2 3 4 5 6 7
target: 100 138 181 229 282 339 401
Clear a round and three concrete rewards are rolled at random — typically one of each kind:
- New die — adds a randomly-rolled die to your pool (more dice, more combos), up to a cap of 12 dice. Past the cap this reward is replaced by an upgrade or sticker, so adding dice can't be the only strategy.
- Upgrade — bumps every upgradable die up one tier (e.g. d6 → d8). Scales with your pool, so it grows in value as the game goes on. When two or fewer dice can still be upgraded, it also grants a fresh (upgradable) die so the path doesn't stall out once most dice are maxed.
- Sticker — attaches a multiplier (
x3…x10) or addition (+50/+100) sticker to one die.
Generation is random, but the choice is yours — pick the one option that best
fits your pool. Reward logic lives in src/reward.ts.
From round 4 onward, each round rolls a random modifier — a one-round rule that changes how you play. They're a mix of boons, banes, and twists, e.g.:
- Pairs Pay Double — pair bonuses doubled.
- Straight Fever — straights need only 4 in a row.
- Big Numbers — dice that roll in their top half score double.
- Bonus Roll / Drought — 6 / 4 rolls this round.
- Combo Lockout — no combo bonuses, but the target is 40% lower (a raw-value round, not a wall).
- Tax Season / Clearance — target ±20%.
- Crit Day — each natural 20 on a d20 doubles your roll (stacks), so it rewards builds that have upgraded into d20s.
A modifier is plain data (src/modifier.ts); Game.calculate(dice, modifier?)
and the App read the fields they need, so scoring stays decoupled. Rounds 1-3
have no modifier (onboarding).