CS 1410-20 Homework 2

Due: Friday, September 9th, 2011 10:45am

Now that we have compund data and variants, we can begin the implementation of Mildy Annoyed Ducks (MAD).

The game scene is 800 pixels wide and 600 pixels high. The duck starts with its center 200 pixels from the left edge of the scene and 400 pixels from the top. You should give those constants names in your program, such as (define WIDTH 800).

The player clicks to drag the duck, which initially sits in an invisible (for now) slingshot. A player clicks on the duck to start dragging it—constrained by the limits of the slingshot—and releases the click to fling the duck. The duck flies with an initial velocity that is determined by the stretched slingshot, and the duck’s velocity is adjusted as it flies by gravity. When the duck leaves the screen, the game resets to the initial position.

In a future homework assignment, we’ll add blocks to the game that a player can destroy by flinging a duck.

This assignment looks long, but it’s not as bad as it looks. A full implementation with data definitions, contracts, functions, tests, and generous spacing can be around 200 lines of code.

Part 1 – Slingshot Angle and Distance

The MAD slingshot stretches no more than 80 pixels from the duck’s initial position. For example, if the user drags 100 pixels to the left of the duck’s initial position, then the duck will move only 80 pixels to the left. More generally, dragging the duck moves the duck from its starting position in a straight line toward the mouse click, but it moves the duck no more than 80 pixels from the starting position. If the user drags 100 pixels to the left and 100 pixels above the starting position, then the duck will move only 80 times the square root of 2 left and up.

The slingshot constraint is easiest to implement by working in terms of an angle and distance. Implement slingshot-position-from-angle, which takes two numbers and returns a posn. The arguments are an angle a (in radians increasing counter-clockwise from due east) and a distance d from the starting position (200, 400). The result point should be at the given angle from the starting position. If d is 80 or less, the result point is d pixels from the starting position; if d is more than 80, the result point will be only 80 pixels from the starting position.

Recall that you can multiply (cos a) by the result point’s distance from the starting point to get its horizontal offset from the starting point. You can multiple (sin a) by the distance to get the vertical offset from the starting point.

For example, (slingshot-position-from-angle pi 10) should produce approximately (make-posn (- START-X 10) START-Y) if START-X and START-Y correspond to the starting position. As another example, (slingshot-position-from-angle (/ pi 4) 100) should produce (make-posn (+ START-X (/ 80 (sqrt 2))) (- START-Y (/ 80 (sqrt 2)))).

Your tests will need to use check-within, since calculations involving pi, cos, or sin are usually inexact. You should be able to predict the results within 0.01.

Part 2 – Mouse to Slingshot Constraint

A user click arrives from the window manager as a position, rather than and angle and distance. Write the function slingshot-position, which takes two numbers and returns a posn. The two numbers represent the screen position where the user clicks: number of pixels right from the scene’s left edge, and number of pixels down from the scene’s top edge. The result of slingshot-position is the duck’s new location.

Your slingshot-position function should call slingshort-position-from-angle with a conversion of the input x and y coordinates to an angle a and distance d. To get the angle a, call atan with two arguments: the difference between y and the starting vertical position, and the difference between x and the starting horizontal position. To get the distance d, take the square root of the sum of the differences. (The Racket name for the square-root function is sqrt.)

For example, (slingshot-position (+ START-X 30) (+ START-Y 30)) should produce approximately (make-posn (+ START-X 30) (+ START-Y 30)), since the input position is within the slingshot’s stretching ability. In contrast, (slingshot-position (- START-X 100) (- START-Y 100)) should produce

  (make-posn (- START-X (/ SLINGSHOT-MAX (sqrt 2)))
             (- START-Y (/ SLINGSHOT-MAX (sqrt 2))))
assuming that SLINGSHOT-MAX is 80, since the combination of 100 pixels in both direction is more than 80 pixels away from the starting position.

Part 3 – Slingshot Scene

Implement the function duck-at-posn-scene, which takes a posn for the duck’s position and returns a 800-by-600 scene.

Here’s the duck:

You can skip tests for this function.

The big-bang form from 2htdp/universe lets you put your slingshot computation and duck-as-posn-scene pieces together. You can try out the combination by temporarily adding the following lines to the end of your program:

  ;; mouse-slingshot : posn x y string -> posn
  (define (mouse-slingshot p x y e)
    (slingshot-position x y))
   (make-posn START-X START-Y)
   [to-draw duck-at-posn-scene]
   [on-mouse mouse-slingshot])

