Decoding Clojure

This is a terrible mangling of a piece of code by Eric Lavigne (thanks!), I take full responsibility for any lines starting with and the afterword.

I'm a total newbie when it comes to clojure, so when I got my hands on a little clojure program to play the game of 'island-wari' I went and dissected it bit by bit, clojure manual in hand to see how it works.

The original code did not contain any comments at all so this was a both trying to figure out what the langauge does *and* what the program does.

                                                 
//
// This all started here: http://news.ycombinator.com/item?id=1043180                                                                          //
// To 'recover' the original clojure source simply do
// 'grep -v //' on this file.                                                                                                                                       
//
// An explanation of the game of 'island wari' is here:
// http://waynesword.palomar.edu/ww0603.htm
// 
// In short, it is a two player game with 7 'slots' for each
// player, the rightmost slot for a player (positions 6 and 13)
// is called the homebase, the players take turns and indicate
// a bin they wish to play, stones then get distributed
// to all bins counterclockwise of the bin played. If the last
// stone lands on the players homebase that player gets to play
// again, players have to skip the homebase of their opponent
// when placing stones. Whoever has their side of the board
// empty first wins the game.                                                 
// 
// The 'pseudo-C' language equivalents for the shorter functions are
// there simply because I tried to translate the functions in 
// to something that I can readily understand, they are not 
// used anywhere (and won't even compile)
//                                                                        

// this sets up the namespace and imports compojure


(ns wari
  (:use compojure)
  (:gen-class))   


//////////////////////////////////////////////////////////////////////////////////

// return a list of board positions where stones will be placed
// given a starting position and an indication which 'home base'
// should be avoided, as well as the number of stones that      
// will need to be placed                                       


