diff --git a/deps.edn b/deps.edn index c21aef5..7deac6d 100644 --- a/deps.edn +++ b/deps.edn @@ -6,6 +6,8 @@ :git/sha "1e15f0f6a129ef7512351efc65f7475209d8cc4c"} #_{:local/root "../babashka/sci"} reagent/reagent {:mvn/version "1.1.1"} + no.cjohansen/replicant {:mvn/version "2025.02.02"} + re-frame/re-frame {:mvn/version "1.3.0"} cljsjs/react {:mvn/version "18.2.0-1"} cljsjs/react-dom {:mvn/version "18.2.0-1"} diff --git a/resources/public/cljs/replicant-tictactoe/core.cljs b/resources/public/cljs/replicant-tictactoe/core.cljs new file mode 100644 index 0000000..2897329 --- /dev/null +++ b/resources/public/cljs/replicant-tictactoe/core.cljs @@ -0,0 +1,33 @@ +;; COPIED FROM https://github.com/cjohansen/replicant-tic-tac-toe/blob/7a33fb12f0cd6658b2f555ff673dee031d4aa921/src/tic_tac_toe/core.cljs + +(ns replicant-tictactoe.core + (:require [replicant.dom :as r] + [replicant-tictactoe.game :as game] + [replicant-tictactoe.ui :as ui])) + +(defn start-new-game [store] + (reset! store (game/create-game {:size 3}))) + +(defn main [] + ;; Set up the atom + (let [store (atom nil) + el (js/document.getElementById "app")] + + ;; Globally handle DOM events + (r/set-dispatch! + (fn [_ [action & args]] + (case action + :tic (apply swap! store game/tic args) + :reset (start-new-game store)))) + + ;; Render on every change + (add-watch store ::render + (fn [_ _ _ game] + (->> (ui/game->ui-data game) + ui/render-game + (r/render el)))) + + ;; Trigger the first render by initializing the game. + (start-new-game store))) + +(main) \ No newline at end of file diff --git a/resources/public/cljs/replicant-tictactoe/game.cljs b/resources/public/cljs/replicant-tictactoe/game.cljs new file mode 100644 index 0000000..3f62d7f --- /dev/null +++ b/resources/public/cljs/replicant-tictactoe/game.cljs @@ -0,0 +1,41 @@ +;; COPIED FROM https://github.com/cjohansen/replicant-tic-tac-toe/blob/7a33fb12f0cd6658b2f555ff673dee031d4aa921/src/tic_tac_toe/game.cljs + +(ns replicant-tictactoe.game) + +(defn create-game [{:keys [size]}] + {:next-player :x + :size size}) + +(def next-player {:x :o, :o :x}) + +(defn winner? [tics path] + (when (= 1 (count (set (map tics path)))) + path)) + +(defn get-winning-path [{:keys [size tics]} y x] + (or (winner? tics (mapv #(vector y %) (range 0 size))) + (winner? tics (mapv #(vector % x) (range 0 size))) + (when (= y x) + (winner? tics (mapv #(vector % %) (range 0 size)))))) + +(defn maybe-conclude [game y x] + (if-let [path (get-winning-path game y x)] + (-> (dissoc game :next-player) + (assoc :over? true + :victory {:player (get-in game [:tics [y x]]) + :path path})) + (let [tie? (= (count (:tics game)) (* (:size game) (:size game)))] + (cond-> game + tie? (dissoc :next-player) + tie? (assoc :over? true))))) + +(defn tic [game y x] + (let [player (:next-player game)] + (if (or (get-in game [:tics [y x]]) + (<= (:size game) x) + (<= (:size game) y)) + game + (-> game + (assoc-in [:tics [y x]] player) + (assoc :next-player (next-player player)) + (maybe-conclude y x))))) \ No newline at end of file diff --git a/resources/public/cljs/replicant-tictactoe/style.css b/resources/public/cljs/replicant-tictactoe/style.css new file mode 100644 index 0000000..7ec052a --- /dev/null +++ b/resources/public/cljs/replicant-tictactoe/style.css @@ -0,0 +1,50 @@ +/* COPIED from https://github.com/cjohansen/replicant-tic-tac-toe/blob/7a33fb12f0cd6658b2f555ff673dee031d4aa921/resources/public/styles.css */ +.cell { + aspect-ratio: 1 / 1; + background: rgba(255, 255, 255, 0.8); + border-radius: 6%; + border: none; + display: block; + flex: 1 1 0%; + outline: none; + position: relative; + width: 100%; +} + +.cell-content { + opacity: 1; + transition: opacity 0.25s; +} + +.transparent { + opacity: 0; +} + +.cell-dim { + background: rgba(249, 249, 240, 0.3); +} + +.cell-highlight { + background: #fcfcf3; +} + +.clickable { + cursor: pointer; +} + +.board { + --gap: 0.75rem; + background: #833ab4; + background: linear-gradient(90deg, #833ab4 0%, #fd1d1d 50%, #fcb045 100%); + display: flex; + flex-direction: column; + gap: var(--gap); + padding: var(--gap); + max-width: 80vh; +} + +.row { + display: flex; + flex-direction: row; + gap: var(--gap); +} \ No newline at end of file diff --git a/resources/public/cljs/replicant-tictactoe/ui.cljs b/resources/public/cljs/replicant-tictactoe/ui.cljs new file mode 100644 index 0000000..c53dc65 --- /dev/null +++ b/resources/public/cljs/replicant-tictactoe/ui.cljs @@ -0,0 +1,75 @@ +;; COPIED FROM https://github.com/cjohansen/replicant-tic-tac-toe/blob/7a33fb12f0cd6658b2f555ff673dee031d4aa921/src/tic_tac_toe/ui.cljs + +(ns replicant-tictactoe.ui) + +(def mark-x + [:svg {:xmlns "http://www.w3.org/2000/svg" + :viewBox "0 -10 108 100"} + [:path + {:fill "currentColor" + :d "m1.753 69.19.36-1.08q.35-1.09 1.92-2.97 1.58-1.87 3.85-3.84 2.29-1.97 4.6-3.54 2.31-1.57 4.93-3.24 2.62-1.66 4.65-2.9 2.04-1.23 3.91-2.27 1.87-1.05 3.98-2.31 2.11-1.27 4.12-2.5 2.01-1.24 4.33-2.51l4.6-2.52q2.27-1.25 4.84-2.86 2.56-1.62 5.03-3.09 2.47-1.47 4.5-2.88 2.03-1.4 3.82-2.82t3.81-3.47q2.01-2.06 3.7-3.51 1.69-1.46 3.47-3.03 1.77-1.57 4.01-3.69 2.24-2.11 4.13-3.7 1.89-1.58 3.93-2.97 2.04-1.39 4.05-2.49 2.01-1.11 5.26-2.54 3.24-1.44 4.48-1.46 1.24-.01 2.42.37 1.18.37 2.18 1.11 1 .74 1.71 1.75.71 1.02 1.06 2.21.34 1.19.3 2.43-.05 1.24-.5 2.39-.44 1.16-1.23 2.12-.79.95-1.84 1.61-1.05.65-2.26.94-1.21.28-2.44.16-1.23-.11-2.37-.62-1.13-.5-2.04-1.34-.91-.84-1.51-1.93-.6-1.08-.81-2.3-.22-1.22-.04-2.45.18-1.23.75-2.33.56-1.1 1.45-1.97.89-.86 2.01-1.4 1.11-.54 2.35-.69 1.23-.15 2.44.1t2.29.87q1.07.63 1.88 1.56.82.93 1.29 2.08.48 1.14.56 2.38.09 1.24-.23 2.44-.31 1.19-.99 2.23-.68 1.04-1.66 1.8-.98.76-2.15 1.18l-1.16.41-2.28 1.17q-2.28 1.18-4.38 2.7-2.1 1.51-4.2 3.44-2.1 1.92-4.18 3.7-2.08 1.77-3.9 3.44-1.81 1.68-3.41 3.13-1.6 1.46-3.38 3.09-1.79 1.62-3.44 2.97-1.66 1.34-3.53 2.4-1.88 1.06-4.17 2.65-2.3 1.6-4.79 2.74-2.48 1.14-4.98 2.71-2.5 1.57-4.51 2.47-2.01.9-3.99 1.87-1.98.97-3.88 2.02-1.91 1.05-4.38 2.34-2.46 1.28-4.94 2.53-2.47 1.25-4.48 2.38-2 1.12-3.96 2.14-1.95 1.01-3.83 1.99-1.89.98-4.37 2.05-2.48 1.06-2.96 2.01-.48.96-.78 1.49-.3.53-.71.97-.41.44-.92.77-.51.34-1.09.54-.57.2-1.17.25-.6.06-1.2-.03t-1.16-.32q-.56-.23-1.05-.59-.49-.35-.89-.82-.39-.46-.65-1.01-.27-.54-.4-1.14-.13-.59-.12-1.19.02-.6.18-1.19l.16-.59Z"}] + [:path + {:fill "currentColor" + :d "m28.099 4.991 2.69 1.97q2.69 1.96 4.5 3.22 1.8 1.28 4.54 3.46 2.74 2.18 4.57 3.89t3.38 3.72q1.54 2.02 2.88 4.3 1.34 2.28 2.83 4.46 1.48 2.18 2.63 4.14 1.15 1.96 2.74 4.07 1.59 2.1 3.59 4.19 1.99 2.08 4.23 4.48 2.24 2.4 3.7 4.04 1.47 1.64 2.91 3.23 1.44 1.59 3.08 3.58 1.64 1.99 3.51 4.08 1.87 2.09 3.55 3.77 1.69 1.68 4.1 3.51 2.42 1.83 3.9 2.58 1.48.74 2.14 1.34.66.6 1.15 1.33.5.74.8 1.57.31.84.4 1.72.1.88-.02 1.76-.12.88-.44 1.71-.33.82-.84 1.55-.51.72-1.19 1.3-.67.58-1.46.98-.79.41-1.65.61-.87.2-1.76.19-.88-.01-1.74-.24-.86-.22-1.64-.64-.78-.42-2.27-2.72-1.48-2.3-1.52-3.49-.03-1.19.31-2.33.35-1.14 1.04-2.11.69-.97 1.66-1.67.96-.7 2.1-1.05 1.14-.35 2.33-.32 1.19.02 2.31.43t2.05 1.15q.93.75 1.58 1.75.64 1 .93 2.15.29 1.16.2 2.35-.09 1.18-.56 2.28-.47 1.1-1.26 1.99-.79.88-1.83 1.47t-2.2.82q-1.17.23-2.35.07-1.19-.16-2.25-.68-1.07-.53-1.92-1.37-.84-.84-1.37-1.9-.54-1.07-.7-2.25-.17-1.18.06-2.35.22-1.17.8-2.21.58-1.04 1.47-1.84.88-.79 1.98-1.27 1.09-.47 2.28-.57 1.18-.1 2.34.18 1.16.29 2.16.93 1.01.63 1.76 1.56.74.93-.33-.26-1.07-1.18-.41-.58.66.59 1.15 1.33.5.74.8 1.57.31.83.4 1.72.1.88-.02 1.76-.12.88-.44 1.7-.33.83-.84 1.55-.51.73-1.19 1.31-.67.58-1.46.98-.79.41-1.65.61-.87.2-1.75.19-.89-.01-1.75-.24-.86-.22-1.64-.64-.78-.42-2.73-1.57-1.95-1.14-4.26-2.95-2.31-1.8-3.87-3.43-1.57-1.62-3.17-3.29-1.6-1.66-3.55-4.05-1.95-2.39-3.33-4.15-1.39-1.76-2.77-3.4-1.38-1.64-3.07-3.56-1.7-1.91-3.91-4.13-2.2-2.22-3.74-4.1-1.54-1.88-2.79-3.75-1.24-1.87-2.4-4.33t-2.39-4.46q-1.23-2.01-2.4-4.59-1.17-2.59-2.53-5.01-1.36-2.43-3.35-4.44-1.99-2.02-4.52-4.27-2.54-2.25-5.33-4.04-2.81-1.79-3.28-2.21-.47-.41-.83-.92-.35-.51-.58-1.1-.22-.58-.3-1.2-.08-.62-.01-1.23.08-.62.29-1.21.22-.58.58-1.1.35-.51.81-.93.47-.42 1.02-.71t1.16-.45q.61-.15 1.23-.15t1.22.14q.61.15 1.17.44l.55.28Z"}]]) + +(def mark-o + [:svg {:xmlns "http://www.w3.org/2000/svg" + :viewBox "0 0 114 114"} + [:path + {:fill "none" + :stroke "currentColor" + :stroke-linecap "round" + :stroke-width "6" + :d "M74.616 8.935c7.73 2.38 15.96 9.34 21.58 16.04 5.63 6.69 10.57 15.46 12.18 24.11 1.6 8.65.74 19.67-2.53 27.77-3.27 8.11-10.12 15.37-17.09 20.88-6.98 5.51-16.07 10.81-24.76 12.17-8.7 1.35-19.32-.76-27.42-4.06-8.1-3.29-15.73-8.93-21.21-15.73-5.48-6.81-10.32-16.5-11.67-25.09-1.35-8.6.19-18.39 3.57-26.51 3.38-8.11 9.99-16.6 16.71-22.19 6.72-5.59 13.95-10.52 23.63-11.36 9.68-.84 28.04 4.34 34.45 6.32 6.42 1.97 4.37 4.6 4.04 5.55m-48.33-9.69c7.65-3.32 19.78-3.63 28.63-2.01 8.86 1.63 17.85 5.89 24.49 11.76 6.64 5.87 12.7 15.08 15.37 23.48 2.67 8.41 2.5 18.4.65 26.95-1.85 8.54-5.98 17.59-11.77 24.34-5.78 6.74-14.56 13.05-22.93 16.11-8.37 3.06-18.75 4.19-27.29 2.25-8.54-1.93-17.37-7.89-23.96-13.87-6.59-5.97-12.89-13.58-15.57-21.96-2.69-8.39-2.31-19.94-.56-28.34 1.75-8.4 5.21-15.74 11.06-22.09 5.85-6.35 19.92-13.32 24.04-16.01 4.12-2.7.37-1.1.67-.16"}]]) + +(defn render-cell [{:keys [content on-click dim? highlight? clickable?]}] + [:button.cell + {:on {:click on-click} + :class (cond-> [] + dim? (conj "cell-dim") + highlight? (conj "cell-highlight") + clickable? (conj "clickable"))} + (when content + [:div.cell-content + {:replicant/mounting {:class "transparent"} + :replicant/unmounting {:class "transparent"}} + content])]) + +(defn render-board [{:keys [rows]}] + [:div.board + (for [row rows] + [:div.row + (for [cell row] + (render-cell cell))])]) + +(defn render-game [{:keys [board button]}] + [:div + (render-board board) + (when button + [:button {:on {:click (:on-click button)} + :style {:margin-top 20 + :font-size 20}} + (:text button)])]) + +(def player->mark + {:x mark-x + :o mark-o}) + +(defn game->ui-data [{:keys [size tics victory over?]}] + (let [highlight? (set (:path victory))] + {:button (when over? + {:text "Start over" + :on-click [:reset]}) + :board + {:rows + (for [y (range size)] + (for [x (range size)] + (if-let [player (get tics [y x])] + (let [victorious? (highlight? [y x])] + (cond-> {:content (player->mark player)} + victorious? (assoc :highlight? true) + (and over? (not victorious?)) (assoc :dim? true))) + (if over? + {:dim? true} + {:clickable? true + :on-click [:tic y x]}))))}})) \ No newline at end of file diff --git a/resources/public/replicant_tictactoe.html b/resources/public/replicant_tictactoe.html new file mode 100644 index 0000000..94da4c1 --- /dev/null +++ b/resources/public/replicant_tictactoe.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + +

