(ns glimmer-gfx.tictactoe "Tic-tac-toe on the gfx backend: a 3x3 grid of buttons and one pure rule. The board is a vector of 9 cells, each :x, :o or nil. You are X; O replies with the first free square, which is dumb on purpose -- the point here is that a game is just another glimmer component." (:require [glimmer.ratom :as ra] [glimmer.core :as ui] [glimmer-gfx.core])) (def ^:private lines [[0 1 2] [3 4 5] [6 7 8] [0 3 6] [1 4 7] [2 5 8] [0 4 8] [2 4 6]]) (defn winner "The mark occupying a whole line, or nil." [board] (some (fn [l] (let [[a b c] (map board l)] (when (and a (= a b c)) a))) lines)) (defn reply "O's move: the first empty square. ponytail: no minimax, add one when losing to this stops being funny." [board] (first (remove #(board %) (range 9)))) (defn play "X takes `i`, then O replies -- unless the game is already over." [board i] (if (or (board i) (winner board)) board (let [b (assoc board i :x)] (if-let [o (and (not (winner b)) (reply b))] (assoc b o :o) b)))) (def ^:private empty-board (vec (repeat 9 nil))) (def state (ra/atom empty-board)) (defn- status [board] (case (winner board) :x "You win" :o "O wins" (if (every? some? board) "Draw" "Your turn"))) (defn- cell [board i] [:button {:label (case (board i) :x "X" :o "O" " ") :kind (when (= :x (board i)) :primary) :on-click #(ra/swap! state play i)}]) (defn app [] (let [board (ra/deref state)] [:page {:max-width 300} [:card {:spacing 8} [:title {:label "Tic-tac-toe"}] [:label {:label (status board)}] (into [:vbox {:spacing 6}] (for [row (partition 3 (range 9))] (into [:hbox {:spacing 6}] (for [i row] (cell board i))))) [:spacer {:size 4}] [:button {:label "new game" :on-click #(ra/reset! state empty-board)}]]])) (defn -main [& _] (ui/run app :title "tic-tac-toe" :width 300 :height 340))