oddstream / lsol

A polymorphic solitaire engine in Lua and LÖVE
14 stars 5 forks source link
card-game game love lua patience polymorphism solitaire

Minimal Polymorphic Solitaire in Lua and LÖVE

Towards a polymorphic solitaire engine in Lua+LÖVE.

Screenshot

Play it by downloading/installing the LÖVE runtime and the lsol.love file from this repo, then running 'love lsol.love'. It's tested on Linux, Windows and Android via the Google Play Store.

Variants

It currently knows how to play around 100 variants, including:

♥ Accordian ♥ Agnes Bernauer ♥ Agnes Sorel ♥ Algerian ♥ Alhambra ♥ American Toad ♥ American Westcliff ♥ Assembly ♥ Athena ♥ Australian ♥ Baker's Dozen ♥ Baker's Dozen (Wide) ♥ Baker's Game ♥ Baker's Game Relaxed ♥ Beleaguered Castle ♥ Bisley ♥ Black Hole ♥ Blockade ♥ Busy Aces ♥ Canfield ♥ Chinese Freecell ♥ Classic Westcliff ♥ Crimean ♥ Cruel ♥ Double Freecell ♥ Duchess ♥ Easthaven ♥ Eight Off ♥ Eight Off Relaxed ♥ Flat Castle ♥ Forty Thieves ♥ Forty and Eight ♥ Freecell ♥ Gargantua ♥ Gate ♥ Giant ♥ Good Thirteen ♥ Josephine ♥ Klondike ♥ Klondike (Turn Three) ♥ La Belle Lucie ♥ Limited ♥ Little Spider ♥ Lucas ♥ Martha ♥ Miss Milligan ♥ Mount Olympus ♥ Penguin ♥ Pyramid ♥ Pyramid Relaxed ♥ Rainbow Canfield ♥ Red and Black ♥ Rosamund ♥ Royal Cotillion ♥ Russian ♥ Scorpion ♥ Sea Haven Towers ♥ Simple Simon ♥ Somerset ♥ Spider ♥ Spider One Suit ♥ Spider Two Suits ♥ Spiderette ♥ Spiderette One Suit ♥ Spiderette Two Suits ♥ Storehouse Canfield ♥ The Fan ♥ Thoughtful ♥ Three Shuffles and a Draw ♥ Trefoil ♥ Tri Peaks ♥ Tri Peaks Open ♥ Ukrainian ♥ Usk ♥ Usk Relaxed ♥ Wasp ♥ Yukon ♥ Yukon Cells ♥ Yukon Relaxed

Variants are added when the whim takes me, or when some aspect of the engine needs testing/extending, or when someone makes a case for something interesting.

Some variants have been tried and discarded as being a bit silly, or just too hard:

Some games I've added reluctantly:

Some will never make it here because they are just poor games:

Screenshot

Other features

Deliberate minimalism

A lot a features have been tried and discarded, in order to keep the game (and player) focused. Weniger aber besser, as Dieter Rams taught us. Design is all about saying "no", as Steve Jobs preached. Just because a feature can be implemented, does not mean it should be.

Screenshot

FAQ

What makes this different from the other solitaire implementations?

This solitaire is all about Flow.

Anything that distracts from your interaction with the flow of the game has been either been tried and removed or not included.

Crucially, the games can be played by single-clicking the card you wish to move, and the software figures out where you want the card to go (mostly to the foundation if possible, and if not, the biggest tableau). If you don't like where the card goes, just try clicking it again or dragging it.

Also, I'm trying to make games authentic, by taking the rules from reputable sources and implementing them exactly.

Why are the graphics so basic?

Anything that distracts from your interaction with the flow of the game, or the ability to scan a deck of cards, has either been tried and removed, or not included. This includes: fancy card designs (front and back), keeping an arbitrary score, distracting graphics on the screen.

The user interface tries to stick to the Material Design guidelines, and so is minimal and tactile.

I looked at a lot of the other solitaire websites and apps out there, and think how distracting some of them are. Features seem to have been added because the developers thought they were cool; they never seem to have stopped to consider that just because they could implement a feature, that they should.

Sometimes the cards are really huge or really tiny

If you're running the app on a desktop, resize the window; the cards will scale automatically.

If you're runnning on a mobile device, try rotating the device. (Solitaire apps are better suited to the larger and squarer screens of tablets, rather than phones.)

