Skip to content

olsongl/darts

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Darts Scoring App

Abstract

A single-file, self-contained darts scoring application for playing 301 and 501 games. The entire application—server, game logic, and user interface—lives in one Python file (server.py) with no external dependencies beyond Python's standard library.

The app runs a local HTTP server that serves an embedded HTML/CSS/JavaScript frontend. All game state is managed server-side in Python, while the browser handles rendering and user interaction. Players and win statistics persist to a JSON file, surviving server restarts.

Key characteristics:

  • Zero dependencies — Uses only Python standard library
  • Single file — Everything in server.py (711 lines)
  • Three languages — Python (backend), JavaScript (frontend logic), HTML/CSS (UI)
  • Stateful server — Game state lives in Python memory, synced via REST API
  • Persistent leaderboard — Win counts saved to darts_data.json

Quick Start

python3 server.py
# Open http://localhost:8000

Detailed Architecture

File Structure

server.py          # The entire application (711 lines)
darts_data.json    # Auto-created persistence file for players & wins

Language Breakdown

Section Language Lines Purpose
Backend server Python 1-261 HTTP handling, game logic, persistence
Frontend UI HTML/CSS/JS 265-699 Embedded in Python string literal
Server bootstrap Python 701-711 Entry point

Backend (Python)

File: server.py Language: Python 3 Lines: 1-261, 701-711

Persistence Layer (Lines 12-24)

DATA_FILE = "darts_data.json"    # Line 14
load_data()                       # Lines 16-20: Load players & wins from disk
save_data()                       # Lines 22-24: Write players & wins to disk
  • Players and win counts persist across server restarts
  • Auto-creates file on first save
  • Simple JSON format: {"players": {...}, "wins": {...}}

Game State (Lines 26-31)

players = {}   # id -> {id, name}
wins = {}      # playerId -> win count (integer)
game = None    # Current game state (dict or None)

All state is held in Python global variables. The game object structure:

{
    "variant": 501,              # 301 or 501
    "playerIds": ["1", "2"],     # Turn order
    "scores": {"1": 501, "2": 501},
    "currentPlayerIndex": 0,     # Index into playerIds
    "currentTurn": [],           # List of {segment, score}
    "turnHistory": [],           # Completed turns
    "phase": "playing",          # "playing" | "finished"
    "winnerId": None             # Set when game ends
}

Game Logic Functions (Lines 33-189)

Function Lines Purpose
new_game(variant, player_ids) 33-43 Create fresh game state
segment_score(seg) 45-61 Parse segment string ("T20") to points
is_double(seg) 63-64 Check if segment is a double
current_player_id() 66-69 Get active player's ID
apply_throw(segment) 71-131 Core scoring logic — handles bust, win, turn advancement
advance_player() 133-135 Move to next player (round-robin)
undo_throw() 137-165 Undo last throw or restore previous turn
skip_turn() 167-189 End turn early, advance to next player

Bust Detection (Lines 84-87)

A throw busts if:

  • new_score < 0 — Went below zero
  • new_score == 1 — Can't finish (need double, minimum 2)
  • new_score == 0 && !is_double(segment) — Must finish on a double

On bust, score resets to start-of-turn value (line 100-101).

Win Detection (Lines 102-115)

When new_score == 0 (and not busted):

  • Set phase to "finished"
  • Record winner ID
  • Increment win counter
  • Persist to disk

HTTP Server (Lines 191-261)

Class: DartsHandler (extends SimpleHTTPRequestHandler)

GET Endpoints

Path Lines Response
/ 195-199 Serves the embedded HTML frontend
/api/state 200-201 Returns {players, game, wins} as JSON

POST Endpoints

Path Lines Purpose
/api/player 212-216 Create new player
/api/player/delete 218-223 Delete player by ID
/api/game/start 225-230 Start new game with selected players
/api/game/throw 232-236 Record a dart throw
/api/game/undo 238-240 Undo last throw
/api/game/skip 242-244 Skip remaining throws, advance turn
/api/game/end 246-248 End current game

JSON Response Helper (Lines 253-258)

All API responses use json_response() which:

  • Sets Content-Type: application/json
  • Adds CORS header for cross-origin requests
  • JSON-encodes the response data

Server Bootstrap (Lines 701-711)

if __name__ == "__main__":
    port = 8000
    server = HTTPServer(("", port), DartsHandler)
    server.serve_forever()

Frontend (HTML/CSS/JavaScript)

