;; Clojure Number Guess
;; Matt DeBoard, 2011
;;
;; A re-write of "number guess" assignment in Clojure, a Lisp dialect that
;; uses the JVM runtime. The best way to read this is from the bottom up,
;; since the functions are necessarily ordered by their scope, with most-inner
;; scoped functions at the top, outermost scoped function at the bottom.
;;
;; The build system is Leiningen (https://github.com/technomancy/leiningen).
;; Lein compiles the Clojure source to a jarfile (which I have also included).
;; This can be run like normal, i.e. `java -jar numguess-1.0.0-standalone.jar`
;; for evaluation.
(ns numguess.core
  (:gen-class))

(def banner
  "Please think of a number between one and one hundred, and I will try to figure
out your number. You tell me if I'm too high, too low, or correct.\n")

;; To do: Map all these values into a single atom.
(def floor (atom 0))
(def ceiling (atom 100))
(def guess (atom 50))
(def counter (atom 1))

(defn update-vals [x y]
  "Function to update atom values. When dealing with a division equation that
resolves to a float, it actually outputs a rational number, so (/ 7 2) would
yield, literally, '7/2' in Clojure. So casting it to an int behaves like
Java would normally."
  (int
   (/ (+ x y) 2)
   ))

(defn too-high []
  "Core algorithm. (swap!) and (reset!) are functions used to change state for
atoms, which in turn are the synchronous & independent mutable objects used
in Clojure, which otherwise employs only immutable objects."
  (swap! counter + 1)
  (reset! ceiling @guess)
  (swap! guess update-vals @floor))

(defn too-low []
  "Core algorithm"
  (swap! counter + 1)
  (reset! floor @guess)
  (swap! guess update-vals @ceiling))

(defn answer-case [input]
  "Switch-case depending on user input."
  (case input
    "h" (too-high)
    "l" (too-low)
    "c" false))

(defn valid? [input]
  "Checks whether the user input is valid by checking for membership in a
predefined list of valid values."
  (some #{input} '("l" "h" "c")))

(defn prompt-read []
  "Reads input from the user and returns it."
  (println "\nWas I:")
  (println "(h)igh")
  (println "(l)ow")
  (println "(c)orrect")
  (print (format "Choose h, l or c: "))
  (flush)
  (read-line)
  )

(defn -main []
  "Main input loop. Prints the welcome banner once, then enters into the loop."
  (println banner)
  (loop []
    (printf "%d). My guess is: %d\n" @counter @guess)
    ;; If user input was a valid value, then let i equal that value. Otherwise,
    ;; recur.
    (if-let [i (valid? (prompt-read))]
      ;; If the user input was "c", then print the success message. Otherwise,
      ;; recur.
      (if-not (answer-case i)
        (do (printf "\nGot it, and it only took %d tries!\n" @counter) (flush))
        (recur))
      (recur))))

Generated by matt using scpaste at Sun Sep 11 15:21:17 2011. HADT. (original)