The rules for a variation are wrong

There's no ISO or ANSI or FIDE-like governing body for solitaire; so there's no standard set of rules. Other implementations vary in how they interpret each variant. For example, some variants of American Toad build the tableau down by suit, some by alternate color. So, rather than just making this stuff up, I've tried to find a well researched set of rules for each variant and stick to them, leaning heavily on Wikipedia, Jan Wolter (RIP, and thanks for all the fish), David Parlett and Thomas Warfield. Where possible, I've implemented the games from the book "The Complete Book of Solitaire and Patience Games" by Albert Morehead and Geoffrey Mott-Smith.

Keyboard shortcuts?

What about scores?

Nope, the software doesn't keep an arbitary score. Too confusing. Just the number of wins, number of moves, the average 'completeness percentage' and your winning streak (streaks are great).

A game isn't counted until you move a card. Thereafter, if you ask for a new deal or switch to a different variant, that counts as a loss.

You can 'cheat' the score system by restarting a deal and then asking for a new deal.

'Completeness percentage' is calculated from the number of unsorted pairs of cards in all the piles.

Odd features

You can restart a deal without penalty; it's not cheating, because you could just set a bookmark at the start of a game and return to that position.

You cannot move cards from a foundation pile. Most sources I've read explicity ban moves from the foundations, so I've implemented a blanket ban. There's always undo, if you've got into a bad situation.

Movable cards are not highlighted unless you ask for help by tapping the lightbulb icon. For the longest time, I thought that highlighting movable cards was a neat feature, but I now realize that this feature ruins the essence of most games. In trying to replicate and assist the feeling of playing with real cards, this feature is a step too far.

What's with the discard piles?

Some games, like Spider or Simple Simon, have discard piles instead of foundations. These are optional piles for you to place completed sets of cards into, if you wish to clear some space in the tableaux.

Most other solitaire implementations just have foundation piles that fulfill this role.

What about a timer?

The timer doesn't start until you make a first move, and (should) pause when the app is minimized or is in the background. The elasped time for the current game is reported in the center of the status bar (next to the number of moves made) and doesn't update until after you've moved (i.e. it doesn't update every second - that seems a bit stressful). As the timer was not added until version 29, the app can't report average time taken in the statistics, because there were games completed without being timed.

What's with the settings?

Simple cards

Use a set of card faces that have minimal graphics, just ordinal and suit. Can be easier to 'scan', especially on small devices.

Colorful cards

Normally, cards are either red or black. This setting makes the cards either just black, red/black or four-colored, depending on the variant being played. In variants where the tableaux build in suit (like Forty Thieves, Penguin or Eight Off) this can be a real help.

Gradient shading

By default, the baize and card backgrounds have a shading effect, where the center is lighter than the edges. You can turn this off if you find it annoying or distracting. There is no performance penalty or benefit either way.

Compress piles

With this on, piles of cards that a long and would overshoot the screen (usually the bottom of the screen, but also the right edge) are compressed dynamically (up to a point) so that all the cards can be seen. However, this can make the cards hard to read.

In any case, the baize can be dragged up or down to make all the cards visible.

Power moves

Some variants (eg Freecell or Forty Thieves) only allow you to move one card at a time. Moving several cards between piles requires you to move them, one at a time, via an empty pile or cell. Enabling power moves automates this, allowing multi-card moves between piles. The number of cards you can move is calculated from the number of empty piles and cells (if any).

Auto collect

Enabling this will cause cards to be moved to the Foundation piles after every move you make.

Safe collect

In games like Klondike that build tableau cards in alternating colors, you can sometimes get into trouble by moving cards to the foundations too soon. With this option turned on, the titlebar collect button will only move cards to the foundation piles when it is safe to do so.

Mirror baize

For left-handed players on mobile devices.

Mute sounds

So you can, for example, listen to an audio book while playing.

Allow orientation (Android only)

With this set to 'on' (the default), rotating the phone/tablet will re-orient the baize.

With this set to 'off', the orientation will be fixed to whatever it was when the app started.

Is the game rigged?

No. The cards are shuffled randomly using a Fisher-Yates shuffle driven by a Park-Miller pseudo random number generator, which is in itself seeded by a random number. This mechanism was tested and analysed to make sure it produced an even distribution of shuffled cards.

