1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
(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))
|