I'm making a simple LTK game in Common Lisp and would like to have the player character jump upon pressing space. However, the window is constantly updating, so I cannot wait for user input. I want to check each frame whether the space key is pressed. Despite much googling, I couldn't find the way to do this in Lisp. All I found are traditional I/O prompts that stop the flow of the program.
Thanks for any help.
Are you using a Tk canvas? Maybe you could bind event handlers to key presses?
(with-ltk ()
(let ((canvas (make-canvas nil)))
(grid canvas 1 1)
(bind canvas
"<Left>"
(lambda (e)
(message-box "Left" "Click" "okcancel" "info")))
(focus canvas)))
When you receive a key press, you can set a variable that you inspect when processing a frame.
This works fine for one button, but binding e.g. both the up arrow and the left arrow for movement only registers one at a time. Is there a way to fix that?
Here is a more detailed example. Tk can bind on KeyPress and KeyRelease events. You only need to manage the pressed/unpressed state of the key you want to observe, and set the right variables accordingly.
(with-ltk ()
(let ((canvas (make-canvas nil))
(x 50)
(y 50)
(speed 5)
(w 10)
(h 10)
(dx 0)
(dy 0))
(grid canvas 1 1)
(macrolet
((on (event expr)
(check-type event string)
(let ((evar (gensym)))
`(bind canvas ,event
(lambda (,evar)
(declare (ignore ,evar))
,expr)))))
(on "<KeyPress-Left>" (decf dx))
(on "<KeyPress-Right>" (incf dx))
(on "<KeyRelease-Left>" (incf dx))
(on "<KeyRelease-Right>" (decf dx))
(on "<KeyPress-Up>" (decf dy))
(on "<KeyPress-Down>" (incf dy))
(on "<KeyRelease-Up>" (incf dy))
(on "<KeyRelease-Down>" (decf dy)))
(focus canvas)
(let ((rectangle (create-rectangle canvas x y 10 10)))
(labels ((game-loop ()
(incf x (* dx speed))
(incf y (* dy speed))
(set-coords canvas rectangle (list x y (+ x w) (+ y h)))
(after 50 #'game-loop)))
(game-loop)))))
The first part of the above function creates a canvas and binds event handlers for KeyPress/KeyRelease events for Left, Right, Down and Up keys. This is a bit verbose, but is simple enough for an example. Alternatively, you could bind only on "<KeyPress>" and "<KeyRelease>" events (no additional key in the string), and use a table indexed by the keycode of your key event.
The second part is the game loop, where both deltas dx and dy are actually used to move the rectangle; at the end of the game loop, after ensures the game loop is run again after some time.
Event handlers do not directly manipulate canvas elements, they are only used to translate UI events to game logic changes. You have to be careful about how and when events happen. For example, the first version of the above used to do:
(on "<KeyPress-Left>" (setf dx -1))
(on "<KeyPress-Right>" (setf dx 1))
(on "<KeyRelease-Right>" (setf dx 0))
(on "<KeyRelease-Left>" (setf dx 0))
But that was wrong because the sequence press-left, press-right and release-right would make the rectangle stop.
Related
Suppose I have this Racket code:
(struct pos (x y))
(displayln (pos 5 6))
This displays #<pos>. Is there a way to make it display the field names and values too?
With the #:transparent option, the values are displayed:
(struct pos (x y) #:transparent)
(displayln (pos 5 6))
This displays #(struct:pos 5 6), but I also want to display the field names (x and y). Is there a way to display both the field names and values? For example: #(struct:pos #:x 5 #:y 6).
I am looking for something similar to how Common Lisp structs are displayed. Common Lisp example:
(defstruct pos x y)
(format t "~A~%" (make-pos :x 5 :y 6))
This prints #S(POS :X 5 :Y 6).
If you don't want to use third-party libraries, take a look at the very last example of make-constructor-style-printer.
If you don't mind using third-party libraries, you can just use Rebellion's record.
Essentially, what I'm doing is taking the enemies list (on line 1), which is holding a list of coordinates and iterate through each pair in the enemies list at the bottom.
I want to go through each enemy in the list, get the y coordinate, add 10 and then go to the next enemy and add 10, so on and so forth. For some reason, it adds 10 ONCE and then stops, and the enemies do not fall down the screen. I don't know why this is happening. Why is it not running through the for loop anymore? Thank you so much for any help.
NOTE: I removed some code at the top for the sake of being less confusing. The update() function is just the pygame flip function.
enemies = [[100,0], [150,0]]
while True:
for enemy in enemies:
x = enemy[0]
y = enemy[1]
y += 10
pygame.draw.rect(screen, (255,0,0), (x, y,10,10))
# uses flip to update the screen
update()
# FPS
clock.tick(20)
You're trying to modify a local variable, not the value in the list. You need to write:
enemy[1] += 10
Since integers are immutable (they cannot be changed), the line y = enemy[1] can be thought of as "copy the value from enemy[1] into y".
I was wrong about what the problem was. The event handlers worked fine, and everything works now. I found the answer on http://gloss.ouroborus.net/, and the relevant paragraph is cited below. I apologize for asking a misleading question. I would delete the question but I don't see a way to do that.
from gloss.ouroborus.net:
Q: On my MacBook Pro under OSX, gloss programs freeze after displaying the first few frames.
A: This can happen on dual GPU systems when the OS switches from the integrated GPU (baked into the processor) to the discrete one (separate from the main processor). The gloss program can sometimes draw a few frames before getting stuck, otherwise it just shows a black window. This is probably a bug in OSX not switching the graphics context properly. The work-around is to disable automatic GPU switching under System Preferences / Energy Saver.
Original question:
I wrote a game in Haskell Gloss which uses mouse click and keyboard handlers. It compiles and runs fine on a PC, but, while it compiles and loads on an Apple and presents the game screen, the mouse and keyboard handlers malfunction. The mouse handler recognizes that the mouse button has been been clicked, but reports the same pair of numbers for the mouse position no matter where the mouse is. The keyboard handler does not work for 's' and 'f', but the escape key DOES work.
Here are the handlers:
mousehandle::Event->(Board,Board)->IO(Board,Board)
mousehandle (EventKey (MouseButton LeftButton) Down _ pt#(x,y))
(board,solved) =
return (board',solved)
where
board' = if candidates == [] then board
else rotateCell board (fst (head candidates))
candidates = getCandidates (x,y) board
mousehandle (EventKey (Char 's') Down _ _ ) (board1, board2) = return (board2, board1)
mousehandle (EventKey (Char 'f') Down _ _ ) (board1, board2) = do
print "filename?"
ans <- getLine
games2File [board1, board2] ans
return (board1, board2)
mousehandle (EventKey (SpecialKey KeyEsc) Down _ _) _ = exitSuccess
mousehandle _ x = return x
I know what the returned mouse position coordinates are for each click because a trace in the function getCandidates prints them out to the console.
The computer is a Mac Book Pro. The Haskell used is GHC 7.6.3, installed as part of the full Haskell Platform. Gloss was installed with "cabal install gloss".
Any suggestions would be welcome.
So, I did a simple simulation of a bouncing ball to learn haskell. The ball is supposed to bounce against the image borders and to be accelerated downward by gravity.
The problem is that the ball is "magically" bouncing higher as time passes. I would expect it to keep the same maximum height instead.
I suspect there is something wrong with bounce and not with move because bounce "teleports" the ball back inside the frame, which is not physically accurate. I tried different way to simulate a correct behavior but could not find anything working right.
What would be a correct way to do this?
Here is the code, running on CodeWorld:
main = simulationOf initial step draw
data World = Ball Point Vector
radius = 40
border = 250 - radius
g = -500
initial (x:y:vx:vy:_) = Ball (400*x - 200, 400*y - 200)
(400*vx - 200, 400*vy - 200)
step t world = bounce (move t world)
move t (Ball (x,y) (vx,vy)) = Ball (x + vx*t, y + vy*t) (vx, vy + g*t)
bounce (Ball (x,y) (vx,vy)) = Ball (nx,ny) (nvx, nvy)
where nx = fence (-border) border x
ny = fence (-border) border y
nvx = if nx /= x then -vx else vx
nvy = if ny /= y then -vy else vy
fence lo hi x = max lo (min hi x)
draw (Ball (x,y) _) = translate x y (solidCircle radius)
This is a well-known artifact of the algorithm you're using to integrate the movement's differential equation.
The "real physics" is (I will only discuss the y component)
∂y/∂t = v(t)
∂v/∂t = g
You model this by a discrete sequence of heights and velocities
yi = yi − 1 + vi − 1 ⋅ Δt
vi = vi − 1 + g ⋅ Δt
This sure resembles the differential equations, just written as difference quotients – but it's not the same thing (except in the limit Δt → 0): in reality, the velocity itself changes during the time step, so it's not quite correct to alter the position just according the constant v value before that time step. Simply ignoring that complication is an approximation called Euler's method, and it's known to suck rather badly.
The much more accurate standard alternative is the fourth-order Runge-Kutta method, give that a try.
n.m.'s points about the bouncing is also valid, though to do it really properly you should calculate the exact time of impact, to neither neglect too much acceleration nor get too much that actually never happened.
Suppose the ball hits the ground exactly in the middle of the quantum of time. In reality (or rather in the frictionless "reality") the absolute value of velocity stays the same in the beginning and in the end of the quantum, but in your model it increases.
One should handle acceleration in bounce. The simplest possible way to do that is to do this:
move t (Ball (x,y) (vx,vy)) = Ball (x + vx*t, y + vy*t) (vx, vy)
step t world = bounce (move t world) t
bounce (Ball (x,y) (vx,vy)) t = Ball (nx,ny) (nvx, nvy)
...
nvy = if ny /= y then -vy else vy + g * t
The velocity won't increase during the bounce.
This is still not entirely accurate, the velocity still creeps up, but slower.
I've been writing some simple racket GUI programs to prepare for a class I'm teaching in the fall. I'm having some problems with animation. I'm using a basic canvas, and using the model for animation in which the entire canvas is refreshed each frame, by calling the paint procedure. An example program is below.
My problem is that I have to either run the animation as a separate thread, or call yield after each instance of refresh-now. Why is this? I expected the refresh-now to cause the image to refresh right away, without additional work on my part.
I have read the animation examples on the racket pages, and see that they usually draw directly to the canvas. I understand that since the canvas is double-buffered this works fine ... but for my application it's easier to just have the paint procedure carry the load, since I need a working paint procedure anyway in case of minimizing, etc. (Of course, the yield is not a huge burden, but it would be easier to teach if it were not needed.)
Thanks,
John
#lang racket
; Demonstrate simple animation in Racket
(require racket/gui)
(define min-x 0)
(define min-y 0)
(define max-x 200)
(define max-y 200)
; Three vertexes of the triangle, expressed relative to a starting x and y location.
(define triangle-vertexes [list
(list 10 0)
(list 0 20)
(list 20 20)])
(define triangle-x 20)
(define triangle-y 20)
; Move a triangle by a (delta-x, delta-y) pair
(define (move-triangle adjust)
(set! triangle-x (+ triangle-x (first adjust)))
(set! triangle-y (+ triangle-y (second adjust))))
; Adjust the location of a vertex by adding an (x,y) adjustment to it.
; Could also be defined using map.
(define (triangle-adjust adjust vertex)
(list (+ (first adjust) (first vertex))
(+ (second adjust) (second vertex))))
; Create the paint-callback function.
; It should:
; - draw a triangle at the current location
(define (draw-triangle dc)
(let ((vertex1 (triangle-adjust (list triangle-x triangle-y) (first triangle-vertexes)))
(vertex2 (triangle-adjust (list triangle-x triangle-y) (second triangle-vertexes)))
(vertex3 (triangle-adjust (list triangle-x triangle-y) (third triangle-vertexes))))
(send dc draw-line (first vertex1) (second vertex1) (first vertex2) (second vertex2))
(send dc draw-line (first vertex2) (second vertex2) (first vertex3) (second vertex3))
(send dc draw-line (first vertex3) (second vertex3) (first vertex1) (second vertex1))))
(define frame (new frame%
[label "Animation Example"]
[width 800]
[height 800]))
(define triangle-canvas (new canvas% [parent frame]
[paint-callback
(lambda (canvas dc)
(display "callback called")
(draw-triangle dc))]))
(send frame show #t)
; run a thunk (a procedure of zero arguments) n times
; only useful if thunk has side-effects
(define (loop n thunk)
(cond
((> n 0) (thunk)
(loop (- n 1) thunk))
(else false)))
; Animate the triangle. We have to either run this in a different thread from
; the event loop or yield each time we want something to be drawn.
(define (animate-triangle)
(loop 30
(lambda ()
(move-triangle (list 10 10))
(send triangle-canvas refresh-now)
; (send triangle-canvas flush)
(yield)
; (sleep 0.1)
)))
This isn't an answer to your question about refresh-now, but a better alternative to an explicit thread and loop is the timer% class:
;; This goes after (send frame show #t), replacing loop and animate-triangle
(define timer-counter 0)
(define timer
(new timer%
(interval 100) ;; update every 100 ms
(notify-callback
(lambda ()
(cond [(< timer-counter 30)
(set! timer-counter (add1 timer-counter))
(move-triangle (list 10 10))
(send triangle-canvas refresh)]
[else
(send timer stop)])))))
If you redefine your stopping condition based on the triangle's state, you can get rid of the auxiliary timer-counter; I put it in to mimic the behavior of your original code.
The timer is created in the same eventspace as the frame, and the eventspace has an event-handling thread, which is why you don't have to create your own thread explicitly.
How to Design Programs, 2nd ed has another approach to animation, where the canvas and updates are managed automatically. You just call big-bang with functions to (functionally) update the "state of the world" and render the "state of the world" as an image. Depending on exactly what you're teaching, it may or may not be useful to you.