(defn where-stones-fall [start skip stones]
  (take stones (remove #(= skip %) (drop (inc start) (cycle (range 14))))))


// (range 14) becomes:
// (0 1 2 3 4 5 6 7 8 9 10 11 12 13)
// (cycle (range 14)) yields:       
// (0 1 2 3 4 5 6 7 8 9 10 11 12 13   0 1 2 3 4 5 6 7 8 9 10 11 12 13  etc )
// (inc start) is                                                           
// start + 1                                                                
// so                                                                       
// (drop (inc start) (cycle (range 14)))                                    
// gives                                                                    
// the elements from the cycle found from start+1 onwards                   
// so if start is 5 then this will yield:                                   
// ( 5 6 7 8 9 10 11 12 13   0 1 2 3 4 5 6 7 8 9 10 11 12 13  etc )         
// (we start at '5' becaue the original range started at 0, the first       
// 5 elements are then 0,1,2,3,4).                                          
// now we are at the remove call, which looks like this:                    
// (remove #(= skip %) (5 6 7 8 9 10 11 12 13   0 1 2 3 4 5 6 7 8 9 10 11 12 13  etc ))
// assuming a start of 5                                                               
// bin to skip can be                                                                  
// 6 or 13 depending on whose move it is                                               
// so the remove call will take the 'skip' entry and remove it from the list           
// if 'skip' was 6 then the list now looks like this:                                  
// (5 7 8 9 10 11 12 13   0 1 2 3 4 5 7 8 9 10 11 12 13  etc )                         
// now we are at the outer braces:                                                     
// (take stones (5 7 8 9 10 11 12 13   0 1 2 3 4 5 7 8 9 10 11 12 13  etc ))           
// this will return a list of the first 'stones' entries from                          
// the colection                                                                       
// for 'stones' is 10 this would return:                                               
// (5 7 8 9 10 11 12 13 0 1)                                                           

// the relevant clojure built-in functions used (from the clojure
// documentation):                                               

// (take n coll)
// Returns a lazy sequence of the first n items in coll, or all items if
// there are fewer than n.                                              

// (remove pred coll)
// Returns a lazy sequence of the items in coll for which
// (pred item) returns false. pred must be free of side-effects.

// (drop n coll)
// Returns a lazy sequence of all but the first n items in coll.

// (inc x)
// Returns a number one greater than num.

// (cycle coll)
// Returns a lazy (infinite!) sequence of repetitions of the items in coll.

// (range end)
// Returns a lazy seq of nums from 0 (inclusive) to end (exclusive)

//////////////////////////////////////////////////////////////////////////////////

// figure out which bin to skip, this passes the opponents 'home base'
// bin                                                                


(defn bin-to-skip [turn]
  (if (= turn 1) 13 6)) 


// bin-to-skip(turn) { if (turn == 1) return 13 else return 6 }

//////////////////////////////////////////////////////////////////////////////////

// the bin to play again depends on who is playing, it is equal to the 
// homebase bin for the current player                                 


(defn bin-to-play-again [turn]
  (bin-to-skip (if (= turn 1) 2 1)))


// bin-to-play-again(turn) { return bin-to-skip(turn == 1 ? 2 : 1) }

//////////////////////////////////////////////////////////////////////////////////


(defn alter-board [board turn move]
  (let [stones (where-stones-fall move (bin-to-skip turn) (nth board move))]
    (map (fn [b i]                                                          
           (+ (if (= move i) 0 b)                                           
              (count (filter #(= i %) stones)))                             
         )                                                                  
         board (range 14)                                                   
    )                                                                       
  )                                                                         
)                                                                           


// alter-board returns the board as it is after the move done by the player whose
// turn it is. First 'stones' is set to become a list of board positions that    
// will be receving a stone.                                                     

// I fiddled a bit with the parentheses because it wasn't entirely clear to me
// which belonged to which, and which part constituted the body of the function
// argument to 'map'. So, map appears to receive 3 arguments here, the function
// body, and two collections. The first collection is the current board, the   
// second collection is the numbers (0 ... 13).                                

// A quick check outside in the repl:
// (map (fn [i] (* 5 i)) [0 1 2 3 4 5])
// returns                             
// (0 5 10 15 20 25)                   
// but strangely                       
// (map (fn [i] (* 5 i)) (0 1 2 3 4 5))
// fails with a 'java.lang.ClassCastException: java.lang.Integer cannot be cast to 
// clojure.lang.IFn (NO_SOURCE_FILE:0)'                                            
//                                                                                 
// Why is it that map only works on vectors and not on lists ?                     
// Ah, solved that one the (0 1 2 3 4 5)                                           
// does not work as a list parameter in that spot because '0' is                   
// seen as the function name. Probably a classic newbie mistake,                   
// I'm expecting here to pass a list of numbers but to do that you                 
// have to *make* a list. The [] notation of the vector does not                   
// deal with this in the same way because the vector does not start                
// with an '(' indicating the next token is a function name.                       
//                                                                                 
// Stupid me :)                                                                    
//                                                                                 
// So, the list example then becomes:                                              
// (map (fn [i] (* 5 i)) (list 0 1 2 3 4 5))                                       
//                                                                                 
// where 'list' will return the remaining arguments as a list.                     
// And sure enough, that works.                                                    

// map then returns 
// (fn [b i]        
//   (+ (if (= move i) 0 b)
//      (count (filter #(= i %) stones))
//   )                                  
// )                                    
//                                      
// fn is then applied to each of the counts of stones in each bord position
// (in parameter 'b') and the rank of the position (in parameter 'c'),     
// stones and move are also 'in scope'.                                    
//                                                                         
// That little anonymous function then does the following:                 
// If the position under consideration is the one that is                  
// being played it gets set to contain '0' stones, otherwise               
// we start off with the number of stones found there                      
// that takes care of the '(if (= move i) 0 b)', then the                  
// '(filter #(= i %) stones)' expression returns the                       
// collection consisting of 'nil' in case the current board                
// position is not in the list of board positions to receive               
// a stone and a collection of one element, the board position             
// if it is to receiave a stone.                                           
//                                                                         
// count then counts the number of elements in the collection              
// (0 or 1) and will add that to the number of stones already              
// there or to 0 in the case of the position being played.                 
//                                                                         

//////////////////////////////////////////////////////////////////////////////////


(defn next-turn [board turn move]
  (let [last-stone (last (where-stones-fall move (bin-to-skip turn) (nth board move)))]
    (cond (= (bin-to-play-again turn) last-stone) turn                                 
          (= turn 1) 2                                                                 
          (= turn 2) 1)))                                                              


//
// this calls where-stones-fall to get a list of the slots where stones will
// be placed, the (bin-to-skip turn) will return 6 or 13 depending on whose turn
// it is, the (nth board move) will return the number of stones in the          
// board position that is being played.                                         
//                                                                              
// This is where I don't think 'lisp' like languages are more 'expressive' than 
// many other languages, after all:                                             
//                                                                              
// array[12];                                                                   
//                                                                              
// vs                                                                           
//                                                                              
// (nth array 12)                                                               
//                                                                              
// hardly seems an improvement, but I think anybody proficient in lisp          
// would probably read this with the same ease. It just feels as though         
// the 'functional' technique is taken a little further than is practical       
// here. Of course, having only one technique also has a certain elegance       
// but I think that other languages have an advantage here. Is there a          
// lisp that supports subscripted arrays with square brackets ? That would      
// also take care of passing 'nth' anything other than a vector (which          
// leads to some pretty cryptic error message)                                  
//                                                                              
// The 'let' function binds the 'last-stone' symbol to the last entry of        
// the list of board positions where stones will be placed, if the              
// last stone is placed in the home base of the player that just played         
// then that player will get to play again, otherwise the other player          
// will play next                                                               

//////////////////////////////////////////////////////////////////////////////////


(defn move-result [board turn move]
  {:pre [(or (= turn 1) (= turn 2))
         (or (and (= turn 1) (< move 6))
             (and (= turn 2) (> move 6) (< move 13)))
         (< 0 (nth board move))]}                    
  {:turn (next-turn board turn move)                 
   :board (alter-board board turn move)})            


//////////////////////////////////////////////////////////////////////////////////


(defn board-route [turn board]
  (apply str (concat ["/" turn "/0/"] (interpose "/" board)))) 


// board-route(turn,board) { return "/" + turn + "/0/" + implode("/",board) }

//////////////////////////////////////////////////////////////////////////////////

// render board displays the board the player has chosen to continue
// the game with, and creates links for the new board if a position 
// is 'playable', clicking on a position then effects the next move 

// the 'whosturn' variable makes sure the right side of the board
// has the links placed.                                         


(defn render-board [whosturn slots]
  {:pre [(or (= whosturn 1) (= whosturn 2))]}
  (let [vert [:img {:src "/vertical.png"}]   
        horiz [:img {:src "/horizontal.png"}]
        small [:img {:src "/small.png"}]     
        big [:img {:src "/big.png"}]         
        top [:img {:src "/top.png"}]         
        bot [:img {:src "/bottom.png"}]      
        picname-to-imgcol #(vector :td [:img {:src (str "/" % ".png")}])
        slot-to-link                                                    
        (fn [move]                                                      
          (let [result (move-result slots whosturn move)]               
            [:td [:a                                                    
                  {:href (board-route (result :turn) (result :board))}  
                  [:img {:src (str "/" (nth slots move) ".png")}]]]))]  
    [:table {:width "50%"}                                              
     [:tr (map #(vector :td %) (interpose horiz (repeat 7 small)))]     
     [:tr [:td vert]                                                    
      (map                                                              
       (fn [slot]                                                       
         (cond (= slot "vertical") (picname-to-imgcol "vertical")       
               (= (nth slots slot) 0) (picname-to-imgcol 0)             
               (= whosturn 2) (picname-to-imgcol (nth slots slot))      
               :default (slot-to-link slot)))                           
       (interpose "vertical" (reverse (range 6))))                      
      [:td vert]]                                                       
     [:tr [:td vert] [:td top]                                          
      (map #(vector :td %) (interpose big (repeat 5 vert)))             
      [:td top] [:td vert]]                                             
     [:tr [:td vert] (picname-to-imgcol (nth slots 6))                  
      (map #(vector :td %) (interpose big (repeat 5 vert)))             
      (picname-to-imgcol (nth slots 13)) [:td vert]]                    
     [:tr [:td vert] [:td bot]                                          
      (map #(vector :td %) (interpose big (repeat 5 vert)))             
      [:td bot] [:td vert]]                                             
     [:tr [:td vert]                                                    
      (map                                                              
       (fn [slot]                                                       
         (cond (= slot "vertical") (picname-to-imgcol "vertical")       
               (= (nth slots slot) 0) (picname-to-imgcol 0)             
               (= whosturn 1) (picname-to-imgcol (nth slots slot))      
               :default (slot-to-link slot)))                           
       (interpose "vertical" (range 7 13)))                             
      [:td vert]]                                                       
     [:tr (map #(vector :td %) (interpose horiz (repeat 7 small)))]    ]))


// this routine is uncharacteristically long!
//                                           
// It creates a table with 13 columns x 7 rows, the outer ones are
// filled with 'small.png' in the corners, 'vertical.png' on the left
// and right edge and 'horizontal.png' on the top and bottom edges.  
//                                                                   
// For each of the players their respective 'bins' are displayed,    
// with up to 48 stones per 'bin'.                                   
//                                                                   
// This job would be *much* better suited to a templating engine     
// of sorts, which could be passed a vector containing the           
// number of stones for each position as well as an optional link    
// for that position in case it is played.                           

//////////////////////////////////////////////////////////////////////////////////

//
// the despatcher, it matches incoming requests to methods and urls 
//                                                                  


(defroutes wari-routes
  (GET "/*"           
       (or (serve-file (params :*)) :next))
  (GET "/"                                 
       (html [:h1 "Welcome to the Island Wari game server."]
             [:a {:href (board-route                        
                         1                                  
                         (apply concat (repeat 2 (concat (repeat 6 4) [0]))))} 
              "New game"]))                                                    
  (GET "/:whosturn/:computerplays/:x/:x/:x/:x/:x/:x/:x/:x/:x/:x/:x/:x/:x/:x"   
       (let [turn (Integer/parseInt (params :whosturn))                        
             board (map #(Integer/parseInt %) (params :x))]
         (html
          (if (or (= turn 1) (= turn 2))
            (render-board turn board)
            [:p turn]))))
  (ANY "*"
       (page-not-found)))


// the despatcher tries to match each of the routes in turn until it
// finds one that returns a non-nil result. So the first thing tried
// is to serve up a static file. If that fails a check is made to see
// if the homepage of the server is requested, the homepage is output
// 'inline' to the client.
//
// Then a move is tried, and if that matches the turn and board
// variables are recoverd from the request resource and if the
// turn variable is either 1 or 2 a new board is genereated
//
// finally, if no match can be made page-not-found is called which
// will display compojures standard 404 page.

//////////////////////////////////////////////////////////////////////////////////


(defn -main [& args]
  (run-server
   {:port 8084}
   "/*" (servlet wari-routes)))


// this calls the run-server function with a port number, a match template
// and a servlet to handle the various requests

//////////////////////////////////////////////////////////////////////////////////

Afterword:

What strikes me as clever is the use of 'lazy' evaluation to return constructs
like infinite lists (or actually, just as much as you'll use).

I'm still very much in 'braces' hell, it is going to take me a while
before I can read this stuff quickly.

Is there some kind of templating system to use with compojure? I'd hate
to give this code to a designer to make the html it outputs look good,
especially if the site would have many pages with a standardized lay-out
that might prove to be a serious problem.

Long rendering function and array subscripting

Hi Jacques. I hope you are enjoying Clojure so far.

The render-board function is very long. That is the first thing I would want to change when I improve this program. That function needs to be broken into several smaller functions, and I think this will also make the program as a whole shorter. I have not tried any of Clojure's HTML templating options because I actually prefer to produce the HTML directly in Clojure. I would probably change my mind if I worked with a designer.

It is possible to get array subscripting by doing this as another poster suggested:

([5 8 19 7] 2) --> 19

However, I would not do that because then my code would work differently if the array were replaced by a list.

('(5 8 19 7) 2) -->

Regarding braces hell:

I see that you moved many of my closing parentheses onto their own lines and matched them vertically with their opening parentheses. I did the same thing when I was first starting to learn Lisp. More experienced Lisp programmers complained that it looked weird and was more difficult to read.

The first issue is figuring out how many closing parentheses to use when programming. Most programming text editors can highlight matching parentheses, so you know which opening parentheses is matched by the closing parentheses that you just typed.

The second issue is reading code. In that case, think about indentation instead of parentheses. My indentation is always correct because Emacs indents my code for me.

([100 101 102] 1) should

([100 101 102] 1) should return 101 - here collection is treated as function

The same works with maps ({ "a" 1, "b" 2 } "b") works because map is the function of its keys - I like that idea very much and find it just as readable - it also means that collection can be passed everywhere we expect function - for example:

(map {"John" 1, "Ann" 2} ["John" "Ann" "John"]) returns [1 2 1] - stupid example, I know :)

About templating engine - compojure is the templating engine here, it just requires you to cut code into separate functions from your function that returns "html" - this big function just needs a few "extract anonymous function" refactorings.

Every templating engine has some equivalent of map (foreach), if and filter, and I think having full power of clojure there is nice, of course most details should be extracted so designer can only write css and clojure-html.

Enlive

For html templating in clojure you could try enlive:
http://wiki.github.com/cgrand/enlive/getting-started

Haven't actually used it, but heard good things about it.

Templating

Hey,

For templating you should have a look at cgrands Enlive:

http://github.com/cgrand/enlive

There's also a little more info to be found at http://clj-me.cgrand.net.

/Lau

If you read this far you should Follow jmattheij on Twitter