The Caves of Clojure: Part 1

Posted on July 7th, 2012.

Lately I've had an urge to start playing a few games again, namely Nethack and Dwarf Fortress (the latter being triggered by this book). Aside from being incredibly fun, they also made me want to play around with writing a roguelike game of my own.

I could write them in Python, but lately I've been falling out of love with the language. It's a solid workhorse that's not exciting or beautiful to me at all any more. My affection has shifted more toward Clojure. Despite its Rubyesque culture of brokenness, rampant lack of documentation, and warty JVM interop it's still a wonderful language that's captured me (for now).

I've had the idea of writing a few roguelike games for a while, but the other day I stumbled on Trystan Spangler's blog and his series of articles that walk through writing a roguelike game in Java.

I'm going to do a series of blog posts, each corresponding roughly to one of Trystan's posts, as I work my way through writing a roguelike in Clojure. I may or may not get all of the way through his twenty-post series. We'll see.

I'll assume you know Clojure during this series and won't be explaining every single thing.

If you want to follow along, the code for this series is on Bitbucket and on GitHub. There are tags like entry-01 in the repo which you can update to and see the code as it stood at the end of that entry.

Let's jump in. This entry corresponds to post one in Trystan's tutorial.

  1. Summary
  2. project.clj
  3. clojure-lanterna
  4. core.clj
  5. Running

Summary

The first thing to do is to bootstrap an environment for development. I'll be using Clojure 1.4, Leiningen 2, and clojure-lanterna 0.9.0.

Trystan used Eclipse but I'll be using Vim and my fork of SLIMV.

project.clj

I'm starting with a fairly simple project.clj file:

(defproject caves "0.1.0-SNAPSHOT"
  :description "The Caves of Clojure"
  :url "http://stevelosh.com/blog/2012/07/caves-of-clojure-01/"
  :license {:name "MIT/X11"}
  :dependencies [[org.clojure/clojure "1.4.0"]
                 [clojure-lanterna "0.9.0"]]
  :main caves.core)

Nothing particularly crazy here.

clojure-lanterna

Trystan used his own library called AsciiPanel to handle the drawing of characters to the screen. I'm going to use my own library clojure-lanterna, which is a wrapper around the Lanterna Java library.

I chose Lanterna because it supports drawing to a Swing-based "terminal" and to the actual console. But unlike some other libraries, it doesn't actually link against native code (it's pure Java) so it's more portable.

Being able to output to either a console or a GUI means that I can develop through Swank really easily with the Swing terminal, but actuall play the finished product in a terminal like God intended.

core.clj

The full core.clj file I came up with was this:

(ns caves.core
  (:require [lanterna.screen :as s]))


(defn main [screen-type]
  (let [screen (s/get-screen screen-type)]
    (s/in-screen screen
                 (s/put-string screen 0 0 "Welcome to the Caves of Clojure!")
                 (s/put-string screen 0 1 "Press any key to exit...")
                 (s/redraw screen)
                 (s/get-key-blocking screen))))


(defn -main [& args]
  (let [args (set args)
        screen-type (cond
                      (args ":swing") :swing
                      (args ":text")  :text
                      :else           :auto)]
    (main screen-type)))

We're very much just bootstrapping things for now. The -main function parses the command line arguments to figure out what type of terminal we should use, and then passes that along to main.

For now, main simply:

I chose to split the meat of the setup into a main function instead of just putting everything in -main because that will make it easy for me to work through Swank instead of the command line as we'll see in a moment.

Running

There are two main ways to actually make this thing work.

First, we can run it as a standalone program with lein trampoline run at the command line. I can pass a parameter to specify what type of terminal to use, like lein trampoline run :swing.

We can also run it from a REPL, or a SLIME/Swank environment by simply evaluating (main :swing) in the namespace. This is why I split out everything but the command line argument parsing into a separate function.

Either way, once you run it you get something like this:

Screenshot

This is the Swing terminal which I happened to start from swank. Press a key and it will go away.

It doesn't look like much, but it's a black triangle. In the next entry we'll start doing more interesting things.