Post

Interactive Snooker Simulator (p5.js + Matter.js)

Interactive Snooker Simulator (p5.js + Matter.js)

What is Snooker?

If you’ve ever seen people in waistcoats on TV, calmly rolling balls across a huge green table with a poker face — that’s snooker. Not billiards, not pool, but snooker — the most strategically complex and elegant form of cue sports.

Unlike regular pool with its 15 balls and chaos, snooker follows a strict order:

  • The table has 15 red balls (1 point each) and 6 colours — yellow (2), green (3), brown (4), blue (5), pink (6), and black (7).
  • Players must alternate: pot a red → then any colour → red again → colour again.
  • While reds remain on the table, potted colours are re-spotted to their original positions. Once all reds are gone, colours must be potted in strict order: from yellow through to black.
  • Hit the wrong ball? Potted the cue ball? That’s a foul — your opponent gets 4 penalty points.

This rigid system turns every shot into a miniature chess game: it’s not just about potting — you need to leave the cue ball in a good position for your next shot.


What I Built

I wrote a full-featured 2D snooker simulator that runs right in the browser — no installation, no downloads, just open and play. Two players at one computer, realistic collision physics, complete rules with fouls and scoring.

👉 Play it now → roboeggs.github.io/snooker/

🛠️ Important: open DevTools before playing!

All game events — ball collisions, fouls, scoring, turn changes — are logged to the browser console. To see what’s happening in the game, open DevTools before you start playing:

BrowserWindows / LinuxmacOS
ChromeF12 or Ctrl + Shift + JCmd + Option + J
FirefoxF12 or Ctrl + Shift + KCmd + Option + K
EdgeF12 or Ctrl + Shift + JCmd + Option + J
SafariCmd + Option + C (enable “Develop menu” in settings first)

Go to the Console tab — you’ll see messages like:

1
2
3
4
5
6
7
8
🎮 GAME STARTED! Place the cue ball in a convenient spot.
✓ Cue ball placed! You can start your shot.
💥 COLLISION: Cue ball → RED ball
✅ CORRECT! You hit the red ball.
⏳ Waiting for balls to stop...
🔴 All red balls are potted! Switching to colored balls in order.
❌ ERROR! You must hit the YELLOW ball, but you hit BLUE.
🏁 GAME OVER! All balls are potted.

Tip: Position the browser window and Console panel side by side so you can play and watch events at the same time.

If you’re not sure how the controls work right away — there’s a video below showing how to aim and strike:


How the Strike Works — and Why It’s Not a “Slingshot”

The most common strike implementation in browser billiards is the “slingshot”: hold the mouse, pull back, release. The further you pull — the harder you hit. Simple, but it feels like a catapult, not a cue.

In my snooker, the strike works differently. The force is determined by the cursor velocity at the moment of release — just like in real snooker, where the power depends on how fast you push the cue forward.

How it works under the hood:

  1. Press the mouse button — the cue automatically pulls back, the aiming direction locks in.
  2. Drag the mouse — the cue follows the cursor along the aiming line. The system records a velocity history over the last 5 frames and averages them for smoothness.
  3. Release the mouse — at this moment, the average cursor velocity is read. It’s multiplied by a coefficient (VELOCITY_MULTIPLIER = 0.003) and capped at a maximum (MAX_STRIKE_FORCE = 0.15). The resulting force vector is applied to the cue ball as a Matter.js impulse.

The result — the strike feels physical: flick the mouse quickly → powerful shot, guide it gently → soft control. It’s a completely different feeling from just “pull back and release.”

The cue also visually reacts to the force — it changes colour from white to red based on distance, showing how powerful the shot will be.


What Happens On Screen

The entire game is rendered in real time via p5.js. Every frame:

  • The table is drawn with green baize, wooden cushions, and golden corner accents near the pockets.
  • Balls are objects with shadows and highlights, each linked to a physics body in Matter.js.
  • The cue smoothly follows the mouse with dynamic smoothing: the faster you move the mouse, the faster the cue responds; the slower you move, the smoother it rotates. No jerky movement.
  • The aiming line shows the cue ball’s trajectory. If another ball is in the path, a collision point and rebound vector are drawn so you can see where the target ball will go.

Implemented Rules

Everything by the book:

RuleHow It Works
🔴 AlternationRed first, then colour — the system tracks the phase and won’t let you make a silent mistake
⚠️ FoulsHit the wrong ball or potted the cue ball → +4 points to opponent, turn passes
🔄 Colour Re-spottingWhile reds remain on the table, potted colours return to their spots
🏁 Final SequenceWhen reds are gone, colours are potted in order: yellow → green → brown → blue → pink → black
👥 Two PlayersTurn passes after a miss or foul, scores displayed at the top of the screen

Controls

ActionResult
Click on tableAt start — place cue ball in the D zone
Hold mouseCue pulls back, aiming direction locks
Drag mouseAdjust cue position
Release mouseStrike! Force = cursor velocity
DToggle cue aiming mode
1 / 2 / 3Change ball setup (classic / random reds / all random)

Technologies

The project is written in pure JavaScript (ES6+) with no frameworks, bundlers, or npm dependencies. Open index.html — everything works.

  • p5.js — 2D graphics, 60 FPS animation, mouse and keyboard input
  • Matter.js — physics engine: elastic collisions, impulses, pocket sensors
  • HTML5 Canvas — all rendering on a single canvas element

Project Structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
snooker/
├── index.html                 # Entry point
├── main.js                    # Initialization, game loop
├── setup.js                   # Table, cushions, pockets, ball placement
├── render.js                  # Rendering: table, balls, cue, trajectories
├── physics.js                 # Collisions, pocket detection
├── Player.js                  # Player: score, fouls, strike state
├── SnookerBall.js             # Ball: movement, freezing, position reset
├── core/
│   └── GameConfig.js          # All constants: dimensions, physics, scoring
├── game/
│   ├── GameLogic.js           # Game brain: shot validation, potting logic
│   └── GameStateManager.js    # Finite state machine for game phases
├── systems/
│   └── InputHandler.js        # Input: mouse, keyboard, velocity calculation
└── utils/
    └── helpers.js             # Utilities: vertex rendering, cue calculations

Academic Report

For those who want to dive into the technical side of the project — a detailed academic report covering the architecture, physics algorithms, state diagrams, and performance analysis:

Read the report (PDF)

The report covers:

  • Application architecture and the Finite State Machine (FSM) pattern
  • Collision physics model powered by Matter.js
  • Snooker rule system and foul handling
  • Class structure (Player, SnookerBall, GameStateManager)
  • User input processing and rendering via p5.js

Source Code

All code is open — feel free to explore, star, and fork:

GitHub

This post is licensed under CC BY 4.0 by the author.