<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Arshad Khan</title>
    <description>The latest articles on DEV Community by Arshad Khan (@addictedk0126).</description>
    <link>https://dev.to/addictedk0126</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F62024%2F0af373f8-8f39-4472-ae72-3d80ec8d08b4.png</url>
      <title>DEV Community: Arshad Khan</title>
      <link>https://dev.to/addictedk0126</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/addictedk0126"/>
    <language>en</language>
    <item>
      <title>I Built a Snooker Scoring PWA Because the Game Room Scoreboard Only Had 2 Slots</title>
      <dc:creator>Arshad Khan</dc:creator>
      <pubDate>Fri, 26 Jun 2026 21:44:06 +0000</pubDate>
      <link>https://dev.to/addictedk0126/i-built-a-snooker-scoring-pwa-because-the-game-room-scoreboard-only-had-2-slots-1f8g</link>
      <guid>https://dev.to/addictedk0126/i-built-a-snooker-scoring-pwa-because-the-game-room-scoreboard-only-had-2-slots-1f8g</guid>
      <description>&lt;p&gt;I Built a Snooker Scoring PWA Because the Game Room Scoreboard Only Had 2 Slots&lt;/p&gt;

&lt;p&gt;We were three friends. One scoreboard. Zero solutions.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So I did what any developer would do. I built an app.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Here's how I built it, what I learned, and why a PWA was the right choice.&lt;/p&gt;

&lt;p&gt;The Problem&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I couldn't find an app that:&lt;/p&gt;

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

&lt;p&gt;So I decided to build exactly that.&lt;/p&gt;

&lt;p&gt;Tech Stack&lt;/p&gt;

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

&lt;p&gt;Features&lt;/p&gt;

&lt;p&gt;🎮 Three Game Modes&lt;/p&gt;

&lt;p&gt;1v1 — Standard head-to-head&lt;br&gt;
Teams — 2v2 or 3v3 alternating configurations&lt;br&gt;
Free-for-All — 2 to 8 players, round-robin turns&lt;/p&gt;

&lt;p&gt;This was the core reason I built it — Free-for-All mode handles exactly the situation my friends and I were in.&lt;/p&gt;

&lt;p&gt;⚖️ Strict Rules Engine&lt;/p&gt;

&lt;p&gt;The scoring logic is built as a pure state-machine reducer. It handles:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;🔊 Sound Effects — Zero Dependencies&lt;/p&gt;

&lt;p&gt;Instead of importing a sound library, I used the Web Audio API directly — oscillators, gains, and envelopes — to synthesize:&lt;/p&gt;

&lt;p&gt;Pot confirmation chimes&lt;br&gt;
Foul warning buzzes&lt;br&gt;
Break milestone sounds (25, 50, 100 points)&lt;br&gt;
Victory fanfare&lt;/p&gt;

&lt;p&gt;This keeps the bundle lean and works completely offline.&lt;/p&gt;

&lt;p&gt;📶 Offline-First PWA&lt;/p&gt;

&lt;p&gt;Using vite-plugin-pwa with Workbox caching strategies, the app:&lt;/p&gt;

&lt;p&gt;Installs directly to your phone homescreen&lt;br&gt;
Works without internet after first load&lt;br&gt;
Has a custom app icon&lt;/p&gt;

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

&lt;p&gt;☁️ Cloud Sync with Supabase&lt;/p&gt;

&lt;p&gt;When you're signed in with Google, SnookerBee syncs:&lt;/p&gt;

&lt;p&gt;Full match history&lt;br&gt;
Player statistics (highest break, frames won, fouls)&lt;br&gt;
Frame-level action logs&lt;/p&gt;

&lt;p&gt;The database uses Row Level Security (RLS) so each user can only access their own data.&lt;/p&gt;

&lt;p&gt;Database Schema&lt;/p&gt;

&lt;p&gt;Three tables power the cloud sync:&lt;/p&gt;

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

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

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

&lt;p&gt;All three tables have RLS enabled with user-scoped policies.&lt;/p&gt;

&lt;p&gt;Lessons Learned&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;PWA is underrated for sports apps&lt;br&gt;
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.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Web Audio API is powerful and underused&lt;br&gt;
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.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;State machines for game logic are worth the upfront cost&lt;br&gt;
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.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Supabase RLS is your friend&lt;br&gt;
Row Level Security meant I never had to write server-side authorization code. The database enforces it at the query level.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What's Next&lt;/p&gt;

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

&lt;p&gt;Try It&lt;/p&gt;

&lt;p&gt;🌐 Live App: snookerbee.vercel.app&lt;br&gt;
💻 GitHub: github.com/arshad0126/snookerbee&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Built with React 19, TypeScript, Vite, Supabase, and a lot of missed pots.&lt;/p&gt;

&lt;p&gt;Tags: #webdev #react #pwa #supabase #typescript #buildinpublic #javascript #opensource&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>pwa</category>
      <category>superbase</category>
    </item>
  </channel>
</rss>
