[plt-scheme] Ludum Dare entry done in Scheme

From: Henk Boom (lunarc.lists at gmail.com)
Date: Wed Feb 27 22:48:29 EST 2008

 Ludum Dare (http://www.imitationpickles.org/ludum/) is a
mostly-from-scratch 48-hour video game development competition. I
participated in a Ludum Dare  warmup competition last weekend, writing
my entry in mzscheme. I find that it's really easy to implement
features quickly in scheme, as the question is never "How can I
possibly do this?", instead it is "What is the most convenient or
appropriate way to do this?".

My entry was a shooter called "Black Box"
(http://www.imitationpickles.org/ludum/2008/02/25/black-box/) (source
is available in that post). Having been done in a time period of a
little over 48 hours (it was a warm-up, so the rules were relaxed),
the code is far from pretty. I'm interested in learning how to write
games like this in a more organized fashion, to make adding new types
of enemies easier. I'm also interested in knowing how to write
something like this in a functional style, as this implementation is
pretty fundamentally imperative. The game itself is just over 600
lines of code without libraries.

The game state is encapsulated in a function "make-game-scene" with
internal procedures defined for inner game logic. This has the result
that most of the game logic code has to physically be inside of this
function to be able to access other inner procedures.

The entities in my game are represented by a "body" struct:

(define-struct body (type collide!-proc draw-proc update!-proc pos
last-pos size))

The type field indicates whether the body is the player, an enemy, or
a player/enemy shot. pos is a vector representing the current position
of the body and last-pos represents its last position (used for
collision detection). The *proc values are procedures to call when
they collide or need to update or be rendered. They do not take the
body as an argument, so for example a body which flies in a straight
line is implemented like so:

(define (make-straight-shot player? initial-pos vel)
  (define (collide)
    (remove-body this)
      (make-spark (body-pos this)
                  (vect+ vel (rand-vect (/ (vect-magnitude vel) 8))))))
  (define (draw)
    (draw-body-polygon 4 this 0))
  (define (update dt)
    (cycle-pos! this (vect+ (body-pos this)
                            (vect* vel dt)))
    (when (out-of-bounds? this)
      (remove-body this)))
  ;-- local
  (define this
    (make-body (if player? 'player-shot 'enemy-shot) collide draw update
               initial-pos initial-pos straight-shot-size))

In this code, add-body and remove-body are procedures inside
make-game-scene which imperatively add and remove bodies from the list
of bodies. cycle-pos! is simply a macro which puts the current
position into last-pos and a new position into pos.

I found that in this case this organization seemed to work pretty well
at first, but as it got larger it became more and more awkward to put
everything in make-game-scene. I yearned for the freedom to organize
code by role instead of by the environment in which it needed to
reside. One solution to this problem would be (as I commented in the
code) to use the class.ss library to represent the objects in the
game. This would give me more freedom in my interfaces as well as let
me physically separate the different bits of code.

Also, I'm curious how to implement this with a functional approach.
Specifically, what should I do for dynamic dispatch? Would something
like this be a good idea?

; contains application-style update procedures, which take the body as
the first argument
(define-struct body-class (type collide-proc draw-proc update-proc))
; contains a reference to a class struct for dispatch. This struct is
minimal to reduce the overhead of allocation/construction.
(define-struct body (class pos last-pos size))

Several problems come to mind:
How would a body add/remove bodies to/from the scene?
If a body needs a persistent reference to a specific other body, how
do I maintain this reference if one frame's bodies are distinct
objects from the previous one's?

Any input is welcome.


Posted on the users mailing list.