Tic-Tac-Toe

(def SIZE 3)
(def NUM_CELLS (* SIZE SIZE))
(def CELLS (range NUM_CELLS))
(def WIN_LINES
  (concat
    (for [N (range SIZE)] ; horiz
      (map #(+ (* N SIZE) %) (range SIZE)))
    (for [N (range SIZE)] ; vert
      (map #(+ N (* SIZE %)) (range SIZE)))
    (list                 ; main diag
      (map #(* (+ 1 SIZE) %) (range SIZE)))
    (list                 ; off diag
      (map #(* (- SIZE 1) (+ 1 %)) (range SIZE)))))
(defrecord Game [grid turn])
(defrecord ScoredGame [game score])
(defn new-game [] (->Game (vec CELLS) "X"))
(defn draw-game [game]
  (println (clojure.string/join
              "\n-----------------------------\n"
              (for [part (partition SIZE (get game :grid))]
                (str " "
                    (clojure.string/join " | " part)
                     " ")))))
(defn make-move [game cell]
  (let [player (get game :turn)
        grid (get game :grid)]
    (assoc game
       :grid (assoc grid cell player)
       :turn (if (= "X" player) "O" "X"))))
(defn did-win? [player grid]
  (loop [to-check WIN_LINES]
    (cond
      (empty? to-check) false
      (every? #(= player %) (map #(get grid %) (first to-check))) true
      :else (recur (rest to-check)))))

(defn open? [grid cell]
  (number? (get grid cell)))

(defn did-tie? [grid]
  (not-any? #(open? grid %) CELLS))
(defn score-game [game]
  (cond
    (did-win? "X" (get game :grid)) +999
    (did-win? "O" (get game :grid)) -999
    :else (rand)))
(declare minmax)
(declare maxmin)
(defn ^ScoredGame maxmin [^Game game ply]
  (let [score (score-game game)
        grid (get game :grid)]
    (cond
      (did-win? "X" grid) (->ScoredGame game score)
      (did-win? "O" grid) (->ScoredGame game score)
      (<= ply 0)          (->ScoredGame game score)
      (did-tie? grid)     (->ScoredGame game score)

      :else
      (apply max-key :score
         (for [cell CELLS :when (open? grid cell)]
           (let [move (make-move game cell)
                 worst (minmax move (- ply 1))
                 score (/ (get worst :score) 2)]
             (->ScoredGame move score)))))))
(defn ^ScoredGame minmax [^Game game ply]
  (let [score (score-game game)
        grid (get game :grid)]
    (cond
      (did-win? "X" grid) (->ScoredGame game score)
      (did-win? "O" grid) (->ScoredGame game score)
      (<= ply 0)          (->ScoredGame game score)
      (did-tie? grid)     (->ScoredGame game score)

      :else
      (apply min-key :score
         (for [cell CELLS :when (open? grid cell)]
           (let [move (make-move game cell)
                 worst (maxmin move (- ply 1))
                 score (/ (get worst :score) 2)]
             (->ScoredGame move score)))))))
(defn ai-move [game ply]
  (if (= "X" (get game :turn))
    (get (maxmin game ply) :game)
    (get (minmax game ply) :game)))
(defn play-game []
  (loop [game (new-game)]
    (draw-game game)
    (println)
    (let [score (score-game game)
          grid (get game :grid)]
      (cond
        (did-win? "X" grid) (println "X wins!")
        (did-win? "O" grid) (println "O wins!")
        (did-tie? grid) (println "It's a tie!")

        (= "X" (get game :turn))
        (recur (ai-move game 6))

        (= "O" (get game :turn))
        (recur (ai-move game 2))

        :else (println "this shouldn't happen" game)))))
;; (play-game)
"uncomment to play, ctrl+enter to go again"