7
\$\begingroup\$

The following code draws a red rectangle bouncing between the borders of a white display. I'm not particularly happy with the update function:

(require 'lispbuilder-sdl)

(defun update-h-speed (state h-border top-left-border)
  (if (or (< (cdr (assoc 'h-coord state)) top-left-border) ;;if too left
      (> (cdr (assoc 'h-coord state)) h-border))       ;;if too right
      ;; negate the horizontal speed
      (acons 'h-speed (- (cdr (assoc 'h-speed state))) state)
      state))

(defun update-v-speed (state v-border top-left-border)
  (if (or (< (cdr (assoc 'v-coord state)) top-left-border) ;;if too high
      (> (cdr (assoc 'v-coord state)) v-border))       ;;if too low
      ;; negate the vertical speed
      (acons 'v-speed (- (cdr (assoc 'v-speed state))) state)
      state))

(defun update-coords (state)
  (acons 'h-coord (+ (cdr (assoc 'h-coord state))
               (cdr (assoc 'h-speed state)))
       (acons 'v-coord (+ (cdr (assoc 'v-coord state))
                  (cdr (assoc 'v-speed state))) state)))

(defun update (state width height size)
  (let ((h-border (- width (/ size 2)))
    (v-border (- height (/ size 2)))
    (top-left-border (/ size 2)))
    (update-coords
     (update-h-speed
      (update-v-speed state v-border top-left-border) h-border top-left-border))))

(defun game (&optional (width 320) (height 240) (speed 2) (size 20))
  (let ((decoy (pairlis (list 'h-coord 'v-coord 'h-speed 'v-speed)
            (list (/ width 2) (/ height 2) speed speed))))
    (sdl:with-init ()
      (sdl:window width height :title-caption "My game")
      (setf (sdl:frame-rate) 60)
      (sdl:with-events ()
    (:quit-event () t)
    (:key-down-event (:key key)
             (when (sdl:key= key :sdl-key-escape) (sdl:push-quit-event)))
    (:idle ()
           ;; update state
           (setq decoy (update decoy width height size))
           (sdl:clear-display sdl:*white*)
           ;; draw the rectangle according to the updated state
           (sdl:draw-box (sdl:rectangle-from-midpoint-*
                  (cdr (assoc 'h-coord decoy)) (cdr (assoc 'v-coord decoy)) size size)
                 :color sdl:*red*)
           (sdl:update-display))))))

In fact, the big-bang function in Racket made such things quite easy. With big-bang, I usually created a structure to hold every object (just coordinates and speeds of the rectangles) and modified each part of the structure with separate functions.

Please note that this is not a question about making this animation work.

1) I would like to get advice about, how to refactor such programs to make the update function more functional, so that it is easier to expand (add new features) and debug.

2) I'm particularly confused about whether I should prefer special variables to hold the structures, or prefer to create them locally as is the case. Do you prefer one over the other during different cycles of programming (like development and distribution)?

Note: I preferred an assoc list in place of a hash table, because of the few number of pairs, and the acons enables a sort of history feature, if this thing will ever going to work.

\$\endgroup\$
1
\$\begingroup\$

(This answer might be a bit off base from what you're looking for, but I think you'll find it interesting.)

The closest thing to what you appear to be looking for in a functional-style that I can think of is (Arrowized) Functional Reactive Programming.

In essence you'd define an arrow (like a function) that takes your user's input as an argument and returns a graphics object for display as its output. This would then be applied like a filter to an input sequence to generate an output graphic sequence. (You'd pass the arrow to a function which would call it with the input and take the output to display.)

                    +-------+
(lazy input seq) -> | arrow | -> (graphic seq)
                    +-------+

The paper Genuinely Functional User Interfaces shows examples using this technique (and gives a better explanation); it also has a paddle ball example that seems close to what you're working on.

In the Haskell implementation linked above, state is maintained under the hood via circular definitions (i.e. black magic), but it can be hacked-in using internal state in the arrow implementations themselves; I've been able to put something together like this in Clojure (using closures in a generator-esque setup for the "arrows", e.g. stepper, integral).

When using it, your code would probably look something like this (modified from an example in the paper):

(defun draw (graphic)
  ; implement your sdl drawing stuff here
  )

(defun make-squarrow (g-width g-height s-width s-height speed)
  ; arrow would be a macro you'd have to write
  (arrow (input)
    (h-coord <- integral -< h-speed)
    (v-coord <- integral -< v-speed)
    (let h-bounce = (or (< h-coord 10) (> h-coord (- g-width 10))))
    (let v-bounce = (or (< v-coord 10) (> v-coord (- g-height 10))))
    (h-speed <- stepAccum speed -< (ebind negate h-bounce))
    (v-speed <- stepAccum speed -< (ebind negate v-bounce))
    (returnA -< (square h-coord v-coord s-width s-height))
    ))

(defun game (&optional (width 320) (height 240) (speed 1))
  (let ((sq (make-squarrow width height 20 20 10)))
    (sdl:with-init ()
      (sdl:window width height :title-caption "My game")
      (setf (sdl:frame-rate) 60)
      (sdl:with-events ()
        (:quit-event () t)
        (:key-down-event (:key key)
          (draw (sq key)))
        (:idle ()
          (draw (sq nil)))))))

You'd probably have to implement quite a bit to get going with this though -- I don't know if there are any Common Lisp libraries for (A)FRP.


Disclaimer: I've only been looking into AFRP, I've not done anything with it myself (besides tinker), so the details here might be a bit off.

\$\endgroup\$
  • \$\begingroup\$ Thank you, I'll check the paper and get back. Bu the way, did you see the last edit, which was just 2 minutes before your submit? \$\endgroup\$ – barerd Sep 3 '14 at 17:24
  • \$\begingroup\$ @barerd I have now ;) I'd glanced at that and the edit 2 hrs before it, but hadn't read them in detail. What you have now looks a bit closer to what I was talking about (I think). \$\endgroup\$ – paul Sep 3 '14 at 18:03
  • \$\begingroup\$ @barerd Another thing you might look into is the state monad, which is (in case you're not familiar) a purely functional way to simulate mutability, although you'll still need a mutable variable for cycling the state back through the monad. I think I prefer the local let-binding like you have it now, over a global, since not everything should need to know about the game's internal state. \$\endgroup\$ – paul Sep 3 '14 at 18:06
  • \$\begingroup\$ Well, I studied all of them. Now I see how it helps to learn different languages. I think I can apply some of these ideas for a more modular implementation. Thank you for the guidance. \$\endgroup\$ – barerd Sep 4 '14 at 5:34
  • \$\begingroup\$ @barerd Happy I was able to be of help! I'm not sure that my answer deserves to be the accepted one, but if that's how you feel about it then that's that :) (I won't be surprised if you find a better answer later on though!) \$\endgroup\$ – paul Sep 4 '14 at 17:38
3
\$\begingroup\$

I would use CLOS for that. The game state is an CLOS object then. If the thing needs a history, then I would add the history to the CLOS object.

Named slots of something in the history of Lisp:

  • assoc lists (ca. 1960)

  • hash-tables

  • structures (70s)

  • classes (70s/80s...)

If you look at your update a few things don't look nice:

  • progn is not needed
  • way to many SETQs, construct a return value instead
  • the use of an association list, where a CLOS object should have been used
  • no explicit return value

(defun update-coords (state)
  (acons 'h-coord (+ (cdr (assoc 'h-coord state))
                     (cdr (assoc 'h-speed state)))
         (acons 'v-coord (+ (cdr (assoc 'v-coord state))
                            (cdr (assoc 'v-speed state)))
                state)))

Sometimes I would want to get rid of the nesting in above function.

(defun update-coords (state)
  (list* (cons 'h-coord (+ (cdr (assoc 'h-coord state))
                           (cdr (assoc 'h-speed state))))
         (cons 'v-coord (+ (cdr (assoc 'v-coord state))
                           (cdr (assoc 'v-speed state))))
         state))

Alternatively you can use the backquote notation. , evaluates. ,@ evaluates and splices the result in.

(defun update-coords (state)
  `((h-coord . ,(+ (cdr (assoc 'h-coord state))
                   (cdr (assoc 'h-speed state))))
    (v-coord . ,(+ (cdr (assoc 'v-coord state))
                   (cdr (assoc 'v-speed state))))
    ,@state))

I would add Practical Common Lisp by Peter Seibel to the reading list. It teaches a larger subset of more idiomatic Common Lisp than for example On Lisp.


With CLOS the code looks much simpler. Example without history:

(defclass game-state ()
  (h-coord v-coord h-speed v-speed))

(defmethod update-coords ((state game-state))
  (with-slots (h-coord h-speed v-coord v-speed)
      state
    (incf h-coord h-speed)
    (incf v-coord v-speed))
  state)
\$\endgroup\$
  • \$\begingroup\$ Thank you. I'm glad that the question catched your attention as I regularly visit your answers to improve my lisp programming. But I don't know CLOS yet. I don't think it would be harder, but I'm studying "A gentle introduction to symbolic computation", "On Lisp", "Land of Lisp" and "Common Lisp - The Language" piece by piece. Currently, I'm exercising only with lists to grasp the essence of Lisp. Could you very mind to check the updated update function and comment on it? \$\endgroup\$ – barerd Sep 4 '14 at 10:36
  • \$\begingroup\$ Excellent. I also learned that CLOS is so easy to integrate to the Common Lisp I learned so far. By the way, Practical Common Lisp flies compared to other books. \$\endgroup\$ – barerd Sep 4 '14 at 12:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.