nandi/jolt-nativepublic Fork 0
6a3304ddddcc7d3e9486b470fea5933a1f81f8e8
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

tests.clj · 222 lines · 10.5 KBClojure Blame HistoryRaw
Paint glimmer with nothing under it f554a01 nandi 12d ago1(ns glimmer-gfx.tests
2 "Every check here runs headless: the backend measures, places, paints and
3 dispatches into a plain framebuffer, so none of it needs a window or a
4 display. `jolt test`."
5 (:require [glimmer.ratom :as ra]
6 [glimmer.core :as gui]
7 [glimmer.backend :as backend]
8 [glimmer-gfx.core :as gfx]
Play three games on the gfx backend e98a184 nandi 11d ago9 [glimmer-gfx.tictactoe :as ttt]
10 [glimmer-gfx.maze :as maze]
11 [glimmer-gfx.asteroids :as ast]
Paint glimmer with nothing under it f554a01 nandi 12d ago12 [glimmer-gfx.raster :as r]
13 [clojure.string :as str]))
14
15;; --- the rasterizer ----------------------------------------------------------
16
17(defn- check-raster! []
18 (let [b (r/buf 200 120)
19 at (fn [x y] (aget ^ints (:px b) (+ (* y (:w b)) x)))]
20 (r/clear b 0)
21 (r/rect! b 10 10 5 5 0xff0000)
22 (assert (= 0xff0000 (at 12 12)))
23 (assert (= 0 (at 9 9)) "a rect must not bleed past its edge")
24 (r/rect! b -5 -5 3 3 0x00ff00) ; offscreen, must not throw
25 (r/line! b 0 0 199 119 0x0000ff)
26 (assert (= 0x0000ff (at 0 0)))
27 (r/clear b 0)
28 (r/text! b 0 0 "A" 0xffffff 1)
29 (assert (some #(= 0xffffff (at % 0)) (range 3)) "the glyph drew nothing")))
30
31;; --- a component, through the real reconciler --------------------------------
32
33(def ^:private clicks (ra/atom 0))
34
35(defn- counter []
36 [:card {}
37 [:title {:label "Counter"}]
38 [:label {:label (str "Count: " (ra/deref clicks))}]
39 [:hbox {:spacing 8}
40 [:button {:label "-1" :on-click #(ra/swap! clicks dec)}]
41 [:button {:label "+1" :kind :primary :on-click #(ra/swap! clicks inc)}]]])
42
43(defn- walk [n] (cons n (mapcat walk (:children @n))))
44(defn- tagged [root tag] (first (filter #(= tag (:tag @%)) (walk root))))
45(defn- buttons [root] (filter #(= :button (:tag @%)) (walk root)))
46(defn- labelled? [root prefix]
47 (some #(and (= :label (:tag @%))
48 (str/starts-with? (str (:label (:props @%))) prefix))
49 (walk root)))
50(defn- centre [n]
51 (let [[x y w h] (:rect @n)] [(+ x (quot w 2)) (+ y (quot h 2))]))
52
53(defn- check-tree! [root]
54 (assert (tagged root :card) "no card in the tree")
55 (assert (= 2 (count (buttons root))) "expected two buttons")
56 (assert (labelled? root "Count: 0") "the label did not render the ratom"))
57
58(defn- check-layout! [root]
59 (let [[cx cy] (:rect @(tagged root :card))
60 [tx ty] (:rect @(tagged root :title))
61 [_ by bw bh] (:rect @(first (buttons root)))]
62 (assert (= [0 0] [cx cy]) "the card should sit at the origin")
63 (assert (= [10 10] [tx ty]) "a card must inset its child by PAD")
64 (assert (and (pos? bw) (= 26 bh)) "button box is the wrong size")
65 (assert (> by ty) "the title must be placed above the buttons"))
66 ;; an hbox lays its children left to right, with the gap it was given
67 (let [[b1 b2] (map #(:rect @%) (buttons root))]
68 (assert (= (second b1) (second b2)) "hbox children must share a baseline")
69 (assert (= (+ (first b1) (nth b1 2) 8) (first b2)) "hbox gap not honoured")))
70
71(defn- check-clicks! [root buf]
72 (let [plus (second (buttons root))
73 p (centre plus)
74 state (atom {})
75 before (ra/deref clicks)]
76 (gfx/render-once root buf 400 {:mouse p :down? true :released? false} state)
77 (assert (= before (ra/deref clicks)) "a press alone must not fire the handler")
78 (gfx/render-once root buf 400 {:mouse p :down? false :released? true} state)
79 (assert (= (inc before) (ra/deref clicks)) "the click did not fire")
80 (gfx/render-once root buf 400)
81 (assert (labelled? root (str "Count: " (inc before)))
82 "the reactive re-render never reached the tree")
83
84 ;; press, then release somewhere else: not a click
85 (gfx/render-once root buf 400 {:mouse p :down? true :released? false} state)
86 (gfx/render-once root buf 400 {:mouse [399 239] :down? false :released? true} state)
87 (assert (= (inc before) (ra/deref clicks)) "releasing off the button must not fire")))
88
89(defn- check-painted! [root buf]
90 (gfx/render-once root buf 400)
91 (let [at (fn [x y] (aget ^ints (:px buf) (+ (* y (:w buf)) x)))
92 [x y] (:rect @(tagged root :card))
93 row (+ 4 (second (:rect @(second (buttons root)))))]
94 (assert (= (:card gfx/theme) (at (+ x 2) (+ y 2))) "card background not painted")
95 (assert (some #(= (:accent gfx/theme) (at % row)) (range 400))
96 "the primary button is not painted in the accent colour")))
97
98(defn- check-scheduled! [root buf]
99 ;; With a loop running, glimmer refuses to re-render inline and posts the work
100 ;; through :schedule instead. That is the path a real window takes, and a
101 ;; different one from every check above.
102 (reset! backend/loop-running? true)
103 (try
104 (let [before (ra/deref clicks)
105 p (centre (second (buttons root)))
106 state (atom {})]
107 (gfx/render-once root buf 400 {:mouse p :down? true :released? false} state)
108 (gfx/render-once root buf 400 {:mouse p :down? false :released? true} state)
109 (assert (= (inc before) (ra/deref clicks)) "the handler did not fire under a loop")
110 (gfx/render-once root buf 400) ; the next frame drains the queue
111 (assert (labelled? root (str "Count: " (inc before)))
112 "the scheduled re-render never reached the tree"))
113 (finally (reset! backend/loop-running? false))))
114
Play three games on the gfx backend e98a184 nandi 11d ago115;; --- asteroids ----------------------------------------------------------------
116
117(defn- check-asteroids! []
118 (assert (= [5.0 5.0] (ast/wrap [645.0 485.0])) "the world must wrap")
119 (assert (ast/hit? [10 10] [12 12] 5))
120 (assert (not (ast/hit? [10 10] [200 200] 5)))
121 (assert (ast/hit? [5 5] [635 475] 20) "a hit across the seam still counts")
122 (let [g (ast/new-game)
123 ;; thrust moves the ship; nothing else does
124 drifting (nth (iterate #(ast/step % #{0x77}) g) 5)]
125 (assert (not= (get-in g [:ship :pos]) (get-in drifting [:ship :pos]))
126 "thrust did not move the ship")
127 (assert (:thrusting (:ship drifting)))
128 ;; turning is a rotation, not a translation
129 (let [turned (ast/step g #{0xff53})]
130 (assert (pos? (get-in turned [:ship :angle])) "right did not turn"))
131 ;; holding space auto-fires, but paced -- not one bullet per frame
132 (let [a (ast/step g #{0x20})
133 b (ast/step a #{0x20})
134 held (nth (iterate #(ast/step % #{0x20}) g) 20)]
135 (assert (= 1 (count (:bullets a))) "space did not fire")
136 (assert (= 1 (count (:bullets b))) "the cooldown did not hold the trigger")
137 (assert (< 1 (count (:bullets held)) 20) "auto-fire is off, or unpaced"))
138 ;; a bullet placed on a rock splits it and scores
139 (let [rk (assoc (first (:rocks g)) :pos [100 100] :vel [0 0] :size 3)
140 st (ast/step (assoc g :rocks [rk]
141 :bullets [{:pos [100 100] :vel [0 0] :life 30}])
142 #{})]
143 (assert (= 2 (count (:rocks st))) "the rock did not split in two")
144 (assert (every? #(= 2 (:size %)) (:rocks st)) "the pieces are the wrong size")
145 (assert (= 10 (:score st)) "no score for the hit")
146 (assert (empty? (:bullets st)) "the bullet survived its own hit"))
147 ;; the smallest rock leaves nothing behind, and an empty field respawns
148 (let [rk (assoc (first (:rocks g)) :pos [100 100] :vel [0 0] :size 1)
149 st (ast/step (assoc g :rocks [rk]
150 :bullets [{:pos [100 100] :vel [0 0] :life 30}])
151 #{})]
152 (assert (seq (:rocks st)) "an empty field should respawn")
153 (assert (every? #(= 3 (:size %)) (:rocks st)) "respawned rocks are not full size"))
154 ;; a rock on top of the ship ends it
155 (let [rk (assoc (first (:rocks g)) :pos (get-in g [:ship :pos]) :vel [0 0])]
156 (assert (:lost? (ast/step (assoc g :rocks [rk]) #{}))))
157 ;; and it all paints: poly! into a real framebuffer, nothing thrown
158 (let [buf (r/buf 640 480)]
159 (@#'ast/draw! buf (nth (iterate #(ast/step % #{0x77 0x20}) g) 30))
160 (assert (some #(not= (:bg @#'ast/colours) %) (seq ^ints (:px buf)))
161 "the frame painted nothing"))
162 ;; bullets expire
163 (let [st (ast/step (assoc g :bullets [{:pos [10 10] :vel [0 0] :life 1}]) #{})]
164 (assert (empty? (:bullets st)) "the bullet outlived its life"))))
165
166;; --- the maze -----------------------------------------------------------------
167
168(defn- check-maze! []
169 (let [level ["####" "#..#" "#..#" "####"]
170 C 24]
171 (assert (maze/wall? level 0 0))
172 (assert (not (maze/wall? level 1 1)))
173 (assert (maze/wall? level 9 9) "off the map must read as wall")
174 (assert (maze/free? level C C) "the open cell should be free")
175 (assert (not (maze/free? level (+ (* 2 C) 1) C))
176 "a box straddling the far wall is not free")
177 ;; walking into a wall costs the blocked axis only: this slides down it
178 (let [p [C C]]
179 (assert (= p (maze/move level p [-1 0])) "moved through a wall")
180 (assert (= p (maze/move level p [0 0])))
181 (let [[x y] (maze/move level [(* 2 C) C] [1 1])]
182 (assert (= x (* 2 C)) "should have been stopped by the wall on the right")
183 (assert (> y C) "but should still have slid downward")))
184 ;; the dot under the player's centre is the one that goes
185 (assert (= #{[2 1]} (maze/eat #{[1 1] [2 1]} [C C])))))
186
187;; --- the game ----------------------------------------------------------------
188
189(defn- check-tictactoe! []
190 (let [empty (vec (repeat 9 nil))]
191 (assert (nil? (ttt/winner empty)))
192 (assert (= :x (ttt/winner [:x :x :x nil nil nil nil nil nil])))
193 (assert (= :o (ttt/winner [:x :x nil nil nil nil :o :o :o])))
194 (assert (nil? (ttt/winner [:x :x :o nil nil nil nil nil nil])))
195 ;; X plays 4, O answers in the first free square
196 (let [b (ttt/play empty 4)]
197 (assert (= :x (b 4)) "X did not take the square it clicked")
198 (assert (= :o (b 0)) "O did not reply"))
199 ;; an occupied square, and a finished game, are both no-ops
200 (assert (= (ttt/play empty 4) (ttt/play (ttt/play empty 4) 4)))
201 (let [won [:x :x :x :o :o nil nil nil nil]]
202 (assert (= won (ttt/play won 5)) "the board moved after the game was over"))
203 ;; a winning move ends it: no reply gets appended
204 (let [b (ttt/play [:x :x nil :o :o nil nil nil nil] 2)]
205 (assert (= :x (ttt/winner b)))
206 (assert (nil? (b 5)) "O replied to a move that had already won"))))
207
Paint glimmer with nothing under it f554a01 nandi 12d ago208(defn -main [& _]
209 (check-raster!)
Play three games on the gfx backend e98a184 nandi 11d ago210 (check-tictactoe!)
211 (check-maze!)
212 (check-asteroids!)
Paint glimmer with nothing under it f554a01 nandi 12d ago213 (let [root (gfx/root-node)
214 buf (r/buf 400 240)]
215 (gui/mount root :vbox [counter])
216 (gfx/render-once root buf 400)
217 (check-tree! root)
218 (check-layout! root)
219 (check-clicks! root buf)
220 (check-painted! root buf)
221 (check-scheduled! root buf))
222 (println "ok"))