Keep it simple.
A simple interface is better than a simple implementation.
Complete is better than simple.
Consistent is better than complete.
Correctness trumps all. Incorrectness is simply not allowed.
If the interface is hard to use, it is probably a bad idea.
If the interface is easy to use, it may be a good idea.
A weak language makes it hard to write good code, but a powerful
language makes it easy to write bad code that looks good.
The first thing that comes to mind is not always the best
thing.
When performance matters, get your declarations right.
There is always another way to do it, and maybe a better one.
Lists aren’t the only data type.
The right thing and 2 shillings will get you a cup of tea.
Friday, May 10, 2024
The Way of Lisp or The Right Thing
Monday, May 6, 2024
Motion without Integration
A game where things move around has equations of motion that describe how objects move. We need to solve these to produce the locations of the objects as a function of time, and we have to do it in lock step real time. Fortunately, we usually don’t need highly realistic models or highly accurate results. Game phyiscs, while inspired by real world physics, can be crude approximations.
If the velocity of an object changes over time, we need to integrate the velocity to determine the position. This is typically done with forward Euler integration: the current velocity is multiplied by a small Δt and this is added to the current position. In other words, we assume the object moves linearly over the amount of time represented by Δt. If Δt is small enough, the linear segments of motion will approximate the actual curve of motion.
In a game, we use the game loop, which runs every few milliseconds, as our source of time. We subtract the current time from the previous time the game loop was run to obtain our Δt. We update each object’s state using forward Euler integration over Δt.
Our game loop keeps track of the last time it was run and subtracts
that from the current time. It runs entity-step! on
each entity in the game, passing in dticks.
(let ((start-tick (sdl2:get-ticks)))
(let ((last-tick start-tick))
(loop
(let* ((tick (- (sdl2:get-ticks) start-tick))
(dticks (- tick last-tick)))
(map nil (lambda (entity)
(entity-step! entity dticks))
entities)
(setq last-tick tick)))))
entity-step! performs our forward Euler
integration:
(defun entity-step! (entity dticks) (incf (position entity) (* (velocity entity) dticks)))
Assuming the game loop runs reasonably frequently, the position of the entity will be a reasonable approximation of the integral of the velocity. If the game loop gets delayed for some reason, e.g. garbage collection, the corresponding increase in the value of dticks will make up for it.
But sometimes we have the fortune of having a closed form solution to the time integral. If this is the case, we can compute the entity’s state directly and we don’t need to integrate the state changes on every iteration of the game loop. Let me give two examples.
In the platformer game, there are potions that the player can take to
restore his health. A potion appears as a vial that floats above
the ground. A “bobbing” effect is achieved by changing
the height above the ground in a time varying manner. Now we could
implement this by having the game loop modify the y position of the
potion on each iteration, but there is a closed form solution. We
can compute the y position at any time by adding a constant base y
position to a factor of the sine of the current time. This is
easily done by specializing the get-y method on
potions:
;;; Make potions bob up and down.
(defmethod get-y ((object potion))
(+ (call-next-method)
(* 5 (sin (* 2 pi (/ (sdl2:get-ticks) 1000))))))
call-next-method invokes the next most specific
method — in this case, the get-y method of the
entity class, which returns the base y position. We add a sine wave
based on the current time.
This is only thing we need to modify to make a potion bob up and
down. Values dependent on the y position, such as the attackbox,
will bob up and down with the potion because it uses
the get-y method to determine what the potion is.
A second example is cannonball motion. Cannonballs travel linearly
away from the cannon at a constant velocity. Rather than stepping
the cannonball by adding its velocity to its position on each
update, we specialize the get-x method on cannonballs
(defmethod get-x ((cannonball cannonball))
(+ (call-next-method)
(* (get-speed cannonball)
(- (sdl2:get-ticks) (get-start-tick cannonball)))))
If we specialize both the get-x and get-y
methods we can easily get objects to trace out any desired parametric
curve, like a Lissajous figure.
Saturday, May 4, 2024
Animation: Two Methods
Animated sprites are much cooler that static ones and really make your game come to life. They are pretty easy to implement, but there are a couple of ways to implement them, and I've seen several projects use the less optimal strategy.
The less optimal strategy is straightforward. We keep a time counter in the animated entity and increment it every time we update the entity. When the counter exceeds the amount of time for an animation frame, we advance to the next frame and reset the ticker.
(defclass entity ()
((animation-ticker :initform 0 :accessor animation-ticker)))
(defun entity-step! (entity dticks)
(incf (animation-ticker entity) dticks)
(when (> (animation-ticker entity) ticks-per-frame)
(advance-to-next-frame! entity)
(decf (animation-ticker entity) ticks-per-frame)))
Now this works, but the reason it is less optimal is that it involves maintaining time varying state in the entity. Recall that the game has two primary threads running: a render thread which runs 60 times a second and draws the game on the screen, and an game loop thread which runs maybe 200 times a second and updates the state of the entities in the game. The point of this is to separate and abstract the drawing of the game from the running of the mechanics of the game. By using this less optimal method, we involve the game loop thread in maintaining state that is used only by the render thead. To anthromorphize, the entity “knows” that it is animated and is taking an active role in keeping things moving.
A better way is to put the responsibility of animation solely within the render thread by having it select which animation frame to display when it draws the sprite. The sprite contains a vector of animation frames and a timestamp at which the animation was started. When redrawing the sprite, the elapsed time is computed by subtracting the start timestamp from the current time, and the frame to display is computed by dividing the elapsed time by the time per frame modulo the length of the frame vector.
(defclass animation ()
((start-tick :initform (get-ticks) :reader start-tick)
(frames :initarg frames :reader frames)))
(defun display-animation! (animation)
(let* ((elapsed-time (- (get-ticks) (start-tick animation)))
(absolute-frame (floor elapsed-time ticks-per-frame))
(frame (mod absolute-frame (length (frames animation)))))
(display-frame! (svref (frames animation) frame))))
This way of doing it no longer needs time varying state, and we’ve abstracted the display of the animation from the handling of the entity’s state. The entity no longer has to “know” that it is animated or do anything about it. There no longer has to be communication between the game loop thread and the render thread on every animation frame. This could be important if the render thread is running on a different machine than the game loop thread. If the game loop thread lags (maybe because of garbage collection or entering the error handler), we still get smooth animation.
Tuesday, April 30, 2024
Statements and Expressions
In some languages, like Lisp and OCaml, every language construct is an expression that returns a value. Other languages, like Java or Python, have two kinds of language constructs: expressions, which combine compositionally and which have return values, and statements, which combine sequentially and which have no return values and thus must operate by side effect. Having statements in your language needlessly makes things more complicated, but language designers seem to want to go much further and add complexity that just seems capricious.
You cannot usually use a statement in a context where an expression is expected because there is no return value. You can use an expression where a statement is expected by simply discarding the return value. This means there are two kinds of contexts. A sane designer would provide a way to switch between statement and expression contexts, but language designers typically omit these.
Language constructs, such as binding or iteration, must be provided as statements or expressions or both. Language designers seem to randomly decide which constructs are statements and which are expressions based on whims and ease of compiler implementation. If you need to use a construct, but aren’t in the right kind of context, you need to switch contexts, but without a way to do this, you may have to rewrite code. For example, if you have a subexpression that could raise an exception that you want to handle, you’ll have to rewrite the containing expression as a series of statements.
A sane language designer would use the same
syntax for the same construct in both expression and statement form,
but typically the construct will have a very different syntax.
Consider sequential statements in C. They are terminated by
semicolons. But sequential expressions are separated by commas.
Conditional statements use if/else, but conditional expressions
use ?:. When refactoring, you cannot simply move code between
contexts, you have to change the syntax as well.
Writing a function adds a new expression to the language. But function bodies aren’t expressions, they are statements. This automatically destroys referential transparency because you cannot substitute the function body (statements) at the call site (expression context).
try/catch is a nightmare. Usually you only
get try/catch as a statement, not an expression, so you
cannot use exception handling in a subexpression. You cannot
get a value out of a try/catch without a side
effect. Throw, on the other hand, throws a value, so
it could throw to an expression context, except
that catch is a statement.
Having two kinds of language constructs and two different contexts in which you can use some of them but not others, and different syntaxes depending on the context just makes it that much more difficult to write programs. You have to keep track of which context is current and which syntax to use and constantly switch back and forth as you write a program. It would be easy for a compiler to introduce the necessary temporaries and rewrite the control flow so that you could use all language constructs in either context, but language designers don’t bother and leave it up to the programmer to do it manually.
And all this mess can be avoided by simply making everything be an expression that returns a value.
Thursday, April 25, 2024
State Machines
One of the things you do when writing a game is to write little state machines for objects that have non-trivial behaviors. A game loop runs frequently (dozens to hundreds of times a second) and iterates over all the state machines and advances each of them by one state. The state machines will appear to run in parallel with each other. However, there is no guarantee of what order the state machines are advanced, so care must be taken if a machine reads or modifies another machine’s state.
CLOS provides a particularly elegant way to code up a state
machine. The generic function step! takes a state
machine and its current state as arguments. We represent the state
as a keyword. An eql specialized method
for each state is written.
(defclass my-state-machine ()
((state :initarg :initial-state :accessor state)))
(defgeneric step! (state-machine state))
(defmethod step! ((machine my-state-machine) (state (eql :idle)))
(when (key-pressed?)
(setf (state machine) :keydown)))
(defmethod step! ((machine my-state-machine) (state (eql :keydown)))
(unless (key-pressed?)
(setf (state machine) :idle)))
The state variables of the state machine would be held in other slots in the CLOS instance.
One advantage we find here is that we can write an :after
method on (setf state) that is eql
specialized on the new state. For instance,
in a game the :after method could start a new animation
for an object.
(defmethod (setf state) :after ((new-state (eql :idle)) (machine my-state-machine)) (begin-idle-animation! my-state-machine))
Now the code that does the state transition no longer has to worry about managing the animations as well. They’ll be taken care of when we assign the new state.
Because we’re using CLOS dispatch, the state can be a class
instance instead of a keyword. This allows us to create
parameterized states. For example, we could have
a delay-until state that contained a timestamp.
The step! method would compare the current time to the
timestamp and go to the next state only if the time has expired.
(defclass delay-until ()
((timestamp :initarg :timestamp :reader timestamp)))
(defmethod step! ((machine my-state-machine) (state delay-until))
(when (> (get-universal-time) (timestamp state))
(setf (state machine) :active)))
Variations
Each step! method will typically have some sort of
conditional followed by an assignment of the state slot. Rather
that having our state methods work by side effect, we could make
them purely functional by having them return the next state of the
machine. The game loop would perform the assignment:
(defun game-loop (game)
(loop
(dolist (machine (all-state-machines game))
(setf (state machine) (step machine (state machine))))))
(defmethod step ((machine my-state-machine) (state (eql :idle)))
(if (key-pressed?)
:keydown
:idle))
I suppose you could have state machines that inherit from other state machines and override some of the state transition methods from the superclass, but I would avoid writing such CLOS spaghetti. For any object you’ll usually want exactly one state transition method per state. With one state transition method per state, we could dispense with the keyword and use the state transition function itself to represent the state.
(defun game-loop (game)
(loop
(dolist (machine (all-state-machines game))
(setf (state machine) (funcall (state machine) machine)))))
(defun my-machine/state-idle (machine)
(if (key-pressed?)
(progn
(incf (kestroke-count machine))
#'my-machine/state-keydown)
#'my-machine/state-idle))
(defun my-machine/state-keydown (machine)
(if (key-pressed?)
#'my-machine/state-keydown
#'my-machine/state-idle))
The disadvantage of this doing it this way is that states are no longer keywords. They don’t print nicely or compare easily. An advantage of doing it this way is that we no longer have to do a CLOS generic function dispatch on each state transition. We directly call the state transition function.
The game-loop function can be seen as a multiplexed
trampoline. It sits in a loop and calls what was returned from last
time around the loop. The state transition function, by returning
the next state transition function, is instructing the trampoline to
make the call. Essentially, each state transition function is tail
calling the next state via this trampoline.
State machines without side effects
The state transition function can be a pure function, but we can
remove the side effect in game-loop as well.
(defun game-loop (machines states) (game-loop machines (map 'list #'funcall states machines)))
Now we have state machines and a driver loop that are pure functional.
Friday, April 19, 2024
Plaformer Game Tutorial
I was suprised by the interest in the code I wrote for learning the platformer game. It wasn’t the best Lisp code. I just uploaded what I had.
But enough people were interested that I decided to give it a once over. At https://github.com/jrm-code-project/PlatformerTutorial I have a rewrite where each chapter of the tutorial has been broken off into a separate git branch. The code is much cleaner and several kludges and idioticies were removed (and I hope none added).
Monday, April 1, 2024
You May Not Need That :around Method
I’ve seen this “anti-pattern” a few times in CLOS
code. A superclass ’super will have a
subclass ’sub and there will be a primary method
specialized to the superclass.
(defmethod foo ((instance super) arg) (format t "~&Foo called on ~s." arg))
Then I’ll see an :around method defined on the
subclass:
(defmethod foo :around ((instance sub) arg) (format t "~&Start foo...~%") (call-next-method) (format t "~&End foo.~%"))The intent here is clearly that code in the method specialized on the subclass is invoked “around” the call to the method specialized on the superclass.
But the :around qualifier is not necessary and
probably doesn’t do what is intended. If we remove
the :around qualifier, then the most specific primary
method will be the foo method specialized
on ’sub. And the (call-next-method)
invokation will chain up to the foo method specialized
on ’super. It will work as was likely intended.
:around methods are useful when
the superclass wants to run a method “around”
the subclass. :around methods are combined from least
specific to most specific — the opposite order of primary
methods — so that the superclass can wrap the call to the
subclass. An good example of where an :around method
would be handy is when you need to sieze a lock around the call to
the method. The superclass would sieze the lock in
an :around method that would run before any of the
subclass primary methods ran.
Ordinary chaining of methods doesn’t need
the :around qualifier. Just chain the methods.