A puzzlescript file is divided into 8 sections:
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 ]
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.
[ > 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 ]
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:
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.
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.noundo
option in the preamble, since that option adapts the key hint message in the title screen.
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.
If you can think of an interesting use for diagonal directions, just let me know and I'll try add it back in.
Just because you can, it doesn't mean that you should, but if you want to do something realtime have a look here.
It's definitely possible, just experimental. See this document.
Definitely possible, just a bit half-baked. See this document.