The examples in that chapter were all written in the syntax of clingo 3, which changed significantly with the release of clingo 4 (but has fortunately stayed the same in clingo 5, which is current as of 2025). All but one of them were straightforward to port, which I've done here. The existence of clingo-wasm also makes it possible to make interactive web demos.
For a full tutorial, please see the book chapter. This page just has the examples with a brief description of each one. For each example, there are three tabs. From right to left: the ASP code for the example; an answer set produced by running the solver (press "Solve" again to get a different one); and a visual rendering of that answer set as a level. You can edit the ASP code on the code tab if you'd like.
The first three examples generate mazes.
A common representation of a maze, borrowed from the maze-generation literature, is a tree. We have an n×n grid of maze cells, and each one has exactly one parent cell, which must be one of its four neighbors. Cells are visualized as connected if and only if they have a parent/child relationship.
This corresponds to the code from Fig. 8.1 (maze-core.lp
).
Hint: Try changing width to 10 or 50 to get larger mazes.
Click the "Solve" button to run the solver.
The above code does not guarantee that all cells in the maze are part of the
same tree, so they do not necessarily form what we normally think of as
a maze. We can add three lines of ASP code to the previous code, which adds a
requirement to be fully connected. This corresponds to adding the code from
Fig. 8.3 (maze-reach.lp
) to the code from the previous example.
Side note: this illustrates ASP's elaboration tolerance: When things go right, more requirements can be added by simply appending more lines of code, without editing the previous code.
Click the "Solve" button to run the solver.
Next we will add a style constraint: we will arbitrarily decide that we want
very "horizontal" mazes, with long left-to-right corridors and not many
vertical connections. This is a soft constraint, meaning that code from the
previous two examples takes priority: it's more important for the maze to fit
the basic definition, and to be fully connected, than it is for it to be
horizontal. But, all else being equal, we do prefer more horizontal mazes. In
this case, we implement the soft constraint using a #minimize
rule.
This corresponds to adding the code from Fig. 8.5 (maze-bias.lp
) to the
code from the previous two examples.
Technical note: This straightforward implementation is not efficient. It works fine at the default 5×5, but will grind to a halt if you increase the maze size much above that. In the interests of keeping the code as close to the book as possible, I will resist the urge to optimize it for now.
Click the "Solve" button to run the solver.
The next three examples generate levels for a dungeon-crawler game. The player enters at the top-left corner, and to clear the level, they need to bring a gem (G) to an altar (A) before exiting at the bottom-right corner.
A level is again an n×n grid. Two cells are said to be adjacent if they touch each other horizontally or vertically (we don't yet use this fact, but will use it for typical grid-world movement later). Every grid cell has at most one of the three sprites: wall, gem, or altar (it may also be empty). The altar and gem are limited to one per level each (the number of walls is not limited).
This corresponds to the code from Fig. 8.7 (level-core.lp
).
Technical note: The examples you generate below will probably never have any walls in them, even though there are answer sets with walls. I randomize the solving in this demo, but clingo is not really designed for random sampling, so the distribution can sometimes be strange (and can vary between versions of the solver). Two better solutions if you want more random sampling: 1) generate a lot of levels and just choose among them randomly (generating a lot of levels is fast), or 2) use xorro, which is designed to sample answer sets. Or, move on to the next example that adds style constraints.
Click the "Solve" button to run the solver.
Next we will add some style constraints. Unlike with the maze style constraints, these will be "hard" constraints, meaning that all generated levels must follow them.
This corresponds to adding the code from Fig. 8.9 (level-style.lp
)
to the code from the previous example. Again, the new style-constraint code
is simply concatenated to the existing core level code.
Click the "Solve" button to run the solver.
So far we have added some constraints about the layout, but have not yet guaranteed that the level is playable. Recall that we described gameplay as follows: "The player enters at the top-left corner, and to clear the level, they need to bring a gem (G) to an altar (A) before exiting at the bottom-right corner."
Therefore there are three reachability requirements: to get from the entrance to the gem, from the gem to the altar, and from the altar to the exit. We will implement this as a little state-machine simulation.
This corresponds to adding the code from Fig. 8.11 (level-sim.lp
)
to the code from the previous two examples.
Click the "Solve" button to run the solver.
There is one final example in the book chapter, in Section 8.6. Our simulation constraint above added a constraint that levels must be playable. But once we have that, a next step could be to add style constraints on the playability. For example, we may want the level to be playable, but not too easily playable: The minimum path through the level cannot be shorter than some number of steps. Unfortunately, this is not directly solvable in base ASP. It requires what my coauthor on this chapter, Adam Smith, calls quantifying over play. The version presented in our book chapter (which was adapted from his FDG 2013 paper) required a meta-programming pipeline that does not directly port to current clingo-wasm, so it is omitted here.