Skip to content

puradox/tinhorn

Repository files navigation

tinhorn — a terminal window running tinhorn, an amber die for a cursor, and the reply: a dice cup for your terminal

crates.io downloads CI license: MIT OR Apache-2.0

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.
  • Real pixels in kitty, Ghostty & WezTerm — the arena drawn as a true image, sharp block glyphs everywhere else. Same table, same seed.
  • 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)
tinhorn --graphics blocks # force block glyphs (auto uses real pixels in kitty)

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).

Real pixels

In a terminal that speaks the kitty graphics protocolkitty, Ghostty, WezTerm — the arena isn't text glyphs: the same GPU frame is handed to the terminal as a real image, drawn at your display's resolution while the chrome is painted around it. Everywhere else it falls back to a blit of quadrant block glyphs (2×2 sub-pixels a cell), and you'd be hard-pressed to spot the switch.

It's the same table under either one — same camera, same physics, the very same seeded RNG — so tinhorn --seed 42 3d6 lands the identical total whether it's drawn in pixels, in block glyphs, or printed by -p. Detection sniffs the terminal (and stands down under tmux, which quietly eats the picture); force the call with --graphics kitty or --graphics blocks.

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, reroll, multiply) written right after its dN; these apply in pool order — reroll → 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.

Reroll

Input Meaning
4d6r1 reroll any 1, repeating until it clears
d20ro1 ro rerolls once — one redraw, then live with it
d20r<3 a compare point works too (<, >, =); bare N = =N
6d6r2r4r6 chain compare points to reroll several faces

A reroll throws the old face out before anything else touches the pool, so a die you'd have dropped or exploded is settled first. The die lands on its kept face in the arena; a -v breakdown shows what was tossed (1r4 = rolled a 1, rerolled, kept the 4). A plain r whose compare would match every face is rejected (it could never clear); use ro if you really mean one redraw.

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.

About

A terminal dice roller: shake the dice, watch em' fly, hear it bounce.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Contributing

Stars

5 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors