21
Apr
10

Tic-Tac-Toe: Clojure

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)
Advertisement

0 Responses to “Tic-Tac-Toe: Clojure”



  1. Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.