Browser-based 8-ball pool with a custom 2D rigid-body engine. Drag to aim and pull back to shoot. The table uses sliding → rolling → resting motion, spin-coupled cloth friction, adaptive collision substeps for fast breaks, and simplified APA-style single-player rules.
- Full 16-ball rack on a 2:1 table with six pockets
- Pull-back aiming with live ghost-ball prediction
- Custom physics: Coulomb sliding friction, pure rolling, cushion bounce, and spin
- Adaptive collision micro-steps so high-speed breaks do not tunnel
- Hit-stop, directional screen shake, chalk particles, and motion trails
- Procedural Web Audio whose volume and pitch follow impact speed
- Single-player 8-ball: legal break, group assignment, fouls, and 8-ball win/loss
- Keyboard re-rack (
R)
No backend, login, or network service is required after dependencies are installed.
- Node.js 18+
- npm 9+
- A modern browser with Canvas 2D support
npm install
npm run devVite opens http://127.0.0.1:5173/ by default.
npm run dev # Start the Vite development server
npm run build # Type-check and create the production build
npm run preview # Preview the production build
npm test # Run physics unit tests once| Action | Input |
|---|---|
| Aim and shoot | Drag on the table, pull back to set power, release to strike |
| Re-rack | R |
On break, place the cue behind the head string by aiming from the kitchen and hitting the triangle hard. After a foul the cue is respotted for the next shot.
- Break: legal if at least one ball is pocketed or four object balls hit a cushion. Pocketing the 8 on the break is a loss.
- Open table: the first legally pocketed object ball assigns solids or stripes.
- Fouls: scratch, no contact, or hitting the wrong group first. The cue is respotted.
- Win: clear your group, then pocket the 8 legally.
- Loss: pocket the 8 early, scratch while shooting the 8, or foul on the 8.
UI copy is in Chinese.
World units are centimetres. Integration uses a 120 Hz fixed timestep with extra collision micro-steps when balls travel farther than ~0.32 radii per step.
- Sliding uses Coulomb friction with solid-sphere slide/roll coupling (
I = ⅖ mR²) - Pure rolling begins when contact slip drops below a small epsilon
- Ball–ball restitution is 0.94; cushion restitution falls with impact speed
- Pockets capture by radius and swallow active balls out of the sim
src/
main.ts Game loop, HUD, re-rack
config.ts Table, friction, impulse, and juice constants
game/
Game.ts 8-ball phases, fouls, win/loss
balls.ts Ball numbers, colours, groups
rack.ts Triangle rack layout
physics/
World.ts Fixed-step world and hit-stop
Ball.ts Rigid body with velocity and spin
collisions.ts Ball–ball and cushion contacts
friction.ts Slide → roll → rest
pockets.ts Pocket capture
predict.ts Ghost aim path
spin.ts Visual rotation from ω
Table.ts Cushions and playing surface
input/AimController.ts Pull-back aim and power curve
render/ Canvas renderer, shake, particles
audio/PoolAudio.ts Procedural impact sounds
math/Vec2.ts 2D vector helpers
npm run buildHost the contents of dist/ on any static host (GitHub Pages, Netlify, Cloudflare Pages, or a local HTTP server). No server-side routing or environment variables are required.
- Vite — development and production builds
- TypeScript — typed implementation
- Vitest — physics tests
All rendering, audio, and physics are original. No third-party art or audio assets are included.