aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2023-02-23 16:28:43 +0000
committers-ol <s+removethis@s-ol.nu>2023-02-23 16:36:00 +0000
commitfc3eec14905b5832baae2f1fe25a0f505367f296 (patch)
treea24e709a4cc1e5b79a0136bcc1b5639490d84b8c /src
downloadelectric-sudoku-fc3eec14905b5832baae2f1fe25a0f505367f296.tar.gz
electric-sudoku-fc3eec14905b5832baae2f1fe25a0f505367f296.zip
initial commit
Diffstat (limited to 'src')
-rw-r--r--src/app/sudoku.cljc154
-rw-r--r--src/logback.xml16
-rw-r--r--src/user.clj28
-rw-r--r--src/user.cljs22
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))