Haskell embedded domain-specific language (eDSL) for the algorithmic construction of maps for the computer game "Doom"
GPL-3.0 License
Liquorice is a system for constructing maps for the computer game Doom by writing algorithms to describe the geometry. A Liquorice program is a series of instructions to move a virtual "turtle" or "pen", which define the walls, floors, rooms and monsters as it goes.
More specifically, Liquorice is an embedded domain-specific language (eDSL) within the pure functional programming language Haskell.
-- simple example, triangle (for orientation); unique texture per line
import Liquorice.Monad
import Liquorice.Render
main = buildWad "example1.wad" $ runWadL $ do
mid "ZZWOLF1"
draw 128 0
mid "ZZWOLF2"
draw 0 128
turnaround
mid "ZZWOLF3"
draw 128 128
rightsector 0 128 160
turnaround
step 64 32
thing
See examples/birds.hs, for an example program that generates a complete playable map. The map targets Doom 1 / The Ultimate Doom (map slot E2M8), and requires a doom engine with raised engine limits, such as Crispy Doom.
The generated PWAD, with nodes, ready to play: birds.zip
birds.hs is a re-implementation/transformation of "Bird Cage" for WadC.
Doom's engine source code was open-sourced in 1996, but you need a copy of the game's data files to use them. You can buy The Ultimate Doom and Doom II at Gog, amongst other places. Failing that, you could try FreeDoom, a free-content game for Doom engines.
Examples of powerful open source engines include Crispy Doom, Eternity Engine and GZDoom.
The commands that a typical Liquorice program will use are predominantly
monadic, and so make use of Haskell's "do-notation" for ordering. Internally,
the majority of these monadic functions are wrappers around pure equivalents
which transform an input Context
type into an output, e.g.:
xoff :: Int -> Context -> Context
xoff x c = c { paletteXoff = x }
place :: Int -> Int -> (Context -> Context) -> Context -> Context
place x y fn c = c & step x y
& fn
& step (-1 * x) (-1 * y)
The pure functions can be combined using the infix operator &
(from
Data.Function
), or the more usual composition operator (which reads
back-to-front). However, the monadic versions are probably more user-
friendly, and so the separate pure implementations might go away or
stop being explicitly exported at some point. We also probably need to
use the monadic versions if we want to introduce randomness or debug IO
in the middle of a program.
A series of monadic Liquorice statements are converted into a final Context
via runWadL
. A Context
is written to an output PWAD file via
buildWad :: FilePath -> Context -> IO ()
Internally, buildWad
first converts a Context
into an intermediate data
structure WadMap
, which closely resembles the binary structure of a PWAD.
(WadMap
itself is a specialisation of Wad
, imposing the presence of map
specific lumps)
Pros:
Cons:
The following three source files are the ones that you will want to import as modules to your Liquorice program:
Liquorice.hs
The main Context
definition and most basic pure operationsLiquorice/Monad.hs
monadic wrappers around the above, + some moreLiquorice/Render.hs
exports buildWad
for exporting a PWADThese are back-end implementation details:
Liquorice/Pure.hs
most of the "standard library", in pure functionsLiquorice/Line.hs
Line
type, line splitting and testsLiquorice/Wad.hs
Wad and WadMap definitions and binary serialisationTestMain.hs
HTF test harnessLiquorice was created by Jonathan Dowland and is distributed under the terms of the GNU Public License, version 3 (see COPYING).
The design of Liquorice is heavily influenced by Wad Language (WadC), which in turn owes a debt to LOGO.