2
\$\begingroup\$

I am studying the book Realm of Racket.

On chapter 5, there is a challenge:

Find an image of a locomotive. Create an animation that runs the locomotive from just past the left margin to just past the right margin of the screen. Next, modify your program so the locomotive wraps around to the left side of the screen after passing the right margin.

I created the following code using Racket and Dr. Racket.

However, I bet it could be improved.

My doubt is specially about the transition from the right side to the left side. I think there should be a better way of doing this:

#lang racket

(require 2htdp/universe 2htdp/image)   

(define LOCOMOTIVE image-inserted-through-DrRacket )

(define HEIGHT 800)

(define WIDTH 800)

;essa função muda a velocidade com que a figura se mexe

(define (add-3-to-state current-state)
  (if (< current-state 1055)
      (+ current-state 6)
      (- current-state 800)))

(define (locomotive-running current-state)
  (place-image LOCOMOTIVE current-state (/ WIDTH 2)
               (empty-scene WIDTH HEIGHT)))

(big-bang 0
          (on-tick add-3-to-state)
          (to-draw locomotive-running))

That's the image that I used:

enter image description here

\$\endgroup\$

2 Answers 2

0
\$\begingroup\$

A few comments

  • The function add-3-to-state adds 6 to the state. That's the problem with magic numbers or unnamed numerical constants.
  • (/ WIDTH 2) is a constant. It does not need to be calculated each time through the code.
  • Two levels of abstraction are intermingled. Concepts like WIDTH and HEIGHT are at one level. LOCOMOTIVE is at a higher level. Because it is the only entity at that higher level it can be generalized to any arbitrary image. This keeps the level of abstraction consistent throughout the code.

A more generalized approach

Roll up all the settings into one place with a structure.

(require 2htdp/universe 2htdp/image)
(struct settings (image width height increment) #:transparent)

Create a higher order function that takes a settings structure and returns a function that can serve as the argument for to-draw. The returned function is a closure over a particular settings.

;; settings->(state->image)
(define (make-draw s)
  (define offset (/ (settings-width s) 2))
  (define scene (empty-scene (settings-width s)
                             (settings-height s)))
  (lambda (state)
    (place-image (settings-image s)
                 state
                 offset
                 scene)))

Create a higher order function that takes a settings structure and returns a function that can serve as the argument for on-tick. The returned function is also a closure over a particular settings.

;; settings->(state->state)
(define (make-update s)
  (define end (+ (settings-width s) (image-width (settings-image s))))
  (lambda (state)
    (if (< state end)
        (+ state (settings-increment s))
        (- state (settings-width s)))))

Implementation of animation

(define my-settings (settings (circle 20 "solid" "blue")
                              800
                              800
                              6))
(define draw (make-draw my-settings)) ; draw is a function
(define update (make-update my-settings)) ; update is a function
(big-bang 0
          (on-tick update)
          (to-draw draw))

Caveat

Sometimes it's worth generalizing the implementation. The next level of this implementation would be a function that takes a settings and returns a big-bang animation.

But sometimes it isn't worth doing because it's the road to factory-factory-factory type implementations.

\$\endgroup\$
1
\$\begingroup\$

Broadly speaking, I'd say it looks pretty good. I'm a bit confused by the '1055'; I'm guessing that you're trying to wait until the train leaves the screen entirely? Perhaps you could write this as

(+ WIDTH (/ (image-width LOCOMOTIVE) 2))

... and also replace the 800 with WIDTH. Alternatively, you could return either 0 or

(- (/ (image-width LOCOMOTIVE) 2))

when the train goes off the right edge, so that it makes its entrance smoothly.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.