Maze display app for Always Turn Left

Last time, I presented a solution for Always Turn Left, a Google Code Jam problem. Given that their large dataset was quite big (up to 10k moves), I thought: “It would be interesting to see what mazes those moves produce”. So I set to write (in Clojure, of course) a maze-display app (using Seesaw, of course). Here’s what came out of that.

(ns com.icyrock.clojure.codejam.maze-display
  (:use clojure.java.io
        flatland.ordered.map
        seesaw.border
        seesaw.chooser
        seesaw.color
        seesaw.core
        seesaw.graphics
        seesaw.mig)
  (:require [seesaw.bind :as ssb]))

First, declare a lot of things I’m to use later. Most Seesaw and one thing from here, which is a Clojure implementation of ordered sets / maps which I wanted to try out.

(def state
  {:frame (atom nil)
   :file (atom nil)
   :cases (atom nil)
   :curr-case (atom nil)
   :maze (atom nil)})

Main state – contains:

  • Main frame
  • Currently selected case-file
  • Loaded cases themselves
  • Currently selected case
  • Maze bound to the currently selected case
(def room-width 16)
(def room-height 16)

Default room size when drawn, in pixels.

(def default-style
  (style
   :foreground "#000000"
   :stroke (stroke :width 3 :cap :round)))

Default style to use when drawing walls. It’s a black, 3-pixel wide line, with rounded edges.

(defn draw-wall [g w h wall]
  (case wall
    :n (draw g (line 0 0 w 0) default-style)
    :s (draw g (line 0 h w h) default-style)
    :w (draw g (line 0 0 0 h) default-style)
    :e (draw g (line w 0 w h) default-style)))

This draws a wall. Given that translation is used below, the north-west corner of the room is always at (0, 0), so the above is easy to understand given the case keys (:n for north, :s for south, :w for west and :e for east).

(defn draw-room [g w h walls-desc]
  (let [walls (case walls-desc
                \1 #{   :s :w :e}
                \2 #{:n    :w :e}
                \3 #{      :w :e}
                \4 #{:n :s    :e}
                \5 #{   :s    :e}
                \6 #{:n       :e}
                \7 #{         :e}
                \8 #{:n :s :w   }
                \9 #{   :s :w   }
                \a #{:n    :w   }
                \b #{      :w   }
                \c #{:n :s      }
                \d #{   :s      }
                \e #{:n         }
                \f #{           }
                )]
    (doseq [wall walls]
      (push g
            (draw-wall g w h wall)))))

The room is a set of cases to decipher the letter as set of walls for that room, as given in the problem description and then draw each of these walls.

(defn paint-maze 
  (try
    (let [w room-width
          h room-height
          maze @(state :maze)]
      (when maze
        (anti-alias g)
        (translate g w h)
        (doseq [row maze]
          (push g
                (doseq [room row]
                  (draw-room g w h room)
                  (translate g w 0)))
          (translate g 0 h))))
    (catch Exception e
      (invoke-later (alert e))
      (println e))))

Main paint function:

  • Check if maze is valid (i.e. user has selected a case)
  • Turn on anit-aliasing
  • Go through the rows of the maze
  • Translate to the position of the current room
  • Draw it
(defn content-panel []
  (mig-panel
   :constraints ["fill" "[|grow]"]
   :items [[(button :id :load
                    :text "Load file...") ""]
           [(text :id :file-name) "growx, wrap"]
           [(scrollable (listbox :id :cases)
                        :border (line-border)) "grow"]
           [(let [s (scrollable (canvas :id :maze-pict
                                        :background "#ffffff"
                                        :paint paint-maze)
                                :border (line-border))]
              (-> s (.getHorizontalScrollBar) (.setUnitIncrement (* 3 room-width)))
              (-> s (.getVerticalScrollBar) (.setUnitIncrement (* 3 room-height)))
              s) "grow, push"]]))

Main window contents:

  • “Load” button
  • Current file name
  • List box for cases
  • Canvas for the maze

Uses MigLayout, of course.

