Clojure, for those who don’t know, is a newish LISP implemented on top of the JVM. It’s designed to play nicely with Java libraries, which I didn’t end up exercising, and is largely lazy and functional, which I did.
If you’ve been following along, you might notice that this implementation is very similar to the one for Scheme. That’s because I cheated and just ported it over. There are some pointed differences in its API, but it was actually pretty easy to translate. No major changes in the basic logic, with the exception of one function which I had to rewrite to get around a JVM-imposed restriction on how recursive you can get inside a let.
I like scheme better on purely aesthetic grounds, but Clojure seems more useful, and is certainly more relevant lately. A few notable innovations:
- Shortcut lambdas:
#(+ %1 10) => (fn [x] (+ x 10)) - Extensive use of vectors as syntactic distinguishers, e.g. denoting function parameters
- Java Method calls:
(.toString x) - Lots more that I didn’t end up using
Here’s the code:
; Defining the players -- Infinite Lazy Sequence
(def players (cycle ["X" "O"]))
; Setting up the board
(def *starting-board*
[[1 2 3]
[4 5 6]
[7 8 9]])
(defn space-available? [space board]
(some (fn [row] (some #(= %1 space) row)) board))
; Moving -- returns a transformed board
(defn move [player space board]
(letfn [(move-in-row
([accum row]
(cond
; if we reach the end, that's it
(empty? row) accum
; if we have found the space, take it
(= space (first row)) (concat accum (cons player (rest row)))
; if not, keep going
true (recur (concat accum (list (first row))) (rest row))))
([row] (move-in-row '() row)))]
(map move-in-row board)))
(defn space->string [space]
(if (number? space)
(str "(" space ")")
(str " " space " ")))
(defn row->string [row] (apply str (interpose "|" (map space->string row) )))
(defn board->string [board]
(apply str (interpose "\n---+---+---\n" (map row->string board))))
(defn third [seq] (nth seq 2))
(defn winner? [board]
(or
; rows
(apply = (first board))
(apply = (second board))
(apply = (third board))
; columns
(apply = (map first board))
(apply = (map second board))
(apply = (map third board))
; diagonals
(= (first (first board))
(second (second board))
(third (third board)))
(= (third (first board))
(second (second board))
(first (third board)))))
(defn full? [board]
(not (some number? (mapcat identity board))))
(defn print-board [board]
(do
(newline)
(print (board->string board))
(newline)
(newline)))
(defn play [board players]
(do
(print-board board)
(let [ player (first players) ]
(cond
(winner? board) (print (str (first (next players)) " Wins!\n"))
(full? board) (print "It's a Draw!\n")
true (do
(print (str "Select a square, " player ": "))
(flush)
(let [answer (read)]
(if (and answer (space-available? answer board))
(recur (move player answer board) (next players))
(recur board players))))))))
(play *starting-board* players)
0 Responses to “Tic-Tac-Toe: Clojure”