Search code examples
iocommon-lisp

Analogy of Python is_pressed in Common Lisp?


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.


Solution

  • 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.