Bird's-eye view of a PuzzleScript file

A puzzlescript file is divided into 8 sections:

Rules

See Rules 101 for an introduction to rules.

Rules are about pattern replacement. 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.

Let's look at the standard sokoban more closely.

[ > 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 one wanted to specifically search for crates that were not moving, one would type

[ > 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 can 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.

One 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

(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.

and never finish compiling.

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

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., 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 ]

Commands

Sometimes you want other things to happen than to just replace tiles with tiles. That's what commands are for.

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 say

late [ Player Shadow ] -> CHECKPOINT SFX3

If you wanted to play a sound effect as well.

You can combine commands with regular rules, if you want:

 [ > Player | Person ] -> [ < Player | Person ] message Woah, buddy, back off!

Here're all the 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 your do the RESTART command, they'll spawn here instead.
nosave
When this command is executed, the current state of the game will not be pushed to the undo stack. Undoing at a later point in the game will skip that frame.
One possible use of the function is to combine one input with another (for instance, pressing action then a direction to shoot a missile in this direction). In that case you don't want an UNDO to bring you back to the state where you wait for a direction so you should use NOSAVE when the direction is input. Another similar use case is when an input can be understood as undoing the previous action (e.g., entering a mode then exiting it). Finally, a possible use is to prevent animations (especially, randomized animations) forcing an UNDO state even when the player did not move.
Please do not use this command systematically to prevent players from ever undoing. Instead, use the noundo option in the preamble, since that option adapts the key hint message in the title screen.
restart
Same as pressing R
Win
Wins the level!
Message
Gives the player a message. So you can say things like "Message Hello World". Reads everything to its right, so it has to be the rightmost argument.
sfx0 ... sfx10
Sound effects banks - you associate these to sounds in the sounds section, and then you can trigger them in rules by mentioning them.

What stuff is hard to do?

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, just let me know and I'll try add it back in.

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.

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.

Extended Rigid Bodies

It's definitely possible, just experimental. See this document.

Randomness

Definitely possible, just a bit half-baked. See this document.