I've been playing around with Clojure a bit, so here is a Clojure version of the little puzzle I wrote in my first blog entry If you don't have clojure set up, go to http://clojure.org/ and get going.
First, we'll make our own little namespace. We will have to call out to java to set up the gui, so we import some swing classes into our namespace as well:
user> (ns com.jalat.flipper)
nil
com.jalat.flipper> (import '(java.awt Color GridLayout Dimension)
'(java.awt.event ActionEvent ActionListener)
'(javax.swing ImageIcon JFrame JPanel JButton JLabel JTextField JOptionPane BorderFactory))
If you read my first post, you know that the object of the game is to get a layout where all buttons are highlighted, except the center button. I'm going to use true/false values for this, and I'm going to put the values into a vector. The [] is Clojure syntax for a vector.:
com.jalat.flipper> (def solution [true true true
true false true
true true true])
#'com.jalat.flipper/solution
I'm going to put the moves we can do into a hash, {} is the syntax for a hash. I'm using numbers for the key, and each vaule is a vector where true is a field that will be flipped by the move, and false is a field that will be unchanged by the move.
com.jalat.flipper> (def moves {1 [true true false
true true false
false false false]
2 [true true true
false false false
false false false]
3 [false true true
false true true
false false false]
4 [true false false
true false false
true false false]
5 [true false true
false true false
true false true]
6 [false false true
false false true
false false true]
7 [false false false
true true false
true true false]
8 [false false false
false false false
true true true]
9 [false false false
false true true
false true true]})
#'com.jalat.flipper/moves
In Clojure hashes can be called as if they were a function by giving the key as an argument. so to look up the move with the key "1", just call it:
com.jalat.flipper> (moves 1) [true true false true true false false false false]
So we got a solution and moves, all we need is a starting position. We have the function rand which gives a float between 0 and 1 if we don't give any arguments. Clojure has a shortcut for lambda which is #(). I'm not using any arguments in this case but they would be specified with % and an optional number if there are more than one argument. #(+ 5 %) would be an anonymous function adding five to the argument it receives. repeatedly is a function that just runs it's argument forever and returns it as a lazy sequence. The lazyness is important as this would otherwise be known as a endless loop. take to the rescue. Take returns the first n elements of a sequence, this prevents repeadedly to run forever. Finally, into takes the sequence and stuffs it into a vector.
com.jalat.flipper> (defn scramble []
"Generate a new starting position"
(into [] (take 9 (repeatedly #(< 0.5 (rand))))))
com.jalat.flipper> (scramble)
[true true false false true true false true false]
Clojures data structures are immutable, but I'll need a way of keeping track of what the current position is, and how many moves we've done so far. For that I'll use references. A reference is a pointer to a data structure. While the data itself can not be modified, the reference can be modified inside a transaction. There is a shortcut to deref, just put @ in front of the reference. A transaction is set up with dosync, any code within the dosync will happen within the transaction:
com.jalat.flipper> (def state (ref (scramble))) #'com.jalat.flipper/state
com.jalat.flipper> state clojure.lang.Ref@2b71a3
com.jalat.flipper> @state [false true true false true false true true true]
com.jalat.flipper> (def num-moves (ref 0)) #'com.jalat.flipper/num-moves
com.jalat.flipper> (defn new-puzzle []
"Set up a starting position, and zero out the moves"
(dosync
(ref-set num-moves 0)
(ref-set state (scramble))))
#'com.jalat.flipper/new-puzzle
Next, two functions to perform a move, flip takes two vectors (Sequences, actually. I could have used lists) and does a xor on the two vectors. apply-move starts a transaction, and in that transaction updates the number of moves we've done, finds the correct move and calls flip on that move and the current state. The alter function used takes the data that the ref givens as it's first argument, and gives that as the first argument to the third argument together with the rest of the arguments. Finally the ref will be pointed to the result of the function. So (alter num-moves inc) will find the current number of moves, increase it by one, and point the num-moves reference to the increased number.
com.jalat.flipper> (defn flip [pieces state]
"Takes two true/false sequences, one specifies which bits
of the other to flip true/false. (Or the other way around,
it's a symmetric operation.)"
(map (fn [x y] (and (not (and x y))
(or x y)))
pieces state))
#'com.jalat.flipper/flip
com.jalat.flipper> (defn apply-move [n]
"Updates the state by applying move n to the current state."
(dosync
(alter num-moves inc)
(alter state flip (moves n))))
#'com.jalat.flipper/apply-move
Time for some java integration. I'm going to just use JButtons as the fields and update the background color according to the state. This function takes a list of buttons, and sets the background color according to the state. Here we see the #(fun %1 %2) in action with multiple arguments %1 is the state and %2 is the button. A symbol starting with . is considered a method call on a object. What is here (.setBackground button (.red Color)) would in Java be written as button.setBackground(Color.red) Finally. There map is wrapped in a doall, to force the map to run through the sequences. Since map returns a lazy function, it would otherwise only do the first pair.
I'm not entirely happy about how I'm doing this. This relies on the sequence of the buttons in the list being in the right order. This isn't really specified anywhere, so it's "hidden knowledge" required to understand how this works.
com.jalat.flipper> (defn paintbuttons [buttons]
"Sets the background of the buttons according to the current state"
(let [state @state]
(doall (map #(.setBackground %2 (if %1
(.red Color)
(.black Color)))
state buttons))))
#'com.jalat.flipper/paintbuttons
Finally a function to set up the gui. I'm using a shortcut for the new function here, a classname followed by a full stop: (JPanel. "Hello") Is equivalent to new JPanel("Hello"); in Java. Another convenience macro is doto. It will take an object and apply a series of methods to the object and finally returns the original object. Finally there is proxy. Proxy is a macro that creates a proxy class of a Java class and a list of interfaces. (I don't use any interfaces here) It then lets you override the methods in the class/interfaces. I use it here to add listeners to the buttons in the interface.
com.jalat.flipper> (defn make-gui []
"Sets up the playing field and adds listeners"
(new-puzzle)
(let [panel (JPanel. (GridLayout. 0 3))
moveField (doto (JTextField. "0")
(setHorizontalAlignment (.RIGHT JTextField))
(setEditable false))
buttons (map #(doto (JButton. (ImageIcon. (str "/Users/asbjxrn/Clojure/projects/com/jalat/flipper/" % ".gif")))
(setBackground (if (@state (dec %))
(.red Color)
(.black Color)))
(setBorder (.createEmptyBorder BorderFactory))
(setSize (Dimension. 40 40))
(setContentAreaFilled false)
(setOpaque true))
(range 1 10))]
(doall (map #(.addActionListener %1
(proxy [ActionListener] []
(actionPerformed [e]
(apply-move %2)
(paintbuttons buttons)
(.setText moveField (str @num-moves))
(when (= solution @state)
(.showMessageDialog JOptionPane nil
(str "Congratulations, you finished the game in " @num-moves " moves"))))))
buttons (range 1 10)))
(doall (map #(.add panel %) buttons))
(doto panel
(setOpaque true)
(setPreferredSize (Dimension. 300 400))
(add (doto (JButton. "Start")
(addActionListener
(proxy [ActionListener] []
(actionPerformed [e]
(new-puzzle)
(paintbuttons buttons)
(.setText moveField (str @num-moves)))))))
(add (JLabel. "Moves:"))
(add moveField))
(doto (JFrame. "Flipper")
(setContentPane panel)
(pack)
(setVisible true))))
#'com.jalat.flipper/make-gui
Finally, the only thing remaining is to launch the gui:
com.jalat.flipper> (make-gui) javax.swing.JFrame[frame0,0,22,300x422,layout=java.awt.BorderLayout,title=Flipper,resizable,normal,defaultCloseOperation=HIDE_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,22,300x400,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=449,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]
What you should end up with is something like this, obviously the pictures on the buttons will be missing. I added them when I created the buttons in the make-gui function.


