Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
Realtime poker chip tracker for your home game. Everyone plays from their own phone while the cards stay real.
GamesTypeScript · CSSGitHub @KarthikSubramanian07OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Chips on your phone, cards on the table. A realtime chip tracker for live home poker.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Chips on your phone. Cards on the table. A realtime chip tracker for live home poker: deal real cards, and everyone bets, raises and scoops pots from their own phone.
poker home-game chip-tracker texas-holdem realtime websockets durable-objects cloudflare-pages react pwa
Somebody always forgets the chips. Somebody else "borrows" a stack. Side pots turn into a group negotiation. So the cards stay physical (shuffling is half the fun) and the chips moved into everyone's pocket.Read the full README ↗Chips on your phone. Cards on the table. A realtime chip tracker for live home poker: deal real cards, and everyone bets, raises and scoops pots from their own phone.
poker home-game chip-tracker texas-holdem realtime websockets durable-objects cloudflare-pages react pwa
Play it: piss-poker.pages.dev
Somebody always forgets the chips. Somebody else "borrows" a stack. Side pots turn into a group negotiation. So the cards stay physical (shuffling is half the fun) and the chips moved into everyone's pocket.
Before writing a line, we read the code of 22 open source chip trackers and put 9 betting engines through scripted side pot and all-in scenarios. Every tracker had real rule bugs (short all-ins reopening betting, the big blind posted twice when the table shrinks, kicks deleting chips, clients trusted to say who they are), and none of the engines passed every scenario. So this one was built from scratch around a small, heavily tested rules engine and a server that owns the truth.
F fold, C check or call, R raise, arrows to size, Enter to confirm, N next hand) on iPad and desktop. Light and dark follow the device setting. phones, iPads, laptops Cloudflare
┌──────────────────────┐ HTTPS ┌─────────────────────────────────────────────┐
│ React 19 SPA (Vite) │──────────▶│ Pages: static app + /api/* Pages Function │
│ shared/engine.ts │ │ │ service binding │
│ (legal moves, UI) │ WSS │ ▼ │
│ │◀─────────▶│ Worker "piss-poker-api" │
└──────────────────────┘ │ POST /api/rooms create a table │
│ GET /api/rooms/:code peek at a table │
│ GET /api/rooms/:code/ws WebSocket upgrade │
│ │ idFromName(code) │
│ ▼ │
│ Durable Object "Room" (one per table) │
│ shared/engine.ts (authoritative rules) │
│ hibernating WebSockets, SQLite storage │
│ undo history, presence, claims, alarms │
└─────────────────────────────────────────────┘
The server owns the truth. Phones send intentions (act, award, undo); the room's Durable Object validates them against the rules engine and broadcasts the new state to everyone. A client cannot move chips, act out of turn or pretend to be someone else.
Every change is versioned. Each state carries a version v, and every action names the version it was decided on. Double taps, two people pressing "Deal next hand" at once, and actions taken from a stale screen are rejected with STALE. The sender gets the fresh state back.
Identity is a device secret. Each device generates a random 256-bit token. The room stores only a SHA-256 hash of it (salted with the table code) and never broadcasts it. Taking over a seat from a new device needs approval from someone at the table, unless nobody else is around.
Undo rewinds the game, not the guest list. The last 30 game states are kept. Membership lives outside that history, so undoing never removes someone who just sat down or brings back someone who was removed.
Resilient on real phones. iOS freezes sockets when a phone locks, so clients ping every 10 seconds (answered by the runtime without waking the object), reconnect on focus, visibility and network changes, and back off with jitter. Rooms hibernate when idle and delete themselves after 12 quiet hours.
| Path | What lives there |
|---|---|
shared/engine.ts | Pure betting engine: blinds, turn order, raises, side pots, payouts. No dependencies. |
shared/protocol.ts | Wire message types and strict validation of everything a client sends. |
shared/settle.ts | Ledger, cents conversion that always sums to zero, minimal transfers. |
shared/names.ts | Name cleanup: Unicode normalization, invisible and bidi characters stripped, grapheme limits. |
worker/src/room.ts | The Room Durable Object: sockets, permissions, undo, presence, rate limits, expiry. |
worker/src/index.ts | API router, origin allowlist, room creation with per-address rate limiting. |
functions/api/[[path]].ts | Pages Function forwarding /api/* to the worker. |
functions/_middleware.ts | Accept negotiation (text/markdown), real 404s, SPA rewrite for /t/*. |
public/ | Trust pages, llms.txt, markdown mirrors, 404.html, SEO assets. |
src/ | The React app: screens, the action dock, sheets, table display. |
test/ | Engine scenarios, a seeded fuzzer, protocol and settlement tests. |
worker/test/ | Durable Object integration tests running inside workerd. |
e2e/ | Playwright suite with several simulated devices, plus a screenshot walkthrough. |
npm run test:unit # engine scenarios + 1,500 fuzzed games + protocol + settle-up
npm run test:worker # Durable Object and WebSocket protocol inside workerd
npm run test:e2e # Playwright: full hands across simulated phones, iPad and desktop
FUZZ_GAMES=25000 npm run test:unit # the long fuzz run
Scenario tests cover every rule bug found while auditing other projects:
The fuzzer plays random games of 2 to 9 players with short, mixed and deep stacks, random leaves and deliberate illegal moves. After every action it checks:
25,000 games pass clean.
Integration tests cover stale versions, concurrent next and undo, seat claims, kicks, host handoff, oversized and malformed messages, rate limits and room expiry.
End-to-end tests cover:
npm install
npm run dev # worker on :8787 and Vite on :5173 with /api proxied
Open two browser windows (or a normal and a private window) at http://localhost:5173 to play against yourself.
Other scripts: npm run typecheck, npm run build, npm run shots (screenshots of every screen in /tmp/pp-shots), npm run assets (regenerates icons and the OpenGraph image).
Pushes to main ship automatically once typecheck, unit, worker and end-to-end tests pass:
deploy-web job calls a Cloudflare Pages deploy hook (repository secret PAGES_DEPLOY_HOOK). Pages then builds (npm run test:unit && npm run build) and serves piss-poker.pages.dev.deploy-api job runs wrangler deploy. It needs a CLOUDFLARE_API_TOKEN secret with Workers Scripts: Edit permission (CLOUDFLARE_ACCOUNT_ID is already set). The worker rarely changes, so without the token it is skipped and can be shipped with npm run deploy:api.Manual deploy from a logged-in machine: npm run deploy.
React 19, Vite 8, TypeScript 5.9, Cloudflare Pages, Workers, Durable Objects (SQLite-backed, WebSocket hibernation), Vitest 4 with @cloudflare/vitest-pool-workers, Playwright, Geist and Instrument Serif, uqr for QR codes.
MIT © Karthik Subramanian