@@ -57,9 +57,29 @@ module Pong where
5757
5858import JavaScript
5959import Keyboard.Raw
60+import Window as Win
6061
6162
62−time = lift (\t -> t / 1000) (Time.fps 60)
63+------------------------------------------------------------------------
64+------ Extracting timesteps from JavaScript ------
65+------------------------------------------------------------------------
66+
67+-- Export our desired FPS to JavaScript. We just want a steady 30 frames
68+-- per second for this game.
69+
70+desiredFPS = constant (castIntToJSNumber 30)
71+
72+foreign export jsevent "desiredFPS"
73+ desiredFPS :: Signal JSNumber
74+
75+
76+-- Import the current time from JavaScript. Events will come in roughly
77+-- 30 times per second.
78+
79+foreign import jsevent "trigger" (castIntToJSNumber 0)
80+ jsTime :: Signal JSNumber
81+
82+time = lift castJSNumberToFloat jsTime
6383
6484
6585-- Determine the time that has elapsed since the last event. This is
@@ -97,11 +117,11 @@ defaultKeyInput = KeyInput False Neutral Neutral
97117
98118updateDirection upKey downKey key direction =
99119 case direction of
100− Up -> if key == downKey then Neutral else Up
101− Down -> if key == upKey then Neutral else Down
102− Neutral -> if key == upKey then Up else
120+ { Up -> if key == downKey then Neutral else Up
121+ ; Down -> if key == upKey then Neutral else Down
122+ ; Neutral -> if key == upKey then Up else
103123 if key == downKey then Down else Neutral
104−
124+ }
105125
106126updateDirection1 = updateDirection 87 83 -- 'w' for up and 's' for down
107127updateDirection2 = updateDirection 38 40 -- 'UP' for up and 'DOWN' for down
@@ -196,9 +216,10 @@ defaultGame = GameState BetweenRounds
196216
197217stepPaddle delta dir (Paddle y) =
198218 case dir of
199− Up -> Paddle $ clamp 20 (gameHeight-20) (y - 200 * delta)
200− Down -> Paddle $ clamp 20 (gameHeight-20) (y + 200 * delta)
201− Neutral -> Paddle y
219+ { Up -> Paddle $ clamp 20 (gameHeight-20) (y - 200 * delta)
220+ ; Down -> Paddle $ clamp 20 (gameHeight-20) (y + 200 * delta)
221+ ; Neutral -> Paddle y
222+ }
202223
203224
204225-- We must also step the ball forward. This is more complicated due to
@@ -220,13 +241,14 @@ stepVelocity velocity lowerCollision upperCollision =
220241 if upperCollision then makeNegative velocity else velocity
221242
222243stepBall delta (Ball (x,y) (vx,vy)) (Paddle y1) (Paddle y2) =
223− let hitPaddle1 = within 20 y1 y && within 8 25 x
224− hitPaddle2 = within 20 y2 y && within 8 (gameWidth - 25) x
225− vx' = stepVelocity vx hitPaddle1 hitPaddle2
226− vy' = stepVelocity vy (y < 7) (y > gameHeight - 7)
227− scored = x > gameWidth || x < 0
228− x' = if scored then halfWidth else x + vx' * delta
229− y' = if scored then halfHeight else y + vy' * delta
244+ let { hitPaddle1 = within 20 y1 y && within 8 25 x
245+ ; hitPaddle2 = within 20 y2 y && within 8 (gameWidth - 25) x
246+ ; vx' = stepVelocity vx hitPaddle1 hitPaddle2
247+ ; vy' = stepVelocity vy (y < 7) (y > gameHeight - 7)
248+ ; scored = x > gameWidth || x < 0
249+ ; x' = if scored then halfWidth else x + vx' * delta
250+ ; y' = if scored then halfHeight else y + vy' * delta
251+ }
230252 in ( Ball (x',y') (vx',vy')
231253 , if x > gameWidth then 1 else 0
232254 , if x < 0 then 1 else 0 )
@@ -237,16 +259,15 @@ stepBall delta (Ball (x,y) (vx,vy)) (Paddle y1) (Paddle y2) =
237259
238260stepGame (Input delta (KeyInput space dir1 dir2))
239261 (GameState state (Score s1 s2) ball paddle1 paddle2) =
240− let (ball',s1',s2') = if state == Play then stepBall delta ball paddle1 paddle2
241− else (ball, 0, 0)
242− state' = case state of
243− Play -> if s1' /= s2' then BetweenRounds else state
244− BetweenRounds -> if space then Play else state
245− in GameState state'
246− (Score (s1+s1') (s2+s2'))
247− ball'
248− (stepPaddle delta dir1 paddle1)
249− (stepPaddle delta dir2 paddle2)
262+ let { (ball',s1',s2') = if state == Play then stepBall delta ball paddle1 paddle2
263+ else (ball, 0, 0)
264+ ; state' = case state of { Play -> if s1' /= s2' then BetweenRounds else state
265+ ; BetweenRounds -> if space then Play else state }
266+ } in GameState state'
267+ (Score (s1+s1') (s2+s2'))
268+ ball'
269+ (stepPaddle delta dir1 paddle1)
270+ (stepPaddle delta dir2 paddle2)
250271
251272
252273-- Now we put it all together. We have a signal of inputs that changes whenever there
@@ -275,13 +296,14 @@ gameState = foldp stepGame defaultGame input
275296-- This function displays the current score and directions.
276297
277298scoreBoard w inPlay p1 p2 =
278− let code = text . monospace . toText
279− stack top bottom = flow down [ code " ", code top, code bottom ]
280− msg = width w . centeredText . monospace $ toText "Press SPACE to begin"
281− board = flow right [ stack "W" "S", spacer 20 1
282− , text . Text.height 4 . toText $ show p1 ++ " " ++ show p2
283− , spacer 20 1, stack "↑" "↓" ]
284− score = container w (heightOf board) midTop board
299+ let { code = text . monospace . toText
300+ ; stack top bottom = flow down [ code " ", code top, code bottom ]
301+ ; msg = width w . centeredText . monospace $ toText "Press SPACE to begin"
302+ ; board = flow right [ stack "W" "S", spacer 20 1
303+ , text . Text.height 4 . toText $ show p1 ++ " " ++ show p2
304+ , spacer 20 1, stack "↑" "↓" ]
305+ ; score = container w (heightOf board) midTop board
306+ }
285307 in if inPlay then score else score `above` msg
286308
287309
@@ -302,4 +324,18 @@ display (w,h) (GameState state (Score p1 p2) (Ball pos _) (Paddle y1) (Paddle y2
302324-- We can now define a view of the game (a signal of Elements) that changes
303325-- as the GameState changes. This is what the users will see.
304326
305−main = lift2 display Window.dimensions gameState
327+view = lift2 display Win.dimensions gameState
328+
329+
330+-- Here we tell the JavaScript FPS manager that all of the computations for
331+-- this frame are complete. This allows the manager to calculate when the next
332+-- step should happen.
333+
334+done = lift (\_ -> castBoolToJSBool True) view
335+foreign export jsevent "finished"
336+ done :: Signal JSBool
337+
338+
339+-- And finally, we display the view of the game to the user: Pong in Elm!
340+
341+main = view