<?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: Forrest Miller</title>
    <description>The latest articles on DEV Community by Forrest Miller (@forrestmiller).</description>
    <link>https://dev.to/forrestmiller</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3872475%2F1b38c5a4-9313-4bc3-8bfd-a21db422888e.jpg</url>
      <title>DEV Community: Forrest Miller</title>
      <link>https://dev.to/forrestmiller</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/forrestmiller"/>
    <language>en</language>
    <item>
      <title>I Built a Real-Time Multiplayer Bingo Engine with Next.js, Supabase, and Ably</title>
      <dc:creator>Forrest Miller</dc:creator>
      <pubDate>Fri, 10 Apr 2026 21:10:09 +0000</pubDate>
      <link>https://dev.to/forrestmiller/i-built-a-real-time-multiplayer-bingo-engine-winextjs-webdev-javascript-reactth-nextjs-2og1</link>
      <guid>https://dev.to/forrestmiller/i-built-a-real-time-multiplayer-bingo-engine-winextjs-webdev-javascript-reactth-nextjs-2og1</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;BingWow&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://bingwow.com&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; is a free multiplayer bingo platform. You type a topic, AI generates a card, and up to 20 people play together in the browser. No downloads, no accounts. Teachers use it for classroom review games, event planners use it for baby showers and team building, and watch party hosts use it for live TV events.

Here's how the real-time multiplayer works under the hood.

&lt;span class="gu"&gt;## The Stack&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Next.js 16**&lt;/span&gt; (App Router) on Vercel
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Supabase**&lt;/span&gt; (PostgreSQL + Auth + Storage)
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Ably**&lt;/span&gt; for real-time pub/sub
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Tailwind v4**&lt;/span&gt; for styling

&lt;span class="gu"&gt;## The Hard Problem: Simultaneous Bingo Claims&lt;/span&gt;

Most of the game is simple. A player taps a cell, the client marks it optimistically, and a fire-and-forget POST goes to the server. No waiting, no blocking.

The moment it gets complicated is bingo. When a claim completes a line, you need to atomically determine who won. Two players might complete their lines within milliseconds of each other. The server has to pick one winner and broadcast the result to everyone.

We solve this with a Supabase RPC function called &lt;span class="sb"&gt;`claim_and_process`&lt;/span&gt;. It runs in a single database transaction: insert the claim, check if it completes a line, and if so mark the round winner. The atomicity of the transaction means two simultaneous bingo claims can't both win.

&lt;span class="gu"&gt;## Board Generation: Deterministic Randomness&lt;/span&gt;

Every player needs a unique board, but late joiners need to reconstruct the exact same board a player would have gotten had they joined at the start. We solve this with seeded PRNGs.

The room gets a random seed at creation. Each player's board is derived from &lt;span class="sb"&gt;`(roomSeed XOR hash(playerId))`&lt;/span&gt;. The hash function is FNV-1a, the PRNG is Mulberry32. Both are deterministic: same inputs, same board, every time.

This means we never store boards in the database. Any client can reconstruct any player's board from the seed and player ID. Late joiners derive their board locally and overlay the current claim state from a single &lt;span class="sb"&gt;`game/state`&lt;/span&gt; API call.

&lt;span class="gu"&gt;## Wildcard Mode: Shared Clues with Individual Variation&lt;/span&gt;

In wildcard mode (always on for online play), players share about 2/3 of their clues with 1/3 unique per player. The room RNG selects a "core" set, and each player's own RNG picks their variable clues from the remaining pool.

This creates the right balance: enough overlap that calling clues is meaningful, enough variation that copying someone else's board doesn't work.

&lt;span class="gu"&gt;## Real-Time Events via Ably&lt;/span&gt;

Every room subscribes to an Ably channel &lt;span class="sb"&gt;`room:{code}`&lt;/span&gt;. Events include &lt;span class="sb"&gt;`claim`&lt;/span&gt;, &lt;span class="sb"&gt;`bingo`&lt;/span&gt;, &lt;span class="sb"&gt;`new-round`&lt;/span&gt;, &lt;span class="sb"&gt;`player-joined`&lt;/span&gt;, &lt;span class="sb"&gt;`chat`&lt;/span&gt;, and &lt;span class="sb"&gt;`unclaim`&lt;/span&gt;. Claims are fire-and-forget from the server -- the DB is the source of truth, and Ably events are convenience notifications.

On reconnect after a disconnect, the client calls &lt;span class="sb"&gt;`fetchGameState`&lt;/span&gt; to reconcile any missed events. A heartbeat POST every 2 minutes keeps &lt;span class="sb"&gt;`last_active_at`&lt;/span&gt; fresh for the expiry cron.

&lt;span class="gu"&gt;## AI Card Generation&lt;/span&gt;

When a user types a topic on &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;bingwow.com/create&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://bingwow.com/create&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;, we stream clues from Gemini via SSE. The user sees clues appear one by one as the AI generates them. They can edit any clue inline, change the grid size (3x3, 4x4, 5x5), or regenerate with a different tone.

Background images are generated in parallel via Replicate's FLUX Schnell model (~2 seconds), then screened by Claude Haiku for text artifacts before being attached to the card.

&lt;span class="gu"&gt;## What I'd Do Differently&lt;/span&gt;

&lt;span class="gs"&gt;**Skip the custom PRNG.**&lt;/span&gt; Mulberry32 was fun to implement but a simple &lt;span class="sb"&gt;`crypto.getRandomValues`&lt;/span&gt; with a seed would have been simpler. The deterministic requirement is real, but there are libraries for this.

&lt;span class="gs"&gt;**Use server-sent events for game state instead of polling + Ably.**&lt;/span&gt; Ably works great but adds a dependency. SSE from a Next.js route handler could handle the claim/bingo broadcasts with one fewer service.

&lt;span class="gu"&gt;## Try It&lt;/span&gt;

The whole thing is free at &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;bingwow.com&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://bingwow.com&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;. Teachers can find classroom-specific guides at &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;bingwow.com/for/teachers&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://bingwow.com/for/teachers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;. The card creator is at &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;bingwow.com/create&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://bingwow.com/create&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>gamedev</category>
      <category>nextjs</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