(defn split-cases [acc line]
  (let [case (re-find #"^Case #\d+:$" line)]
    (if case
      ;; Found case start line
      (assoc acc
        :curr-case case
        :cases (assoc (acc :cases) case []))
      ;; Continuation of the current case (maze definition)
      (let [cases (acc :cases)
            curr-case (acc :curr-case)
            curr-maze (cases curr-case)
            new-maze (conj curr-maze line)
            new-cases (assoc cases curr-case new-maze)]
        (assoc acc
          :cases new-cases)))))

When loading, split the cases one by one, taking into account maze description has two kinds of lines:

  • Case start
  • Maze lines for the current case
(defn load-cases [file]
  (with-open [r (reader file)]
    (let [lines (reduce conj [] (line-seq r))
          {:keys [cases]} (reduce split-cases {:cases (ordered-map)} lines)]
      (reset! (state :cases) cases))))

Case loader function:

  • Use reader to read from the file
  • Get the lines
  • Reduce using previous split-cases function
(defn load [e]
  (let [frame (to-frame e)]
    (choose-file frame
                 :type :open
                 :success-fn (fn [fc file] (reset! (state :file) file)))))

Just shows the standard Java file chooser to pick the file.

(defn set-listeners [frame]
  (listen (select frame [:#load])
            :action load))

(defn set-bindings [frame]
  ;; File binding
  (ssb/bind
   (state :file)
   (ssb/tee
    (ssb/b-do* load-cases)
    (ssb/bind
     (ssb/transform #(.getPath %))
     (select frame [:#file-name]))))
  ;; Cases binding
  (ssb/bind
   (state :cases)
   (ssb/transform #(keys %))
   (ssb/tee
    (ssb/property (select frame [:#cases]) :model)
    (ssb/b-do* (fn [v] (selection! (select frame [:#cases]) (first v))))))
  ;; Case selection binding
  (ssb/bind
   (ssb/selection (select frame [:#cases]))
   (ssb/b-do* #(reset! (state :curr-case) %)))
  ;; Selected case binding
  (ssb/bind
   (state :curr-case)
   (ssb/transform #(@(state :cases) %))
   (ssb/b-do* #(reset! (state :maze) %)))
  ;; Maze binding
  (ssb/bind
   (state :maze)
   (ssb/b-do* (fn [maze] (let [canvas (select frame [:#maze-pict])
                               cw (* room-width (+ 2 (count (first maze))))
                               ch (* room-height (+ 2 (count maze)))]
                           (config! canvas :preferred-size [cw :by ch])
                           (.revalidate canvas)
                           (repaint! canvas))))))

These two set up the listeners (only one in this case – button click) and bindings which nicely describe the state machine for this simple app:

  • When file is selected, load the cases and display the file name
  • When cases were loaded, populate the list box with the case map description
  • When a case is selected, update the current case
  • When the current case changes, update the maze
  • When the maze is updated, draw it
(defn maze-display []
  (native!)
  (let [f (frame :title *ns*
                 :width 1200 :height 700
                 :on-close :dispose
                 :visible? true
                 :content (content-panel))]
    (.setLocation f (java.awt.Point. 100 100))
    (reset! (state :frame) f)
    (set-listeners f)
    (set-bindings f)))

Main function:

  • Make the frame
  • Set its location
  • Set the listeners and bindings

The final result looks like this:

maze-display-app


Clojure, Seesaw and Java Web Start with Leiningen

Here’s a quick tutorial about how you can put your Clojure + Seesaw applications on the Web to be started using Java Web Start. I will be using Leiningen build tool.

Sample Clojure + Seesaw application using Leiningen

Let’s make a sample app first.

$ lein new com.icyrock.clojure.csjws
Created new project in: /home/icyrock.com/clojure/com.icyrock.clojure.csjws
Look over project.clj and start coding in com/icyrock/clojure/csjws/core.clj

Edit your project.clj to this:

(defproject com.icyrock.clojure.csjws "1.0.0-SNAPSHOT"
  :description "com.icyrock.clojure.csjws"
  :dependencies [[org.clojure/clojure "1.3.0"]
                 [seesaw "1.4.0"]]
  :main com.icyrock.clojure.csjws.core)

Run lein deps to fetch the dependencies.

Edit the only file in src subtree:

src/com/icyrock/clojure/csjws/core.clj

to the following:

(ns com.icyrock.clojure.csjws.core
  (:gen-class)
  (:use [seesaw core mig]))

(defn make-button [row col]
  (button :text (format "(%d, %d)" row col)
          :listen [:action
                   (fn [e] (alert (format "Hi, you clicked on (%d, %d)!" row col)))]))

(defn make-content []
  (mig-panel
   :constraints ["fill"]
   :items
   (let [rows 4 cols 4]
     (for [row (range rows) col (range cols)]
       [(make-button row col)
        (if (and (< row (dec rows)) (= col (dec cols))) "grow, wrap" "grow")]))))

(defn -main [& args]
  (native!)
  (-> (frame :title "com.icyrock.clojure.csjws.core"
             :content (make-content)
             :width 400
             :height 400
             :on-close :exit)
      show!))

Run it with lein run – this will give you a 4×4 button grid such as this:

Signing

Seesaw is using reflection, for which you need security permissions to run. To be able to grant these, jar needs to be signed. Here are the steps needed for a self-signed jar:

  • Generate public / primary key pair
  • Sign jar given that certificate

The first step is needed only if you don’t have the certificate (or want to use a new one). All you need is contained in the JDK itself.

Generate public / primary key pair

$ keytool -genkeypair -alias icyrock.com \
-keystore icyrock.com.jks -validity 365 \
-storepass password -keypass password \
-dname "CN=icyrock.com,O=icyrock.om,C=US"

This will give you a file called icyrock.com.jks, protected by password “password”, which contains a key pair with alias “icyrock.com” valid for 365 days and protected by password “password”. You can list this:

$ keytool -list -keystore icyrock.com.jks -storepass password -keypass password -v

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: icyrock.com
Creation date: Apr 11, 2012
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=icyrock.com, O=icyrock.om, C=US
Issuer: CN=icyrock.com, O=icyrock.om, C=US
Serial number: 4f864b76
Valid from: Wed Apr 11 23:26:46 EDT 2012 until: Thu Apr 11 23:26:46 EDT 2013
Certificate fingerprints:
     MD5:  34:8E:22:BF:D8:1A:C8:6F:40:ED:8B:B5:97:5A:65:75
     SHA1: 2E:49:87:1D:14:11:C6:C3:F1:AE:FC:6A:60:82:10:38:8E:B4:67:FA
     Signature algorithm name: SHA1withDSA
     Version: 3

Note that the above is actually a X.509 certificate (see keytool docs for more info). This certificate can be used to sign jars with jarsigner, which is all we need.

Security note

The file that keytool generated (icyrock.com.jks) is called JKS – Java Key Store – for a very good reason. It stores key information. This line from the above is important:

Entry type: PrivateKeyEntry

That should be sufficient for anyone to consider this file very important from security standpoint. It contains information that allows anyone in its possesion to create signed files. Those files will bear that certificate details, but the content might be vastly different. In real life, this is called identity theft. In the same way you would not give your house key to a random person, you should not give your .jks files to random people.

On the other hand, you can give out your certificate – this can be used by other people to verify your jar. To export the certificate, you do this:

$ keytool -export -keystore icyrock.com.jks -storepass password -keypass password -file icyrock.com.crt -alias icyrock.com

You can now give this cert file, which others can import into their .jks keystore like this:

$ keytool -importcert -file icyrock.com.crt -keystore icyrock.com-cert.jks -storepass password -keypass password -alias icyrock.com-cert

The certificate itself is different than the key, here’s the output:

$ keytool -list -keystore icyrock.com-cert.jks -storepass password -keypass password -v
Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: icyrock.com-cert
Creation date: Jul 14, 2012
Entry type: trustedCertEntry

Owner: CN=icyrock.com, O=icyrock.om, C=US
Issuer: CN=icyrock.com, O=icyrock.om, C=US
Serial number: 4f864b76
Valid from: Wed Apr 11 23:26:46 EDT 2012 until: Thu Apr 11 23:26:46 EDT 2013
Certificate fingerprints:
     MD5:  34:8E:22:BF:D8:1A:C8:6F:40:ED:8B:B5:97:5A:65:75
     SHA1: 2E:49:87:1D:14:11:C6:C3:F1:AE:FC:6A:60:82:10:38:8E:B4:67:FA
     SHA256: 2D:63:93:C0:26:8D:8C:5C:16:AD:22:D9:31:42:05:F2:9D:B8:9C:75:BC:68:F1:40:D6:8B:95:7C:61:D2:79:2B
     Signature algorithm name: SHA1withDSA
     Version: 3

The important line here is:

Entry type: trustedCertEntry

which says it contains the certificate only. To compare to the house analogy, this would be as if you gave someone your address. They could then come to your house and confirm that’s really your house, but without the key that only you have they could not come in. In a similar way, this certificate can be used to verify .jar files, but not sign them.

Creating uberjar and signing it

Leiningen has uberjar command which creates a jar that contains your code plus all dependencies packed into one jar. Run that and you should get a jar called com.icyrock.clojure.csjws-1.0.0-standalone.jar in the root folder of the project.

Let’s sign that jar with the certificate we created above:

$ jarsigner -keystore icyrock.com.jks \
-storepass password -keypass password \
-signedjar com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar \
com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone.jar icyrock.com

This will generate a new file:

com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar

that is now signed. You can verify this file:

$ jarsigner -verify \
com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar 
jar verified.

Warning: 
This jar contains entries whose certificate chain is not validated.

Re-run with the -verbose and -certs options for more details.

Due to our certifiacte being self-signed, the above warning is printed. Now the certificate file we generated above comes into play:

$ jarsigner -keystore icyrock.com-cert.jks -verify \
com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar 
jar verified.

In this case, jarsigner used the provided certificate to confirm that all contents of the .jar file actually were signed with the key corresponding to that certificate.

You can start this using regular java -jar command:

$ java -jar \
com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar 

Java Web Start

Now that you have a signed file, you can put it on some Web server to be picked up by javaws. For this, you first need to make a .jnlp file. Say you have a local Web server accessible via http://localhost and you want to put this under http://localhost/clojure/. Here’s a JNLP file that would work for that arrangement:

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+" 
      codebase="http://localhost/clojure/"
      href="csjws.jnlp">
  <information>
    <title>com.icyrock.clojure.csjws.core</title>
    <vendor>icyrock.com</vendor>
    <icon href="com.icyrock.clojure.csjws.png"/>
    <offline-allowed/>
  </information>
  <security>
      <all-permissions/>
  </security>
  <resources>
    <j2se version="1.6.0+"/>
    <jar href="com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar"/>
  </resources>
  <application-desc main-class="com.icyrock.clojure.clsjws.core"/>
</jnlp> 

Now if you put this JNLP and the signed jar file (com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar) so they are accessible via http://localhost/clojure/, you should be able to then do:

$ javaws http://localhost/clojure/com.icyrock.clojure.csjws.jnlp

Of course, you can do that with any browser that has Java plug-in enabled by going to the same location:

http://localhost/clojure/com.icyrock.clojure.csjws.jnlp

This should launch Java Web Start. It will download the file and ask you to acknowledge the certificate (as it is self-signed, not by a trusted CA authority). To confirm this is the right certificate, you can compare the fingerprints presented by javaws and the certificate itself. In general, only if these match and only if you trust the provider of the certificate should you proceed – otherwise, you can potentially allow malicious code to execute without security restrictions, which is not a desirable result.

This may look like this:

When you click on “More information…”, you get this:

When you click on “Certificate details…”, you get this:

Here you can see the SHA1 fingerprint. Take this and compare to the certificate. Usually, for self-signed certificates, this can be given e.g. on the site for users to compare. If these match, you can be pretty sure the code was signed by that certificate and all is good. The best way is to give the certificate (icyrock.com-cert.jks or icyrock.com.crt) to your users, which can then import it and the browser will automatically do the verification for them. You can close these two and click Run on the original dialog – the app will launch.


Clojure and Seesaw

There’s a very nice desktop graphics library called Seesaw for Clojure. You can see a lot of examples of how to use it inside seesaw/test/examples folder of the distribution or you can browse online at GitHub.

I played with it – here’s a small LED-matrix digital clock implementation. On Ubuntu 11.10 it looks like this:

Here’s the code:

(ns com.icyrock.clojure.seesaw.led-matrix
  (:import [ java.util Calendar])
  (:use seesaw.core
        seesaw.graphics))

(def lcd-dot-style-off
  (style
   :background "#181818"
   :stroke (stroke :width 3)))

(def lcd-dot-style-on
  (style
   :background "#00bc00"
   :stroke (stroke :width 3)))

(def lcd-dot-styles
  {false lcd-dot-style-off
   true lcd-dot-style-on})

(defn draw-lcd-dot [g width height is-on]
  (let [dot-style (lcd-dot-styles is-on)
        border (-> dot-style :stroke .getLineWidth)
        border2 (* 2 border)]
    (draw g
          (ellipse border border (- width border2) (- height border2)) dot-style)))

(def lcd-symbol-dots
  {\0
   [".***."
    "*...*"
    "*...*"
    "*...*"
    "*...*"
    "*...*"
    ".***."]
   \1
   ["..*.."
    ".**.."
    "..*.."
    "..*.."
    "..*.."
    "..*.."
    "*****"]
   \2
   [".***."
    "*...*"
    "....*"
    "...*."
    "..*.."
    ".*..."
    "*****"]
   \3
   [".***."
    "*...*"
    "....*"
    "..**."
    "....*"
    "*...*"
    ".***."]
   \4
   ["...*."
    "..**."
    ".*.*."
    "*..*."
    "*****"
    "...*."
    "...*."]
   \5
   ["*****"
    "*...."
    "****."
    "....*"
    "....*"
    "*...*"
    ".***."]
   \6
   [".***."
    "*...*"
    "*...."
    "****."
    "*...*"
    "*...*"
    ".***."]
   \7
   ["*****"
    "....*"
    "...*."
    "..*.."
    "..*.."
    "..*.."
    "..*.."]
   \8
   [".***."
    "*...*"
    "*...*"
    ".***."
    "*...*"
    "*...*"
    ".***."]
   \9
   [".***."
    "*...*"
    "*...*"
    ".****"
    "....*"
    "*...*"
    ".***."]
   \:
   ["....."
    "....."
    "..*.."
    "....."
    "..*.."
    "....."
    "....."]
   })

(defn draw-lcd-symbol [g width height symbol]
  (let [dots (lcd-symbol-dots symbol)
        dot-width (/ width (count (first dots)))
        dot-height (/ height (count dots))]
    (doseq [row dots]
      (doseq [cell row]
        (draw-lcd-dot g dot-width dot-height (= cell \*))
        (translate g dot-width 0))
      (translate g (- width) dot-height))))

(defn get-time-string []
  (let [ c (Calendar/getInstance)
        h (.get c Calendar/HOUR_OF_DAY)
        m (.get c Calendar/MINUTE)
        s (.get c Calendar/SECOND)]
    (format "%02d:%02d:%02d" h m s)))

(defn paint-lcd-symbol [ c g]
  (try
    (let [symbols (get-time-string)
          symbol-count (count symbols)
          width (.getWidth c)
          height (.getHeight c)
          symbol-width (/ width symbol-count)]
      (doseq [symbol symbols]
        (push g
              (draw-lcd-symbol g (- symbol-width 20) height symbol))
        (translate g symbol-width 0)))
    (catch Exception e
      (println e))))

(defn content-panel []
  (border-panel
   :center (canvas :id :clock
                   :background "#000000"
                   :paint paint-lcd-symbol)))

(defn make-frame []
  (let [f (frame :title "com.icyrock.clojure.seesaw.led-matrix"
                 :width 1200 :height 300
                 :on-close :dispose
                 :visible? true
                 :content (content-panel))]
    (.setLocation f (java.awt.Point. 100 300))
    (timer (fn [e] (repaint! (select f [:#clock])) 1000))))

(defn -main [& args]
  (native!)
  (make-frame))
(-main)

You can find the code at com.icyrock.clojure GitHub repository – just clone and fire up in your favorite IDE.