diff options
| author | s-ol <s+removethis@s-ol.nu> | 2023-02-23 16:28:43 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2023-02-23 16:36:00 +0000 |
| commit | fc3eec14905b5832baae2f1fe25a0f505367f296 (patch) | |
| tree | a24e709a4cc1e5b79a0136bcc1b5639490d84b8c /src | |
| download | electric-sudoku-fc3eec14905b5832baae2f1fe25a0f505367f296.tar.gz electric-sudoku-fc3eec14905b5832baae2f1fe25a0f505367f296.zip | |
initial commit
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/sudoku.cljc | 154 | ||||
| -rw-r--r-- | src/logback.xml | 16 | ||||
| -rw-r--r-- | src/user.clj | 28 | ||||
| -rw-r--r-- | src/user.cljs | 22 |
4 files changed, 220 insertions, 0 deletions
diff --git a/src/app/sudoku.cljc b/src/app/sudoku.cljc new file mode 100644 index 0000000..d975bec --- /dev/null +++ b/src/app/sudoku.cljc @@ -0,0 +1,154 @@ +(ns app.sudoku + #?(:cljs (:require-macros app.sudoku) + :clj (:import [de.sfuhrm.sudoku Creator])) + + (:require [hyperfiddle.electric :as e] + [hyperfiddle.electric-dom2 :as dom] + [hyperfiddle.electric-ui4 :as ui] + [clojure.string :as string])) + + +;;; logic + +(defn map2dv [f] + (mapv #(mapv (partial f %) (range 9)) (range 9))) + +(defn random-cell [x y] + (cond + (< (rand) 0.2) (int (rand 10)) + (< (rand) 0.3) #{1} + (< (rand) 0.5) #{1 2 3} + :else #{})) + +#?(:clj (defn make-sudoku [clear-count] + ; VERY_EASY: more than 50 given numbers, remove less than 31 numbers + ; EASY: 36-49 given numbers, remove 32-45 numbers + ; MEDIUM: 32-35 given numbers, remove 46-49 numbers + ; HARD: 28-31 given numbers, remove 50-53 numbers + ; EXPERT: 22-27 given numbers, remove 54-59 numbers + (let [matrix (Creator/createFull) + riddle (Creator/createRiddle matrix clear-count)] + (map2dv (fn [x y] + (if (.getWritable riddle y x) #{} + (str (.get riddle y x)))))))) + +(defn toggle-in-set [v n] + (cond + (not (set? v)) #{n} + (contains? v n) (disj v n) + :else (conj v n))) + +(defn set-notes [v] + (if (number? v) #{v} v)) + ; (and (not notes) (set? v) (= 1 (count v))) (first v) + +;;; app state + +; dynamic def for sharing state between server/client +(e/def state) + +; server-side atom for actual storage +(def !state #?(:clj (atom (make-sudoku 64)))) + +;;; view stuff + +(defn i->num [v] + (inc (+ v (quot v 3)))) + +(e/defn Cell [pos f !f] + (let [val (get-in state pos) + typ (cond + (string? val) :fixed + (number? val) :single + (empty? val) :empty + :else :multi)] + (dom/div (dom/props {:tabIndex (case typ :fixed -1 0) + :class ["cell" (str "cell-" (name typ)) (when (= f pos) "focused")] + :style {:grid-column (i->num (first pos)) + :grid-row (i->num (last pos))}}) + (dom/text (case typ + :fixed val + :single val + (string/join " " (sort val)))) + (when-not (= typ :fixed) + (dom/on "focus" + (e/fn [e] (reset! !f pos))) + (dom/on "click" + (e/fn [e] (.stopPropagation e))) + (dom/on "keydown" + (e/fn [e] + (cond + (contains? #{"Backspace" "Clear" "Delete"} (.-key e)) + (e/server (swap! !state update-in pos (constantly #{}))) + + (string/starts-with? (.-code e) "Digit") + (let [n (-> e .-code last int)] + (when (<= 1 n 9) + (.preventDefault e) + (if (.-shiftKey e) + (e/server (swap! !state update-in pos toggle-in-set n)) + (e/server (swap! !state update-in pos (constantly n))))))))))))) + +(e/defn Keyboard [pos] + (let [v (get-in state pos) + !notes (atom (and (set? v) (< 0 (count v)))) + notes (e/watch !notes)] + (dom/div + (dom/props {:class "keyboard"}) + (dom/on "click" + (e/fn [e] (.stopPropagation e))) + + (e/for [n (range 1 10)] + (ui/button (e/fn [] + (if notes + (e/server (swap! !state update-in pos toggle-in-set n)) + (e/server (swap! !state update-in pos (constantly n))))) + (dom/text n))) + (ui/button (e/fn [] (e/server (swap! !state update-in pos (constantly #{})))) + (dom/props {:class "clear"}) + (dom/text "clear")) + + (ui/button + (e/fn [] + (when-not notes (e/server (swap! !state update-in pos set-notes))) + (swap! !notes not)) + (dom/props {:class ["notes" (when notes "notes-active")]}) + (dom/text "notes"))))) + +(e/defn App [] + (e/client + (binding [state (e/server (e/watch !state))] + (dom/link (dom/props {:rel :stylesheet :href "/app.css"})) + (dom/h1 (dom/text "minimal sudoku game")) + (dom/p (dom/text "it's multiplayer, try two tabs!")) + (dom/p (dom/text "click a cell and use the keypad or use the number keys and shift to enter numbers and notes.")) + + (let [!difficulty (atom 31) + difficulty (e/watch !difficulty)] + (dom/div + (dom/label (dom/props {:for "difficulty"}) (dom/text "difficulty")) + (ui/range difficulty (e/fn [v] + (println v) + (reset! !difficulty v)) + ; (partial reset! !difficulty)) + (dom/props {:id "difficulty" + :min 1 + :max 59})) + (ui/button (e/fn [] (e/server + (println (type difficulty)) + (reset! !state (make-sudoku difficulty)))) + (dom/text "regenerate")))) + + (let [!focus (atom nil) + focus (e/watch !focus)] + (dom/on "click" + (e/fn [e] (reset! !focus nil))) + + (dom/div + (dom/props {:class "sudoku"}) + (e/for [y (range 9) x (range 9)] + (Cell. [x y] focus !focus))) + + (when focus + (dom/hr) + (Keyboard. focus)))))) diff --git a/src/logback.xml b/src/logback.xml new file mode 100644 index 0000000..4ea0edc --- /dev/null +++ b/src/logback.xml @@ -0,0 +1,16 @@ +<!-- Options: http://logback.qos.ch/manual/layouts.html --> +<configuration scan="true" scanPeriod="5 seconds"> + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%highlight(%-5level) %logger: %msg%n</pattern> + </encoder> + </appender> + + <root level="WARN"> + <appender-ref ref="STDOUT" /> + </root> + + <logger name="hyperfiddle" level="DEBUG" additivity="false"><appender-ref ref="STDOUT" /></logger> + <logger name="hyperfiddle.electric-impl.runtime" level="WARN" additivity="false"><appender-ref ref="STDOUT" /></logger> + <logger name="hyperfiddle.electric-jetty-adapter" level="WARN" additivity="false"><appender-ref ref="STDOUT" /></logger> +</configuration> diff --git a/src/user.clj b/src/user.clj new file mode 100644 index 0000000..9419d04 --- /dev/null +++ b/src/user.clj @@ -0,0 +1,28 @@ +(ns user) ; Must be ".clj" file, Clojure doesn't auto-load user.cljc + +; lazy load dev stuff - for faster REPL startup and cleaner dev classpath +(def start-electric-server! (delay @(requiring-resolve 'hyperfiddle.electric-jetty-server/start-server!))) +(def shadow-start! (delay @(requiring-resolve 'shadow.cljs.devtools.server/start!))) +(def shadow-watch (delay @(requiring-resolve 'shadow.cljs.devtools.api/watch))) + +(def electric-server-config + {:host "0.0.0.0", :port 8080, :resources-path "resources/public"}) + +(defn main [& args] + (println "Starting Electric compiler and server...") + (@shadow-start!) ; serves index.html as well + (@shadow-watch :dev) ; depends on shadow server + (def server (@start-electric-server! electric-server-config)) + (comment (.stop server))) + +; Userland Electric code is lazy loaded by the shadow build due to usage of +; :require-macros in all Electric source files. +; WARNING: make sure your REPL and shadow-cljs are sharing the same JVM! + +(comment + (main) ; Electric Clojure(JVM) REPL entrypoint + (hyperfiddle.rcf/enable!) ; turn on RCF after all transitive deps have loaded + (shadow.cljs.devtools.api/repl :dev) ; shadow server hosts the cljs repl + ; connect a second REPL instance to it + ; (DO NOT REUSE JVM REPL it will fail weirdly) + (type 1))
\ No newline at end of file diff --git a/src/user.cljs b/src/user.cljs new file mode 100644 index 0000000..d1b96fb --- /dev/null +++ b/src/user.cljs @@ -0,0 +1,22 @@ +(ns user ^:dev/always ; recompile (macroexpand) electric-main when any cljs src changes + (:require + app.sudoku + hyperfiddle.electric + hyperfiddle.electric-dom2)) + +(def electric-main + (hyperfiddle.electric/boot ; Electric macroexpansion - Clojure to signals compiler + (binding [hyperfiddle.electric-dom2/node js/document.body] + (app.sudoku/App.)))) + +(defonce reactor nil) + +(defn ^:dev/after-load ^:export start! [] + (assert (nil? reactor) "reactor already running") + (set! reactor (electric-main + #(js/console.log "Reactor success:" %) + #(js/console.error "Reactor failure:" %)))) + +(defn ^:dev/before-load stop! [] + (when reactor (reactor)) ; teardown + (set! reactor nil)) |
