[lisp-game-dev] post-mortem for CONS

David O'Toole dto1138 at gmail.com
Tue Mar 30 21:21:17 UTC 2010


#+TITLE: LGDC10 Postmortem

 *Tuesday March 30, 2010*

First let me thank everyone for participating in the 2010 Lisp Game
Design Challenge! I think the whole contest has been a success. With
ten registered entries (8 individuals and 2 teams), I think we'll have
a lot to discuss in the coming days. The LGDC also brought some new
people to the lispgames community, and we're continuing to build a
knowledge base for developing and delivering games in Lisp across
multiple platforms. For example the http://www.cliki.net/LispGameDesignChallenge
cliki registration page now has a big list of links to various libraries
and tools, and the IRC channel's ranks have swelled to an average
of over 30 people. I've read the postmortems mailed so far and I
think these documents will be a great contribution to the lispgames noosphere.

And now for my postmortem!

For my 7-day Challenge entry I implemented the core gameplay mechanics
and settings of a loosely Lisp-themed sci-fi combat game called CONS.
I'm classifying my entry as a failure because I did not finish a
complete game within the time limit. I will continue working on CONS
though, and I'm looking forward to fleshing out the design.

* What's lispy about my entry?

The player is a multi-segment Lisp list snake! See the design document
at http://dto.github.com/notebook/cons.html for details. In addition
some interesting "minilanguage" techniques were used for this game,
see the algorithms section below.

* Tools and technology

My entry is programmed in Common Lisp running on SBCL. I used my own
[[file:xe2-reference.org][XE2 game engine]], which is based on LISPBUILDER-SDL.
Image editing was
done with the GIMP, and program development with GNU Emacs and SLIME
running on Ubuntu. For music and sound I used varying combinations of
Hexter (a software Yamaha DX7 emulator), Audacity (digital audio
editor), Ardour (digital audio recording workstation), and
Milkytracker (MOD tracking application.)

* What went right?

 - The 7-day process is good for focusing on the core play mechanics,
   despite being a bit stressful at times.
 - My design called for five different level types plus an overworld
   map. This forced me to focus on improving XE2's random level
   generation framework, and led to some interesting new
   functionality. See the "interesting algorithms" section below.
 - Focusing on usability led to simplified controls---gameworld
   interaction uses only the arrow keys, Z, X, and Shift. The number
   one category of user feedback complaints about my games relate to
   controls, so this is a big win. I also made the controls
   user-configurable through an interactive spreadsheet screen, which
   is also joystick-compatible (with some glitches at the moment.)
   This is (finally) a port of my circa-2006 OO spreadsheet program
   "Cellmode". However, this was a time-sink, see next section.

* What went wrong?

My only real mistake was developing CONS as a 7-day thing. The design
is a bit larger than my recent games and only the skeleton was
finished by that time. Also, I spent an inordinate amount of time on
the input configuration screen, and on the music for the game, when I
should have focused on fleshing out the gameplay.

* What did you learn?

Don't attempt to port major features from an old codebase while in the
middle of a 7-day challenge. Although porting Cell-mode and creating
the configuration UI was unquestionably time well spent, I should have
waited until after my entry was complete to work on these features.

* What interesting algorithms or designs did i use?

There are two main points here, one relates to level generation and
the other to user interfaces. (The combination of both elements mean
we will soon have a user interface for level generation.)

