From a81638cb0cfd13bc3f6e50f61e9906833bd54d12 Mon Sep 17 00:00:00 2001
From: Jeroen van Dijk
Date: Thu, 13 Feb 2025 20:15:17 +0200
Subject: [PATCH] Add replicant tictactoe example
---
deps.edn | 2 +
.../public/cljs/replicant-tictactoe/core.cljs | 33 ++++++++
.../public/cljs/replicant-tictactoe/game.cljs | 41 ++++++++++
.../public/cljs/replicant-tictactoe/style.css | 50 +++++++++++++
.../public/cljs/replicant-tictactoe/ui.cljs | 75 +++++++++++++++++++
resources/public/replicant_tictactoe.html | 46 ++++++++++++
shadow-cljs.edn | 2 +
src/sci/configs/replicant/replicant_dom.cljs | 15 ++++
src/scittle/replicant.cljs | 8 ++
9 files changed, 272 insertions(+)
create mode 100644 resources/public/cljs/replicant-tictactoe/core.cljs
create mode 100644 resources/public/cljs/replicant-tictactoe/game.cljs
create mode 100644 resources/public/cljs/replicant-tictactoe/style.css
create mode 100644 resources/public/cljs/replicant-tictactoe/ui.cljs
create mode 100644 resources/public/replicant_tictactoe.html
create mode 100644 src/sci/configs/replicant/replicant_dom.cljs
create mode 100644 src/scittle/replicant.cljs
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}})