I've been working on VSolitaire for a while now. It's a free solitaire game where you can play against other people in real time. No ads. No download. Just open your browser and go.
The concept sounds simple: two players get the same deck, same starting layout. Both play simultaneously. You see your opponent's progress live. First to clear the foundations wins.
Building it was anything but simple.
The tech stack
Frontend: SvelteKit with TypeScript. I picked Svelte because the reactivity model fits a card game perfectly. When a card moves, the UI updates. No virtual DOM diffing, no unnecessary re-renders. The game logic runs client-side for responsiveness, and card dragging uses touch-optimized event handlers so it feels good on phones too.
Backend: NestJS with Socket.IO for WebSocket connections. The server validates every move, manages rooms, handles matchmaking queues, and tracks scores. NestJS gave me a clean way to organize the gateway pattern for all the real-time events.
The hard part: making multiplayer feel instant
The trickiest engineering challenge was synchronization. When you drag a card in a multiplayer game, two things need to happen: it needs to feel instant on your screen, and your opponent needs to see it.
I settled on optimistic updates. When you make a move, it shows immediately on your screen. The server validates it in the background. If the move is invalid, it rolls back. This keeps the game feeling snappy even on slower connections.
The WebSocket event flow looks roughly like this:
Client A makes move -> optimistic update on A's screen
-> sends game_move_optimistic to server
Server validates -> broadcasts score_update to Client B
-> Client B sees opponent progress update
Matchmaking
There are three ways to play against someone:
Quick Match - join a queue, get paired with a random opponent. Simple FIFO queue with timeout handling.
Private Rooms - create a room, share a code with a friend. The room persists until both players leave.
Public Rooms - browse open games and jump in. These show up in a lobby list that updates in real time.
Each mode uses the same underlying room service, just with different entry points.
The same-deck problem
Both players need the exact same card layout. The deck shuffle happens server-side, and both clients receive identical initial state. I generate a seed on the server when the game starts, use it to produce a deterministic shuffle, and send the full deal to both players in the game_start event.
What I learned
Card games look simple from the outside. The rules of Klondike solitaire fit on a napkin. But implementing valid move detection, auto-complete logic, win conditions, undo support, and then layering multiplayer sync on top of all that... it adds up fast.
The biggest lesson: optimistic updates are worth the complexity. The difference between a 50ms response and a 200ms response is the difference between a game that feels like dragging physical cards and one that feels like filling out a web form.
Try it
If you want to check it out: vsolitaire.com. It's free, zero ads, works on any device. I'd love feedback from other devs, especially if you notice anything off with the WebSocket sync or have ideas for the matchmaking system.
The leaderboard resets weekly with cash prizes for top players if you're feeling competitive.
Top comments (0)