Development on my 2010 7DRL challenge entry XIOTANK was troubled by
the issue of how to generate solvable puzzles while still randomizing
the layout. This is a hard problem, and occasionally-unsolvable levels
have been a criticism of XONG (see http://playthisthing.com/xong )
In XIOTANK the more elaborate combination-lock style puzzle was
unexpectedly hard to implement, and the result was still less than satisfying.
Late in development, I overemphasized combat to compensate for this,
and the result was (according to some players) an overly difficult game.

I realized I had a bunch of level generation tools but nothing to tie
them together. With some new functionality implemented in XE2 this has
changed.

First came support for randomly generating sentences (Lisp lists of
individual words, strings, or symbols) according to simple
context-free grammars.
http://en.wikipedia.org/wiki/Context-free_grammar

This is similar to L-systems except that
substitutions are not simultaneous.

Here's an example grammar for a fictional sci-mission:

:  (defparameter *test-grammar*
:    '((mission >> (at location please goal+ in exchange for reward))
:      (location >> mars zeta-base nebula-m corva-3)
:      (goal+ >> goal (goal and goal+))
:      (goal >> (defeat foe) (defend friend) (activate button)
(retrieve documents)
:       (collect mineral+))
:      (mineral+ >> mineral (mineral and mineral+))
:      (mineral >> endurium technetium molybdenum francium a-biosilicates)
:      (foe >> scanner biclops unique)
:      (friend >> transport skiff soldier scientist)
:      (unique >> zx-90 xioblade)
:      (reward >> money part)
:      (money >> 10000 20000 30000 40000 50000)
:      (part >> muon-pistol lepton-cannon ion-shield-belt)))

When "foe" is encountered for example, it is replaced with either
"scanner" "biclops" or "unique". (Sublists represent concatenation.)

A few example output sentences.

:  (AT MARS PLEASE COLLECT A-BIOSILICATES IN EXCHANGE FOR MUON-PISTOL)
:  (AT NEBULA-M PLEASE DEFEAT XIOBLADE IN EXCHANGE FOR MUON-PISTOL)
:  (AT CORVA-3 PLEASE RETRIEVE DOCUMENTS AND ACTIVATE BUTTON IN EXCHANGE FOR
:        ION-SHIELD-BELT)

These are unintepreted data, but it is straightforward to give them an
interpretation. I haven't yet done this with mission structures as in
the example, but I *have* made a working implementation of a simple
LOGO-like language,
to interpret randomly-generated LOGO programs like this:

:   9 :JUMP :PUSHLOC CONS-GAME::=BARRIER= :COLOR 90 :RIGHT 7 :DRAW
:POPLOC 9 :JUMP
:   :PUSHLOC CONS-GAME::=BARRIER= :COLOR 90 :RIGHT 7 :DRAW :POPLOC 9 :JUMP
:   :PUSHLOC CONS-GAME::=BARRIER= :COLOR 90 :RIGHT 7 :DRAW :POPLOC 9 :JUMP
:   :PUSHLOC CONS-GAME::=BARRIER= :COLOR 45 :RIGHT 7 :DRAW :POPLOC 10 :JUMP 90
:   :RIGHT :ORIGIN :PUSHLOC 45 :RIGHT 5 :JUMP CONS-GAME::=EXIT= :COLOR :DROP
:   :POPLOC 90 :RIGHT 20 :JUMP 90 :LEFT 25 :JUMP 90 :LEFT :PUSHLOC
:   CONS-GAME::=BARRIER= :COLOR 5 :DRAW :PUSH-COLOR :PUSHLOC 90 :LEFT
:   CONS-GAME::=BOMB-DEFUN= :COLOR 1 :JUMP 2 :DRAW :POPLOC :COLOR 5 :DRAW 90
:   :RIGHT 4 :DRAW 2 :JUMP 4 :DRAW 90 :RIGHT 10 :DRAW 90 :LEFT
:   CONS-GAME::=BARRIER= :COLOR 5 :DRAW :PUSH-COLOR :PUSHLOC 90 :LEFT
:   :RIGHT 4 :DRAW 2 :JUMP 4 :DRAW 90 :RIGHT 10 :DRAW 90 :RIGHT 6 :JUMP :PUSHLOC
:   CONS-GAME::=PURPLE-BRICK= :COLOR 5 :DRAW 90 :RIGHT 5 :DRAW 90
:RIGHT 2 :DRAW 1
:   :JUMP 2 :DRAW 90 :RIGHT 10 :DRAW 90 :LEFT CONS-GAME::=PURPLE-BRICK= :COLOR 5
:   :DRAW 90 :RIGHT 5 :DRAW 90 :RIGHT 2 :DRAW 1 :JUMP 2 :DRAW 90 :RIGHT 10 :DRAW
:   :POPLOC :PUSHLOC CONS-GAME::=BLUE-BRICK= :COLOR 5 :DRAW 90 :RIGHT 6 :DRAW 90
:   :RIGHT 5 :DRAW 90 :RIGHT 6 :DRAW 90 :LEFT CONS-GAME::=BLUE-BRICK= :COLOR 5
:   :DRAW 90 :RIGHT 6 :DRAW 90 :RIGHT 5 :DRAW 90 :RIGHT 6 :DRAW 90 :LEFT 1 :JUMP
:   :NOOP :PUSHLOC 90 :RIGHT 3 :JUMP :DROP-REACTOR :POPLOC :POPLOC :POPLOC
:   :DROP-DRONES

