Tips and Tricks

A General Guide

The documentation here refers to PuzzleScript, and for the most part it applies to the original PuzzleScript, PuzzleScript Plus and PuzzleScript Next.

Make sure you are familiar with the buttons at the top of the editor. Documentation provides a lot of information on how to make PuzzleScript games.

Learn the intricacies of PuzzleScript by exploring on your own and by analyzing the work of other people. The examples, loaded from the top of the editor, are great reference and training wheels but it is encouraged to check out more public PuzzleScript games and to “hack” them to explore the code. Most but not all games have a 'hack' link.

Don’t just look at it - you can edit, rebuild and introduce new objects at will. Partially breaking the game can expose what parts are responsible for what features and can help you understand the code of a cool game.

Code styling

The style of code can greatly help you and other people analyze and edit it, just like in any document.

Empty lines and rows of equal signs (===) can help separate significant game parts. Make pauses.

Comments, enclosed in parentheses (), can not only comment the code but also act as anchors for sections that you often want to scroll to. It is recommended to keep comments brief but the objects/aliases verbose and fully defined themselves, so they are meaningful without the need for comments.

Code can be “commented out” to disable objects / rules / levels / anything by adding parentheses around it. Use this temporarily while developing, remove the ones you don't need to avoid clutter.

Another good way to use comments is to briefly and succinctly explain what the code in a section does. This is good for when you have complicated or potentially confusing code and you know that you will likely have to modify that code later. Write the code for your future self to read, as it will save time that otherwise might be spent trying to figure it out.

Interface Essentials

RUN fully unloads, loads and compiles the game.

REBUILD only injects the code but leaves the state untouched. Rebuild generally works if you haven’t modified your collision layers (and by extension, haven’t added or removed any objects from the game).

If your game seems buggy, it’s recommended to try Running instead of Rebuilding but otherwise Rebuilding may be faster and it does not reset your progress in a level. Ctrl+Left click (⌘+Left click on Mac) on a level rebuilds the game and loads the level clicked on; this resets the state just as well as running does.

Many PuzzleScript errors and messages are interactive and have links to rules that caused the error.

Notes on Saving

