Soliteur
I give you… SOLITEUR
I’ve been playing Solitaire on a daily basis since I was around 11 years old, and for the past 3 years I’ve had this idea for a Solitaire game engine floating around in my head. I wanted a flexible game engine that would let me define the game setup and rules in a JSON file. It had to be built on a relatively modern tech stack. It had to look good with the look and feel of playing with an actual deck of cards, which means that all of the movements had to be properly animated without shortcuts for things like Freecell “supermoves”.
I made a conscious decision not to look at anyone else’s code, since I really wanted to get a feel for building something like this by myself. In hindsight this might have been a mistake, since I basically bumbled through a bunch of dead ends before I got to someplace that I was happy with.
The big problem turned out to be sequencing the actual animation of the cards and syncing the state of the game with what the user saw on screen. The core issue was that when you click on a card and it needs to move to a foundation, the game state should update immediately, so you can’t click on that card again, but the visual representation needs to animate smoothly over a few hundred milliseconds. I ended up with a command queue system. Every user action generates a series of commands like “pick up card”, “move to pile”, “flip card”. The game engine executes these to update the internal state, but the renderer maintains its own animation queue that plays them out visually.
The game definition files turned out cleaner than I expected. The engine reads them and wires up all the move validation, accept rules, and dealing logic. The scoring system, stock/waste pile behavior, and game options like draw count are all configurable.
For the tech stack I went with TypeScript, Vite, and Canvas 2D. No frameworks for the UI - just plain HTML/CSS for the menus and dialogs.
I have a simple card design that I wanted to use for a mobile version in the future, which is just emoji and text, the fancy card fronts are by Chris Aguilar. 1
For the daily challenges, I needed a way to verify that puzzles are solvable and estimate their difficulty. I decided to stick to FreeCell for now since all cards are visible from the start, making it straightforward to verify solvability.
The difficulty rating is based on the minimum number of moves required, how often you need to use free cells, and how much “lookahead” the puzzle requires. Easy puzzles can mostly be solved by moving cards to foundations whenever possible. Hard puzzles require you to plan several moves ahead and use the cells strategically. 2
I pre-generate puzzle pools and schedule them out months in advance, so everyone gets the same three puzzles each day.
If I started over, I’d probably use a proper ECS instead of the object-oriented approach I landed on. The current code works, but there are places where the card, pile, and game state classes are more tightly coupled than I’d like.
I’d also spend more time upfront on the animation system. The command queue works, but it evolved organically and has some rough edges.
Overall, I’m happy with how it turned out. It scratches the itch I’ve had for years, and the foundation is solid enough that I can keep adding games and features without major rewrites.
-
The classic card style uses vector playing card graphics by Chris Aguilar, licensed under LGPL 3.0. ↩︎
-
The difficulty ratings for FreeCell daily challenges are calculated using methods informed by the following research: Chen, Michael W.H. “The Hardness Analysis of FreeCell Games”. BSc Thesis, University of Amsterdam, 2015, Demaine, Erik D. and Hearn, Robert A. “Games, Puzzles, and Computation”. ↩︎