git.s-ol.nu electric-sudoku / 0c7c674
support multiple games in parallel s-ol 6 months ago
2 changed file(s) with 89 addition(s) and 43 deletion(s). Raw diff Collapse all Expand all
55 font-family: sans-serif;
66 font-weight: 400;
77
8 width: min-content;
8 width: 31.1rem;
99 margin: auto;
1010 padding: 1rem 2rem;
1111
1818 grid-template-rows: repeat(3, 3.2rem) 0.15rem repeat(3, 3.2rem) 0.15rem repeat(3, 3.2rem);
1919 width: fit-content;
2020
21 gap: 0.1em;
21 gap: 0.1rem;
2222 place-items: center;
2323
24 padding: 0.5em;
25 margin: 1em 0;
24 padding: 0.5rem;
25 margin: 1rem 0;
2626 background: #999;
2727 }
2828
4040 ;;;;;;;;;;;;;
4141
4242 ; dynamic def for sharing state between server/client
43 (e/def state)
43 (e/def ^:dynamic state)
44 (e/def ^:dynamic code)
4445
4546 #?(:clj (defn make-atom [init-val]
4647 (let [state-file-path (System/getenv "SUDOKU_STATE_FILE")]
5354 (atom init-val)))))
5455
5556 ; 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))
5761
5862
5963 ;;; view stuff
8690 (e/fn [e]
8791 (cond
8892 (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
9195 (string/starts-with? (.-code e) "Digit")
9296 (let [n (-> e .-code last int)]
9397 (when (<= 1 n 9)
9498 (.preventDefault e)
9599 (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)))))))))))))
98102
99103 (e/defn Keyboard [pos]
100104 (let [v (get-in state pos)
107111
108112 (e/for [n (range 1 10)]
109113 (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)))))
113117 (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 #{}))))
115119 (dom/props {:class "clear"})
116120 (dom/text "clear"))
117121
118122 (ui/button
119123 (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)))
121125 (swap! !notes not))
122126 (dom/props {:class ["notes" (when notes "notes-active")]})
123127 (dom/text "notes")))))
124128
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
125190 (e/defn App []
126191 (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.)))))))