tinhorn 0.1.3

A dice cup for your terminal: shake the cup, watch dice fly, hear them land.
tinhorn-0.1.3 is not a library.

A tinhorn is a small-time gambler, named for the tin shaker chuck-a-luck dealers rattled their dice in.

Step right up: a terminal dice roller with a genuine tin-cup shake — the dice land how they land. Nothing up these sleeves, friend: seed the roll (--seed 42) and watch the very same throw land twice.

tinhorn: typing 2d20kh1 vs 15, shaking the cup, releasing at the peak, and landing a natural 20 — SUCCESS by 5

And what'll it cost you to see all this? Not one thin dime!

  • A real physics arena. Dice are tossed, bounced, knocked together, and rolled off each other's backs.
  • Your throw. Shake the cup and catch the meter at its peak. Put some arm into it!
  • Set the stakes. Call your number — d20+5 vs 15 — and the arena hands down the verdict, margin and all. Playing it low? d20 < 10 flips the bet to roll-under. The stats pane quotes you fair odds before you take it.
  • Sound from thin air. Every click, knock, and thunk synthesized live from the very impact that made it. No samples anywhere on the premises.
  • Fancy notation. Advantage, drop-the-lowest, exploding dice, multipliers — the works.
  • One-shot mode for quick gambles.

Install

You'll need a Rust toolchain. On Linux, the sound needs the ALSA headers to build (macOS and Windows need nothing extra):

sudo apt install libasound2-dev pkg-config   # Debian/Ubuntu
sudo dnf install alsa-lib-devel              # Fedora

Then install from crates.io:

cargo install tinhorn

If your fingers insist on the old ways: alias roll=tinhorn.

Run

tinhorn                  # start empty, type an expression
tinhorn 3d6              # roll 3d6 the moment it opens
tinhorn "d6+d8"          # quote anything with shell-special characters
tinhorn --mute           # start silent (Ctrl-Q toggles at runtime)

macOS asked about the microphone? Recent macOS raises that prompt for any app playing audio through an output device that also carries mic inputs (a USB interface, a headset) — even Apple's afplay trips it. tinhorn never records and opens the default output device only, so deny it freely; --mute skips audio entirely and never asks.

Keys

Key Action
Enter roll, per the mode (shake: press again to throw)
Tab cycle the mode — shake → roll → insta
type / Backspace edit the dice expression
(Home/End) move the caret in the expression (jump to ends)
scroll an open pane that's taller than the screen
? toggle the dice-notation help overlay
Ctrl-H toggle the roll-history pane
Ctrl-S toggle the statistics pane
Ctrl-Q mute / unmute — Q for quiet
Esc / Ctrl-C quit (Esc closes a pane or shake first)

Three roll modes cycle on Tab: shake (drop into the cup and catch the power meter), roll (dice tumble straight in), and insta (landed and tallied at once).

Dice notation

A roll is a sequence of dice terms and optional flat modifiers, separated by +, ,, whitespace, or simply written next to each other. A term can carry modifiers (keep/drop, explode, multiply) written right after its dN; these apply in pool order — explode → keep/drop → multiply — and stack. Add stakes to check the total against a target.

The basics

Input Meaning
3d6 three six-sided dice
d20 one die — d6 means 1d6
d% percentile — shorthand for d100
d6+d8 one d6 and one d8, summed
2d20-1 dice plus a flat +/ modifier

Sizes are capped (≤ 60 dice, ≤ 1000 sides) so a fat-fingered 999d99999 can't wedge the renderer.

Keep / drop

Input Meaning
2d20kh1 advantage — roll two d20, keep the highest 1
2d20kl1 disadvantage — keep the lowest 1
4d6dl1 drop the lowest 1 (the classic ability-score roll)
4d6dh1 drop the highest 1

kh/kl/dh/dl default to 1 (2d20kh = 2d20kh1) and clamp to the pool size. Dropped dice are still thrown and bounce around — you watch advantage discard the lower d20 — but they're rendered dimmed and left out of the total.

Stakes

Call a target and the arena hands down a verdict — margin and all. At most one per roll, and it must come last: d20 > 4d6 is an error, not a surprise.

Input Meaning
d20+5 > 15 meet or beat — succeed on a total ≥ 15
d20 vs 15 the same; vs is the word alias for >
d20 < 10 roll-under — succeed on a total ≤ 10

Both comparisons are inclusive: you win on the number.

Exploding

Input Meaning
3d6! a max face rolls another die (repeats)
d10!>8 explode on any face > 8 (< and = too)

Exploding plays out live: a die that settles on a qualifying face drops one more die into the arena, which can explode in turn — capped at 40 extra dice per term so d2! can't grow without bound.

Multiply

Input Meaning
4d6*2 multiply this term's kept sum by 2
4d6!kh3*2 stack them: explode, keep the best 3, then double

A multiplier binds to its own term: in 3d6*2 + d8 only the d6 sum is doubled.

Scripting (one-shot mode)

With an output flag — or whenever stdout isn't a terminal — tinhorn skips the animation, evaluates the roll once, prints a result, and exits, so it drops straight into scripts and pipelines:

tinhorn -p 3d6              # 13            (just the total)
tinhorn 3d6 | cat           # 13            (piped stdout → one-shot automatically)
total=$(tinhorn -p 2d20kh1) # capture it in a variable
tinhorn --seed 42 4d6dl1    # reproducible: the same seed always rolls the same dice
tinhorn -v 4d6dl1+2         # a full breakdown (dropped dice in [brackets])
tinhorn --json 2d20kh1+3    # machine-readable for jq & friends

tinhorn -p d20+4 vs 14 && echo "the potion works"   # the exit code IS the check

Under -p/-v, a staked roll exits 0 on success and 1 on failure, so scripts branch on the check itself; --json and piped output always exit 0, and a parse error goes to stderr and exits 2.

The --json output carries every die and its flags, the per-term subtotals, the flat modifier, the total, and — when staked — target, goal (over or under), success, and margin (how far the check was made or missed by, whichever way the stake runs).

Contributing

Want a look behind the table? The design notes, the test suite, and the house rules all live in CONTRIBUTING.md — pull up a chair.

License

Licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.