Scittle tic-tac-toe

+

What is Scittle?

+

Read the main page for more details.

+

The game

+
+

The following source was loaded and interpreted + from cljs/replicant_tictactoe/core.cljs using the + script tag: +


+<script type="application/x-scittle" src="cljs/replicant-tictactoe/ui.cljs"></script>
+<script type="application/x-scittle" src="cljs/replicant-tictactoe/game.cljs"></script>
+<script type="application/x-scittle" src="cljs/replicant-tictactoe/core.cljs"></script>
+
+
+

+
+ + + diff --git a/shadow-cljs.edn b/shadow-cljs.edn index 73529a6..8040d70 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -24,6 +24,8 @@ :depends-on #{:scittle}} :scittle.reagent {:entries [scittle.reagent] :depends-on #{:scittle}} + :scittle.replicant {:entries [scittle.replicant] + :depends-on #{:scittle}} :scittle.re-frame {:entries [scittle.re-frame] :depends-on #{:scittle.reagent :scittle}} diff --git a/src/sci/configs/replicant/replicant_dom.cljs b/src/sci/configs/replicant/replicant_dom.cljs new file mode 100644 index 0000000..ea42f12 --- /dev/null +++ b/src/sci/configs/replicant/replicant_dom.cljs @@ -0,0 +1,15 @@ +(ns sci.configs.replicant.replicant-dom + (:require [replicant.dom :as rd] + [sci.core :as sci])) + +(def rdns (sci/create-ns 'replicant.dom nil)) + +(def replicant-dom-namespace + {'render (sci/copy-var rd/render rdns) + 'unmount (sci/copy-var rd/unmount rdns) + 'set-dispatch! (sci/copy-var rd/set-dispatch! rdns)}) + +(def namespaces {'replicant.dom replicant-dom-namespace}) + +(def config {:namespaces namespaces}) + diff --git a/src/scittle/replicant.cljs b/src/scittle/replicant.cljs new file mode 100644 index 0000000..327b451 --- /dev/null +++ b/src/scittle/replicant.cljs @@ -0,0 +1,8 @@ +(ns scittle.replicant + (:require + [sci.configs.replicant.replicant-dom :refer [replicant-dom-namespace]] + [scittle.core :as scittle])) + +(scittle/register-plugin! + ::replicant + {:namespaces {'replicant.dom replicant-dom-namespace}})