Question

I have written a multithreaded bouncing balls program in clojure. After starting the animation thread, I do-

(send-balls) 

to start the bouncing balls threads. The balls dont move and this is displayed on the console -

(#<Agent@5675b3ee FAILED: #<Ref@313d21d6: {:x 759, :y 629, :x-speed 3, :y-speed 1}>> #<Agent@22cf3539 FAILED: #<Ref@247881db: {:x 794, :y 258, :x-speed 2, :y-speed 3}>> #<Agent@51af4309 FAILED: #<Ref@445ead9d: {:x 831, :y 251, :x-speed 4, :y-speed 2}>>)

Can someone point out whats happening here?

(import
 '(java.awt Color Graphics Dimension)
 '(java.awt.image BufferedImage)
 '(javax.swing JPanel JFrame))

(def width 1000)
(def height 1000)

(def number-of-balls 3)

(def rad 20)

(def origin-x 100)
(def origin-y 100)
(def box-height 500)
(def box-width 700)
(def max-x (+ origin-x box-width (* 4 rad)))
(def max-y (+ origin-y box-height (* 4 rad)))
(def min-x origin-x)
(def min-y origin-y)

(defn init-x
 []
 (+ (rand-int (- max-x min-x)) min-x))

(defn init-y
 []
  (+ (rand-int (- max-y min-y)) min-y))

(defstruct ball :x :y :x-speed :y-speed)

(def balls
 (apply vector (map (fn [_] (ref (struct ball (init-x) (init-y)
(rand-int 10) (rand-int 10))))
                 (range number-of-balls))))

(def ball-agents (apply vector (map agent balls)))

(defn get-ball
 [n]
 (balls n))


(defn set-new-x
 [ball]
 (let [x (@ball :x)
       x-speed (@ball :x-speed)
       new-x (+ x x-speed)]
   (dosync
     (if (and (>= new-x min-x) (<= new-x max-x))
      (ref-set ball (assoc @ball :x new-x))
            (ref-set ball (assoc @ball :x-speed (* -1 x-speed)))))
   (println "the new x is " @(ball :x)))
 @ball)

(defn set-new-y
 [ball]
 (let [y (@ball :y)
       y-speed (@ball :y-speed)
       new-y (+ y y-speed)]
   (dosync
     (if (and (>= new-y min-y) (<= new-y max-y))
             (ref-set ball (assoc @ball :y new-y))
             (ref-set ball (assoc @ball :y-speed (* -1 y-speed))))))
 @ball)

(defn paint-balls
 [bg x y]
 (doto bg
   (.setColor (. Color red))
   (.fillOval x y rad rad)))


(defn render
 [g]
 (let [img (new BufferedImage width height
                (. BufferedImage TYPE_INT_ARGB))
      bg (. img (getGraphics))]
  (doto bg
     (.setColor (. Color white))
     (.fillRect 0 0 (. img (getWidth)) (. img (getHeight)))
     (.setColor (. Color red))
     (.drawRect origin-x origin-y (+ origin-x box-width) (+ origin-y box-height)))
  (dorun
    (for [i (range number-of-balls)]
      (do
        (paint-balls bg (@(get-ball i) :x) (@(get-ball i) :y)))))
  (. g (drawImage img 0 0 nil))
  (. bg (dispose))))

(def panel (doto (proxy [JPanel] []
                       (paint [g] (render g)))
            (.setPreferredSize (new Dimension
                                    width
                                    height))))

(def frame (doto (new JFrame) (.add panel) .pack .show))

(def animator (agent nil))

(defn bounce
 [x]
 (while true
   (set-new-x @*agent*)
   (set-new-y @*agent*)
   (. Thread (sleep 100))
   (println "here in bounce " *agent*)))




(defn animation
 [x]
 (send-off *agent* animation)
 (. panel (repaint))
 (. Thread (sleep 100)))

(defn send-balls
 []
 (doall
   (for [i (range number-of-balls)]
     (do
       (send-off (ball-agents i) bounce)))))


(send-off animator animation)
Was it helpful?

Solution

As i see the main problem - functions that you send-off to agents operate NOT with agent, but with its value (the ref). By eliminating @ in set-new-x and set-new-y functions you could make it work.

(ns balls)

(import
  '(java.awt Color Graphics Dimension)
  '(java.awt.image BufferedImage)
  '(javax.swing JPanel JFrame))

(def width 1000)
(def height 1000)

(def number-of-balls 3)

(def rad 20)

(def origin-x 100)
(def origin-y 100)
(def box-height 500)
(def box-width 700)
(def max-x (+ origin-x box-width (* 4 rad)))
(def max-y (+ origin-y box-height (* 4 rad)))
(def min-x origin-x)
(def min-y origin-y)

(defn init-x
 []
 (+ (rand-int (- max-x min-x)) min-x))

(defn init-y
 []
  (+ (rand-int (- max-y min-y)) min-y))

(defstruct ball :x :y :x-speed :y-speed)

(def balls
 (apply vector (map (fn [_] (ref (struct ball (init-x) (init-y)
(rand-int 10) (rand-int 10))))
                 (range number-of-balls))))

(def ball-agents (apply vector (map agent balls)))

(defn get-ball
 [n]
 (balls n))


(defn set-new-x
  [ball]
  (let [x (ball :x)
        x-speed (ball :x-speed)
        new-x (+ x x-speed)]
    (dosync
      (if (and (>= new-x min-x) (<= new-x max-x))
        (alter ball assoc :x new-x)
        (alter ball assoc :x-speed (* -1 x-speed)))))
    ball)

(defn set-new-y
  [ball]
  (let [y (ball :y)
        y-speed (ball :y-speed)
        new-y (+ y y-speed)]
    (dosync
      (if (and (>= new-y min-y) (<= new-y max-y))
        (alter ball assoc :y new-y)
        (alter ball assoc :y-speed (* -1 y-speed))))
   ball))

(defn paint-balls
 [bg x y]
 (doto bg
   (.setColor (. Color red))
   (.fillOval x y rad rad)))


(defn render
 [g]
 (let [img (new BufferedImage width height
                (. BufferedImage TYPE_INT_ARGB))
      bg (. img (getGraphics))]
  (doto bg
     (.setColor (. Color white))
     (.fillRect 0 0 (. img (getWidth)) (. img (getHeight)))
     (.setColor (. Color red))
     (.drawRect origin-x origin-y (+ origin-x box-width) (+ origin-y box-height)))
  (dorun
    (for [i (range number-of-balls)]
      (do
        (paint-balls bg (@(get-ball i) :x) (@(get-ball i) :y)))))
  (. g (drawImage img 0 0 nil))
  (. bg (dispose))))

(def panel (doto (proxy [JPanel] []
                       (paint [g] (render g)))
            (.setPreferredSize (new Dimension
                                    width
                                    height))))

(def frame (doto (new JFrame) (.add panel) .pack .show))

(def animator (agent nil))

(defn bounce
 [ball_cur]
 (do
   (Thread/sleep 100)
   (send-off *agent* bounce)
   (set-new-x (set-new-y ball_cur))))

(defn animation
 [x]
 (send-off *agent* animation)
 (. panel (repaint))
 (. Thread (sleep 100)))

(defn send-balls
 []
 (doall
   (for [i (range number-of-balls)]
     (do
       (send-off (ball-agents i) bounce)))))


(send-off animator animation)
(send-balls)

OTHER TIPS

I think you don't need refs inside agents. Please see below for a working version with just agents. You can load the code eg. via load-file then simply issue start. A frame will pop up with the desired animation. It can be stopped by reset!ing the returned atom to false. You can have as you many independent animation frames as you wish by calling start more than once.

Hope that helps.

(import
 '(java.awt Color Graphics Dimension)
 '(java.awt.image BufferedImage)
 '(javax.swing JPanel JFrame))

(def width 1000)
(def height 1000)

(def number-of-balls 3)

(def rad 20)

(def origin-x 100)
(def origin-y 100)
(def box-height 500)
(def box-width 700)
(def min-borders {:x origin-x
                  :y origin-y})
(def max-borders {:x (+ origin-x box-width (* 4 rad))
                  :y (+ origin-y box-height (* 4 rad))})

(defn init
 [coord]
 (+ (rand-int (- (get max-borders coord) (get min-borders coord)))
    (get min-borders coord)))

(defn init-balls
  []
  (->> (repeatedly number-of-balls
                   #(array-map :x (init :x) :y (init :y)
                               :x-speed (rand-int 10)
                               :y-speed (rand-int 10)))
    (map agent)
    vec))

(defn update-coordinate
  [ball coord-key speed-key]
  (let [coord (get ball coord-key)
        speed (get ball speed-key)
        new-c (+ coord speed)]
    (if (<= (get min-borders coord-key) new-c (get max-borders coord-key))
      (assoc ball coord-key new-c)
      (assoc ball speed-key (- speed)))))

(defn paint-ball
  [bg x y]
  (doto bg
    (.setColor Color/red)
    (.fillOval x y rad rad)))

(defn render
  [g balls]
  (let [img (BufferedImage. width height BufferedImage/TYPE_INT_ARGB)
        bg  (.getGraphics img)]
    (doto bg
      (.setColor Color/white)
      (.fillRect 0 0 (.getWidth img) (.getHeight img))
      (.setColor Color/red)
      (.drawRect origin-x origin-y
                 (+ origin-x box-width) (+ origin-y box-height)))
    (doseq [b balls]
      (let [ball @b]
        (paint-ball bg (:x ball) (:y ball))))
    (.drawImage g img 0 0 nil)))

(defn bounce
  [ball running?]
  (when @running?
    (send-off *agent* bounce running?))
  (Thread/sleep 100)
  (-> ball
    (update-coordinate :x :x-speed)
    (update-coordinate :y :y-speed)))

(defn animation
  [panel running?]
  (while @running?
    (javax.swing.SwingUtilities/invokeAndWait #(.repaint panel))
    (Thread/sleep 100)))

(defn start
  []
  (let [running? (atom true)
        balls    (init-balls)
        panel    (doto (proxy [JPanel] []
                         (paint [g] (render g balls)))
                   (.setPreferredSize (Dimension. width height)))
        frame    (doto (JFrame.) (.add panel) .pack .show)]
    (doseq [b balls]
      (send-off b bounce running?))
    (future (animation panel running?))
    running?))

Your send (or send-off) function (in this case: bounce) should return the (new) state of the agents. This is fully described here.

There are a couple of problems with the code -

  1. As Maurits pointed out, bounce does not return the new state of the agent.
  2. There is no place in the bounce function where bounce is added to the action queue of the agent again. This is needed as the new coordinated need to be calculated again and again.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top