A polished, browser-based Blackjack game with solo and sequential local multiplayer against a classic automated Dealer.
Overview · Showcase · Features · Architecture · Tech Stack · Quick Start · Testing · Documentation
Blackjack is a lightweight card game designed around a focused casino-style interface. In BOT mode, Player 1 plays against the Dealer. In Two Players mode, Player 1 completes their hand first, Player 2 plays second, and the Dealer resolves the round last. Each mode keeps its own scoreboard.
The interface uses a continuous charcoal card table, ivory playing cards, restrained typography, Lucide icons, responsive layouts, and inline round feedback that keeps the game visible.
- Three game modes — Play Player 1 vs Dealer in BOT mode, sequential Player 1 → Player 2 → Dealer in local Two Players mode, or peer-to-peer multiplayer with host authority in Online P2P mode.
- Mode-specific scoreboards — Local, BOT, and Online table victories are tracked independently.
- Responsive casino-style UI — Neutral charcoal surfaces, ivory controls, animated cards, and responsive behavior.
- Classic Dealer flow — The Dealer keeps the second card hidden until its turn, hits below 17, and stands on 17+.
- P2P Multiplayer with Information Hiding — Host acts as authoritative table master over WebRTC DataChannels. The Dealer hole card and undealt cards never leak to the guest.
- Clear game feedback — Outcome badges and inline notifications communicate wins, pushes, losses, busts, and ready/rematch states without blocking the table.
The animated preview shows a short excerpt of bot play, local two-player mode, Online P2P connection/gameplay, and round feedback. Open the full video below for the complete flow.
- Full 52-card deck with suits, face cards, and shuffled dealing.
- 21-point scoring with flexible Ace values of 1 or 11.
- Classic Dealer hole-card flow with the second Dealer card hidden until the Dealer turn.
- BOT mode for Player 1 vs Dealer.
- Sequential local Two Players mode: Player 1 → Player 2 → Dealer.
- Online P2P mode:
- WebRTC RTCDataChannel peer-to-peer gameplay with zero gameplay relay over the server.
- Short 4-character room codes for easy table sharing.
- Cloudflare Worker + Durable Object signaling layer with max 2 peers per room.
- Host authority: Host (Player 1) executes deck shuffling, card dealing, rule validation, dealer autoplay, and scoring.
- Guest validation: Guest (Player 2) sends only versioned, typed intentions (
hit,stand,ready,rematch). - Strict information hiding: Deck order, undealt cards, and the dealer's hidden hole card are never sent to the network before reveal.
- Full rematch synchronization and connection lifecycle handling (creating, waiting, connecting, connected, disconnected, retry).
- Dealer autoplay that hits below 17 and stands on 17+.
- Binary per-player scoring against the Dealer, with independent scoreboards by mode.
- 30-second round timer with automatic round resolution.
- Temporary win, push, loss, and bust notifications.
- Game rules dialog with keyboard support through
Escape. - Keyboard controls:
1for Player 1,2for Player 2,Hto hit,Sto stand, andRto reset scores. - Responsive layout with a Blackjack favicon and Lucide interface icons.
The project is structured as a client-first application that connects to a standalone signaling service for Online P2P multiplayer:
- Presentation Layer (
src/App.tsx): Controls visual hierarchy, mode switching (BOT, Local, Online), lobby UX, scoreboard presentation, keyboard shortcuts, rules dialog, and round feedback. - Domain Rules & Engine (
src/lib/game-logic.ts,src/lib/deck.ts,src/hooks/useBlackjackGame.ts): Pure card dealing, dynamic Ace valuation (1 or 11), hand outcome comparison, and reducer-driven game loop. - Online P2P Subsystem (
src/lib/online/,src/hooks/useOnlineBlackjack.ts):signaling.ts: WebSocket client connecting to the signaling Worker to exchange WebRTC SDP and ICE candidates.peer.ts: NativeRTCPeerConnectionandRTCDataChannelmanager for encrypted, low-latency peer communication.authority.ts:HostAuthorityManagerrunning domain rules exclusively on the Host, gating deals behind mutual readiness, and verifying guest intentions.serializer.ts: Projects canonical game state intoPublicGameState, replacing hidden cards with{ label: '?', value: 0, isHidden: true }and stripping undealt cards and RNG data.types.ts: Protocol definitions (PROTOCOL_VERSION = 1) and strict runtime validators.
- External Signaling Service (
blackjack-signaling): Standalone Cloudflare Worker + Durable Objects service responsible only for WebRTC signaling. This frontend repository contains no Worker runtime or deployment configuration. - Styling (
src/index.css): Dark casino theme tokens, responsive layouts, card tilt and deal animations, and mobile safe-area adaptations.
| Area | Technologies |
|---|---|
| Frontend | React 19, TypeScript 5.9 |
| Tooling | Vite 7 |
| Styling | Tailwind CSS 4 |
| Icons | Lucide React |
| Networking | WebRTC (RTCPeerConnection, RTCDataChannel) |
| Signaling | External blackjack-signaling Cloudflare Worker, Durable Objects, WebSockets |
| Testing | Vitest 4, Playwright 1.63 |
| Quality | ESLint 10, Prettier 3, TypeScript strict mode |
| Container | Docker node:22-alpine + nginx:1.30.4-alpine (optional preview/packaging) |
| CI | GitHub Actions |
- Node.js 22 and npm (matching CI)
git clone https://github.com/LeoneMarcos/blackjack.git
cd blackjacknpm cinpm run devConfigure the frontend to use a signaling endpoint:
Copy-Item .env.example .env.localSet VITE_SIGNALING_URL in .env.local to the deployed signaling Worker URL. For fully local signaling development, run the standalone blackjack-signaling repository separately; its default Wrangler endpoint is ws://127.0.0.1:8787.
Then start the frontend:
npm run devOpen two browser tabs or windows to test host creation and guest joining with room codes.
You can build and run the game in an isolated, production-oriented Nginx container:
By default, the container builds without external signaling configuration, ideal for offline BOT and local Two Players gameplay:
# Using Docker Compose (Recommended)
docker compose up -d
# Or build and run directly with Docker
docker build -t blackjack .
docker run -d -p 8083:80 --name blackjack blackjackAccess the game in your browser at http://localhost:8083.
Because VITE_SIGNALING_URL is baked into the frontend bundle at build time, supply it via the VITE_SIGNALING_URL build argument:
# With Docker Compose
VITE_SIGNALING_URL=wss://blackjack-signaling.<subdomain>.workers.dev docker compose up -d --build
# Or with Docker build
docker build --build-arg VITE_SIGNALING_URL=wss://blackjack-signaling.<subdomain>.workers.dev -t blackjack .
docker run -d -p 8083:80 --name blackjack blackjackNote: Docker packages the static frontend bundle; it does not host or execute the standalone signaling backend, which is deployed independently on Cloudflare Workers.
To stop the container:
docker compose down- Trust Boundary: The Host browser tab acts as the game server authority. Guest intentions (
hit,stand,ready,rematch) are validated against game phase, turn, and schema. Malicious or malformed guest messages are rejected without affecting Host game state. Undealt deck order and the dealer's hole card remain strictly in Host memory and are never serialized onto the network before the Dealer's turn. - NAT / Connectivity: Uses standard Google STUN servers (
stun.l.google.com:19302). Most home and office networks connect directly. Strict symmetric NATs without TURN may fail to establish a direct P2P connection. - Production Signaling: The signaling backend is deployed independently from the
blackjack-signalingrepository. SetVITE_SIGNALING_URL=wss://blackjack-signaling.<your-subdomain>.workers.devin the frontend deployment environment.
Verified commands to test, lint, format-check, and build the project:
npm test
npm run lint
npm run typecheck
npm run format:check
npm run build
npm run test:e2eThe automated suite covers BOT, sequential local Two Players, and Online P2P, including Dealer hole-card visibility, keyboard safeguards, host authority, signaling validation, and a two-context WebRTC flow. The feature branch includes unit/integration coverage for the frontend online subsystem plus a two-context Playwright P2P flow. To record the approved showcase flow locally, run npm run showcase:prepare; it starts Vite when needed, keeps the raw WebM, and produces a GitHub-compatible H.264 MP4. The Publish Showcase workflow performs the same capture in GitHub Actions and regenerates the canonical MP4, screenshots, and short README GIF preview when relevant product/showcase inputs change; it can also be run manually.
ARCHITECTURE.md— System architecture and component roles.DESIGN.md— Visual styling tokens, UI behavior, and responsive contracts.PRODUCT.md— Core game rules and product requirements.STACK.md— Technical stack constraints and tooling specifications.TEST_PLAN.md— Test plan and validation strategy.docs/STATUS.md— Final release status and validation evidence.
This project is licensed under the Apache License 2.0. See LICENSE for details.


