beginners 2025-10-01

Hi Everyone. Could someone help me understanding why this code from Alex Clojure book doesn't work? I can't move the snake with arrow keys.

@seancorfield Yes. This is what I'm trying to figure out. 😞 @smith.adriane How can I print the arrow keycode in keyPressed? I can't have access to it on REPL. 😞 • The game is running; • It's updating, the green dot (who should be the snake) is always going to right (ignoring my arrow key press); • How should I make sure this? ◦ Is keyPressed receiving key events? ◦ Is update-direction receiving a valid direction? ◦ Does calling update-direction manually work?

> How can I print the arrow keycode in keyPressed? I can't have access to it on REPL. why can't you access it in the repl?

> • How should I make sure this? > ◦ Is keyPressed receiving key events? > ◦ Is update-direction receiving a valid direction? > ◦ Does calling update-direction manually work? I would start with just adding prn statements

👍 1

I wrote this code:

(ns reader.dummy
  (:import (javax.swing JFrame JPanel)
           (java.awt.event KeyListener KeyEvent)))

(defn make-panel []
  (doto (JPanel.)
    (.setFocusable true) ; panels don't get focus by default
    (.addKeyListener
      (proxy [KeyListener] []
        (keyPressed [^KeyEvent e]
          (println "Pressed:" (.getKeyCode e)))
        (keyReleased [^KeyEvent e]
          (println "Released:" (.getKeyCode e)))
        (keyTyped [^KeyEvent e]
          (println "Typed:" (.getKeyChar e)))))))

(defn show []
  (let [frame (JFrame. "KeyListener test")
        panel (make-panel)]
    (.add (.getContentPane frame) panel)
    (doto frame
      (.setSize 300 200)
      (.setVisible true)
      (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE))
    (.requestFocusInWindow panel)
    panel))
When open the the frame, and I press the arrow keys, works.

what key codes do you get when you press the arrow keys? do they match what the code is checking for:

(def VK-LEFT KeyEvent/VK_LEFT)
(def VK-RIGHT KeyEvent/VK_RIGHT)
(def VK-UP KeyEvent/VK_UP)
(def VK-DOWN KeyEvent/VK_DOWN)

Yes, they match.

(def VK-LEFT KeyEvent/VK_LEFT)                              ; 37
(def VK-RIGHT KeyEvent/VK_RIGHT)                            ; 39
(def VK-UP KeyEvent/VK_UP)                                  ; 38
(def VK-DOWN KeyEvent/VK_DOWN)                              ; 40

I did this, trying to debug on the game-panel, but doesn´t work.

(defn game-panel [frame snake apple]
  (proxy [JPanel ActionListener KeyListener] []
    (paintComponent [g]
      (proxy-super paintComponent g)
      (paint g @snake)
      (paint g @apple))
    (actionPerformed [e]
      (update-positions snake apple)
      (when (lose? @snake)
        (reset-game snake apple)
        (JOptionPane/showMessageDialog frame "You lose!"))
      (when (win? @snake)
        (reset-game snake apple)
        (JOptionPane/showMessageDialog frame "You win!"))
      (.repaint this))
    (keyPressed [e]
      (println "DEBUG keyPressed:" (.getKeyCode e))
      (when-let [dir (dirs (.getKeyCode e))]
        (update-direction snake dir)))
    ;(keyPressed [e]
    ;  (update-direction snake (dirs (.getKeyCode e))))
    (getPreferredSize []
      (Dimension. (* (inc width) point-size)
                  (* (inc height) point-size)))
    (keyReleased [e])
    (keyTyped [e])))

How does it fail? What do you expect to see? What happens?

I expecting to see in the REPL: DEBUG keyPressed 38, but nothing was printed.

PS: I'm not pressing right arrow key.

your debug keyboard example adds a key listener while the snake game panel is a key listener. Not sure if there’s something missing from the book code that makes it work, but you can try adapting the code to add a key listener instead of implementing a key listener

🤔 1

anyway, it seems like you’ve isolated the problem. the key events aren’t getting passed to the game code

Dude, I miss this part of the code 😞

you can also try searching slack or the internet to see if someone else ran into the same problem to see how they fixed it

Thank you for the help!

👍 1

(ns reader.snake
  (:import (java.awt Color Dimension)
           (javax.swing JPanel JFrame Timer JOptionPane)
           (java.awt.event ActionListener KeyListener KeyEvent)))

;(:import-static java.awt.event.KeyEvent VK_LEFT VK_RIGHT VK_UP VK_DOWN)