SAVE will store your current project in the drop-down list labeled LOAD. Note that this list only stores a limited number of project states (currently 50 PuzzleScript Plus), and that continuing to save new projects will overwrite old entries. Unless you’ve saved the project text somewhere, they will be lost. It’s recommended to either

  • copy your project text and save it into a text file,
  • ditto, and then use version control such as Git, or
  • use the SHARE feature to save a copy of the project as a gist file in your GitHub account.
  • Rules

    Developing the Mechanics of a Game

    Two objects on the same layer cannot end up in the same tile. Don’t put objects into the same layer just because you feel like it, having a new layer for almost everything can help avoid problems that are hard to track down. Combinations with or have multiple objects in them and should only be thoughtfully included into collision layers.

    The game cycle goes like this:

  • The player presses a direction key
  • all Player objects are set to move that way and a new turn starts
  • non-late rules execute
  • the engine handles movement - this is the movement phase
  • late rules execute
  • rule commands are executed
  • if needed, the engine shows a message or loads a new level
  • and finally, the level is presented to the player and it all begins again.
  • All late rules execute after all non-late rules. You can mix the two in code and this can be easier to read but having all late rules follow non-late rules in code will likely prove less confusing when fixing bugs. Enabling verbose_logging in the prelude will show you which rules executed in order, which didn’t, and some more info. Clicking on line numbers will then take your cursor to them.

    Layers and Collision

    From documentation: The main purpose of layers is in resolving movements: two objects on the same layer cannot coexist in a single cell. The order of layers also determines in what order things are drawn. The Background layer is a special layer. [...]

    Normally, two objects can coincide in the same space. For example, a Throne and a Player can both exist on the same tile. [ Throne Player ] will check for this coincidence while [ Throne no Player ] will check for an empty throne and [ Throne ] will detect both cases (which is all of them). If the Player is further down the layers than Throne, it will look like they’re on the Throne (otherwise, they will hide under the Throne!)

    When two objects are in the same layer, the engine will make sure only one of them is present on a tile at once - for example, Player and Wall. If a Player moves into a Wall (whether by user input or by rules that force them to), the engine’s movement phase will only move that Player if the Wall is also leaving that tile (for example, if it can be pushed), otherwise, the Player won’t move. If a rule makes the Player appear on a tile with a Wall in it, the Player will replace the Wall, deleting it, regardless of whether the rule even mentioned Walls. If you want to make sure this doesn’t happen, you may specify no Wall.

    random [ action Player ][ no Wall no Player ] -> [ ][ Player ]
    (when X is pressed, teleport Player to a random spot that’s not a wall - this way, no walls will be deleted)
    

    Notes:

    Inverting a Clause: Temporary and/or Invisible Objects

    Example problem: If player is not next to a gem, they vanish.

    Checking for a player next to a gem is easy: [ Player | Gem ]

    However, checking for the opposite might deceive you: [ Player | no Gem ] will match any player that has a space next to them not occupied by a Gem. In fact, the only way it will not match anything is if every Player is completely surrounded by Gems. But we don’t want 4 Gems to surround the Player, we just need one for them not to vanish. Here’s how to actually do it:

    [ Player | Gem ] -> [ Player Temp | Gem ]
    [ Player no Temp ] -> [ ] (vanish)
    [ Temp ] -> [ ]
    

    Using a temporary object (can be defined as invisible or as visible), we can record and capture some pattern or phenomena and then check for its absence.

    Notes:

    Invisible objects or objects that immediately get cleared can be hard to debug. To make debugging easier, there are multiple ways to show where rules matched to the screen during development. The main development trick in showing an object is putting it at the bottom of the layers (gets drawn last) and resetting it just before use instead of right after. This can be done to the Temp object itself (by moving the “clearing” rule from the last to the first) or by creating a yet another object that is very clearly visible but only mimics the positions of the Temp object:

    [ Exclamation ] -> [ ] | [ Temp ] -> [ Temp Exclamation ]

    which is more useful if you are reusing the Temp object multiple times (not recommended). The extra Exclamation object can be completely removed from the game code (after debugging is done and clear visibility is no longer needed) without any changes to Temp or other parts of the game. These markers mark tiles, not objects! They will not automatically move with objects.

    “Sight” and Obstruction using Simple Raytracing

    Useful for applications like detecting if there’s a wall between an object and a target. Examples include laser beams and instant gravity (as opposed to animated gravity). You may encounter a rule like this in a game:

    [ Player | ... | Gem ] -> [ Player | ... | ]

    It checks for a Gem on the same row or column as a Player - this is often used as an analogy of the Player seeing the Gem (or vice versa if the Gems have eyes, perhaps). However, there are usually many objects in your game you don’t want to see through and the naive approach will not work.

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

    The engine does support two ellipses like this, but it still doesn't work because it will search for any tile in between that matches no Wall, which means that it will execute if even just one space in between is not a Wall. We want the opposite - your sight is blocked if even one wall is in the way. There are two ways to solve this: either by inverting a clause or by using a very minimal ray tracing method.

    PuzzleScript ray tracing is simple and generally the more useful method when dealing with sight and light, especially if the tiles the player can see should be presented to the player (for example, highlighted).

    Let’s first set up a few things; the objects:

    SeenV
    transparent
    SeenH
    transparent
    

    The aliases:

    Seen = SeenV or SeenH
    Obstruction = Wall or Crate (you can add more objects here that are obstructions)
    

    Don’t forget to include the Seen objects in separate new collision layers. The rules:

    late [
    Seen ] -> [ ]
    late [ Player ] -> [ Player SeenH SeenV ]
    late vertical   [ SeenV | no Obstruction ] -> [ SeenV | SeenV ]
    late horizontal [ SeenH | no Obstruction ] -> [ SeenH | SeenH ]
    

    You should be able to use Seen in any “late” rule following this section (for example, the player loses when seen or hit by a laser) or in the normal (non-late) rules of the next turn (for example, checking if the player can move from the new position).

    Notes:

    Counting objects; Checking for global absence; doing something up to N times

    Example problem: when there are less than 3 gems in a level, remove all walls.

    Example solution: The trick is to limit the amount of times a rule runs.

    random [ Gem no Counted ] -> [ Gem Counted ]
    random [ Gem no Counted ] -> [ Gem Counted ]
    
    [ Wall ] -> [ Wall ToBeDeleted ]
    [ Gem no Counted ][ ToBeDeleted ] -> [ Gem ][ ] (invert vanishing)
    [ ToBeDeleted ] -> [ no Wall ] (remove walls AND clear marker)
    
    [ Counted ] -> [ ]
    

    Counted is a temporary object that marks gems, at most one gem in each line. [Gem no Counted] means there are at least 3 gems and we want the opposite in the example. Invert the clause.

    Always clean up temporary objects.

    To check for global absence of an object, no counter is required, just use Gem in place of Gem no Counted.

    random can generally be used to do something up to N times, where N is the lines of the same rule you repeat. If there are more than N, it will be random and unpredictable. But beware that the rules will still be applied as much as they can, even if there are less than N instances. To check for exactly N amount of something, do both “at least N” and “at most N” checks.

    Note: you might want to add late before every rule. Try it and see the difference.

    Background as a Temporary Object

    Want to optimize your code so you don’t have any more temp objects than you absolutely need? Use Background as a temp object.

    Non-late rules can move Background, and may depend on whether Background is moving in a particular direction, which effectively gives you five different temps: left Background, up Background, right Background, down Background, and action Background. (Any objects can move action-wise, so just making objects action can serve as an even more compact temp.) Removing Background from a space is doable, and you can check whether a tile has no Background, so that’s a sixth temp. Just make sure to fill in all spaces without Background at the end of the turn with:

    late [no Background] -> [Background]

    In late rules, the only Background-related temp is the lack of Background, since nothing can move during late rules.

    Checking Movement Against the Border

    [> Thing|] does not match anything if Thing is moving against the border of the level, because the border doesn’t count as an actual game space. This means movement against the border is checkable, and padding levels with a fake border for your rules to work correctly is not necessary. For example, here’s some code to make objects that move against the border immediately vanish:
    [> Thing|] -> [> Background > Thing|]
    [> Thing stationary Background] -> [Background]
    

    Background may be replaced with a normal temp object.

    Making levels

    Editing in code

    Levels in the level section consist of a rectangular grid of single characters that stand for objects or groups of objects, with blank lines in between. These single character aliases for objects can be used in rules and can be defined either:

  • in the object section right next to object names: player p, or
  • in the legend section: p = player
  • Like any alias defined in the legend, a symbol can also stand for a combination of objects on different collision layers:

    a = crate and target

    However, groups of objects using or (as for example codeb = crate1 or crate2) cannot be used in the levels section.

    The background object will be behind every object or group of objects automatically and never needs to be included when defining symbols. The top-left-most background tile (scanning the first column from top to bottom, then the second column, etc.) will be placed behind all objects without an explicit background. Keep in mind that custom, varying backgrounds can complicate the legend section and levels, requiring many additional and combinations.

    When editing levels in code, use text editor features like multiple cursors.

    Run the game starting from any level by Ctrl+Left-clicking on it in the editor.

    What characters can I use?

    Unlike object names, many single characters are accepted besides the usual letters, numbers and underlines, like - or *. In fact just about any Unicode character can be used, and there are thousands of those, including symbols and even emojis!

    😀 = player

    The lowercase letter v is used like a downwards arrow by the engine. It and other special characters used in rules will work just fine but it’s not recommended to use them for clarity’s sake. If you do, a warning will be shown when this is violated, so you don’t have to remember them.

    This can be especially useful for providing clarity when working with objects that have a direction (←↕↗), are connected together like pipes or snakes (┴┐─), have alternative variants (AÀÃÄ) or have a certain color (🔴🔵).

    Useful websites for Unicode characters:

  • List of all blocks including Box Drawing, Block Elements, Arrows.
  • List of Unicode Symbols, use the search bar to find symbols by name.
  • Google Docs Insert > Special characters menu shows similar characters to what you draw with just the mouse – that can then be copied elsewhere.
  • Sharing PuzzleScript code in a text file works even with these special symbols, when the encoding is set to UTF-8 (Unicode).

    There is a case_sensitive PuzzleScript Plus prelude option that recognizes a and A as different symbols, but will also do the same for object names themselves. Even without case sensitivity, you will likely not run out of usable characters any time soon.

    PuzzleScript uses the default browser monospace font. On Windows this will usually be Courier New, coming with over 2700 characters. When using a custom font in the editor, some symbols may be missing and need to be displayed in another font, breaking the alignment of letters to a grid a monospace code font usually provides.

    Using the Level Editor

    The interactive Level Editor can be toggled via its button in the top toolbar or by pressing E when the game is focused.

    The tile palette at the top is generated using all the defined symbols available in the whole game, starting with numbers, symbols and then letters.

    On exiting the editor or clicking on the S (top left) the engine will attempt to express the level in code that can be selected with a single left click and copied into the code. It might not be able to do so fully because an object or a combination of objects on a tile don’t have a single symbol assigned, in which case it will try to match it as well as possible or will just leave a background tile instead if it isn’t possible.

    Generating parts of the levels within rules

    For adding aesthetic detail to levels, it is recommended to limit the number of symbols and fill in details using rules instead, making for an easier level making experience.

  • run_rules_on_level_start must be set for most tricks to work. That way, remaining objects can be added to the level before any player movement.
  • Often, these rules also only need to run once before the first turn of each level. An object that is required in all the rules, then disappears is a very common solution.
  • [Init] [wall] -> [Init] [wall random bricks]
    [Init] -> [ ]
    

    Combined with randomness, the detail will generate differently on every restart in the same level. If that effect is unwanted, a checkpoint could be set after the rules generate details:

    [Init] -> [] checkpoint

    Using TAGS

    Using Less Aliases in the Legends Section

    From time to time, your game may contain objects having two tags.

    TAGS
      ID = 1 2 3 4 5 6 7 8 9
      ID2 = 1 2 3 4 5 6 7 8 9
      OBJECTS
      Obj:ID:ID2 canvas: …
    

    If every object can appear in a level, then in the legends section you will need to define many aliases for these objects. However, there is a clever way to significantly reduce the number of aliases from xy to x+y (from 9x9 = 81 to 9+9 = 18 in this case).

    Here is a minimal example.

      flickscreen 5x5
      run_rules_on_level_start
      OBJECTS
      Setup:ID2; transparent
      LEGENDS
      # = Wall
      x = Background
      1 = Obj:1:1
      2 = Obj:2:1
      etc
      a = Setup:1
      b = Setup:2
      etc
      RULES
      right ID ID2 [ Obj:ID:1 | | | | Setup:ID2 ] -> [ Obj:ID:ID2 | | | | Setup:ID2 ]
      LEVELS
      (use walls to separate the two layers so player does not enter the setup layer)
      #########
      #9xx#fxx#
      #xxx#xxx#
      #xxx#xxx#
      #########
    

    When the game runs, the object Obj:9:1 will become Obj:9:6 as desired.

    Notes:

    This document was adapted from an original authored by @ThatScar and others.