diff --git a/resources/public/index.html b/resources/public/index.html index 95f5b57..9044477 100644 --- a/resources/public/index.html +++ b/resources/public/index.html @@ -2,13 +2,12 @@ - Simple CLJS + Functional Reactive Pong in Clojure using core.async -
Frame:
FPS:
State:
@@ -19,6 +18,6 @@ - + \ No newline at end of file diff --git a/src/cljs/frpong/core.cljs b/src/cljs/frpong/core.cljs index f15c6cc..e1f3c41 100644 --- a/src/cljs/frpong/core.cljs +++ b/src/cljs/frpong/core.cljs @@ -1,6 +1,6 @@ (ns frpong.core - (:require [frpong.helpers :as h] - [cljs.core.async :refer [! chan put!]] + (:require [frpong.helpers :as h :refer (mult tap)] + [cljs.core.async :refer [! chan put! close! sliding-buffer]] [domina :as dom :refer [log]]) (:require-macros [cljs.core.async.macros :as m :refer [go]] [frpong.core :refer (go-loop)])) @@ -34,163 +34,173 @@ (let [c (chan)] (go (loop [prev (! c t)) - (recur t)))) + (if-let [t (! c t)) + (recur t)) + (close! c)))) c)) -(defn ticker [[frames stop-frames] ticks game-state] - (let [ticks-in (tick-chan (h/diff-chan frames))] - (go-loop - (if-not (= :gameover (! ticks (! pos-out pos-next)))) +(defn ^:export frpong [] + (let [width 800 + height 400 + padding 5 + paddle-size 100 + paddle-width 10 + ball-radius 5 + init-pos [50 100] + init-vel [0.2 0.23] + paddle-step 20 + max-paddle-y (- height paddle-size) + ef-paddle-width (+ paddle-width padding) -(defn paddle-positioner [keycodes max-y movement pos-in pos-out] - (let [keys (h/key-chan keycodes)] - (go-loop - (let [pos (! pos-out - (condp = ( y (+ p-pos padding)) - (< y (- (+ p-pos paddle-size) padding)))) - detect-x-collision (fn [x y pl-pos pr-pos] - (cond - (< x ef-paddle-width) - (if (in-y-range? y pl-pos) :collision-left :gameover) - (> x (- width ef-paddle-width)) - (if (in-y-range? y pr-pos) :collision-right :gameover) - :else :moving)) - detect-y-collision (fn [y] (cond - (< y padding) :collision-left - (> y (- height padding)) :collision-right - :else :moving))] - (go-loop - (let [tick (! vel-out [vel-xn vel-yn]) - (>! game-state - (cond - (= bs-x :gameover) :gameover - (or (= bs-x :collision-left) (= bs-x :collision-right) - (= bs-y :collision-left) (= bs-y :collision-right)) :collision - :else :moving)))))) + (defn ticker [frames game-state ticks] + (let [ticks-in (tick-chan (h/diff-chan frames))] + (go (loop [] + (let [gs (! ticks (! pos-out pos-next)))) -(defn game-setup [{:keys [width height padding paddle-size] :as layout} paddle-movement - frames game-state pos vel pl-pos pr-pos] - (let [max-y (- height paddle-size) - ticks (chan) - [tick-pos tick-collsion tick-pl tick-pr] (h/multiplex ticks 4) - [pos-in pos-collision pos-render] (h/multiplex pos 3) - [vel-pos vel-collision] (h/multiplex vel 2) - [pl-pos-in pl-pos-collision pl-pos-render] (h/multiplex pl-pos 3) - [pr-pos-in pr-pos-collision pr-pos-render] (h/multiplex pr-pos 3) - [game-state-ticker game-state-render] (h/multiplex game-state 2)] - (ticker frames ticks game-state-ticker) - (ball-positioner tick-pos vel-pos pos-in pos) - (collision-detector layout tick-collsion game-state pos-collision vel-collision vel - (h/sustain pl-pos-collision tick-pl) - (h/sustain pr-pos-collision tick-pr)) - (paddle-positioner {83 :down 87 :up} max-y paddle-movement pl-pos-in pl-pos) - (paddle-positioner {38 :up 40 :down} max-y paddle-movement pr-pos-in pr-pos) - [game-state-render pos-render pl-pos-render pr-pos-render])) + (defn paddle-positioner [keycodes pos-in pos-out] + (let [keys (h/key-chan keycodes)] + (go-loop + (let [pos (! pos-out + (condp = ( y (+ paddle-y padding)) (< y (- (+ paddle-y paddle-size) padding)))) - (apply renderer - (game-setup layout paddle-movement frames game-state pos vel pl-pos pr-pos)))) + (defn detect-x-collision [x y lpaddle-y rpaddle-y] + (cond + (< x ef-paddle-width) + (if (in-y-range? y lpaddle-y) :collision-left :gameover) + (> x (- width ef-paddle-width)) + (if (in-y-range? y rpaddle-y) :collision-right :gameover) + :else :moving)) -(defn ^:export init [] - (let [layout {:width 800 - :height 400 - :padding 5 - :paddle-size 100 - :paddle-width 10 - :ball-radius 5} - init-vals {:init-pos [50 100] - :init-vel [0.3 0.33] - :paddle-movement 20} + (defn detect-y-collision [y] + (cond + (< y padding) :collision-left + (> y (- height padding)) :collision-right + :else :moving)) - [frames stop-frames] (h/frame-chan) - [frames-fps frames-count frames-game] (h/multiplex frames 3) - fps (h/map-chan #(/ 1000 %) (h/diff-chan frames-fps)) - frames-count (h/counting-chan frames-count) + (defn collision? [x-state y-state] + (or (= x-state :collision-left) (= x-state :collision-right) + (= y-state :collision-left) (= y-state :collision-right))) - fps-el (dom/by-id "fps") - frame-el (dom/by-id "frame")] - (doto (dom/by-id "canvas") - (dom/set-style! "width" (str (:width layout) "px")) - (dom/set-style! "height" (str (:height layout) "px"))) - (doto (dom/by-id "ball") - (dom/set-attr! "r" (:ball-radius layout)) - (dom/set-attr! "cx" (first (:init-pos init-vals))) - (dom/set-attr! "cy" (second (:init-pos init-vals)))) - (doseq [id ["lpaddle" "rpaddle"]] - (doto (dom/by-id id) - (dom/set-attr! "width" (:paddle-width layout)) - (dom/set-attr! "height" (:paddle-size layout)) - (dom/set-attr! "y" (/ (- (:height layout) (:paddle-size layout)) 2)))) - (dom/set-attr! (dom/by-id "lpaddle") "x" 0) - (dom/set-attr! (dom/by-id "rpaddle") "x" (- (:width layout) (:paddle-width layout))) + (defn adjust-vel [state v] + (condp = state + :collision-left (abs v) + :collision-right (- (abs v)) + :moving v + :gameover 0)) - (go-loop - (dom/set-text! fps-el (! vel-out [vel-xn vel-yn]) + (>! game-state + (cond + (= x-state :gameover) :gameover + (collision? x-state y-state) :collision + :else :moving))))) + + (defn renderer [ticks game-state pos pl-pos pr-pos] + (let [ball-el (dom/by-id "ball") + state-el (dom/by-id "state") + lpaddle-el (dom/by-id "lpaddle") + rpaddle-el (dom/by-id "rpaddle") + fps-el (dom/by-id "fps")] + (go-loop + (let [fps (/ 1000 (close? + m (reify + Mux + (muxch* [_] ch) + + Mult + (tap* [_ ch close?] (swap! cs assoc ch close?) nil) + (untap* [_ ch] (swap! cs dissoc ch) nil) + (untap-all* [_] (reset! cs {}) nil)) + dchan (chan 1) + dctr (atom nil) + done #(when (zero? (swap! dctr dec)) + (put! dchan true))] + (go (loop [] + (let [val (