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"