Download the code from this exercise here.A multi segment worm should be able to be represented by a cons'd list of worm segments. Given the examples of data definitions we have, I'm not sure how (or even if) we can implement the restrictions that it must differ in direction from its predecessor in at most one direction, and that its segments must all be touching. I will implement these restrictions in code. If any one knows how you can specify these restrictions in the data definition, please comment and let me know.
A MSWorm is one of: – (cons segment empty) – (cons segment MSWorm)
Changes
The main change is worms are now represented by a series of segments, rather than a single segment. All the other changes flow on from this. As we have been told not to implement collision detection, these functions and related tests have been removed.The draw-worm function now needs to draw a series of worm segments, rather than just a single segment. This is a straight forward list traversal problem so we can recurse down the list drawing a disk each time until we reach the end of the list.
The move-worm function now consists of two calls to seperate functions. The function remove-last will remove the last segment of the worm. This is done by reversing the list, getting the rest of the segments and reversing the results. The other function called will produce a new segment in the correct new position. The results of these two function calls are then cons'd together to form a new worm.
All of the existing tests needed to be changed to cope with with new segment/worm definition. Most of these were just changing make-worm into make-segment, but some tests needed to logically changed.
Code
; Constants
(define WORM-SIZE 10)
(define WORM-MOVE (* WORM-SIZE 2))
(define WIDTH 800) ; width of the game
(define HEIGHT 500) ; height of the game
(define SEGMENT (circle WORM-SIZE "solid" "red"))
; these structs will hold the current list of worm segments, the direction
; the worm is travelling in, and our world object
(define-struct segment(x-pos y-pos))
(define-struct direction(x y))
(define-struct world(worm direction))
(define WORM (list (make-segment 100 100)
(make-segment 100 80)
(make-segment 100 60)) )
; To save repeating these directions in tests and code, we'll define them here
(define DOWN (make-direction 0 1))
(define UP (make-direction 0 -1))
(define RIGHT (make-direction 1 0))
(define LEFT (make-direction -1 0))
; Functions
; draw worm in its current location on the screen. Instead of simply drawing a single dot
; on the screen, we need to recurse down our list of worm segments drawing each one at a
; time
(define (draw-worm background worm)
(cond [(empty? worm) background]
[else (place-image
SEGMENT
(segment-x-pos (first worm)) (segment-y-pos (first worm))
(draw-worm background (rest worm))
)]))
; Draws the current world. Currently this just consists of the snake.
(define (show world)
(draw-worm (empty-scene WIDTH HEIGHT) (world-worm world)) )
; Move the worm in the current direction. Instead of changing the position of
; a single segment, now we add a segment to start of the worm (in the current direction)
; and get rid of the end of the worm
(define (move-worm worm direction)
(cons (new-segment (first worm) direction) (remove-last worm))
)
; return a new segment moved in 'direction' from the segment
; passed in
(define (new-segment segment direction)
(make-segment (+ (segment-x-pos segment)
(* WORM-MOVE (direction-x direction)))
(+ (segment-y-pos segment)
(* WORM-MOVE (direction-y direction)))))
; remove the last worm segment. this is a pretty optimised function. just
; reverse the list, grab the rest of it, and reverse it again to get it the
; correct order. Is their a built in to do the same?
(define (remove-last worm)
(reverse (rest (reverse worm))))
; On each clock tick, move the world further in time. This is a new function
; that takes part of the responsibily of the old move-worm function. It just
; moves the worm and creates a new world based on it.
(define (progress-world world)
(make-world
(move-worm (world-worm world) (world-direction world))
(world-direction world))
)
; handle keyboard events.
(define (handle-key-events ws ke)
(cond
[(string=? "left" ke) (change-direction ws LEFT)]
[(string=? "right" ke) (change-direction ws RIGHT)]
[(string=? "up" ke) (change-direction ws UP )]
[(string=? "down" ke) (change-direction ws DOWN)]
[else ws]
))
; create a new world with the direction the worm is travelling in changed.
(define (change-direction world direction)
(make-world (world-worm world) direction))
; This is the big bang function that drives the game.
(define (worm-main rate)
(big-bang (make-world WORM
(make-direction 1 0))
(to-draw show)
;(stop-when collision-detected final-scene)
(on-key handle-key-events)
(on-tick progress-world rate) ))
; start the game off!
(worm-main 0.1)
;; ### NEW TESTS ###
; Test when we move the worm up, a new segment is added to the start and removed
; from the end.
(check-expect (move-worm WORM LEFT)
(list
(make-segment (- 100 WORM-MOVE) 100)
(make-segment 100 100)
(make-segment 100 80)))
; Check the remove-last function removes the last segment correctly
(check-expect (remove-last WORM)
(list (make-segment 100 100)
(make-segment 100 80)))
; Test that new segment returns a new segment in the correct position
; (as per the current direction)
(check-expect (new-segment (make-segment 100 100) UP)
(make-segment 100 (- 100 WORM-MOVE)))
; ### OLD TESTS
;
;; Test our worm draws as we expect it.
(check-expect (draw-worm (empty-scene 200 200) WORM)
(place-image SEGMENT 100 100
(place-image SEGMENT 100 80
(place-image SEGMENT 100 60 (empty-scene 200 200)))))
; Test our worm moves in the direction we expect
(check-expect (move-worm (list (make-segment 50 50)) DOWN)
(list (make-segment 50 70)))
(check-expect (move-worm (list (make-segment 50 50)) UP)
(list (make-segment 50 30)))
(check-expect (move-worm (list (make-segment 50 50)) LEFT)
(list (make-segment 30 50)))
(check-expect (move-worm (list (make-segment 50 50)) RIGHT)
(list (make-segment 70 50)))
; Test our change-direction function changes the direction, but doesn't impact the postion
(check-expect (change-direction (make-world (make-segment 50 50) DOWN) UP)
(make-world (make-segment 50 50) UP))
(check-expect (change-direction (make-world (make-segment 50 50) DOWN) RIGHT)
(make-world (make-segment 50 50) RIGHT))
No comments:
Post a Comment