The twist on LOGO is that it's a Forth-style prefix notation.
I maintain a stack of data items for each world. The LOGO program
is generated according to a supplied grammar. When a non-keyword
symbol is encountered, push its value onto the stack. When a string or
number is encountered, push it onto the stack also. But when a
keyword-symbol is encountered, we invoke the correspondingly-named
method on the =world= object, which may pull arguments from the
stack. The operation :COLOR sets the "paint color", in this case the
type of game object to draw. The operations :PUSHLOC and :POPLOC save
and restore (respectively) the turtle's current position and
orientation. :DRAW and :JUMP move N squares in the current direction,
either painting (with :DRAW) or not painting
(:JUMP).

Here's part of a "reactor level" generation grammar:

:  '((world >> (rod-square
:  		:origin
:  		:pushloc
:  		45 :right
:  		5 :jump
:  		=exit= :color :drop
:  		:poploc
:  		90 :right
:  		20 :jump
:  		90 :left
:  		25 :jump
:  		90 :left
:  		:pushloc security-structure :poploc
:  		:drop-drones))
:    (rod-angle >> 90 45)
:    (rod >> (9 :jump
:     :pushloc
:     =barrier= :color
:     rod-angle :right
:     7 :draw
:     :poploc))
:    (rod-row >> (rod rod rod rod rod 10 :jump 90 :right))
:    (rod-square >> (rod-row rod-row rod-row rod-row))
:    (side-chamber >> (:pushloc
:  	    room3 90 random-turn
:  	    room3 90 :left
:  	    1 :jump
:  	    gun-maybe
:  	    :pushloc
:  	    90 :right
:  	    3 :jump
:  	    :drop-reactor
:  	    :poploc
:  	    :poploc))
:    (gun-maybe >> :noop :noop (=shocker= :color :drop))
:    (security-structure >> (room 90 :left
:  		  room 90 :left
:  		  room 90 :left
:  		  room 90 :right
:  		  6 :jump
:  		  :pushloc room2 90 random-turn room2 :poploc
:  		  side-chamber))
:    (random-turn >> :right :left)
:    (random-brick >> =purple-brick= =blue-brick=)

This is a big change for XE2. With turtle graphics it's easy to scale
and rotate drawing elements, and express their mututal arrangement in
both absolute and relative terms. But the drawing elements can be
anything, because the keywords in the randomly generated sentences can
trigger any method you choose to define---not just LOGO-like
operations. So all the other existing tools in XE2 (lines, boxes,
circles, plasma) are now tied together into a cohesive and extensible
framework for procedural content generation of all kinds, not just
levels. (I'm interested in seeing what can happen with procedural story
elements.)

A related development is the porting of cell-mode from Emacs Lisp into
Common Lisp. It's a sort of object-oriented spreadsheet program with
dataflow from left-to-right amongst cells of different types. Instead
of implementing a new grid structure, I made a new system called
"forms". One can build a spreadsheet UI out of game objects, or open an
editable spreadsheet view of a game world. Combine this ability to
hand-edit with the procedural generation tools, and we could have a
very powerful interactive gamesdev environment.

Thanks for reading everyone.




More information about the Lisp-game-dev mailing list