40 | 40 |
;;;;;;;;;;;;;
|
41 | 41 |
|
42 | 42 |
; dynamic def for sharing state between server/client
|
43 | |
(e/def state)
|
|
43 |
(e/def ^:dynamic state)
|
|
44 |
(e/def ^:dynamic code)
|
44 | 45 |
|
45 | 46 |
#?(:clj (defn make-atom [init-val]
|
46 | 47 |
(let [state-file-path (System/getenv "SUDOKU_STATE_FILE")]
|
|
53 | 54 |
(atom init-val)))))
|
54 | 55 |
|
55 | 56 |
; server-side atom for actual storage
|
56 | |
(def !state #?(:clj (make-atom (make-sudoku 64))))
|
|
57 |
(def !state #?(:clj (make-atom {})))
|
|
58 |
|
|
59 |
(defmacro swap-state! [& args]
|
|
60 |
`(swap! !state update-in [(e/client code)] ~@args))
|
57 | 61 |
|
58 | 62 |
|
59 | 63 |
;;; view stuff
|
|
86 | 90 |
(e/fn [e]
|
87 | 91 |
(cond
|
88 | 92 |
(contains? #{"Backspace" "Clear" "Delete"} (.-key e))
|
89 | |
(e/server (swap! !state update-in pos (constantly #{})))
|
90 | |
|
|
93 |
(e/server (swap-state! update-in pos (constantly #{})))
|
|
94 |
|
91 | 95 |
(string/starts-with? (.-code e) "Digit")
|
92 | 96 |
(let [n (-> e .-code last int)]
|
93 | 97 |
(when (<= 1 n 9)
|
94 | 98 |
(.preventDefault e)
|
95 | 99 |
(if (.-shiftKey e)
|
96 | |
(e/server (swap! !state update-in pos toggle-in-set n))
|
97 | |
(e/server (swap! !state update-in pos (constantly n)))))))))))))
|
|
100 |
(e/server (swap-state! update-in pos toggle-in-set n))
|
|
101 |
(e/server (swap-state! update-in pos (constantly n)))))))))))))
|
98 | 102 |
|
99 | 103 |
(e/defn Keyboard [pos]
|
100 | 104 |
(let [v (get-in state pos)
|
|
107 | 111 |
|
108 | 112 |
(e/for [n (range 1 10)]
|
109 | 113 |
(ui/button (e/fn []
|
110 | |
(if notes
|
111 | |
(e/server (swap! !state update-in pos toggle-in-set n))
|
112 | |
(e/server (swap! !state update-in pos (constantly n)))))
|
|
114 |
(e/server (if notes
|
|
115 |
(swap-state! update-in pos toggle-in-set n)
|
|
116 |
(swap-state! update-in pos (constantly n)))))
|
113 | 117 |
(dom/text n)))
|
114 | |
(ui/button (e/fn [] (e/server (swap! !state update-in pos (constantly #{}))))
|
|
118 |
(ui/button (e/fn [] (e/server (swap-state! update-in pos (constantly #{}))))
|
115 | 119 |
(dom/props {:class "clear"})
|
116 | 120 |
(dom/text "clear"))
|
117 | 121 |
|
118 | 122 |
(ui/button
|
119 | 123 |
(e/fn []
|
120 | |
(when-not notes (e/server (swap! !state update-in pos set-notes)))
|
|
124 |
(when-not notes (e/server (swap-state! update-in pos set-notes)))
|
121 | 125 |
(swap! !notes not))
|
122 | 126 |
(dom/props {:class ["notes" (when notes "notes-active")]})
|
123 | 127 |
(dom/text "notes")))))
|
124 | 128 |
|
|
129 |
(e/defn show-login []
|
|
130 |
(let [!password (atom "")
|
|
131 |
!out (atom nil)
|
|
132 |
out (e/watch !out)]
|
|
133 |
(if out
|
|
134 |
(dom/div
|
|
135 |
(dom/span
|
|
136 |
(dom/text "game code: ")
|
|
137 |
(dom/code (dom/text out))
|
|
138 |
(dom/text " "))
|
|
139 |
|
|
140 |
(ui/button
|
|
141 |
(e/fn [] (reset! !out nil))
|
|
142 |
(dom/text "leave")))
|
|
143 |
(dom/form
|
|
144 |
(dom/on "submit" (e/fn [e] (.preventDefault e)))
|
|
145 |
(dom/p (dom/text "enter a game code to join or create a game:"))
|
|
146 |
(ui/input (e/watch !password) (e/fn [v] (reset! !password v)))
|
|
147 |
(ui/button
|
|
148 |
(e/fn []
|
|
149 |
(when-not (empty? @!password)
|
|
150 |
(reset! !out @!password)))
|
|
151 |
(dom/text "start"))))
|
|
152 |
out))
|
|
153 |
|
|
154 |
(e/defn Game []
|
|
155 |
(let [!focus (atom nil)
|
|
156 |
focus (e/watch !focus)]
|
|
157 |
(dom/on "click"
|
|
158 |
(e/fn [_] (reset! !focus nil)))
|
|
159 |
|
|
160 |
(dom/p (dom/text "click a cell and use the keypad or use the number keys and shift to enter numbers and notes."))
|
|
161 |
(ui/button
|
|
162 |
(e/fn []
|
|
163 |
(when (.confirm js/window "Really clear this game and restart?")
|
|
164 |
(e/server (swap-state! (constantly nil)))))
|
|
165 |
|
|
166 |
(dom/text "regenerate"))
|
|
167 |
|
|
168 |
(dom/div
|
|
169 |
(dom/props {:class "sudoku"})
|
|
170 |
(e/for [y (range 9) x (range 9)]
|
|
171 |
(Cell. [x y] focus !focus)))
|
|
172 |
|
|
173 |
(when focus
|
|
174 |
(dom/hr)
|
|
175 |
(Keyboard. focus))))
|
|
176 |
|
|
177 |
(e/defn NewGame []
|
|
178 |
(let [!difficulty (atom 31)
|
|
179 |
difficulty (e/watch !difficulty)]
|
|
180 |
|
|
181 |
(dom/div
|
|
182 |
(dom/label (dom/props {:for "difficulty"}) (dom/text "difficulty:"))
|
|
183 |
(ui/range difficulty (e/fn [v] (reset! !difficulty v))
|
|
184 |
(dom/props {:id "difficulty" :min 1 :max 59})))
|
|
185 |
|
|
186 |
(ui/button (e/fn []
|
|
187 |
(e/server (swap-state! (constantly (make-sudoku difficulty)))))
|
|
188 |
(dom/text "generate"))))
|
|
189 |
|
125 | 190 |
(e/defn App []
|
126 | 191 |
(e/client
|
127 | |
(binding [state (e/server (e/watch !state))]
|
128 | |
(dom/link (dom/props {:rel :stylesheet :href "/app.css"}))
|
129 | |
(dom/h1 (dom/text "minimal sudoku game"))
|
130 | |
(dom/p (dom/text "it's multiplayer, try two tabs!"))
|
131 | |
(dom/p (dom/text "click a cell and use the keypad or use the number keys and shift to enter numbers and notes."))
|
132 | |
|
133 | |
(let [!difficulty (atom 31)
|
134 | |
difficulty (e/watch !difficulty)]
|
135 | |
(dom/div
|
136 | |
(dom/label (dom/props {:for "difficulty"}) (dom/text "difficulty"))
|
137 | |
(ui/range difficulty (e/fn [v] (reset! !difficulty v))
|
138 | |
(dom/props {:id "difficulty" :min 1 :max 59}))
|
139 | |
(ui/button (e/fn [] (e/server (reset! !state (make-sudoku difficulty))))
|
140 | |
(dom/text "regenerate"))))
|
141 | |
|
142 | |
(let [!focus (atom nil)
|
143 | |
focus (e/watch !focus)]
|
144 | |
(dom/on "click"
|
145 | |
(e/fn [_] (reset! !focus nil)))
|
146 | |
|
147 | |
(dom/div
|
148 | |
(dom/props {:class "sudoku"})
|
149 | |
(e/for [y (range 9) x (range 9)]
|
150 | |
(Cell. [x y] focus !focus)))
|
151 | |
|
152 | |
(when focus
|
153 | |
(dom/hr)
|
154 | |
(Keyboard. focus))))))
|
|
192 |
(dom/link (dom/props {:rel :stylesheet :href "/app.css"}))
|
|
193 |
(dom/h1 (dom/text "multiplayer sudoku game"))
|
|
194 |
(let [game-code (show-login.)]
|
|
195 |
(when game-code
|
|
196 |
(binding [state (e/server (get (e/watch !state) game-code))
|
|
197 |
code game-code]
|
|
198 |
(if state
|
|
199 |
(Game.)
|
|
200 |
(NewGame.)))))))
|