Changes in paddle positioning

1. Changed one signal per paddle to one signal for both paddles
2. Made keyboard events sampled on ticks for smooth paddle movements
3. Made event channel independent and added close functionality to it
master
Abhinav Sarkar 2013-10-15 16:41:10 +05:30
parent 862ba34420
commit 028514ac0e
2 changed files with 104 additions and 93 deletions

View File

@ -1,5 +1,5 @@
(ns frpong.core (ns frpong.core
(:require [frpong.helpers :refer (mult tap diff-chan key-chan frame-chan sustain)] (:require [frpong.helpers :refer (mult tap diff-chan key-chan frame-chan event-chan)]
[cljs.core.async :refer [<! >! chan put! close! sliding-buffer]] [cljs.core.async :refer [<! >! chan put! close! sliding-buffer]]
[domina :as dom :refer [log]] [domina :as dom :refer [log]]
[domina.events :as ev]) [domina.events :as ev])
@ -52,18 +52,21 @@
(def *width* (- (.-scrollWidth (.-body js/document)) 20)) (def *width* (- (.-scrollWidth (.-body js/document)) 20))
(def *height* (- (.-scrollHeight (.-body js/document)) 125)) (def *height* (- (.-scrollHeight (.-body js/document)) 125))
(def *center* [(/ *width* 2 ) (/ *height* 2)])
(def *padding* 5) (def *padding* 5)
(def *paddle-size* 100) (def *paddle-size* 100)
(def *paddle-width* 10)
(def *ball-radius* 8) (def *ball-radius* 8)
(def *ball-speed* 0.5) (def *ball-speed* 0.5)
(def *center* [(/ *width* 2 ) (/ *height* 2)]) (def *init-vel-deg-lim* [35 55])
(def *paddle-step* 20) (def *perturb-factor* 0.02)
(def *paddle-width* 10)
(def *paddle-step* 5)
(def *max-paddle-y* (- *height* *paddle-size*)) (def *max-paddle-y* (- *height* *paddle-size*))
(def *ef-paddle-width* (+ *paddle-width* *padding*)) (def *ef-paddle-width* (+ *paddle-width* *padding*))
(def *init-paddle-pos* (/ (- *height* *paddle-size*) 2)) (def *init-paddle-pos* (/ (- *height* *paddle-size*) 2))
(def *init-vel-deg-lim* [35 55])
(def *perturb-factor* 0.02)
(def *G* 0.01) (def *G* 0.01)
(defn layout-game (defn layout-game
@ -96,20 +99,20 @@
"Sets up the game by creating the signals and setting up the components and starts the game." "Sets up the game by creating the signals and setting up the components and starts the game."
[] []
(let [frames (frame-chan) ;; frames signal (let [frames (frame-chan) ;; frames signal
keydowns (event-chan :keydown)
keyups (event-chan :keyup)
pos (chan 1) ;; ball position signal pos (chan 1) ;; ball position signal
vel (chan 1) ;; ball velocity signal vel (chan 1) ;; ball velocity signal
acc (chan 1) acc (chan 1)
pl-pos (chan 1) ;; paddle left position signal pd-pos (chan 1) ;; paddles position signal
pr-pos (chan 1) ;; paddle right position signal
game-state (chan 1) ;; game state signal, the state of the game and the current score game-state (chan 1) ;; game state signal, the state of the game and the current score
init-vel (initial-velocity)] init-vel (initial-velocity)]
(setup-components frames game-state pos vel acc pl-pos pr-pos) (setup-components frames keydowns keyups game-state pos vel acc pd-pos)
;; start the game by setting the initial values of the signals ;; start the game by setting the initial values of the signals
(put! pos *center*) (put! pos *center*)
(put! vel init-vel) (put! vel init-vel)
(put! pl-pos *init-paddle-pos*) (put! pd-pos [*init-paddle-pos* *init-paddle-pos*])
(put! pr-pos *init-paddle-pos*)
(put! game-state [:moving 0]))) (put! game-state [:moving 0])))
(defn start-on-space [] (defn start-on-space []
@ -119,47 +122,48 @@
"Creates mult(iple)s of the signals and sets up the components by connecting them using "Creates mult(iple)s of the signals and sets up the components by connecting them using
the signals tapped from the mults. the signals tapped from the mults.
The signals are taken as parameters." The signals are taken as parameters."
[frames game-state pos vel acc pl-pos pr-pos] [[frames stop-frames] [keydowns stop-keydowns] [keyups stop-keyups]
game-state pos vel acc pd-pos]
(let [ticks (chan) ;; ticks signal (let [ticks (chan) ;; ticks signal
ticks-m (mult ticks) ;; mult(iple)s for all signals ticks-m (mult ticks) ;; mult(iple)s for all signals
keydowns-m (mult keydowns)
keyups-m (mult keyups)
pos-m (mult pos) pos-m (mult pos)
vel-m (mult vel) vel-m (mult vel)
acc-m (mult acc) acc-m (mult acc)
pl-pos-m (mult pl-pos) pd-pos-m (mult pd-pos)
pr-pos-m (mult pr-pos)
game-state-m (mult game-state) game-state-m (mult game-state)
;; paddle position signals are not at the same rate as the rest so they need to be ;; calling this will stop the frames, keydowns and keyups signals and hence stop the game
;; sustained at the ticks rate stop-game #(do (stop-frames) (stop-keydowns) (stop-keyups))]
pl-pos-sust (sustain (tap pl-pos-m) (tap ticks-m (chan (sliding-buffer 1000))))
pr-pos-sust (sustain (tap pr-pos-m) (tap ticks-m (chan (sliding-buffer 1000))))]
;; set up the components by tapping into mults ;; set up the components by tapping into mults
(ticker frames (tap game-state-m) ticks) (ticker frames stop-game (tap game-state-m) ticks)
(gravitation (tap pos-m) acc) (gravitation (tap pos-m) acc)
(ball-positioner (tap ticks-m) (tap pos-m) (tap vel-m) (tap acc-m) pos) (ball-positioner (tap ticks-m) (tap pos-m) (tap vel-m) (tap acc-m) pos)
(paddle-positioner {83 :down 87 :up} (tap pl-pos-m) pl-pos) (paddle-positioner
(paddle-positioner {38 :up 40 :down} (tap pr-pos-m) pr-pos) (key-chan (tap keydowns-m) (tap keyups-m) (tap ticks-m) {83 :s 87 :w 38 :up 40 :down})
(tap pd-pos-m) pd-pos)
(collision-detector (tap ticks-m) (tap pos-m) (tap vel-m) (tap acc-m) (collision-detector (tap ticks-m) (tap pos-m) (tap vel-m) (tap acc-m)
pl-pos-sust pr-pos-sust (tap game-state-m) game-state vel) (tap pd-pos-m) (tap game-state-m) game-state vel)
(renderer (tap ticks-m) (tap game-state-m) (tap pos-m) (tap pl-pos-m) (tap pr-pos-m)))) (renderer (tap ticks-m) (tap game-state-m) (tap pos-m) (tap pd-pos-m))))
(defn ticker (defn ticker
"Ticker component. "Ticker component.
Converts `frames` signal to ticks and outputs them to the `ticks` signal Converts `frames` signal to ticks and outputs them to the `ticks` signal
as long as the `game-state` signal is not :gameover. Once the `game-state` signal is as long as the `game-state` signal is not :gameover. Once the `game-state` signal is
:gameover, stops the `frames` signal hence stopping the entire game. :gameover, stops the game by calling the `stop-game` function.
Each tick is the number of milliseconds since the last tick was generated." Each tick is the number of milliseconds since the last tick was generated."
[[frames stop-frames] game-state ticks] [frames stop-game game-state ticks]
(let [ticks-in (tick-chan (diff-chan frames))] (let [ticks-in (tick-chan (diff-chan frames))]
(go (loop [] (go (loop []
(let [[state _] (<! game-state)] (let [[state _] (<! game-state)]
(do (>! ticks (<! ticks-in)) (do (>! ticks (<! ticks-in))
(if-not (= :gameover state) (if-not (= :gameover state)
(recur) (recur)
(stop-frames)))))))) (stop-game))))))))
(defn gravity-acc [[x y]] (defn gravity-acc [[x y]]
(let [[cx cy] *center* (let [[cx cy] *center*
@ -198,14 +202,16 @@
Captures the keydown signal for the provides keycodes and calculates the next paddle Captures the keydown signal for the provides keycodes and calculates the next paddle
position using the current paddle position (from the `pos-in` signal) and keydown signal position using the current paddle position (from the `pos-in` signal) and keydown signal
and outputs it to the `pos-out` signal." and outputs it to the `pos-out` signal."
[keycodes pos-in pos-out] [keys pos-in pos-out]
(let [keys (key-chan keycodes)]
(go-loop (go-loop
(let [pos (<! pos-in)] (let [[lpos rpos] (<! pos-in)
(>! pos-out ks (<! keys)
(condp = (<! keys) move (fn [pos up down]
:up (max (- pos *paddle-step*) 0) (cond
:down (min (+ pos *paddle-step*) *max-paddle-y*))))))) (contains? ks up) (max (- pos *paddle-step*) 0)
(contains? ks down) (min (+ pos *paddle-step*) *max-paddle-y*)
:else pos))]
(>! pos-out [(move lpos :w :s) (move rpos :up :down)]))))
(defn in-y-range? [y paddle-y] (defn in-y-range? [y paddle-y]
(and (> y (- paddle-y *padding*)) (< y (+ paddle-y *paddle-size* *padding*)))) (and (> y (- paddle-y *padding*)) (< y (+ paddle-y *paddle-size* *padding*))))
@ -236,13 +242,13 @@
(defn perturb [v] (* v (+ 1 (/ (- (rand) 0.5) (/ 0.5 *perturb-factor*))))) (defn perturb [v] (* v (+ 1 (/ (- (rand) 0.5) (/ 0.5 *perturb-factor*)))))
(defn collision-detector [ticks pos vel-in acc pl-pos pr-pos game-state-in game-state vel-out] (defn collision-detector [ticks pos vel-in acc pd-pos game-state-in game-state vel-out]
"Collision Detector component. "Collision Detector component.
Detects the collision of the ball with the walls and the paddles and accordingly calculates Detects the collision of the ball with the walls and the paddles and accordingly calculates
and outputs the next ball velocity and next game state to the `vel-out` and `game-state` and outputs the next ball velocity and next game state to the `vel-out` and `game-state`
signals respectively. signals respectively.
Reads the current tick, ball position, ball velocity, ball acceleration, left and right paddle Reads the current tick, ball position, ball velocity, ball acceleration, left and right paddle
positions and game state from the `ticks`, `pos`, `vel-in`, `acc`, `pl-pos`, `pr-pos` positions and game state from the `ticks`, `pos`, `vel-in`, `acc`, `pd-pos`
and `game-state` signals respectively." and `game-state` signals respectively."
(go-loop (go-loop
@ -251,8 +257,7 @@
[vel-x vel-y] (<! vel-in) [vel-x vel-y] (<! vel-in)
[x y] (<! pos) [x y] (<! pos)
[gx gy] (<! acc) [gx gy] (<! acc)
lpaddle-y (<! pl-pos) [lpaddle-y rpaddle-y] (<! pd-pos)
rpaddle-y (<! pr-pos)
[_ score] (<! game-state-in) [_ score] (<! game-state-in)
;; calculate next position and detect collision ;; calculate next position and detect collision
@ -282,7 +287,7 @@
"Renderer component. "Renderer component.
Renders the ball and paddle positions on the browser. Also shows the game state and stats. Renders the ball and paddle positions on the browser. Also shows the game state and stats.
Reads the current values from the signals supplied as parameters." Reads the current values from the signals supplied as parameters."
[ticks game-state pos pl-pos pr-pos] [ticks game-state pos pd-pos]
(let [ball-el (dom/by-id "ball") (let [ball-el (dom/by-id "ball")
state-el (dom/by-id "state") state-el (dom/by-id "state")
score-el (dom/by-id "score") score-el (dom/by-id "score")
@ -292,6 +297,7 @@
(go (loop [fps-p nil state-p nil score-p nil] (go (loop [fps-p nil state-p nil score-p nil]
(let [fps (int (/ 1000 (<! ticks))) (let [fps (int (/ 1000 (<! ticks)))
[x y] (<! pos) [x y] (<! pos)
[lpaddle-y rpaddle-y] (<! pd-pos)
[state score] (<! game-state) [state score] (<! game-state)
state-text (condp = state state-text (condp = state
:moving "Playing" :moving "Playing"
@ -300,6 +306,8 @@
(doto ball-el (doto ball-el
(dom/set-attr! "cx" x) (dom/set-attr! "cx" x)
(dom/set-attr! "cy" y)) (dom/set-attr! "cy" y))
(dom/set-attr! lpaddle-el "y" lpaddle-y)
(dom/set-attr! rpaddle-el "y" rpaddle-y)
(when-not (= fps fps-p) (when-not (= fps fps-p)
(dom/set-text! fps-el fps)) (dom/set-text! fps-el fps))
(when-not (= state state-p) (when-not (= state state-p)
@ -309,11 +317,7 @@
(when (= state :gameover) (when (= state :gameover)
(do (dom/set-text! state-el "press <space> to restart") (do (dom/set-text! state-el "press <space> to restart")
(start-on-space))) (start-on-space)))
(recur fps state-text score)))) (recur fps state-text score))))))
(go-loop
(dom/set-attr! lpaddle-el "y" (<! pl-pos)))
(go-loop
(dom/set-attr! rpaddle-el "y" (<! pr-pos)))))
;; Everything is ready now. Layout the game and start it on pressing <space>! ;; Everything is ready now. Layout the game and start it on pressing <space>!
(defn ^:export frpong [] (defn ^:export frpong []

View File

@ -129,15 +129,22 @@
(defn event-chan [event-type] (defn event-chan [event-type]
(let [c (chan)] (let [c (chan)]
(ev/listen! event-type #(put! c %)) (ev/listen! event-type #(put! c %))
c)) [c #(do (ev/unlisten! event-type) (close! c))]))
(defn key-chan [keycodes] (defn key-chan [keydowns keyups sampler keycodes]
(let [source (event-chan :keydown) (let [c (chan)
c (chan)] ops {keydowns conj
(go-loop keyups disj}]
(let [kc (:keyCode (<! source))] (go (loop [keys #{}]
(when (contains? keycodes kc) (let [[v ch] (alts! [keydowns keyups sampler] :priority true)]
(>! c (keycodes kc))))) (if-not (nil? v)
(if (or (= ch keydowns) (= ch keyups))
(let [k (:keyCode v)]
(if (contains? keycodes k)
(recur ((ops ch) keys (keycodes k)))
(recur keys)))
(do (>! c keys) (recur keys)))
(close! c)))))
c)) c))
(defn frame-chan [] (defn frame-chan []