examples/rolling-out-of-time-with-keys.ss
```#lang s-exp "../moby-lang.ss"
;; Rolling out of time
;;
;; Roll the blue ball onto the red target:
;; if the blue ball shrinks down to zero,
;; then the game ends.

;; A world is a posn, a radius, a vel, a
;; target posn, and a score.
(define-struct world
(posn r vel target-posn score))

(define WIDTH 320)
(define HEIGHT 480)

(define TARGET-RADIUS 30)

;; A velocity has an x and y component.
(define-struct vel (x y))

;; The initial world starts at the center.
(define initial-world
(make-world (make-posn (quotient WIDTH 2)
(quotient HEIGHT 2))
30
(make-vel 0 0)
(make-posn 0 0)
0))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; tick: world -> world
;; Moves the ball by a velocity, and shrinks
;; it.
(define (tick w)
(cond
[(collide? w)
(make-world (posn+vel (world-posn w)
(world-vel w))
30
(world-vel w)
(make-random-posn)
(add1 (world-score w)))]
[else
(make-world (posn+vel (world-posn w)
(world-vel w))
(- (world-r w) 1/3)
(world-vel w)
(world-target-posn w)
(world-score w))]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; tilt: world number number number -> world
;; Adjusts velocity based on the tilt.
(define (tilt w azimuth pitch roll)
(make-world (world-posn w)
(world-r w)
(make-vel roll (- pitch))
(world-target-posn w)
(world-score w)))

;; render: world -> scene
;; Renders the world.
(define (render w)
(maybe-add-game-over
w
(place-image/posn
(text (format "Score: ~a" (world-score w))
20 "black")
(make-posn 20 20)
(place-image/posn
(circle TARGET-RADIUS "solid" "red")
(world-target-posn w)
(place-image/posn
(circle (world-r w) "solid" "blue")
(world-posn w)
(empty-scene WIDTH HEIGHT))))))

;; maybe-add-game-over: world scene -> scene
(define (maybe-add-game-over w a-scene)
(cond
[(game-ends? w)
(place-image/posn
(text "GAME OVER" 30 "red")
(make-posn 20 100)
a-scene)]
[else
a-scene]))

;; collide?: world -> boolean
;; Produces true if the target and the ball
;; have collided.
(define (collide? w)
(< (distance (world-posn w)
(world-target-posn w))
(+ TARGET-RADIUS (world-r w))))

;; game-ends?: world -> boolean
;; Produces true if the game should finish;
;; we end when there's no more ball left.
(define (game-ends? w)
(<= (world-r w) 1))

;; make-random-posn: -> posn
;; Produces a random position for the target.
(define (make-random-posn)
(make-posn (random WIDTH)
(random HEIGHT)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; key: world key -> world
;; Adjust velocity based on key presses.
(define (key w a-key)
(make-world (world-posn w)
(world-r w)
(update-vel-with-key
(world-vel w) a-key)
(world-target-posn w)
(world-score w)))

;; update-vel-with-key: vel key -> vel
;; Adjust the velocity based on which key
;; the user presses.
(define (update-vel-with-key v a-key)
(cond [(key=? a-key "left")
(make-vel (- (vel-x v) 3)
(vel-y v))]
[(key=? a-key "right")
(make-vel (+ (vel-x v) 3)
(vel-y v))]
[(key=? a-key "up")
(make-vel (vel-x v)
(- (vel-y v) 3))]
[(key=? a-key "down")
(make-vel (vel-x v)
(+ (vel-y v) 3))]
[else
v]))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; posn+vel: posn velocity -> posn
;; Adds a position by a velocity.
(define (posn+vel a-posn a-vel)
(make-posn (clamp (+ (posn-x a-posn)
(vel-x a-vel))
0 WIDTH)
(clamp (+ (posn-y a-posn)
(vel-y a-vel))
0 HEIGHT)))

;; clamp: number number number -> number
;; Clamps a number x between a and b.
(define (clamp x a b)
(cond [(> x b) b]
[(< x a) a]
[else x]))

;; distance: posn posn -> number
;; Produces the Euclidean distance between
;; two positions.
(define (distance posn-1 posn-2)
(sqrt
(+ (sqr (- (posn-x posn-1)
(posn-x posn-2)))
(sqr (- (posn-y posn-1)
(posn-y posn-2))))))

;; place-image/posn: image posn scene -> scene
;; Place an image at a position into the
;; scene.
(define (place-image/posn img a-posn a-scene)
(place-image img
(posn-x a-posn)
(posn-y a-posn)
a-scene))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(js-big-bang initial-world
(on-tick 1/20 tick)
(on-tilt tilt)

(on-redraw render)
(on-key key)
(stop-when game-ends?))```