(def VK-LEFT KeyEvent/VK_LEFT)
(def VK-RIGHT KeyEvent/VK_RIGHT)
(def VK-UP KeyEvent/VK_UP)
(def VK-DOWN KeyEvent/VK_DOWN)

(def width 75)
(def height 50)
(def point-size 10)
(def turn-millis 75)
(def win-length 5)
(def dirs {VK-LEFT  [-1 0]
           VK-RIGHT [1 0]
           VK-UP    [0 -1]
           VK-DOWN  [0 1]})

(defn add-points [& pts]
  (vec (apply map + pts)))

(defn point-to-screen-rect [pt]
  (map #(* point-size %) [(pt 0) (pt 1) 1 1]))

(defn create-apple []
  {:location [(rand-int width) (rand-int height)]
   :color (Color. 210 50 90)
   :type :apple})

(defn create-snake []
  {:body (list [1 1])
   :dir [1 0]
   :type :snake
   :color (Color. 15 160 70)})

(defn move [{:keys [body dir] :as snake} & grow]
  (assoc snake :body (cons (add-points (first body) dir)
                           (if grow body (butlast body)))))

(defn win? [{body :body}]
  (>= (count body) win-length))

(defn head-overlaps-body? [{[head & body] :body}]
  (contains? (set body) head))

(def lose? head-overlaps-body?)

(defn eats? [{[snake-head] :body} {apple :location}]
  (= snake-head apple))

(defn turn [snake newdir]
  (assoc snake :dir newdir))

(defn reset-game [snake apple]
  (dosync (ref-set apple (create-apple))
          (ref-set snake (create-snake)))
  nil)

(defn update-direction [snake newdir]
  (when newdir (dosync (alter snake turn newdir))))

(defn update-positions [snake apple]
  (dosync
    (if (eats? @snake @apple)
      (do (ref-set apple (create-apple))
          (alter snake move :grow))
      (alter snake move)))
  nil)

(defn fill-point [g pt color]
  (let [[x y width height] (point-to-screen-rect pt)]
    (.setColor g color)
    (.fillRect g x y width height)))

(defmulti paint (fn [g object & _] (:type object)))

(defmethod paint :apple [g {:keys [location color]}]
  (fill-point g location color))

(defmethod paint :snake [g {:keys [body color]}]
  (doseq [point body]
    (fill-point g point color)))

(defn game-panel [frame snake apple]
  (proxy [JPanel ActionListener KeyListener] []
    (paintComponent [g]
      (proxy-super paintComponent g)
      (paint g @snake)
      (paint g @apple))
    (actionPerformed [e]
      (update-positions snake apple)
      (when (lose? @snake)
        (reset-game snake apple)
        (JOptionPane/showMessageDialog frame "Youo lose!"))
      (when (win? @snake)
        (reset-game snake apple)
        (JOptionPane/showMessageDialog frame "You win!"))
      (.repaint this))
    (keyPressed [e]
      (update-direction snake (dirs (.getKeyCode e))))
    (getPreferredSize []
      (Dimension. (* (inc width) point-size)
                 (* (inc height) point-size)))
    (keyReleased [e])
    (keyTyped [e])))

;; TODO: the game doesn't work as expected, some keys are not working
;;  maybe it's because the import-static thing.
(defn game []
  (let [snake (ref (create-snake))
        apple (ref (create-apple))
        frame (JFrame. "Snake")
        panel (game-panel frame snake apple)
        timer (Timer. turn-millis panel)]
    (doto frame
      (.add panel)
      (.pack)
      (.setVisible true))
    (.start timer)
    [snake, apple, timer]))

Can you print the arrow keycode in keyPressed and compare it to the VK-LEFT, etc keys?

I would try to isolate the issue: • Is the game running? • Is the game updating per the timer? • Is keyPressed receiving key events? • Is update-direction receiving a valid direction? • Does calling update-direction manually work?

The code you've posted does not match the book. Here's what's in the book:

(ns reader.snake
   (:import (java.awt Color Dimension)
            (javax.swing JPanel JFrame Timer JOptionPane)
            (java.awt.event ActionListener KeyListener))
   (:refer examples.import-static :refer :all))

(import-static java.awt.event.KeyEvent VK_LEFT VK_RIGHT VK_UP VK_DOWN)
There's a bug in the book: it should be (:require [examples.import-static :refer :all]) And then it's (import-static ...) not (:import-static ...)

examples/import_static.clj must be part of the source code for the book (but it isn't shown in the book)...

Asking for a coworker: Has anyone figured out a good editing workflow in Zed by any chance?

Not yet. They are still in the process of defining an extension mechanism. Without that, we cannot start with a Clojure plugin.