There are 80,658,175,170,943,878,571,660,636,856,403,766,975,289,505,440,883,277,824,000,000,000,000 possible deals of a pack of 52 playing cards; you're never going to play the same game twice, nor indeed play the same game that anyone else ever has, or ever will.

It's possible that a deal will start with no movable cards, just like it might if you'd dealt physical cards on a physical table.

Any hints and tips?

Terminology and conventions

Screenshot

The seven different types of piles

Stock

All games have a stock pile, because this is where the cards are created and start their life.

In some games, like Freecell, the stock pile is off screen (invisible). In others, like Klondike, it's on screen (usually at the top left corner) and tapping the top card will cause one card to be flipped up and moved to a waste pile. In other games, like Spider, tapping the top card will cause cards to be moved to each of the tableau piles.

All cards in the stock are always face down. You can't move a card to the stock pile. There is only ever one stock pile.

Tableau

Tableau piles are where the main building in the game happens. The player tries to move the cards around the tableau and other piles, so that the cards in each tableau pile are sorted into some game-specific order. For example, in Klondike and Freecell, the tableau cards start in some random order, and must be sorted into increasing rank and alternating color.

Sometimes, there is a constraint on which card may be placed onto an empty tableau, for example in Klondike, and empty tableau can only contain a King.

Some cards in the tableau pile may start life face down; the game will automatically turn the cards up when they are exposed.

Foundation

Foundation piles are where the player is trying to move the cards to, so that the game is completed.

The cards in each foundation usually start with an Ace, and build up, always the same suit. A foundation pile is full (complete) when it contains 13 cards.

Only one card at a time can be moved to a foundation.

Discard

Discard piles aren't usually found in other solitaire implementations.

Discard piles are like foundation piles, except that only a complete set of 13 cards can be moved at once.

Moving completed sets of cards to a discard is optional, and is usally done to create space in the tableaux. You do not have to move cards to a discard pile to complete a game.

Waste

A waste pile can store any number of cards, all face up. You can only move one card at a time to a waste pile, and that card must come from the stock pile. There is only ever one waste pile.

In some games (like Klondike) cards in the waste pile can be recycled back to the stock pile, by tapping on an empty stock pile. The game may restrict the number of times this can happen.

Cell

A cell is either empty, or it can contain one card of any type. Cell cards are always face up, and available for play to tableau or foundation piles. Cells are used as temporary holding areas for cards.

Reserve

A reserve pile contains a series of cards, usually all face down with only the top card face up and available for play to a foundation, tableau or cell pile.

Only one card at a time may be moved from a reserve, and cards can never be moved to a reserve pile.

TODO

History

I've now written a polymorphic solitaire engine seven times:

First, there was a Javascript version that used SVG graphics and ran in web browsers. Game variants were configured using static lookup tables, which I thought was a good idea at the time.

Second, there was a version in Lua, using the retained-mode Solar2d game engine, that made it to the Google Play store. Game variants were configured using static lookup tables, which I still thought was a good idea. Card movement was glitchy on slower hardware.

Third, there was a version in Go, using the immediate-mode Ebiten game engine, with help from gg/fogleman. The design was internally a mess, and the cards didn't scale, so this was abandoned. Game variants were configured using static lookup tables, which was starting to become a source of clumsiness and code smells.

Fourth, there is a version in C that uses the Raylib game engine and uses Lua to script the game variants. The design was good, but has problems with scaling cards.

Fifth, there was a rewritten version in Go, using the Ebiten game engine, with help from gg/fogleman. The design is way better than the original attempt in Go, and allows the potential for scripting games.

Sixth, there is this version in Lua and LÖVE. The design is much better than the previous versions.

Seventh, I rewrote the Go+Ebiten version, splitting the code into client (front end) and server (back end) packages. The idea of this was to allow me to write a general-purpose solver; there are solvers for individual variants, notably Klondike and Freecell, but not a general-purpose one. After creating four versions of the solver - which took too long to run and produced bizarre results - this work is currently stalled. This version of the engine finally allows variants to be scripted using Lua. Internally, the code is quite complex, so I mostly use it as a sandbox for ideas.

Acknowledgements

Original games by Jan Wolter, David Parlett, Paul Alfille, Art Cabral, Albert Morehead, Geoffrey Mott-Smith, Zach Gage and Thomas Warfield.

Sounds by kenney.nl and Victor Vashenko.