Command-line Freecell written in Clojure

6 min read Original article ↗

This rather lengthy post describes my latest project: a highly annotated, command-line freecell clone. It spins off into thoughts on what it was like to develop in clojure, and finishes with some tips and tricks for how to setup your clojure/vim environment.

You can view the source on github.

The Concept

I am an on-again off-again freecell addict. Over the years I’ve found myself mentally dividing the game-playing experience into two parts:

  1. Surveying. Searching the board for possible moves, or possible problems in the future.
  2. Deciding. Based on those possible moves, making a strategic decision about what to do next.

Of the two parts, I find the second to be far and away the more interesting. So I thought, why not make a freecell clone that will automate the boring work?

My solution, the theory

My solution was to attempt to throw annotations at the board that would answer at a glance common questions such as:

  • Which cards are already connected into runs?
  • Which cards, if uncovered, could be moved to the foundation piles right now, or to another cascade?
  • Which cards are out-of-order in their cascade, or duplicates (same rank and color)?

My solution, technical details

Since I was primarily interested in testing the mechanics of my annotations theory, I decided to go with a minimal front-end. Thus, a CLI. It would be fair to say that I was more than a little inspired by Dwarf Fortress.

My solution, the result

I haven’t played that many games yet, so I don’t have any fully-formed opinions. Some observations:

The command-line interface isn’t as much of a problem as I’d expected. You start seeing past the symbols after a game or two, and the Do What I Mean button means I don’t often have to think about the coordinates.

The games seems to go faster, but shallower. Faster in that I just bang on the DWIM button, and shallower in that I have a less clear understanding of what moves I’m making. My win rate doesn’t seem to be affected, though, so maybe more of the game was about searching for possible moves than I’d realized.

I seem to get stuck more often. I’m not sure whether that means I’m breezing through the easy parts more quickly, or whether I’m getting myself into more difficult binds through abuse of the DWIM button. If the latter, maybe the problem will resolve itself with discipline.

I decided to take this project as an opportunity to try a not-completely-trivial clojure project. Some observations.

No state

There is no state in the program. Not a single a = 4 line. No atoms, refs, or agents.

And it was no big deal. Better, it was easy.

I was determined to write in a pure(ish) functional style, but I was concerned about the main game loop. I needn’t have worried. It turned out to be an elegant recursive function, with the state of the world passed from one iteration to the next. Because the state of the world was something I was passing around anyway, it was easy to modify it during a pass to revert to an earlier state or a new game entirely.

Warning, pure opinion and speculation ahead: I have little evidence to support this claim, but I suspect that writing in a functional style provides many of the same benefits as Test Driven Development. It seemed to me that the code naturally divided itself into separate concerns, with few dependencies between the sections. Certainly there was a feeling that once a function was done it was done forever. And why not? All it was doing was making slight tweaks to a common, immutable data structure1.

Ah, the ()

I love the parentheses in clojure.

On the one hand, they’re super easy to work with (see next section). On the other hand, they get completely out of the way. They’re so ubiquitous and consistent, you just stop seeing them. You use them for automatic code-formatting feature, and otherwise just read the indentation. It’s like python, but with automatic indentation, less ambiguity, more consistency, and better editing tools.

My development environment (vim)

As mentioned, because clojure’s syntax is so regular, it’s extremely simple to create quick shortcuts to manipulate the text. Some quick recommendations for vim users.

  • Use tslime to communicate with the REPL
  • Use paredit, as extracted from slimv, to automatically balance your parentheses (plus other functions)
  • Use surround for additional swashbuckling-() goodness

I created a quick script that provides the following functions:

  • Show documentation for word under cursor (or an entered word)
  • Rename variable in function, or globally
  • Load current file
  • Evaluate current expression
  • Surround current expression with additional ()

And I’m sure I’ll add more over time. Also, vim’s built-in support for treating ()s as text objects makes moving, copying, deleting and generally manipulating s-expressions an absolute joy. The consistency of the language lets you stop thinking about syntax or how to edit it, and instead get down to thinking only about the logic.

Live out of the REPL

I’ve used languages that had REPLs before, but I’d never grokked the full power of the idea until this project. The power in the ability to introduce or modify functions in the project as it’s running cannot be overstated.

Several times as I was debugging a troublesome function I redefined the function feeding it input to be more regular or to test some edge case — essentially stubbing it out on the fly. There was no recompile cycle, there was no hassle. The code reshaped itself at the speed of thought. It’s seriously a religious experience: super powerful and eye opening to the person who experienced it, but it makes you sound crazy to those who haven’t been through it.

Conclusions on clojure

Clojure is a joy to work with.

Its stateless, functional style makes programs very easy to reason about, test, debug, and breakdown into logical, by-concern-separated chunks.

Its regular syntax is easy on the eyes, and even easier on your text editor. I’ve never found any other language so easy to manipulate — not even java with its enormous, specialized IDEs.

I’d been concerned about clojure’s relatively long startup time. That turned out to be a non-issue, as I spent all my development time in a single REPL session anyway. Working with the REPL itself was an incredibly organic experience, with a natural flow between source code, quick throw-away examples, and the running program.