Location: Embedded in HTML string literal (lines 265-699) Language: HTML5, CSS3, vanilla JavaScript (ES6+)

HTML Structure (Lines 265-354)

  • Single <div id="app"> container
  • All UI rendered dynamically via JavaScript
  • Mobile-optimized viewport meta tag

CSS Styling (Lines 271-351)

Component Lines Description
Base reset 272-275 Box-sizing, dark background
Buttons 277-288 Color variants (yellow, green, blue, red, etc.)
Player rows 290-299 Flexbox layout, selected/active states
Throw display 306-314 Three boxes showing current turn's darts
Bed selector 316-321 Single/Double/Treble toggle buttons
Number grid 323-327 5x4 grid of numbers 1-20
Checkout hint 337-340 Green box showing finish path
Win screen 342-345 Centered layout with large text

JavaScript Application (Lines 356-696)

State Variables (Lines 357-364)

let players = {};        // Synced from server
let game = null;         // Current game state
let wins = {};           // Win counts per player
let selectedIds = [];    // Players selected for next game
let variant = 501;       // Selected game type
let bed = "S";           // Current bed selection (S/D/T)
let confettiInterval;    // Animation frame ID

API Communication (Lines 366-387)

api(path, data)    // POST request, updates state, re-renders
loadState()        // GET /api/state on page load

All API calls automatically update local state and trigger re-render.

Checkout Table (Lines 389-417)

const CHECKOUTS = {
    170: "T20 T20 DB",
    167: "T20 T19 DB",
    // ... 70+ entries for scores 2-170
};

getCheckout(score, dartsLeft)  // Returns path or null

Standard dart checkout combinations. Only shows if achievable with remaining darts.

Confetti Animation (Lines 419-477)

Function Lines Purpose
startConfetti() 422-468 Creates 150 falling rectangles with physics
stopConfetti() 471-477 Cancels animation, clears particles

Uses HTML5 Canvas with requestAnimationFrame for smooth 60fps animation.

Render Function (Lines 479-662)

The render() function handles three screens:

Screen Lines Condition
Game Over 483-525 game.phase === "finished"
Game In Progress 527-605 game.phase === "playing"
Setup/Lobby 607-661 game === null
Game Over Screen (Lines 483-525)
  • Winner announcement
  • Final scores for all players
  • Leaderboard sorted by wins
  • Confetti animation trigger
  • "New Game" button
Game In Progress Screen (Lines 527-605)
  • Player list with scores (active player highlighted)
  • Three throw boxes showing current turn
  • Checkout hint (when applicable)
  • Bed selector (Single/Double/Treble)
  • Number grid (1-20)
  • Bull buttons (25/50)
  • Action buttons (Undo, Miss, Next)
  • Last turn summary
Setup Screen (Lines 607-661)
  • Player name input
  • Player list (tap to select)
  • Win count badges on players
  • Game type selector (501/301)
  • Start game button
  • All-time leaderboard (top 5)

User Actions (Lines 664-692)

Function Lines Purpose
addPlayer() 665-672 Create player from input field
togglePlayer(id) 674-681 Select/deselect player for game
setBed(b) 684-687 Change bed selection
doThrow(segment) 689-692 Record throw, reset bed to Single

Initialization (Line 695)

loadState();  // Fetch initial state on page load

Data Flow

User clicks "T20"
    ↓
doThrow("T20") called
    ↓
POST /api/game/throw {segment: "T20"}
    ↓
Python: apply_throw("T20")
    ↓
Game state updated in memory
    ↓
JSON response: {game: {...}}
    ↓
JavaScript updates local state
    ↓
render() rebuilds entire UI

Segment Notation

Code Meaning Points
S1-S20 Single 1-20 1-20
D1-D20 Double 1-20 2-40
T1-T20 Treble 1-20 3-60
SB Single Bull 25
DB Double Bull 50
MISS Miss 0

Persistence Format

File: darts_data.json

{
  "players": {
    "1": {"id": "1", "name": "Alice"},
    "2": {"id": "2", "name": "Bob"}
  },
  "wins": {
    "1": 5,
    "2": 3
  }
}

Line Count Summary

Section Lines Count
Python imports & persistence 1-24 24
Game state & logic 26-189 164
HTTP server 191-261 71
HTML/CSS (embedded) 265-351 87
JavaScript (embedded) 356-696 341
Server bootstrap 701-711 11
Total 711

About

Darts demo

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages