Programming Breakout with Racket - debugging

I'm trying to program the breakout game (https://www.youtube.com/watch?v=hW7Sg5pXAok) using DrRacket (BSL). I started by writing the code for the bar controlled by the player. The bar should move alongside with the mouse until following the x-Axis and stay immobile on the y-Axis. My code is the following, the bar is moving but the mouse-event doesn't seem to work, can you help debug it ?
(require 2htdp/universe)
(require 2htdp/image)
(define-struct playerState (locPlayer))
(define playerRectangle (rectangle 20 10 "solid" "red"))
(define playerScene (empty-scene 200 200))
; WorldState -> Image
; draws a player's state to an image
(check-expect (drawPlayer (make-playerState (make-posn 50 99))) (place-image playerRectangle 50 99 playerScene))
(define (drawPlayer state)
(place-image playerRectangle (posn-x (playerState-locPlayer state)) (posn-y (playerState-locPlayer state)) playerScene))
; WorldState -> WorldState
; computes a new player worldstate after a clock tick
(check-expect (tickPlayer (make-playerState (make-posn 50 99))) (make-playerState (make-posn 51 99)))
(define (tickPlayer state)
(make-playerState (make-posn (+ 1 (posn-x (playerState-locPlayer state))) (posn-y (playerState-locPlayer state)))))
; mouseEvent -> WorldState
; computes a new player worldstate if the mouse is moved
(define (mouse state x y mouse-event)
(cond [(string=? mouse-event "move")
(cond
[(> x (posn-x (playerState-locPlayer state))) (make-playerState (make-posn (+ 1 (posn-x (playerState-locPlayer state))) (posn-y (playerState-locPlayer state))))]
[(< x (posn-x (playerState-locPlayer state))) (make-playerState (make-posn (- 1 (posn-x (playerState-locPlayer state))) (posn-y (playerState-locPlayer state))))])]
[else (make-playerState (make-posn (posn-x (playerState-locPlayer state)) (posn-y (playerState-locPlayer state))))]))
(define (main state)
(big-bang state (to-draw drawPlayer) (on-mouse mouse) (on-tick tickPlayer)))
(define initialPlayer (make-playerState (make-posn 100 199)))
(main initialPlayer)

Some things i see here is your tick-handler. This does not need to update the players position if you want the mouse-handler to update the player position so for now you can take that out. From now on when you type i suggest you indent on every half screen, this makes it look more managed. Now the problem with your on-mouse handler is simple. While it is deciding which way you are moving the mouse it confuses itself and makes some really interesting decisions. At the same time if you were to start on the right-hand side of the screen and drag the mouse to the left it will result in an error. So to fix this i decided to do this.
(define (mouse state x y mouse-event)
(cond [(string=? mouse-event "move")
(make-playerState
(make-posn (- 200 x);200 is the width of the screen
(posn-y (playerState-locPlayer state))))]
[else state]))
All this does is give the reversed control to the player. That is what i interpreted, from playing what you had posted.

Related

How to have ball collide with bricks in Breakout (racket)

