Added randomness in initial and bouncing velocity, made the game to start on keypress, added score counting

master
Abhinav Sarkar 2013-10-12 18:30:56 +05:30
parent 1205e93160
commit 05b4abf0ef
2 changed files with 92 additions and 57 deletions

View File

@ -10,6 +10,8 @@
<body> <body>
<div>FPS: <span id="fps"></span></div> <div>FPS: <span id="fps"></span></div>
<div>State: <span id="state"></span></div> <div>State: <span id="state"></span></div>
<div>Score: <span id="score"></span></div>
<div>Velocity: <span id="vel"></span></div>
<svg style="border: 1px black solid" id="canvas"> <svg style="border: 1px black solid" id="canvas">
<circle id="ball" /> <circle id="ball" />
<rect id="lpaddle" /> <rect id="lpaddle" />

View File

@ -1,7 +1,8 @@
(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 sustain)]
[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])
(:require-macros [cljs.core.async.macros :as m :refer [go]] (:require-macros [cljs.core.async.macros :as m :refer [go]]
[frpong.core :refer (go-loop)])) [frpong.core :refer (go-loop)]))
;; ;;
@ -28,8 +29,17 @@
;; | renderer | ;; | renderer |
;; +----------+ ;; +----------+
(defn rand [] (.random js/Math))
(defn abs [x] (.abs js/Math x)) (defn abs [x] (.abs js/Math x))
(def PI 3.141592653589793)
(defn deg->rad [deg] (* (/ deg 180) PI))
(defn cos [x] (.cos js/Math x))
(defn sin [x] (.sin js/Math x))
(defn tick-chan [frames] (defn tick-chan [frames]
(let [c (chan)] (let [c (chan)]
(go (go
@ -50,32 +60,17 @@
paddle-size 100 paddle-size 100
paddle-width 10 paddle-width 10
ball-radius 5 ball-radius 5
init-pos [50 100] ball-speed 0.33
init-vel [0.2 0.23] init-pos [(/ width 2) (/ height 2)]
init-vel (let [sgn #(if (< % 0.5) -1 1)
deg (+ 35 (* 20 (rand)))
rad (deg->rad deg)]
(map #(* ball-speed %)
[(* (sgn (rand)) (sin rad)) (* (sgn (rand)) (cos rad))]))
paddle-step 20 paddle-step 20
max-paddle-y (- height paddle-size) max-paddle-y (- height paddle-size)
ef-paddle-width (+ paddle-width padding) ef-paddle-width (+ paddle-width padding)
init-paddle-pos (/ (- height paddle-size) 2) init-paddle-pos (/ (- height paddle-size) 2)]
[frames stop-frames] (frame-chan)]
(defn start-game
"Sets up the game, creates the signals and sets up the components, and starts the game."
[]
(let [pos (chan 1) ;; ball position signal
vel (chan 1) ;; ball velocity signal
pl-pos (chan 1) ;; paddle left position signal
pr-pos (chan 1) ;; paddle right position signal
game-state (chan 1)] ;; game state signal
(layout-game)
(setup-components frames game-state pos vel pl-pos pr-pos)
;; start the game by setting the initial values of the signals
(put! pos init-pos)
(put! vel init-vel)
(put! pl-pos init-paddle-pos)
(put! pr-pos init-paddle-pos)
(put! game-state :moving)))
(defn layout-game (defn layout-game
"Lays out the game screen." "Lays out the game screen."
@ -95,12 +90,30 @@
(dom/set-attr! (dom/by-id "lpaddle") "x" 0) (dom/set-attr! (dom/by-id "lpaddle") "x" 0)
(dom/set-attr! (dom/by-id "rpaddle") "x" (- width paddle-width))) (dom/set-attr! (dom/by-id "rpaddle") "x" (- width paddle-width)))
(defn start-game
"Sets up the game by creating the signals and setting up the components and starts the game."
[]
(let [frames (frame-chan) ;; frames signal
pos (chan 1) ;; ball position signal
vel (chan 1) ;; ball velocity signal
pl-pos (chan 1) ;; paddle left 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
(setup-components frames game-state pos vel pl-pos pr-pos)
;; start the game by setting the initial values of the signals
(put! pos init-pos)
(put! vel init-vel)
(put! pl-pos init-paddle-pos)
(put! pr-pos init-paddle-pos)
(put! game-state [:moving 0])))
(defn setup-components (defn setup-components
"Multiplexes the signals and sets up the components by connecting them using the signals "Multiplexes the signals and sets up the components by connecting them using the signals
tapped from the multiplexers. tapped from the multiplexers.
The signals are taken as parameters." The signals are taken as parameters."
[frames game-state pos vel pl-pos pr-pos] [frames game-state pos vel pl-pos pr-pos]
(let [ticks (chan) ;; tick signal (let [ticks (chan) ;; ticks signal
ticks-m (mult ticks) ;; multiplexers for all signals ticks-m (mult ticks) ;; multiplexers for all signals
pos-m (mult pos) pos-m (mult pos)
vel-m (mult vel) vel-m (mult vel)
@ -120,9 +133,9 @@
(paddle-positioner {38 :up 40 :down} (tap pr-pos-m) pr-pos) (paddle-positioner {38 :up 40 :down} (tap pr-pos-m) pr-pos)
(collision-detector (tap ticks-m) (tap pos-m) (tap vel-m) (collision-detector (tap ticks-m) (tap pos-m) (tap vel-m)
pl-pos-sust pr-pos-sust game-state vel) pl-pos-sust pr-pos-sust (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 vel-m) (tap pl-pos-m) (tap pr-pos-m))))
(defn ticker (defn ticker
"Ticker component. "Ticker component.
@ -130,12 +143,12 @@
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 `frames` signal hence stopping the entire game.
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 game-state ticks] [[frames stop-frames] game-state ticks]
(let [ticks-in (tick-chan (diff-chan frames))] (let [ticks-in (tick-chan (diff-chan frames))]
(go (loop [] (go (loop []
(let [gs (<! game-state)] (let [[state _] (<! game-state)]
(do (>! ticks (<! ticks-in)) (do (>! ticks (<! ticks-in))
(if (= :gameover gs) (if (= :gameover state)
(stop-frames) (stop-frames)
(recur)))))))) (recur))))))))
@ -163,7 +176,7 @@
:up (max (- pos paddle-step) 0) :up (max (- pos paddle-step) 0)
:down (min (+ pos paddle-step) max-paddle-y))))))) :down (min (+ pos paddle-step) max-paddle-y)))))))
(defn collision-detector [ticks pos vel-in pl-pos pr-pos game-state vel-out] (defn collision-detector [ticks pos vel-in pl-pos pr-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`
@ -187,9 +200,8 @@
(> y (- height padding)) :collision-right (> y (- height padding)) :collision-right
:else :moving)) :else :moving))
(defn collision? [x-state y-state] (defn collision-state? [state]
(or (= x-state :collision-left) (= x-state :collision-right) (or (= state :collision-left) (= state :collision-right)))
(= y-state :collision-left) (= y-state :collision-right)))
(defn adjust-vel [state v] (defn adjust-vel [state v]
(condp = state (condp = state
@ -198,40 +210,60 @@
:moving v :moving v
:gameover 0)) :gameover 0))
(defn perturb [v] (* v (+ 1 (/ (- (rand) 0.5) 25))))
(go-loop (go-loop
(let [tick (<! ticks) (let [;; get all current values
tick (<! ticks)
[vel-x vel-y] (<! vel-in) [vel-x vel-y] (<! vel-in)
[x y] (<! pos) [x y] (<! pos)
lpaddle-y (<! pl-pos) lpaddle-y (<! pl-pos)
rpaddle-y (<! pr-pos) rpaddle-y (<! pr-pos)
[xn yn] (next-pos [x y] [vel-x vel-y] tick) [xn yn] (next-pos [x y] [vel-x vel-y] tick)
[_ score] (<! game-state-in)
;; calculate next position and detect collision
x-state (detect-x-collision xn yn lpaddle-y rpaddle-y) x-state (detect-x-collision xn yn lpaddle-y rpaddle-y)
vel-xn (adjust-vel x-state vel-x) vel-xn (adjust-vel x-state vel-x)
y-state (detect-y-collision yn) y-state (detect-y-collision yn)
vel-yn (adjust-vel y-state vel-y)] vel-yn (adjust-vel y-state vel-y)
(>! vel-out [vel-xn vel-yn]) x-collision (collision-state? x-state)
(>! game-state y-collision (collision-state? y-state)
(cond
;; calculate next game state
state-n (cond
(= x-state :gameover) :gameover (= x-state :gameover) :gameover
(collision? x-state y-state) :collision (or x-collision y-collision) :collision
:else :moving))))) :else :moving)
score-n (if x-collision (inc score) score)
;; add a small random perturbation to the ball velocity on collision with paddles
[vel-xn vel-yn] (if x-collision
(map perturb [vel-xn vel-yn])
[vel-xn vel-yn])]
(>! vel-out [vel-xn vel-yn])
(>! game-state [state-n score-n]))))
(defn renderer (defn renderer
"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 vel pl-pos pr-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")
lpaddle-el (dom/by-id "lpaddle") lpaddle-el (dom/by-id "lpaddle")
rpaddle-el (dom/by-id "rpaddle") rpaddle-el (dom/by-id "rpaddle")
fps-el (dom/by-id "fps")] fps-el (dom/by-id "fps")
vel-el (dom/by-id "vel")]
(go-loop (go-loop
(let [fps (/ 1000 (<! ticks)) (let [fps (/ 1000 (<! ticks))
[x y] (<! pos) [x y] (<! pos)
gs (<! game-state)] [state score] (<! game-state)]
(dom/set-text! fps-el fps) (dom/set-text! fps-el fps)
(dom/set-text! state-el (name gs)) (dom/set-text! state-el (name state))
(dom/set-text! score-el score)
(dom/set-text! vel-el (<! vel))
(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))))
@ -240,5 +272,6 @@
(go-loop (go-loop
(dom/set-attr! rpaddle-el "y" (<! pr-pos))))) (dom/set-attr! rpaddle-el "y" (<! pr-pos)))))
;; Everything is ready now. Start the game! ;; Everything is ready now. Layout the game and start it on any keypress!
(start-game))) (layout-game)
(ev/listen-once! :keypress start-game)))