All projects
In Progress · since November 2026 ·
next.jspostgresraspberry pipwa

Geile Pfeile

A darts scoring app for phone and tablet - darts come in from an auto-detection board via a Raspberry Pi, or by keypad and tap.


Geile Pfeile

Geile Pfeile

Throw. It counts.

Geile Pfeile is a darts scoring app for the phone or tablet next to the board. If the board has an auto-detection system, a small agent on a Raspberry Pi forwards every dart and the game scores itself; if not, you type it in on a keypad or tap where the dart landed on a 2D board. Around that sits everything a darts evening needs: X01, Cricket and 17 other modes, checkout hints, a caller, stats, a TV screen with walk-ons and win music, and a game that keeps going when the Wi-Fi drops.


What it is

Geile Pfeile keeps score for X01 (301 / 501 / 701 or your own start, straight or double in/out, legs and sets) and 18 other modes split into three groups:

  • Matches - X01, Cricket, Baseball, Golf, Scram.
  • Training - free throwing with a 3-dart average, Around the Clock, Bob’s 27, Count-Up, Checkout 121, Random Target, Checkout Practice.
  • Party games - Killer, Shanghai, Halve-It, Gotcha, Elimination, Nine Lives and Chaos (a 301 race with random rule cards).

Darts get in two ways:

  • Auto-detect - pair a Raspberry Pi running the board’s detection software, start a game in auto mode, and darts score by themselves. Pulling the darts out ends the visit.
  • Manual - a single/double/treble keypad, or tap the board where the dart actually landed.

Players are either guests on your account or linked accounts: a friend signs in once in your settings, and from then on you can pick them as a player and their darts count in their own stats - nobody has to be logged in as them.


Why it exists

Auto-detection boards are great at seeing darts and not much else. The scoring apps that ship with them are tied to one vendor, and the generic darts apps don’t know a detection system exists. Geile Pfeile is the app I wanted in between:

  • The board does the counting, the app does the rest. Detection is just one input; the game, the stats and the show around it live in the app.
  • Corrections are part of the game. Detection misses a dart now and then. Tap the dart, drag it to where it really landed, done - and the board’s accuracy is tracked so you know how much to trust it.
  • Darts night, not a spreadsheet. A caller, 180 confetti, walk-on tunes and a TV that shows the match make it feel like an evening rather than data entry.
  • It works without the board. Most darts are still thrown at boards without detection, so manual entry is a first-class mode, not a fallback.

Core features

FeatureWhat it does
🎯 Auto-detectionA Raspberry Pi agent forwards the detection system’s frames; the service turns them into darts in the game the board is bound to. Start, stop, reset and calibrate the board from the app.
⌨️ Manual entryKeypad with single / double / treble, or tap the 2D board. Undo is always one tap away.
✏️ Score correctionTap a dart of the visit, then drag it on the board or pick the right score. A busted or checked-out visit is replayed correctly.
🧮 19 game modesX01 with legs and sets, Cricket, Baseball, Golf, Scram, seven training modes and seven party games.
✅ Checkout hintsThe finish route for the score you’re on, updated after every dart.
🗣️ CallerSpoken scores after every visit, “One hundred and eighty!”, bust, game shot and what the next player requires.
📊 StatsAverage, first 9, checkout %, turn-score spread, a hit map, doubles and progress over time - filterable by mode, start score, last N darts, period and opponents.
⚖️ HandicapFor X01: weaker players start lower, scaled from the averages in matches the players have played against each other.
📺 TV screenA secret link that shows the live match read-only on any browser - a TV stick, a second screen or a Pi in kiosk mode - with a layout designer for every screen.
🎺 Stage effectsOverlays for 180s, 140+, big finishes and busts, sound effects, walk-on cards with a colour, tune and tagline, and per-player win music cut from any song you upload.
📶 Offline playWhen the connection drops mid-match, darts, undo and “Next” keep working on the device and sync when it’s back.
🧪 Board simulator/sim acts as a paired board and agent, so the whole auto-detect path can be tested without hardware.
📱 InstallableA PWA with a service worker - add it to the home screen of the tablet next to the board.

Vision

Geile Pfeile is a work in progress, and the direction is more of a proper darts evening:

  • Board first. Auto-detection should feel invisible: fewer corrections, faster takeouts, smoother pairing.
  • More modes where they make a night better, without crowding the Play screen.
  • The TV as the stage. The tablet is for throwing, the TV is for everyone else in the room - it should keep getting better at showing what just happened.

Technical details

Stack

LayerChoice
FrameworkNext.js 16 (App Router, server actions)
LanguageTypeScript
UIReact 19, Radix UI primitives
StylingTailwind CSS 4
DatabasePostgreSQL via pg
ORMDrizzle ORM + drizzle-kit, migrations applied on startup
AuthBetter Auth - email and password, optional Google
ValidationZod
RealtimeServer-Sent Events fed by Postgres LISTEN/NOTIFY
Board agentNode 22 on a Raspberry Pi, no runtime dependencies
TestsVitest
DeploymentDocker image (app + agent files)

Engineering decisions

  • Event-sourced games. A game’s state is a replay of its dart rows. Undo is “delete the last dart”, a correction is “replace the dart and replay”, and every device sees the same state because there’s only one source of it.
  • A dumb agent, a smart service. The Pi forwards raw detection frames and interprets nothing. All knowledge of the frame format lives in the service, so an engine update that changes the format is a server deploy, never a trip to the Pi.
  • Outbound only. The agent connects out over HTTPS and long-polls for commands, so the detection software stays on 127.0.0.1, nothing needs port forwarding, and it works behind any NAT.
  • One coordinate system. Board geometry is in board units (1.0 = outer edge of the double ring) - the same units the detection system reports - so tap-to-score, hit markers and detected darts all share one model.
  • Row locks per game. Two devices scoring the same game can’t interleave darts: writes lock the game row first.
  • Realtime across instances. Live updates for the game and TV screens go out as Server-Sent Events fed by Postgres LISTEN/NOTIFY, so they work with several app instances behind a proxy, and screens fall back to a slow re-check if a stream stalls.
  • Offline by replaying locally. The server sends the raw game with every update, so the client can compute the game itself when the connection drops, queue its moves, and on reconnect either apply them or let you choose if someone else changed the game meanwhile.
  • Win music never leaves the device whole. You upload a song, pick where the 15 seconds start, and the browser cuts that part into a small WAV - only the clip is stored.

Get & use Geile Pfeile

Use it

Geile Pfeile runs at geilepfeile.app. Create an account, add a guest or link a friend, and start a match with manual entry. Add it to the tablet’s home screen and it behaves like a native app.

Pair a board

If your board has an auto-detection system on a Raspberry Pi: Settings → Add board, then run the command the app shows on the Pi. It installs, pairs and autostarts the agent. Only connect a system you own or are allowed to use.

Local setup

Needs Node 22+ and pnpm 10. Postgres for development comes from the embedded-postgres dev dependency - real Postgres binaries, no container.

git clone https://github.com/elRatto21/GeilePfeile
cd GeilePfeile/web
cp .env.example .env       # set BETTER_AUTH_SECRET (openssl rand -base64 32)
pnpm install
pnpm db:up                 # Postgres on localhost:15432, keep it running

First run

pnpm dev

# Open http://localhost:3000

Migrations apply themselves on startup. Create an account and start a game - and open /sim in a second tab to try auto-detection without a board.

Uses pnpm for all package management.


Geile Pfeile - throw. It counts.