While trying to figure out CLIM, I ran into this example program. It's a simple maze game. The author claims to have tested it in LispWorks (and even has #+Genera
in there, implying that this program would work on a real Lisp Machine), but I'm trying to get it working in SBCL with McCLIM.
Under SBCL/McCLIM, the window draws, but nothing visible happens when you press the movement keys. Non-movement keys cause text to be entered into the pane with the game instructions.
I figured out that the game command keys are changing the game's internal state, so the only problem is that the screen does not update.
Then I realized that you couldn't write code to redraw the maze from the scope of the code that implements the commands. All the methods that draw receive a stream
argument from CLIM, which must be passed to the graphics primitives. For example:
(defun draw-stone (stream x y cell-width cell-height)
(let ((half-cell-width (/ cell-width 2))
(half-cell-height (/ cell-height 2)))
(draw-ellipse* stream
(+ (* x cell-width) half-cell-width)
(+ (* y cell-height) half-cell-height)
half-cell-width 0
0 half-cell-height
:ink +red+)))
But the code that handles keystrokes receives no stream
argument:
(defmacro define-stone-move-command (name keystroke dx dy)
`(define-maze-frame-command (,name :keystroke ,keystroke) ()
(let ((maze-array (maze-array *application-frame*)))
(move-stone maze-array ,dx ,dy)
(check-for-win maze-array))))
What I ended up having to do is to save the stream
argument from the first (and only) call to draw-maze-array
to a global variable, so that I could add update code to the define-stone-command
macro as follows:
(defmacro define-stone-move-command (name keystroke dx dy)
`(define-maze-frame-command (,name :keystroke ,keystroke) ()
(let ((maze-array (maze-array *application-frame*)))
(move-stone maze-array ,dx ,dy)
(check-for-win maze-array)
(draw-maze-array *application-frame* *maze-stream*))))
This slight alteration gives the desired behavior on SBCL with McCLIM, but this doesn't seem right, however. After all, the author claimed that the code worked fine on LispWorks. I have a few questions:
- Can somebody who has LispWorks confirm that this program works as-is on LispWorks?
- Does my alteration to the code make it fail on LispWorks?
- What is the accepted way to handle screen updating in CLIM applications?
Drawing the maze in the command is not the right approach. Putting a maze-stream into a global variable is also bad. ;-)
The display pane has a
:display-function
. The idea is that after a command the whole application frame gets updated automagically. For example for:display-time
:command-loop
, the display pane would be updated automagically, after a command runs. There are other ways to update panes, but in this case a keystroke runs a command and then the top-level-loop would just call the display-function for each applicable pane. The default toplevel-loop reads a command (via mouse, command lines, keystrokes, ...), executes it and updates the application frame - in a loop.The whole redisplay thing is extremely tricky/powerful. It allows from fully automagical redisplay mechanisms to extremely fine-grained control.
You can read about it here: CLIM 2 Spec. Note: there might be quite a bit difference between the spec and what implementations provide...