We built Cards4.net so people could open a card game and start playing without an account or ads. Once a game is underway, they should be able to undo a move or return to it later without wondering whether the board has changed. A saved game ought to preserve their bad decisions just as faithfully as the good ones.

Meeting that expectation has shaped how we use Phoenix and Elixir on the backend, React in the browser, and PostgreSQL for persistence. The rules, controls, and saved games all need to agree about what the player has done.

Keeping game state on the server

In Klondike, moving a card can uncover and turn over the card underneath it. If the player presses Undo, both changes have to be reversed. If they then leave the game, the saved position needs to reflect the board they returned to. We keep the game state on the server so move handling, undo, and persistence all work from the same representation of that position.

When a player drops a card, the browser sends the intended move to our Phoenix backend, where the rules check it against the current state. If the move is legal, the server applies it, records the previous position for Undo, and returns the updated board. The browser can then display the result, including any cards that were revealed. Because the server handles those changes together, the frontend does not need to reconstruct earlier positions from what was visible on screen.

For multiplayer, that arrangement also lets us coordinate a table without relying on a player's browser to decide what happened. Phoenix Channels carry actions to the server and deliver the resulting updates to the players. Since the server holds the complete game state, it can send each seat the information that player is allowed to see while keeping the other hands out of the response.

Keeping a game after its session ends

During play, each solitaire session holds its position in a GenServer, an Elixir process that handles incoming calls in sequence. The session delegates rule checks to the appropriate game module, allowing Klondike, FreeCell, and Spider to share the code for managing sessions and saves.

That process only holds the position in memory, so we also write snapshots to PostgreSQL. When a request arrives for a session that has stopped, the application can load its saved state into a new session. Recovery happens through that code; the supervisor does not automatically restart these temporary session processes after a crash.

We schedule writes separately from move handling to avoid waiting for the database on every move. As a result, the position in memory can be ahead of the last successful save. Testing recovery means checking what was actually written and what can be restored, including the undo history.

Keeping the controls responsive

Putting the rules on the server means every move needs a network round trip before it is accepted. That can delay the result on a slow connection and prevents offline play. A solitaire game that ran entirely in the browser would avoid this dependency. We've accepted it so that rule validation, undo, and saved progress can use the same backend implementation.

The interface still needs to respond while a player is dragging a card. Our boards use React and TypeScript, with Vite handling the frontend build. React manages the board and interaction state, while a requestAnimationFrame loop moves the drag preview directly. This keeps the preview following the pointer without a React state update for every frame, even though accepting the drop still requires a server response.

On a phone, the gesture itself needs attention too. Cards are smaller, columns can be long, and a touch might mean selecting a card or starting a drag. We test these interactions separately from the rules because knowing a stack is legal to move tells us little about how easily someone can pick it up. Cancelled gestures and illegal drops also need to leave the board in a state the player understands.

Reproducing what went wrong

When a player reports that the board behaved unexpectedly, we need to recreate the position they were playing. Supported solitaire deals are generated from seeds, so the same game, variant, and seed produce the same opening layout under the same implementation.

For a problem that occurs later, we also need the moves leading up to it. A screenshot can show what looked wrong, while the seed and move sequence let us follow the game to that point. Keeping those inputs with a regression test gives us a way to check the behavior again after a fix. It also means changes to the shuffle or dealing code need compatibility checks if old seeds are to keep producing the same layouts.

We use error reports and performance measurements to investigate failures and slow pages as well. These serve a different purpose from the traffic figures in Plausible, which we load only after analytics consent. Pageviews can tell us that someone visited; they cannot establish that a game was saved or restored correctly. The privacy policy describes the data collected by these separate systems.

What we'd do earlier

On another card-game project, we'd get one game through a complete cycle of play, undo, save, and resume before expanding the catalog. We'd keep that sequence as a test using a fixed deal, so later changes could be checked against the same starting point. This would give us a repeatable way to catch a change that makes a move work on screen but breaks the position restored after a reload. For a site people return to between other things, getting that return visit right deserves time early in development.

FAQ

What stack does Cards4.net use?

The backend uses Phoenix and Elixir with PostgreSQL. The frontend uses React, TypeScript, and Vite. Solitaire actions use REST, while multiplayer tables use Phoenix Channels. The game boards do not use LiveView.

Can I play without an account?

Yes. Casual play is available to guests. If you want an account, signing in uses a one-time email link. The site does not ask you to create a password.

Can the games work offline?

No. Moves are validated on the server, so gameplay requires an internet connection.

What analytics does the site use?

Plausible loads after analytics consent. The application also has separate error reporting and performance monitoring. See the privacy policy for the details.