HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Getting functional Common Lisp code for handling state in a SDL animation

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
handlinganimationsdllispgettingcodeforstatefunctionalcommon

Problem

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)) 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)) 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)
:colo

Solution

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)

Code Snippets

(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-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))
(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))
(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)

Context

StackExchange Code Review Q#61689, answer score: 3

Revisions (0)

No revisions yet.