惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
D
Docker
有赞技术团队
有赞技术团队
D
DataBreaches.Net
The GitHub Blog
The GitHub Blog
爱范儿
爱范儿
H
Help Net Security
美团技术团队
MyScale Blog
MyScale Blog
B
Blog RSS Feed
C
Check Point Blog
Microsoft Security Blog
Microsoft Security Blog
阮一峰的网络日志
阮一峰的网络日志
A
About on SuperTechFans
小众软件
小众软件
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
GbyAI
GbyAI
G
Google Developers Blog
月光博客
月光博客
Google DeepMind News
Google DeepMind News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Blog — PlanetScale
Blog — PlanetScale
MongoDB | Blog
MongoDB | Blog
F
Fortinet All Blogs

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
I Built a Snooker Scoring PWA Because the Game Room Score...
Arshad Khan · 2026-06-27 · via DEV Community

I Built a Snooker Scoring PWA Because the Game Room Scoreboard Only Had 2 Slots

We were three friends. One scoreboard. Zero solutions.

Every time my friends and I hit the snooker table, we'd run into the same problem — the scoreboard on the wall was built for two players only. One of us always had to manually keep score in their head, or on a piece of paper, or just... trust the other two. Not ideal when points are on the line.

So I did what any developer would do. I built an app.

That app is SnookerBee — a mobile-first Progressive Web App for tracking snooker scores, built with React 19, TypeScript, Vite, Supabase, and Google OAuth. It's live at snookerbee.vercel.app and the full source is on GitHub.

Here's how I built it, what I learned, and why a PWA was the right choice.

The Problem

Snooker is typically played 1v1. Most scoreboards, apps, and trackers assume this. But snooker rooms often have 3-4 people sharing a table in a free-for-all — rotating turns, individual scores, tracking who won which frame.

I couldn't find an app that:

Supported more than 2 players properly
Worked offline (game rooms often have spotty Wi-Fi)
Felt designed for snooker specifically — not a generic "sports tracker"
Was installable on mobile without going through an App Store

So I decided to build exactly that.

Tech Stack

LayerTechnologyFrontendReact 19 + TypeScriptBuild ToolViteRoutingReact Router v7StylingVanilla CSS (custom tokens, glassmorphism)PWAvite-plugin-pwa + WorkboxAudioWeb Audio APIBackendSupabase (PostgreSQL)AuthGoogle OAuth via Supabase AuthHostingVercel

Features

🎮 Three Game Modes

1v1 — Standard head-to-head
Teams — 2v2 or 3v3 alternating configurations
Free-for-All — 2 to 8 players, round-robin turns

This was the core reason I built it — Free-for-All mode handles exactly the situation my friends and I were in.

⚖️ Strict Rules Engine

The scoring logic is built as a pure state-machine reducer. It handles:

Official red-color sequence enforcement
Free ball nomination
Re-spotted black on tied scores
Turn rotation management across all game modes
10-step deep-cloned undo stack

Getting this right was honestly the hardest part of the project. Snooker rules have a lot of edge cases — especially around fouls, free balls, and end-game scenarios.

🔊 Sound Effects — Zero Dependencies

Instead of importing a sound library, I used the Web Audio API directly — oscillators, gains, and envelopes — to synthesize:

Pot confirmation chimes
Foul warning buzzes
Break milestone sounds (25, 50, 100 points)
Victory fanfare

This keeps the bundle lean and works completely offline.

📶 Offline-First PWA

Using vite-plugin-pwa with Workbox caching strategies, the app:

Installs directly to your phone homescreen
Works without internet after first load
Has a custom app icon

This was critical for the game room use case — you don't want your scoring app to fail mid-match because of Wi-Fi.

☁️ Cloud Sync with Supabase

When you're signed in with Google, SnookerBee syncs:

Full match history
Player statistics (highest break, frames won, fouls)
Frame-level action logs

The database uses Row Level Security (RLS) so each user can only access their own data.

Database Schema

Three tables power the cloud sync:

sql-- Matches table
create table matches (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users(id) on delete cascade,
mode text not null,
reds_count int not null,
best_of int not null,
created_at timestamptz default now(),
duration_ms bigint not null,
winner_name text not null
);

-- Players per match
create table match_players (
id uuid primary key default gen_random_uuid(),
match_id uuid references matches(id) on delete cascade,
player_name text not null,
team_name text,
total_score int default 0,
highest_break int default 0,
frames_won int default 0,
fouls_committed int default 0,
time_spent_ms bigint default 0
);

-- Frame-level logs
create table match_frames (
id uuid primary key default gen_random_uuid(),
match_id uuid references matches(id) on delete cascade,
frame_number int not null,
duration_ms bigint,
action_log jsonb
);

All three tables have RLS enabled with user-scoped policies.

Lessons Learned

  1. PWA is underrated for sports apps
    The offline capability + homescreen install covers 90% of what a native app gives you, without the App Store overhead. For a game room tool, this is ideal.

  2. Web Audio API is powerful and underused
    I expected to need a library for sounds. Turns out the native API handles everything I needed with less than 100 lines of code, and zero extra bundle weight.

  3. State machines for game logic are worth the upfront cost
    I almost built the scoring logic with scattered useState calls. Switching to a reducer-based state machine early saved me from dozens of bugs later — especially with undo functionality.

  4. Supabase RLS is your friend
    Row Level Security meant I never had to write server-side authorization code. The database enforces it at the query level.

What's Next

Android app via TWA (Trusted Web Activity) / Bubblewrap — Play Store submission
iOS app via Capacitor.js wrapper
Tournament mode — bracket-style multi-match tournaments
Bluetooth scoreboard integration (experimenting)

Try It

🌐 Live App: snookerbee.vercel.app
💻 GitHub: github.com/arshad0126/snookerbee

If you play snooker with more than one friend — give it a try. And if you find a bug or want to contribute, PRs are welcome.

Built with React 19, TypeScript, Vite, Supabase, and a lot of missed pots.

Tags: #webdev #react #pwa #supabase #typescript #buildinpublic #javascript #opensource