Sections of a PuzzleScript file

A puzzlescript file is divided into the following sections.

Rules

See Rules 101 for an introduction to rules.

Rules are about pattern replacement. In the simplest case there's a left side, and a right side. The engine looks for occurrences of the left side, possibly in many different orientations, and when it finds one, it replaces the contents with what's on the right. (There can also be rule prefixes and commands, but we'll get to those.)

Let's look at the standard sokoban example more closely.

RULES
=====		
[ > Player | Crate ] -> [ > Player | > Crate ]

Left-hand Side

The left-hand side specifies what the rule looks for. It's looking for two cells, side by side, one having a player, and one having a crate. There's an arrow next to the player, and it's pointing at the crate - that means that we are looking for a player that's trying to move towards the crate. Directional arrows are relative - if you want absolute directions, for gravity, for instance you can use absolute directions such as up, down, left, and right.

There is no arrow next to crate, this means that the pattern doesn't care whether or not its moving. If you wanted to specifically search for crates that were not moving, you would do it like this.

[ > Player | stationary Crate ] -> [ > Player | > Crate ]

If you want to search if two things overlap, just mention them side by side.

[ Player Spikes ] -> [ DeadPlayer Spikes ]

If one wanted to match any player movements, not just movements towards the crate, one could replace the arrow with the moving keyword.

[ MOVING Player | stationary Crate ] -> [ moving Player | moving Crate ]

Note that the compiler can infer that the crate should be made to move in the same direction to how the player is moving.

What if you want to search for somewhere there isn't a crate? You can use the no keyword.

[ < Player | no Crate ] -> [ < Player | Crate ] (leave a trail of crates behind you)

It's possible for a rule to search for several disconnected bits. For instance, you could have a bird that vanishes whenever the cat tries to jump up:

[ up Cat ]  [ Bird ] -> [ up Cat ] [ ]

It's possible to restrict rules to only operating in particular directions - for instance, the following removes anything that steps on lava:

down [ Mortal | Lava ]  -> [ Corpse | Lava ]

The direction down indicates what direction the pattern will be oriented.

You can also have ellipses in patterns, that allow for them to be variable length. Here's a lava gun that turns anyone in line with it into a Corpse:

 late [ LavaGun | ... | Mortal ] -> [ LavaGun | ... | Corpse ]

If the compiler was dumb, it would decompile this into something like this, and never finish compiling.

(92) late [ LavaGun | Mortal ] -> [ LavaGun | Corpse ]
(92) late [ LavaGun | | Mortal ] -> [ LavaGun | | Corpse ]
(92) late [ LavaGun | | | Mortal ] -> [ LavaGun | | | Corpse ]
(92) late [ LavaGun | | | | Mortal ] -> [ LavaGun | | | | Corpse ]
(92) ... etc. But fortunately it's smarter than that.

A technical point that might occasionally be relevant: ellipsis-based rules search from smallest to biggest.

Also, it's possible to use two ellipses in a single pattern, such as stuff like this.

(92) [ > Player | ... | Crate | ... | Crate ] -> [ > Player | ... | > Crate | ... | Crate ]

(Please be a little bit careful with using multiple ellipses in a single rule - it might get a bit slow).

Right-hand Side

If we had the following rule

[ > Player | > Crate ] -> [ > Player | Crate ]

it would tell us to remove the movement from crate. However, the following one would not change anything:

[ > Player | Crate ] -> [ > Player | Crate ]

The rule is - the only things that are removed are things that are referred to, and that goes for both movements and objects.

If you want to specify that an object of a particular type be removed, without referring to it on the left-hand side, you can use the no key word:

[ > Player |  ] -> [ > Player | no Wall ]

Prefixes

These are some things you can put at the beginning of a rule, before the left-hand side.
Directions
The possible rule direction prefixes are up, down, left, right, horizontal, vertical, orthogonal. Use these to confine pattern matching to only those directions. If no direction prefix is specified, the default is all four.
+
The rule is added to the previous rule in a rule group. See execution order for more detail.
global
The rule is executed without regard for the radius setting in the prelude.
late
The rule is added to a special set of rules that are executed after the movement phase.
once

The standard defined behaviour per the PuzzleScript documentation is that "Each rule gets applied in turn as often as it can be". A rule with a `once` prefix simply executes once instead. Even if "something happens", the rule is executed just the once.

This turns out to useful in situations where after running the rule, the result is such that the rule would still apply. Without this feature, it's necessary to use movement flags or lay down temporary objects to achieve the same effect. There is a new game Bridges to show it in use. Here is how the code looks (from Bridges).

 Edit// calculate a count for each cell, using the once prefix so the rule applies only once
[ cell ] -> [ cell c0 ]
up    once [ cell count | singley ] -> [ cell count1 | singley ]
down  once [ cell count | singley ] -> [ cell count1 | singley ]
left  once [ cell count | singlex ] -> [ cell count1 | singlex ]
right once [ cell count | singlex ] -> [ cell count1 | singlex ]
up    once [ cell count | doubley ] -> [ cell count2 | doubley ]
down  once [ cell count | doubley ] -> [ cell count2 | doubley ]
left  once [ cell count | doublex ] -> [ cell count2 | doublex ]
right once [ cell count | doublex ] -> [ cell count2 | doublex ]
random
This prefix works not on a single rule but on a rule group. It looks at all possible applications of all rules in that group, and picks one at random.

There are also some other ways to do randomness. See Randomness for a detailed explanation.

rigid
A tag that gets applied to a rule, so that all the members of its rule-group must move if any do, or the entire group is disabled for that turn. See Extended Rigid Bodies for a detailed explanation.
Tags
The rule is expanded for each member of the tag value set, see tags.

Commands

Sometimes just replacing tiles with tiles is not enough, you want other things to happen. That's what commands are for. They go after the right-hand side.

As an example, you can install a checkpoint, so that when people hit reset they're taken back to this point rather than the start of the level:

late [ Player SavePoint ] -> checkpoint

In several games, it's important that if the player can't move, nothing else can happen either - so that you can't make time tick forward by repeatedly walking into walls. There's a prelude setting for this, require_player_movement, but you can also simulate it as follows.

[ Player ] -> [ Player Shadow ]
late [ Player Shadow ] -> cancel
late [ Shadow ] -> [ ]

At the start of the turn, you put a counter on the square the player's in, and if they're still together after the movement phase, then cancel the whole move.

You can combine several commands together, so you could do this if you wanted to play a sound effect as well.

late [ Player Shadow ] -> checkpoint sfx3

You can have as many as you like, but if any command has a text argument, it must the the last on the line because it will swallow the rest.

You can have combine commands with regular rules, if you want.

[ > Player | Person ] -> [ < Player | Person ] message Woah, buddy, back off!
[ > Player | Person ] -> [ < Player | Corpse ] again message Working on it...

Available Commands

again
Means that there'll be a small pause after this turn, after which another turn will be fired off, with no player input - a way of doing non-interactive animations and other fun things. You can control the time between frames with the again_interval prelude switch.

 Editrandom [ no Sheep ] -> [ Sheep ] again

Again is a moderately intelligent command - it only triggers if changes happen - so it won't loop infinitely if it's doing nothing.

cancel
Cancels the entire turn, and reverts back to how it was at the start of the turn. basically "forget everything and pretend nothing happened".
checkpoint
Saves the game state. Now, whenever the person presses R to restart, or you do the RESTART command, they'll spawn here instead.
gosub Check Win
Begins executing rules in the named subroutine. Execution continues until the end of the subroutine, and then returns to the next rule after this one.
 Edit
		// mainline for game
[ player ] -> gosub player move
[ player ] -> gosub show lit
[ player ] -> gosub check win

subroutine player move
// rules for moving player

subroutine show lit
// rules for updating the board

subroutine check win
[ player ] -> [ player won ]
[ cell no lit ][ player won ] -> [ cell ][ player ]
[ won ] -> sfx1
[ won ] -> status You win! Press R to play again, or Esc to select level.
goto
Immediately switches to a different level. This does not count as a win and cannot be undone by the player. Everything to the right of the Goto command is interpreted as a section name.

			 Editlate [ Player One ] -> GOTO Section 1
late [ Player Hub ] -> GOTO Hub
link PuzzleScript Next
Activates a link from this level to another when the player is on a linking object. See this page for more information.
log The Player is next to a Crate PuzzleScript Next
Sends a logging message with a clickable line number to the console. Reads everything to its right, so it has to be the rightmost argument.
[ > Player | Crate ] -> [ > Player | > Crate ] 
late [ Player | Crate ] -> log The Player is next to a Crate
late [ Player | Wall ] -> log The Player is next to a Wall
message You're dead. Press Undo or Restart to try again.
Gives the player a message. Reads everything to its right, so it has to be the rightmost argument.
nosave
Forces the engine to NOT save the current state to the undo stack. Useful if you still want to process turns where not much is happening, but don't want them to flood the undo stack.
prelude flags (zoomscreen, intervals, etc.)
If runtime_metadata_twiddling is enabled, there are some prelude flags you can invoke as a command to change them at runtime. More info here.
quit
If you add quit after a rule, it'll behave as if the player pressed Escape: meaning it will quit out of the level. It is intended to allow mouse-controlled games to click a button to back to the title screen or level select without using the keyboard.
restart
Same as pressing R
sfx0 ... sfx10
Sound effects banks - you associate these with sounds in the sounds section, and then you can trigger them in rules by mentioning them as commands.
status You Win! Press R to play again.
Displays the text in the status line, if enabled the setting in the prelude. The text stays there until it is cleared.
win
Wins the level! After a short pause, it moves on to the next level. If there is a goto level command, it goes to that level instead.
undo
Undoes a turn. Unlike cancel, it will not just cancel the current turn, but will also undo the previous one, just like what would happen if you pressed Z.

Not Really Rules

There are a few things that go in the Rules section but are not really rules.

startloop
Marks the start of a set of rules that can be executed multiple times. Check out the execution order docs for more information (specifically about rule-groups and loop construction).
endloop
Marks the end of a set of rules that started with startloop.
subroutine Check for Dead Player
Marks the start of a set of rules that can be called by the gosub command. The subroutine ends at the next subroutine or the end of the rules section.

Stuff That is Hard to Do

Real-time behaviours

Just because you can, it doesn't mean that you should, but if you want to do something realtime have a look here.

Counting

If you had a rule that said "if there are two walls next to the player, destroy it", that would be tough. You can do it with tricks, like putting temporary counters down as markers, but it's not hooked up that way.

Diagonal Directions

If you can think of an interesting use for diagonal directions, please do let me know.

Extended Movements

Movements that play out over many turns. You can't attach variables to states - all movements are cleared out at the end of turns. You end up having to have a different object for each frame, and it gets messy. Having an platform character that jumps into 4 tiles into the air, I haven't figured out how to do in an elegant way. But there might be a way.