Skip to content

TwistedMelonIO/split-the-difference

Repository files navigation

Split The Difference

A live game show control system for Split The Difference — a 5-round betting game where two teams wager points each round and win or lose them based on challenge outcomes.

Built with Node.js + Express, WebSocket, and OSC. Designed to run in Docker and integrate with QLab (score display) and Elgato Stream Deck (result triggers).

Latest Version: v2.0.0 — Automatic winner detection after Round 5 with QLab cue arming (BLUEWIN / REDWIN).


Overview

Feature Detail
Teams 2 teams, split-screen left/right UI
Starting score 100 points each
Rounds 5 named rounds
Betting +5 increments, max = current score
Scoring Correct = score + bet · Incorrect = score − bet
OSC out Scores pushed to QLab text cues after every change
OSC in Stream Deck triggers correct/incorrect per team
Web UI Operator-facing control panel at port 4002
Settings Passcode-locked settings page (default: 8888)

Round Names

  1. Risk It For a Biscuit
  2. Bet Your Bottom Dollar
  3. Mass Mayhem
  4. Push Your Luck
  5. Double or Disaster

Requirements

Dependency Version Notes
Docker 24+ Required for containerised deployment
Docker Compose v2 (plugin) Included with Docker Desktop
QLab 4 or 5 Receives score text via OSC on port 53000
Elgato Stream Deck Any Sends OSC UDP messages to port 3003
Node.js 22 (Alpine, inside Docker) No host install needed

No Node.js install required on the host machine. Everything runs inside the Docker container.


Installation

1. Clone the repository

git clone https://github.com/TwistedMelonIO/split-the-difference.git
cd split-the-difference

2. Configure environment (optional)

The defaults work out of the box. To customise, copy the example file:

cp .env.example .env

Edit .env as needed:

QLAB_HOST=host.docker.internal   # macOS default — reaches host QLab from container
QLAB_PORT=53000                   # QLab OSC listen port
OSC_LISTEN_PORT=3001              # Internal OSC receive port (mapped to 3003 externally)
SCORE_L_CUE=SCORE_L              # QLab cue name for left team score
SCORE_R_CUE=SCORE_R              # QLab cue name for right team score

3. Build and start

docker compose up -d --build

The first build downloads the Node 22 Alpine image and installs dependencies (~30 seconds on fast connection).

4. Open the control panel

Navigate to http://localhost:4002 in any browser on the host machine.


Updating

Pull the latest code and rebuild:

git pull
docker compose down
docker compose up -d --build

Ports

Port Protocol Purpose
4002 TCP Web UI (operator control panel)
3003 UDP OSC input — Stream Deck messages

Port 3003/udp must be reachable from the Stream Deck network. If the Stream Deck is on the same machine, use 127.0.0.1:3003.


OSC Integration

Stream Deck → Split The Difference (incoming, port 3003)

Configure your Stream Deck OSC plugin to send UDP messages to the host running the container:

Round Control (v1.1.0+)

OSC Address Action
/std/round/1 Start Round 1 (Risk It For a Biscuit)
/std/round/2 Start Round 2 (Bet Your Bottom Dollar)
/std/round/3 Start Round 3 (Mass Mayhem)
/std/round/4 Start Round 4 (Push Your Luck)
/std/round/5 Start Round 5 (Double or Disaster)

Result Triggers

OSC Address Action
/std/team1/correct Team 1 answered correctly (score + bet)
/std/team1/incorrect Team 1 answered incorrectly (score − bet)
/std/team2/correct Team 2 answered correctly (score + bet)
/std/team2/incorrect Team 2 answered incorrectly (score − bet)
/std/reset Master reset (all scores → 100)
  • OSC messages are only acted on during the PLAYING phase (except round start commands)
  • Per-round commands are available in v1.1.0+

Split The Difference → QLab (outgoing, port 53000)

Scores are pushed automatically after every result and on master reset.

Score Updates

/cue/SCORE_L/text "100"
/cue/SCORE_R/text "85"