The first argument to big-bang is the state of a universe, which in this universe is the duck’s position. When big-bang wants to draw the universe, it calls the function provided with to-draw to convert the universe’s state into a scene. When the user clicks on the scene, big-bang calls the function provided with on-mouse to adjust the universe state. The function for on-mouse gets the current state, the mouse X position, the mouse Y position, and the mouse action. The mouse-slingshot function above ignores the current position p, because it doesn’t matter for dragging the duck; only the mouse location matters. The mouse-slingshot function also ignores the event string e, because it doesn’t care (yet) where the user merely moved the mouse, clicked a mouse button, or is dragged the mouse.

When you run the big-bang call, then the duck should follow your mouse but stay within an 80-pixel radius of its starting position.

Part 4 – Flinging a Duck

Implement the function fling-duck, which takes a position and returns a duck, where a duck has a position and a velocity:

  ;; A duck is
  ;;   (make-duck posn num num)
  (define-struct duck (loc dx dy))

The duck produced by fling-duck should have the given position. Its horizontal velocity dx (in pixels per second) should be four times the difference between the given position’s X-coodinate and START-X. Its vertical velocity dy (in pixels per second) should be four times the difference between the given position’s Y-coordinate and START-Y.

For example, (fling-duck (make-posn (- START-X 10) (+ START-Y 30))) should produce (make-duck (make-posn (- START-X 10) (+ START-Y 30)) 40 -120).

Part 5 – Moving a Duck

Implement the function step-duck, which takes a number and a duck. The number represents a fraction of a second, such as 1 for a full second or 1/2 for half a second. The result should be a duck that has moved and accelerated due to gravity.

The duck’s position should change by its current velocity in each direction as scaled by the given fraction of a second. The duck’s velocity in the X direction should stay unchanged (i.e., a frictionless world), and its velocity in the Y direction should change by 200 pixels/second per second.

For example, (step-duck 1/2 (make-duck (make-posn 100 200) 10 -100)) shoudl produce (make-duck (make-posn 105 150) 10 0)).

Now you can make your duck fly with big-bang. Temporarily add the following to your program to try it out:

  ;; duck-tick : duck -> duck
  (define (duck-tick d)
    (step-duck 1/28 d))
  ;; duck-scene : duck -> scene
  (define (duck-scene d)
    (duck-at-posn-scene (duck-loc d)))
   (make-duck (make-posn (- START-X 10) (+ START-Y 30)) 320 -320)
   [to-draw duck-scene]
   [on-tick duck-tick])

In this case, the state of the universe is a duck (at a position and with some velocity) instead of just a position. Also, when an on-tick function is given to big-bang, the function is called 28 times a second, so duck-tick steps the duck by 1/28 second.

Part 6 – A Launch

At any point in time, a duck launch is in one of three modes:

Based on this analysis, we define a launch as follows:

  ;; A launch is either
  ;;   - false
  ;;   - posn
  ;;   - duck

That is, we use false to represent a waiting duck, a posn to represent a duck that is being dragged, and a duck to represent a flying duck.

Implement the function launch-scene, which takes a launch and produces a suitable scene image. For example, (launch-scene false) should produce the same image as (duck-at-posn-scene (make-posn START-X START-Y)).

Part 7 – Stepping a Launch

When the big-bang clock ticks, a waiting or dragging duck stays where it is, but a flying duck moves and accelerates—but if it moves off the screen, then it goes back to waiting mode. “Off the screen” means that the duck’s center is beyond the right or bottom of the scene.

Implement the function step-launch, which takes a launch and produces a launch. For example, (step-launch false) should produce false, (step-launch (make-duck (make-posn 0 0) 10 10)) should produce the same result as (step-duck 1/28 (make-duck (make-posn 0 0) 10 10)), while (step-launch (make-duck (make-posn WIDTH HEIGHT) 1 1)) should produce false since the duck would move off the screen.

Part 8 – Controlling a Launch

Implement the function mouse-launch, which takes a launch, a number for the mouse’s X position, a number for the mouse’s Y position, and a string that is either "button-down", "button-up", "drag", "move", "enter", or "leave". The result should be a launch according to the following:

At this point, you can put all the pieces together, where the universe state is a launch that starts in waiting mode, and launch-scene, step-launch, and mouse-launch all work on the universe:

   [to-draw launch-scene]
   [on-tick step-launch]
   [on-mouse mouse-launch])

If you’re having fun, you might try some of the following MAD extensions (optional; extra credit will be considered by the grader):

Last update: Friday, November 4th, 2011