What I built.
A local social casino project.
- Server-side blackjack engine and public-state mapping.
- Room, account and virtual-wallet workflows.
- Separate game-state and transaction-ledger modules.
Under the hood.
Nuxt / Vue
Table UI and server endpoints
Pinia
Client application state
Supabase
Accounts and persisted game workflows
TypeScript modules
Rules, public state and ledger boundaries
- Player action
- Server game rules
- Internal state
- Public-state projection
- Table UI
A table is a shared-state problem
A card game looks simple until more than one person needs to see the same table. Social Casino is a place to explore that deeper problem: shared state, player actions, room membership and a wallet that stays consistent with the game.
A face-down card is a data problem
The public-state mapper is one of the most interesting parts of this build. During player_turn, when the dealer has at least two cards, it returns the visible card plus an XX placeholder. It does not send the hidden card and ask the interface to cover it with a graphic.
The mapper copies the player cards and returns the phase, bet, doubled flag, outcome and payout. It exposes a remaining-card count when available, rather than the full deck. That gives the UI the information it needs without giving it the next card.
The game and the money-shaped state
The server separates the engine, persisted blackjack state, public-state mapping and ledger into different modules. That makes the boundaries visible: deciding a game result and recording a virtual-chip movement are related responsibilities, but they are not the same calculation.
The harder questions are what happens when requests overlap, a client retries, or persistence fails midway through an action. Module separation alone does not prove those cases are handled. Those are the cases I would put under focused testing before calling this ready for wider use.
Consistency before card animations
This is a local social-game build organised around virtual chips. Some surrounding areas, including leaderboard data, still use mock content. The code review is not a claim of production readiness or real-money functionality.
The lesson in progress
The interesting engineering is the consistency between the table, the player and the ledger. A polished card animation is worth much more when the state underneath it is predictable.
const hideHole = phase === 'player_turn' && dealerCards.length >= 2;
const publicCards = hideHole
? [dealerCards[0], 'XX']
: dealerCards.slice();
// Send the projection, not the internal deck.Explanatory pseudocode, shortened for readability.