In QLab, create two Text cues named SCORE_L and SCORE_R. The cue names can be changed via the Settings page.

Winner Cue Arming (v2.0.0+)

After Round 5, when both results are in, the server automatically compares final scores and arms the winning cue:

Outcome Armed Disarmed
Blue wins (or tie) /cue/BLUEWIN/armed 1 /cue/REDWIN/armed 0
Red wins /cue/REDWIN/armed 1 /cue/BLUEWIN/armed 0

In QLab, create two cues named BLUEWIN and REDWIN. Only the winner's cue will be armed after the final round.


Game Flow

IDLE → BETTING → PLAYING → REVEAL → IDLE (next round)
Phase What happens
IDLE Waiting. Press "Start Round N" to begin.
BETTING Each team sets their bet in +5 increments and locks it.
PLAYING Waiting for Stream Deck result triggers.
REVEAL Both results shown. Scores updated. Advance to next round.

After Round 5 the game ends. Use Master Reset to start a new game.


Keyboard Shortcuts (Operator Panel)

Key Action
Space Advance phase (Next Phase button)
Q Team 1 bet +5
A Team 1 bet −5
W Lock Team 1 bet
P Team 2 bet +5
L Team 2 bet −5
O Lock Team 2 bet

Settings Page

Access at http://localhost:4002/settings.html

Default passcode: 8888

From the settings page you can:

  • Change QLab cue names for left and right scores
  • Rename Team 1 and Team 2
  • View the live application log
  • Trigger a master reset

Architecture

┌─────────────────────────────────────────────┐
│              Docker Container               │
│                                             │
│  Node.js server.js                          │
│  ├── Express (HTTP API + static files)      │
│  ├── WebSocket (ws) — real-time UI sync     │
│  ├── OSC Client → QLab :53000               │
│  └── OSC Server ← Stream Deck :3001         │
│                                             │
│  public/                                    │
│  ├── index.html  — operator control panel   │
│  ├── app.js      — frontend WebSocket logic │
│  ├── styles.css  — neon arena design        │
│  ├── settings.html — passcode-locked config │
│  ├── settings.css                           │
│  └── settings.js                            │
└─────────────────────────────────────────────┘
         │ :4002 (web)    │ :3003/udp (OSC in)
         ▼                ▼
     Browser          Stream Deck

         │ :53000/udp (OSC out)
         ▼
       QLab

Docker Reference

# Start (detached)
docker compose up -d

# Stop
docker compose down

# Rebuild after code changes
docker compose up -d --build

# View live logs
docker logs -f split-the-difference

# Restart without rebuilding
docker compose restart

Environment Variables

Variable Default Description
QLAB_HOST host.docker.internal QLab hostname (macOS Docker host alias)
QLAB_PORT 53000 QLab OSC port
OSC_LISTEN_PORT 3001 Internal OSC receive port
SCORE_L_CUE SCORE_L QLab cue name — left team score
SCORE_R_CUE SCORE_R QLab cue name — right team score

Troubleshooting

QLab not receiving scores

  • Confirm QLab's OSC input is enabled on port 53000 (QLab → Settings → OSC)
  • Confirm QLAB_HOST=host.docker.internal (correct for macOS Docker Desktop)
  • Check the application log (Settings page → Application Logs)

Stream Deck OSC not triggering results

  • Confirm the Stream Deck OSC plugin target is 127.0.0.1:3003 (or the host IP on a remote machine)
  • Confirm the container is running: docker ps
  • OSC messages are only acted on during the PLAYING phase

Port conflict on 4002 or 3003

  • Edit docker-compose.yml and change the host-side port mapping (left side of the colon)
  • Example: change "0.0.0.0:4002:3000" to "0.0.0.0:5002:3000"

Container won't start

  • Run docker logs split-the-difference to see startup errors
  • Ensure Docker Desktop is running

License

Proprietary — © Twisted Melon. All rights reserved.

About

Split The Difference — game show control system with OSC, QLab, and Stream Deck integration

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors