Plain Vanilla: Playing Cards on the Modern Web

I love to code on vacations. I particularly love to code things of no relation to my work, such as card games. And my favorite is FreeCell.

It seems odd to me that all the current excellent apps for this game are larger than 40 megabytes. Surely this can be done more efficiently, and that was my challenge.

Did you know FreeCell has a “literature?” You can visit it here on Wikipedia. I’ve tried to use the lingo it establishes.

My coding philosophy is to keep things simple: vanilla JavaScript, no 3rd party anything, default fonts, everything in one page that can easily become a Progress Web App (PWA). Also, like any decent programmer, I like to write clear, readable code in nice, small functions, logically organized with the highest level first. If something can be static HTML in a modest number of lines, it should be.

Back in the ancient days of 2001, as my first adventure in JavaScript, I created something I called “One-Click FreeCell.” Unlike the Microsoft version — which required two clicks per move — this one figures out the best logical place for a card to move and puts it there. I made cards as GIFs from screenshots. This was not bad on a desktop but very hard to read on today’s mobile devices. You can still play this version at here.

(All the code described here is free and open source on GitHub.

A lot has changed since 2001 — HTML 5, CSS3, Unicode 6.0 with a full set of playing cards, so I first started a new version based on those characters here. They are pretty ugly, frankly, and don’t work at all on IOS.

So the current version — take two — creates cards as an array of inline SVG elements, using a smaller set of emoji for the suit signs and faces.

One-click FreeCell is fairly complicated. When you click on a column it needs to:

  • Compute the maximum size column of cards you can move together (eg — red 10, black 9, red 8), based on the number of open FreeCells (nopen) and the number of empty cascades (nempty). That number turns out to be 1+nopen+nempty.
  • Compute the height of the stack at the end of the cascade that can move together (reduced, if needed, by the maximum computed above).
  • Run through the other cascades to see if they can receive the full stack.
  • If not, try the stack reduced by one card, until you get down to 1 card.
  • If not, try moving the stack to an empty cascade.
  • If not, and there is an open FreeCell, move the last card there.
  • Finally, see if any cards from the top of each cascade or from the FreeCells can jump to the foundation piles — repeating until none move.

To implement this, I created static HTML divs for the four FreeCells, four foundation piles for each suit and the eight cascades, using CSS Flex to space them evenly across the screen.

The Document Object Model (DOM) that allows JavaScript to manipulate your page permits a nice analogy to the game. Each card that is dealt appends a new child div to the cascade, and so you don’t need keep track of where cards are through a separate data structure. When you take a card off the cascade, you simply preform a removeChild. Each card div is given an id based on the card’s number in the array of strings that contain the SVG.

To establish the page as a to turn the finished page into a Progress Web App, I drew on the excellent “Hello World” PWA tutorial on Medium by James Johnson. I’m continuing to tinker with it, as the journey to make the code more elegant is the real destination. For instance, as of this writing, I’ve not yet created an “undo” feature which some FreeCell addicts like myself prefer over simply starting over.

My first version was too fast. One likes to see cards dealt and moved sequentially so you don’t have to figure out what the game just did. In JavaScript you do this with the setInterval function. It can be tricky though, since it launches asynchronous threads, which can cause actions to get out of order if you’re not careful (which I initially wasn’t.)

Feel free to play the game at this link, and to suggest improvements on GitHub/jcoonrod.

A guy committed to human dignity for all.