I've been trying to get Breakout to work in Racket, so far the ball bounces from the paddle (paddle is controlled by mouse) and the bricks are present
Here is the full on code:
(require 2htdp/image)
(require 2htdp/universe)
(define WIDTH 400)
(define HEIGHT 400)
(define BALL-RADIUS 10)
(define BALL-IMG (circle BALL-RADIUS "solid" "red"))
(define REC-WIDTH 50)
(define REC-HEIGHT 10)
(define REC-IMG (rectangle REC-WIDTH REC-HEIGHT "solid" "grey"))
(define BRICK-IMG0 (rectangle 60 30 "solid" "blue"))
(define BRICK-IMG1 (rectangle 60 30 "solid" "green"))
(define SCENE (empty-scene WIDTH HEIGHT))
(define STARTGAME
(text "CLICK TO START" 60 "purple"))
(define GAMEOVER
(text "GAMEOVER" 60 "red"))
(define-struct vel (delta-x delta-y))
; a Vel is a structure: (make-vel Number Number)
; interp. the velocity vector of a moving object
(define-struct ball (loc velocity))
; a Ball is a structure: (make-ball Posn Vel)
; interp. the position and velocity of a object
(define-struct rec (pos))
;
;
(define-struct brick (pos))
;
;
; Posn Vel -> Posn
; applies q to p and simulates the movement in one clock tick
(check-expect (posn+vel (make-posn 5 6) (make-vel 1 2))
(make-posn 6 8))
(define (posn+vel p q)
(make-posn (+ (posn-x p) (vel-delta-x q))
(+ (posn-y p) (vel-delta-y q))))
; Ball -> Ball
; computes movement of ball in one clock tick
(check-expect (move-ball (make-ball (make-posn 20 30)
(make-vel 5 10)))
(make-ball (make-posn 25 40)
(make-vel 5 10)))
(define (move-ball ball)
(make-ball (posn+vel (ball-loc ball)
(ball-velocity ball))
(ball-velocity ball)))
; A Collision is either
; - "top"
; - "down"
; - "left"
; - "right"
; - "none"
; interp. the location where a ball collides with a wall
; Posn -> Collision
; detects with which of the bricks the ball collides
;(check-expect (collision (make-posn 0 12)) "left")
;(check-expect (collision (make-posn 15 HEIGHT)) "down")
;(check-expect (collision (make-posn WIDTH 12)) "right")
;(check-expect (collision (make-posn 15 0)) "top")
;(check-expect (collision (make-posn 55 55)) "none")
(define (collision ball rec)
(cond
[(<= (posn-x ball) BALL-RADIUS) "left"]
[(<= (posn-y ball) BALL-RADIUS) "top"]
[(>= (posn-x ball) (- WIDTH BALL-RADIUS)) "right"]
[(>= (posn-y ball) (- HEIGHT BALL-RADIUS)) "GAMEOVER"]
[(and (>= (posn-x ball) (- (posn-x (rec-pos rec)) (/ REC-WIDTH 2)))
(<= (posn-x ball) (+ (posn-x (rec-pos rec)) (/ REC-WIDTH 2)))
(= (posn-y ball) (posn-y (rec-pos rec)))) "down"]
[else "none"]))
; Vel Collision -> Vel
; computes the velocity of an object after a collision
(check-expect (bounce (make-vel 3 4) "left")
(make-vel -3 4))
(check-expect (bounce (make-vel 3 4) "top")
(make-vel 3 -4))
(check-expect (bounce (make-vel 3 4) "none")
(make-vel 3 4))
(define (bounce vel collision)
(cond [(or (string=? collision "left")
(string=? collision "right"))
(make-vel (- (vel-delta-x vel))
(vel-delta-y vel))]
[(or (string=? collision "down")
(string=? collision "top"))
(make-vel (vel-delta-x vel)
(- (vel-delta-y vel)))]
[else vel]))
; WorldState is a Ball
; interp. the current state of the ball
; WorldState -> Image
; renders ball at its position
;(check-expect (image? (render INITIAL-BALL)) #true)
(define (render a-world)
(draw-ball (world-ball a-world)
(draw-bricklist (world-bricks a-world)
(draw-rect (world-rec a-world)))))
(define (draw-ball ball img)
(place-image BALL-IMG
(posn-x (ball-loc ball))
(posn-y (ball-loc ball))
img))
(define (draw-rect rec)
(place-image REC-IMG
(posn-x (rec-pos rec))
(posn-y (rec-pos rec))
SCENE))
(define (draw-bricklist list img)
[cond ((empty? list) img)
((cons? list) (draw-bricklist (rest list) (draw-brick (first list) img)))])
(define (draw-brick brick image)
(place-image (choice BRICK-IMG0 BRICK-IMG1)
(posn-x (brick-pos brick))
(posn-y (brick-pos brick))
image))
(define (choice a b )
(if (zero? (random 1)) a b ))
; WorldState -> WorldState
; moves ball to its next location
;(check-expect (tick (make-ball (make-posn 20 12) (make-vel 1 2)))
; (make-ball (make-posn 21 14) (make-vel 1 2)))
;(define (tick ball)
; (move-ball (make-ball (ball-loc ball)
; (bounce (ball-velocity ball)
; (collision (ball-loc ball))))))
(define (tick world)
(make-world (move-ball (make-ball (ball-loc (world-ball world))
(bounce (ball-velocity (world-ball world))
(collision (ball-loc (world-ball world))
(world-rec world)))))
(world-rec world)
(world-bricks world)))
(define (mouse world mouse-x mouse-y mouse-event)
(cond
[(string=? mouse-event "move") (make-world (world-ball world)
(make-rec (make-posn mouse-x REC-Y-POS))
(world-bricks world))]
[else world]))
(define INITIAL-BALL (make-ball (make-posn 20 12)
(make-vel 1 2)))
(define REC-Y-POS (- HEIGHT (+ 20 REC-HEIGHT)))
(define INITIAL-REC (make-rec (make-posn 100 REC-Y-POS)))
(define INITIAL-BRICKS (list
(make-brick (make-posn 50 100))
(make-brick (make-posn 111 100))
(make-brick (make-posn 172 100))
(make-brick (make-posn 233 100))
(make-brick (make-posn 294 100))
(make-brick (make-posn 355 100))
(make-brick (make-posn 50 131))
(make-brick (make-posn 111 131))
(make-brick (make-posn 172 131))
(make-brick (make-posn 233 131))
(make-brick (make-posn 294 131))
(make-brick (make-posn 355 131))
(make-brick (make-posn 50 162))
(make-brick (make-posn 111 162))
(make-brick (make-posn 172 162))
(make-brick (make-posn 233 162))
(make-brick (make-posn 294 162))
(make-brick (make-posn 355 162))))
(define-struct world (ball rec bricks))
(define INITIAL-WORLD-STATE (make-world INITIAL-BALL INITIAL-REC INITIAL-BRICKS))
(define (main state)
(big-bang state (on-mouse mouse) (on-tick tick 0.01) (to-draw render)))
(main INITIAL-WORLD-STATE)
The Ball just flies straight through the bricks. I've added this following code to it:
;--------------------------------------------------------------------------------------------------
(define (overlap?
brick-one-x brick-one-y brick-one-width brick-one-height
brick-two-x brick-two-y brick-two-width brick-two-height)
(cond
[(and
(and
(>= (+ brick-one-x brick-one-width) brick-two-x); within right bounds
(<= brick-one-x (+ brick-two-x brick-two-width)); within left bounds
)
(and
(>= (+ brick-one-y brick-one-height) brick-two-y) ;within top bounds
(<= brick-one-y (+ brick-two-y brick-two-height)) ; within bottom bounds
))
#t]
[else ;not overlapping
#f]))
(define (brick-collide-ball? brick ball)
(if
(overlap?
;balls dimensions and location
(posn-x (ball-loc ball))
(posn-y (ball-loc ball))
10
10
;first brick in list
(posn-x (brick-pos brick))
(- (posn-y (brick-pos brick)) 10)
60
30)
#t
#f))
(define (delete-bricks bricklist ball)
(cond
[(empty? bricklist) empty]
((cons? bricklist)
(if (brick-collide-ball? (first bricklist) ball)
(delete-bricks (rest bricklist) ball)
(cons (first bricklist)(delete-bricks (rest bricklist) ball))))))
;--------------------------------------------------------------------------------------------------------
but it does not seem to make an impact on anything. I'm stuck now and would appreciate any tips to improve!
I used the following in my version of the Atari classic:
(define (colliding? b1 b2)
(match-define (body x1 y1 w1 h1) b1)
(match-define (body x2 y2 w2 h2) b2)
(not (or (eq? b1 b2)
(< (+ x1 w1) x2) (> x1 (+ x2 w2))
(< (+ y1 h1) y2) (> y1 (+ y2 h2)))))
Here both the paddle and the ball is a "body", which is an rectangle
with and (x,y) position and a width, w, and a height h.
(struct body (x y w h) #:transparent)
Note: If you ball happens to move very fast, it can happen that it "jumps over" the paddle. This can be fixed by having a maximum velocity.
One way you might be able to address this problem without falling into a velocity-jump in the other answer is to calculate the intersection of the line along which the ball is currently traveling and the bounding line of (presumably the front of) the paddle. Then you just determine if this point is actually on the line segment of the paddle and the line segment between the ball's previous position and it's hypothetical position to determine if a collision would have happened "between" frames and handle that reflection. A more thorough check would be to do the same thing with the edge of the paddle since that game did allow the paddle to hit the ball from the side.
Example calculation:
#lang racket/base
(require racket/struct)
(struct line (a b c) #:transparent) ; ax+by=c
(struct pos (x y) #:transparent)
(define (meet l1 l2)
(let-values (((a b c) (apply values (struct->list l1)))
((d e f) (apply values (struct->list l2))))
(let ((denominator (- (* a e) (* b d))))
(if (zero? denominator)
'parallel ;probably impossible in your game
(let ((x* (- (* c e) (* b f)))
(y* (- (* a f) (* c d))))
(pos (/ x* denominator)
(/ y* denominator)))))))

scheme update a list of bullet location with location speed direction

I'm trying updates each bullet's location based on the bullets direction and speed for a list of bullets. And returns back the updated bullet list.
I think my code works logically but scheme gives me the error code
function call: expected a function after the open parenthesis,
but received (make-signature ...)
;; A Bullet is (make-bullet Posn Number Direction Number)
;; interpetation: represents a bullet, its current position,
;; its speed, its direction and its potential damage
(define-struct bullet (location speed direction damage))
;; A Direction is one of
;; - 'Up
;; - 'Down
;; - 'Left
;; - 'Right
;; a list of bullets(lob) is either
;; - empty
;; - (cons bullet lob)
;; A location is Lof[location]
;; A location is a Posn
;; move-bullets: lob -> lob
;; takes in a list of bullets and updates each bullet's location
;; based on the bullets direction and speed
(define (move-bullets lob)
(cond
[(empty? lob) empty]
[(cons? lob) (cons (create-new-bullet (first lob))
lob)]))
;; create-new-bullet: location direction speed-> location
;; takes in a list of bullets' location and create the "moved" bullet
;; base on given direction, speed
(define (create-new-bullet location dir speed)
(cond
[(symbol=? 'Up dir)
(make-posn (posn-x location) (- (posn-y location) speed))]
[(symbol=? 'Down dir)
(make-posn (posn-x location) (+ (posn-y location) speed))]
[(symbol=? 'Left dir)
(make-posn (- (posn-x location) speed) (posn-y location))]
[(symbol=? 'Right dir)
(make-posn (+ (posn-x location) speed) (posn-y location))]))
hummh. Anything goes wrong here???
You're passing a bullet as a parameter, so you need to extract each of its components. Try this:
(define (create-new-bullet bullet)
(let ((location (bullet-location bullet))
(speed (bullet-speed bullet))
(direction (bullet-direction bullet))
(damage (bullet-damage bullet)))
(cond
[(symbol=? 'Up direction)
(make-bullet
(make-posn (posn-x location) (- (posn-y location) speed))
speed direction damage)]
[(symbol=? 'Down direction)
(make-bullet
(make-posn (posn-x location) (+ (posn-y location) speed))
speed direction damage)]
[(symbol=? 'Left direction)
(make-bullet
(make-posn (- (posn-x location) speed) (posn-y location))
speed direction damage)]
[(symbol=? 'Right direction)
(make-bullet
(make-posn (+ (posn-x location) speed) (posn-y location))
speed direction damage)])))
Also, you forgot to advance the recursion:
(define (move-bullets lob)
(cond
[(empty? lob) empty]
[(cons? lob) (cons (create-new-bullet (first lob))
(move-bullets (rest lob)))]))

how to save the state of a game scheme

for example I have this game:
This game does is move a bird on keyboard or mouse
(define-struct estado (ANCHO ALTO vel tiempo mov punto))
(define mapa (bitmap "mapa.png"))
(define Projo (bitmap "Projo.png"))
(define mario (bitmap "mario.png"))
(define (crear-mundo estado)
(big-bang estado
[to-draw pintar]
[on-tick tiempo-nuevo]
[on-mouse click]
[on-key cambiar-al-nuevo-mundo-teclado]
[stop-when fin-juego]))
(define (pintar nuevo-mundo)
(cond
[(colisiĆ³n nuevo-mundo area)
(place-image Projo
(posn-x (estado-punto nuevo-mundo))
(posn-y (estado-punto nuevo-mundo))
(place-image (text (string-append "Tiempo: " (number->string (quotient (estado-tiempo nuevo-mundo) 28))) 12 "red") 40 20 mapa)
)]
[else (place-image Projo
(posn-x (estado-punto nuevo-mundo))
(posn-y (estado-punto nuevo-mundo))
;(place-image mario 750 500 (empty-scene 800 600))
;(place-image mario 750 500 mapa)
(place-image (text (string-append "Tiempo: " (number->string (quotient (estado-tiempo nuevo-mundo) 28))) 12 "green") 40 20 mapa)
)]
)
)
create the new state of the game where make-posn is the position of the bird
(crear-mundo (make-estado 800 600 10 0 0 (make-posn 100 100)))
How I can save the state of play with the position of the bird when the game is run and what position changes
The simplest would be to just use standard write and read:
#!racket
(define file "file.rc")
(define (save config)
(with-output-to-file file
(lambda () (write config))
#:mode 'text
#:exists 'truncate)
config)
(define (read)
(with-input-from-file file
(lambda () (read))))
So imagine you have a loop somewhere where you pass the new state to the next iteration. Just wrapping it in (save state) will save a new file over the last. (read) will read that file, perhaps when you start up.
You might need to check if it exists but this is basically what you need to read/store state.

How can I improve this auxilary function in Racket?

I'm working in HtDP, Chapter 4 using the BSL language.
The problem I was working on is:
Exercise 136: If you run main, press the space bar (fire a shot), and
wait for a good amount of time, the shot disappears from the canvas.
When you shut down the world canvas, however, the result is a world
that still contains this invisible shot.
Design an alternative tock function, which not just moves shots one
pixel per clock tick but also eliminates those whose coordinates
places them above the canvas. Hint: You may wish to consider the
design of an auxiliary function for the recursive cond clause.
The solution that I came up with is below (in a spoiler). However, I feel that I'm doing something redundant. Basically my application of the auxiliary function isn't quite correct.
(define (main w0)
(big-bang w0
(on-tick ticking)
(on-key fire-key)
(to-draw to-render)))
(define HEIGHT 100)
(define WIDTH 80)
(define TURRET-X-POS (/ WIDTH 2))
(define BKGRND (empty-scene WIDTH HEIGHT))
(define SHOT-IMG (triangle 4 "solid" "red"))
(define (to-render w0)
(cond
[(empty? w0) BKGRND]
[else (place-image SHOT-IMG TURRET-X-POS (first w0) (to-render (rest w0)))]))
(define (fire-key w0 ke)
(cond
[(key=? ke " ") (cons HEIGHT w0)]
[else w0]))
(define (ticking w0)
(cond
[(empty? w0) empty]
[(empty? (only-inbound-shots w0)) empty]
[else (cons (sub1 (first (only-inbound-shots w0)))
(ticking (rest (only-inbound-shots w0))))]))
(define (only-inbound-shots w0)
(cond
[(< (first w0) -4) (rest w0)]
[else w0]))
UPDATE:
(This is much cleaner than before)
(define HEIGHT 100) ;height of scene
(define WIDTH 80) ;width of scene
(define TURRET-X-POS (/ WIDTH 2)) ;position of turret, ie. shot's x-coordinate
(define BKGRND (empty-scene WIDTH HEIGHT)) ; scene itself
(define SHOT-IMG (triangle 4 "solid" "red")) ;image representing the shot
(define Y-BOUNDARY -4) ;y-coordinate where shot is no longer visible in scene
;List-of-numbers -> List-of-numbers
;renders all shots fired
(define (to-render w0)
(cond
[(empty? w0) BKGRND]
[else (place-image SHOT-IMG TURRET-X-POS (first w0)
(to-render (rest w0)))]))
;List-of-numbers, key event -> List-of-numbers
;only allows the space bar to fire a shot
;one space bar event produces one shot
(define (fire-key w0 ke)
(cond
[(key=? ke " ") (cons HEIGHT w0)]
[else w0]))
;List-of-numbers -> List-of-numbers
;during each clock tick, the y-coordinate each of the shot
; in List-of-numbers is updated
;each y-coordinate decreases by -1
(define (ticking w0)
(cond
[(empty? w0) w0]
[else (only-inbound-shots (update-shots w0) Y-BOUNDARY)]))
;List-of-numbers -> List-of-numbers
;does the actual updating of the shots in List-of-numbers
;each shot's value is decreased by -1
(define (update-shots w0)
(cond
[(empty? w0) w0]
[else (cons (sub1 (first w0)) (update-shots (rest w0)))]))
;List-of-numbers -> List-of-numbers
;checks to see if the first shot in the List-of-numbers has gone past the Y-BOUNDARY
;if so then remove shot from the List-of-numbers and return the rest of the List
;otherwise return the List without change
(define (only-inbound-shots w0 y-boundary)
(cond
[(empty? w0) w0]
[(< (first w0) y-boundary) (rest w0)]
[else w0]))
;List-of-numbers -> List-of-numbers
;creates the world of shots
;seed value is empty, additional values created by space bar
(define (main w0)
(big-bang w0
(on-tick ticking)
(on-key fire-key)
(to-draw to-render)))
TESTS added:
I'm still working on the tests.
(define test-shots
(cons -6 (cons -5 (cons 10 empty))))
(define test-shots-2
(cons -6 (cons 2 (cons 7 empty))))
(define test-shots-3
(cons 4 (cons 9 (cons 10 empty))))
(check-expect (to-render test-shots)
(place-image SHOT-IMG TURRET-X-POS -6
(place-image SHOT-IMG TURRET-X-POS -5
(place-image SHOT-IMG TURRET-X-POS 10
BKGRND))))
(check-expect (to-render test-shots-2)
(place-image SHOT-IMG TURRET-X-POS -6
(place-image SHOT-IMG TURRET-X-POS 2
(place-image SHOT-IMG TURRET-X-POS 7
BKGRND))))
TEST with world functions added:
(define HEIGHT 1) ; makes test a little faster
(check-expect
(fire-key
(ticking
(ticking
(ticking
(ticking
(fire-key
(ticking
(ticking
(ticking
(ticking (fire-key empty " ")))))
" ")))))
" ")
(cons -3 (cons 1 empty))
The usual comments about missing contracts, purpose statements, and data definitions apply here. As well as tests of the individual functions; a big reason why world.ss/universe.ss are really nice libraries is that they enable one to test functions that are conceptually performing Input/Output.
I'm inferring a lot about what your data definition is from the code, but (1.) you should not put that onus on the reader, and (2.) it could lead to mistakes in my reasoning.
It looks to me like you have deviated significantly from the template in your definition of ticking; it does not look like any template I can think of. A similar comment applies to only-inbound-shots
You may want to break ticking up into multiple subroutines, and then compose them.
An example of what I mean by this: If you were to make a function to take the average of a list of numbers, a simple way to do it is to make two new functions: the first produces the sum of the numbers, and the second produces the length of the list; these are trivial to write via the Design Recipe. Then average is:
;; average : [Listof Number] -> Number
;; produces average value of input (x_1 x_2 ... x_n
(define (average l)
(/ (sum-of-list l) (length-of-list l)))
But if you were to try to do it in a single definition of average that followed the template for [Listof Number], you would have some problems getting the right answer. (I do not think it can be done properly without using an accumulator or two.)
That factoring into very simple subroutines and then composing them at the end to get the desired effect is what I mean by breaking ticking up and then composing the pieces. (If you're not destructuring your input, function composition is a perfectly valid design process: see HtDP section 3.1.)
More importantly, though, I think is to make some tests for the individual functions. Especially only-inbound-shots: I suggest you think about this function on its own.
Pretend that you don't know who might call it, and only that they will obey its contract (e.g. they will only pass in a World, whatever you defined that to be here).
And then make sure you produce the right answer for any possible legal input they provide.
Don't think about how you use it yourself in your other code above, because you don't want to try to keep all that in your head at the same time. Its actually simpler to generalize here, and think about what only-inbound-shots should do on any possible input.
To provide you with some concrete food for thought on the matter of testing, here are some hypothetical pictures describing the inputs you might try to handle in your tests:
, ,
Update 28 Feb 2013:
While I still recommend writing individual unit tests of each of your functions, end-to-end testing is also important. In this case, the game as currently rendered won't tell you if have shots lying outside the scene or not (because place-image, unlike say overlay, automatically crops them from the rendering).
So, if you want to debug the game while it is running, it can be useful to get that kind of information. Say like a drop down bit of text that renders on top of the game (one often sees this in video games to show you things like Frame Rate). So here is one strategy for getting that information out while the game is running: Swap in an alternative rendering function, that is layered on top of your existing one, but prints out other information about the world w0 argument.
(In this case, it might be useful to see its length, though one can imagine extracting other information.)
;; List-of-numbers -> Image
;; Renders w0 via to-render, with a printout of shot count in top left corner.
(define (to-render-with-count w0)
(place-image/align (text (number->string (length w0)) 30 'blue)
0 0 "left" "top"
(to-render w0)))
Then you hook in to-render-with-count in your big-bang invocation. It may also be useful to slow down the clock tick rate, so that you can see what happens as keystrokes and clock ticks are intermixed, so I have made that change too (in the on-tick clause):
(define (main w0)
(big-bang w0
(on-tick ticking 0.1)
(on-key fire-key)
(to-draw to-render-with-count)))
Now, I can interactively notice interesting trends. Trends that yield situations like this:
How is it that I have 148 balls on the screen but only four are showing? What kind of world would have that happen? (If you close the window created by big-bang, it will return the current world to the Interactions Window, so you will see right there exactly what kind of World would have that happen.)
I put the final answer here because the original question has a lot going on already.
(define HEIGHT 200) ;height of scene
(define WIDTH 80) ;width of scene
(define TURRET-X-POS (/ WIDTH 2)) ;position of turret, ie. where shot's x-coordinate
(define BKGRND (empty-scene WIDTH HEIGHT)) ; scene itself
(define SHOT-IMG (triangle 4 "solid" "red")) ;image representing the shot
(define Y-BOUNDARY -4) ;y-coordinate where shot is no longer visible in scene
;List-of-numbers -> List-of-numbers
;renders all shots fired
(define (to-render w0)
(cond
[(empty? w0) BKGRND]
[else (place-image SHOT-IMG TURRET-X-POS (first w0) (to-render (rest w0)))]))
;List-of-numbers, key event -> List-of-numbers
;only allows the space bar to fire a shot
;one space bar event produces one shot
(define (fire-key w0 ke)
(cond
[(key=? ke " ") (cons HEIGHT w0)]
[else w0]))
;List-of-numbers -> List-of-numbers
;updates world state every clock tick
(define (ticking w0)
(cond
[(empty? w0) w0]
[else (remove-outbound-shots (update-shots w0) Y-BOUNDARY)]))
;List-of-numbers -> List-of-numbers
;updates all shots
(define (update-shots w0)
(cond
[(empty? w0) w0]
[else (cons (sub1 (first w0)) (update-shots (rest w0)))]))
;List-of-numbers -> List-of-numbers
;removes all shots exceeding the y-boundary from list
(define (remove-outbound-shots w0 y-boundary)
(cond
[(empty? w0) w0]
[(< (first w0) y-boundary) (remove-outbound-shots (rest w0) y-boundary)]
[else (cons (first w0) (remove-outbound-shots (rest w0) y-boundary))]))
;List-of-numbers -> List-of-numbers
;creates the world of shots
;seed value is empty, additional values created by space bar
(define (main w0)
(big-bang w0
(on-tick ticking)
(on-key fire-key)
(to-draw to-render)))
Tests:
(define test-shots-1
(cons 1 (cons 4 (cons 10 (cons -6 (cons -5 (cons 1 (cons 4 (cons 10 (cons 10 (cons -6 (cons -9 empty))))))))))))
(define test-shots-4
(cons 10 (cons -6 (cons -5 (cons 1 (cons 4 (cons 10 empty)))))))
(check-expect (remove-outbound-shots test-shots-4 -4) (list 10 1 4 10))
(check-expect (remove-outbound-shots test-shots-1 -4) (list 1 4 10 1 4 10 10))

on-key in racket

(require 2htdp/image)
(require 2htdp/universe)
(define (render t)
(text (number->string t) 10 "red"))
(define (ball-image t)
(place-image (circle 10 "solid" "red")
150
150
(empty-scene 300 300)))
(define (change w a-key)
(cond
[(key=? a-key "left") (ball-image w)]
[(key=? a-key "right") (ball-image w )]
[(= (string-length a-key) 1) w]
[(key=? a-key "up") (ball-image w )]
[(key=? a-key "down") (ball-image w )]
[else w]))
(big-bang 100
(on-tick sub1 )
(to-draw ball-image)
(on-key change))
I am trying to get the red ball I have placed in the middle to move up, down, left, or right. When I press any of the arrow keys, it says it expects a number but given an image. What am I doing wrong?
First of all you need to understand how the world is processed in this main circle:
The system takes the first argument of big-bang - 100, and remembers it as a WorldState.
Then it passes it to a on-tick (sub1) function, provided it exists on each tick.
When the key is pressed, it calls on-key (change) and passes the woldState there, as a w argument.
There you draw some pictures and return it in case of an arrow key is pressed. So when an arrow is pressed, it returns the result of ball-image = result of place-image - image
The system remembers it as a current worldState,
and with the next tick, it passes the new value to the old procedure: sub1.
Since the value is now an image, sub1 rejects it.
--
If you want to move a ball in two directions, you have to store at least two coordinates (x . y). So let now the WorldState be the pair of two numbers. We don't need a on-tick function, since nothing changes on its own. Also we don't need to draw the ball in the keyboard processor, so let's simple change the corresponding value in the pair (worldState), and draw it only during the call (ball-image) which puts the ball into the new place (remember, x = (car t), y = (cdr t), and (x . y) = (cons x y)):
(require 2htdp/image)
(require 2htdp/universe)
(define (ball-image t) ;<-- the t-parameter is our WorldState
(place-image (circle 10 "solid" "red")
(car t) ;<-- here now x variable coordinate
(cdr t) ;<-- here now y variable, instead of 150
(empty-scene 300 300)))
(define (change w a-key)
(cond ;w - is the previous worldState, V here we change it
[(key=? a-key "left") (cons (sub1 (car w)) (cdr w))];and
[(key=? a-key "right") (cons (add1 (car w)) (cdr w))];return
[(= (string-length a-key) 1) w] ;<-- this line is excess
[(key=? a-key "up") (cons (car w) (sub1 (cdr w)))]
[(key=? a-key "down") (cons (car w) (add1 (cdr w)))]
[else w])) ;<-- If the key of no interest, just
return the previous WorldState
(big-bang '(150 . 150) ;<-- initial state
(to-draw ball-image) ;<-- redraws the world
(on-key change)) ;<-- process the event of key press

Resources