Skip to content
ClementSparrow edited this page Oct 8, 2022 · 12 revisions

Welcome to the Pattern:Script wiki!

Until I make a real documentation, here is a short description of what Pattern:Script does, for those who already know PuzzleScript.

Table of Contents

What is Pattern:Script?

  • It is a not-so-simple fork of PuzzleScript. I did not even touch the engine itself, only the parsing and compilation stages, and some of the edition interface.
  • It works mostly as a preprocessor or template system, generating new rules and sprites so that you don't have to do it manually.
  • It brings three new main features: rule parameters, tags, and mappings, and a few useful features like sprite copy and transformations and debug support.

Additions to the language

Order of sections

This is a simple quality of life feature: sections can now be split in multiple parts and written in any order. The declaration of something should still precede its use, though. See some tricks using this feature.

Rule parameters

This new feature solves a very common PuzzleScript issue, but also brings more possibilities when used in conjunction with tags and mappings. So, let's say you have defined a "property" like that:

    LEGEND
    Color = Blue or Yellow

and you want to create a rule that deletes two nearby identical colors. You may think this rule is something like that:

    [ Color | Color ] -> [ | ]

But PuzzleScript would actually expand individually the properties in each cell, so that the rule above is equivalent to these four rules:

    [ Blue | Blue ] -> [ | ]
    + [ Blue | Yellow ] -> [ | ]
    + [ Yellow | Blue ] -> [ | ]
    + [ Yellow | Yellow ] -> [ | ]

...which obviously don't do what you want. In that case, PuzzleScript helps you dealing with the most painful case by allowing you to write a single rule instead of four. But it does not provide any help with the slightly less painful case where you need to write the two rules yourself, like that:

    [ Blue | Blue ] -> [ | ]
    + [ Yellow | Yellow ] -> [ | ]

Pattern:Script introduces rule parameters to solve that, so that you can simply write:

    Color [ Color | Color ] -> [ | ]

You can list property names (separated with spaces) before the brackets and they will be treated as rule parameters: a new rule will be created for each object in a property, in which all occurrences of the property name in the brackets will be replaced with this object (if you do not want a property name to be replaced, just use the LEGEND section to create an alias of this property with a different name).

Note that rule directions in vanilla PuzzleScript can be understood as a special case of rule parameter. Indeed, a rule like:

    horizontal [ Object1 | Object2 ] -> [ | ]

would be extended as:

    left [ Object1 | Object2 ] -> [ | ]
    + right [ Object1 | Object2 ] -> [ | ]

Tags

The next addition is tags. It's basically a way to make object attributes, or variants of objects following some external logic.

Declaring tags

You declare them in a TAGS section. So something like:

    TAGS
    Activity = Active Inactive

will declare a tag class named Activity with two possible values Active and Inactive.

You can reuse tags and tag classes in the declaration of other tag classes, so:

    Polarity = Positive Negative
    Charge = Polarity Neutral

is equivalent to:

    Polarity = Positive Negative
    Charge = Positive Negative Neutral

The directions tag class is predefined. It provides the tags up, right, down, left as you would expect, but also has horizontal and vertical as sub-classes.

Tags in object names

You use tags in objects names. So we can have Player:Neutral, Player:Negative, Player:Positive as objects. Internally, that's EXACTLY the same as writing PlayerNeutral, PlayerNegative, PlayerPositive without the : and without declaring the tags. The main benefit is that you can use tags as rule parameters and in mappings, as we will see below. But it is also useful as you can now declare directly Player:Charge to declare the three types of players at the same time. And Player:Charge gets automatically declared as a property too, i.e., it's as if it automatically added this for you in the LEGEND section:

    Player:Charge = Player:Positive or Player:Negative or Player:Neutral

Remember that directions is a predefined tag class, so if you want to have an object that has an associated direction, just write something like this:

    Rocket:directions
    red

Instead of:

    Rocket:up
    red
     
    Rocket:right
    red
    ...

If you add a sprite in this declaration, all four variants of the rocket will use that sprite, but we will see soon the amazing technique of transforming that base sprite in different ways for each object (for instance, rotating the sprite so that the rocket points in the right direction).

(The following paragraph may get deprecated soon. Instead, the latest declaration of an identifier would always overwrite things from previous declarations.) There are a few subtleties. First, Player:Charge declares all three types of players but marks them as implicitly declared, so that you can redeclare them explicitly after that. The goal is that you can provide a default palette and sprite for Player:Charge, then change the palette and sprite for a specific player object. You can also do the opposite: declare first an object like Player:up then use the builtin directions tag class to declare Player:directions. In an implicit declaration like that, the objects that have already been declared explicitly will not be redeclared. It is useful if you want to create the sprites for some direction of the player by copying and rotating the sprite for Player:up (see below for the copying of sprites).

Tags and collision layers

If you use something like Player:Charge in a collision layer, it will do as if Player:Charge was defined as the following property:

    Player:Charge = Player:Positive or Player:Negative or Player:Neutral.

And it will thus put all three types of players in the same collision layer. But sometimes you want things to go in different layers, and you can use another kind of parameter expansion for that. If you write:

    Charge -> Player:Charge Crate:Charge

It will generate three collisions layers (one for each value of Charge), so that it's equivalent to:

    Player:Positive Crate:Positive
    Player:Negative Crate:Negative
    Player:Neutral  Crate:Neutral

Mappings

Mapping are some kind of simple functions that work on tags or objects.

Declaring mappings

You declare mappings in a MAPPINGS section with a syntax like that:

    Polarity => OpposedPolarity
    Positive Negative -> Negative Positive

Here, we declare a mapping named OpposedPolarity that maps the tags in the Polarity tag class to their opposites. The second line gives the mapping value by value.

We can also declare mappings that map a set of objects onto another, but that has not been much tested currently.

Note that mappings can map tags to tag classes, and this is a very useful advanced technique. For instance, the predefined mappings perpendicular and could be defined manually like that:

    directions => parallel
    up down right left -> vertical vertical horizontal horizontal
     
    directions => perpendicular
    up down right left -> horizontal horizontal vertical vertical

Using mappings

You use mappings in rules (and a few other places) like that:

    Polarity [Crate:Polarity] -> [Crate:OpposedPolarity]

Here, Polarity is used as a (tag class) rule parameter. It means that the rule will be expanded for each value in the tag class. So if you have a rule like:

    Polarity [ Crate:Polarity | Crate:Polarity ] -> [ < Crate:Polarity | > Crate:Polarity ]

It will be expanded into the two rules:

    [ Crate:Positive | Crate:Positive ] -> [ < Crate:Positive | > Crate:Positive ]
    + [ Crate:Negative | Crate:Negative ] -> [ < Crate:Negative | > Crate:Negative ]

The expansion of rules with rule parameters writes a new rule for every value of the parameter and replaces all occurrences of the parameter name in the rule by its value. Mappings build on top of this expansion mechanism: the name of a mapping can be used instead of the name of the parameter, and it will be replaced by the value associated to the parameter's value in the mapping. So the rule:

    Polarity [Crate:Polarity] -> [Crate:OpposedPolarity]

gets expanded as:

    [Crate:Positive] -> [Crate:Negative]
    + [Crate:Negative] -> [Crate:Positive]

Because the mapping OpposedPolarity is defined as:

    Polarity => OpposedPolarity
    Positive Negative -> Negative Positive

Mappings of rule direction

Remember that in Pattern:Script, the rule direction is just one expansion parameter like others, and that the relative directions >, <, v, ^ are predefined mappings for directions, as well as perpendicular and parallel! So you can very well do things like:

    (the player faces the direction of the key pressed)
    [ > Player:directions ] -> [ > Player:> ]
      
    (players can push wheelchairs only when they face one, standing behind it)
    [ > Player:> | WheelChair:> ] -> [ > Player:> | > WheelChair:> ]

Defining Sprites

A few features ave been added to help creating better graphics:

  • Sprites of arbitrary sizes allow making games with pseudo-3D graphics and ease the creation of smaller (objects held in hand) and bigger (bosses) sprites.
  • An object's sprite can be copied from another object, which avoids boring copy/paste operations with the possible associated mistakes, and makes the code shorter and clearer.
  • Sprites can be defined by transformations of a base sprite, such as mirroring or rotating them
  • And all this is compatible with the mechanisms of tag expansion and tag mappings, which helps creating animations and other families of related sprites (e.g., the four possible orientations of an avatar viewed from above).

Sprites of arbitrary size

Sprites do not have to be 5x5 anymore: you can give a sprite matrix of any dimensions, it does not even have to be a rectangle (pro tip: if you have an empty horizontal line in a sprite, now you can just put a single . in that line).

Sprites will be rendered aligned with their bottom-left corner on the bottom-left corner of the cell they are in, although this can be changed with translate transformations (see below). Pro tip: if a sprite is empty at the top, you don't have to fill it with fully transparent lines at the top!

An important use of that feature is to make pseudo-3D rendering: since levels are rendered from top to bottom, you can have sprites that are taller than the cells and they will be drawn on top of the sprites on the row above them, creating a depth effect.

Another important use of the feature is to make objects that are bigger than one cell: you just need one sprite instead of splitting your big boss into multiple sprites that will be rendered in each cell it covers. And if you want to animate such big sprites, it will make it even easier. You still need to have objects for the boss in all the cells it covers if you want to use the collision system, though.

Another use case is to display textual instructions (draw the text in a single sprite) graphical hints, etc. Anything that you may want to display over the normal game grid. (See Tapaban by Menderbug.)

The feature is best used with another new feature, which is collision layer groups and rendering directions: see the updated documentation.

Sprite copy and transformations

Another cool new feature is that you can define the sprite of an object as a copy of another, applying transformations to the sprite as you copy it. So for instance, a Lava sprite could be defined like this:

    Lava:Anim5
    Red Orange #CF4020
    copy: Lava:PrevAnim5 shift:left

Anim5 is the tag class defined as:

    TAGS
    Anim5 = Step1 Step2 Step3 Step4 Step5

And there is a mapping giving the step n-1 from the step n:

    MAPPINGS
    Anim5 => PrevAnim5
    Step1 Step2 Step3 Step4 Step5 -> Step5 Step1 Step2 Step3 Step4

Here, I first defined the last step of the animation with a sprite (Lava:Step5), and then defined the other steps by shifting left the previous step. That's what the line copy: Lava:PrevAnim5 shift:left does. (It works because the objects are created in the order of the definition of the tags).

You can use translate:{direction}:{amount} to translate the sprite of the given amount of pixels in the given direction, shift:{direction} to cycle the pixels of the sprite in the given direction, - and | for horizontal and vertical mirrors, and rot:{direction}:{direction} to rotate the sprite so that the first direction provided becomes the second direction after rotation. So, rot:up:right would define a 90° clockwise rotation, transforming an upward axis into a rightward one.

You can write multiple transformations separated with spaces or new lines, they will be applied in the order you list them.

Transformations can be given directly after a sprite matrix specification, not only after a copy: instruction. This is typically used to avoid providing the N bottom lines of the sprite, using a translate:up:{N} transformation instead.

Other additions

Directions on randomly generated objects

It is now possible to write things like:

    [ action Player ] -> [ right random Player ]

Or even, for randomness lovers:

    [ action Player ] -> [ randomdir random Player ]

Obviously, it helps making animation by randomizing elements (fire, wind, lava, grass...) without entering an infinite loop:

    [ stationary Lava ] -> [ action random Lava ] ("action" is considered being a kind of movement for "stationary")

Changing the volume of individual sound effects

It's all in the section title, and in the (updated) documentation.

New features for games

Any sprite size

Sprites of any size are accepted, but currently all sprites need to have the same size, defined in the preamble with:

    sprite_size {width}x{height}

Rendering groups and rendering directions

See the updated documentation. This can be useful to correctly render objects that extend out of their cell, without being overwritten by nearby objects. In particular, for perspective effects.

Improved Game Menus

Games now have a real pause menu that is distinct from the title screen. You can really pause the game and resume it without having to restart the level or restart from the last checkpoint!

Also, you can now define title_color, author_color, and keyhint_color to give more colors to your menus.

Interface and workflow improvements

Text editor

  • There is now a choice between the default dark mode and a new light mode (which needs to be improved).
  • Support for syntax coloring, autocompletion, and error signaling for the new syntax -> the autocompletion support needs to be improved and extended (e.g., to include tag mapping proposals), some errors are not signaled in the rules because they are only detected at compilation time rather than while parsing.

Level editor

Two very useful features when there are transparent objects:

  • Hovering an object in the "palette" of the level editor will show the textual definition of that object,
  • Hovering a cell in the level will show the content of that cell as text,

Verbose logging

  • the verbose_logging keyword in the preamble of the game is ignored and replaced with a toggle button in the top-left corner of the console.
  • When verbose logging is active, it will show the direction and rule parameters of the rules that are applied, as well as the coordinates of the cells that are matched (the coordinate of the first cell in each bracket of the rule). Hovering a cell coordinate with the mouse highlights the corresponding cell in the editor.

GitHub Integration

  • new "save on cloud / update cloud" button to save the game as a private gist.

Broken, planned, and investigated features

Broken features

  • The puzzlescript.net website, and especially the gallery, is not supported anymore.
  • GitHub logging is slightly more complicated but should still do the work.
  • Game export is currently less good than in vanilla PuzzleScript because it does no minification of javascript/CSS or similar optimizations allowed by the inliner. The good news is that I have worked A LOT on reducing the code size, to the point where exported Pattern:Script games where almost the same size than exported PuzzleScript games (but since, increpare also optimized the code size a little). The reason I do not minify the code is that I want to avoid having to run scripts each time I make a modification on the javascript files, and because I want to add support for plugins, which would interact poorly with the inliner. I also want to export smaller games by pre-parsing them instead of putting the source code (with all its comments) in the game.

Planned features

Note: the distinction between planned and investigated features is not exactly a clear cut.

Syntax

  • add a PALETTE section and/or commands to copy palettes from other objects.
  • add support for alpha channels in sprite colors
  • extend the ... syntax as proposed here: https://github.com/increpare/PuzzleScript/pull/218/commits
  • extend the message syntax to add dimensions and position of a message box, alignment and color of text.

Editors

  • improve the layout of the level editor and its feature (e.g., add rows and columns in the middle of the level).

Engine

  • Provide an alternative to (or completely replace) text screens (menus and messages), to allow direct use of HTML+CSS formatting. The current system could be kept as an optional plugin. There is too much time lost in trying to center a message or skip a line!

Investigated features

These are ideas that show in what direction I would like to take this project, but the best way to implement these ideas is not yet defined.

Syntax

  • It would be nice to package some rules and objects in some kind of "recipe" that they can be reused in other games (e.g., path finding, laser beams, etc.)
  • I also consider ways to extend the tag system to bring it closer to actual object attributes.

Editing

  • Tooltips and fix suggestions for error messages in the editor (in addition to the console).
  • Contextual menus/tooltips to provide information about an identifier and related commands such as jumping to its definition, clone, add to a property, etc.
  • Split the editor in multiple tabs for specific sections, use dedicated editors for sprites, possibly also for rules, promote the level editor as the main representation of levels instead of the textual representation.
  • Modifying a palette should be doable with color pickers and the effect should be visible in the level editor in real time.
  • Do something about sounds (and music): allow for audio files import and plugins for more sophisticated sound generators (including jsfxr).
  • Add support to record a sequence of inputs and replay it, and use this system to attach unit tests to levels.

Engine

Events:

  • Add support for animation of objects being created/destroyed/moved (the later may need to extend the syntax to keep track of objects on both sides of the rules) and animation loops (without having to write rules for them or to enable real_time).
  • Support for game music and alteration of music related to in-game events.
  • Support for javascript callbacks on some events.
  • Support for multiple action buttons triggering different types of actions.
  • Support for mouse-based games.
  • Support for "autoplay" events, sequences of inputs automatically played at the beginning or end of some levels, or on specific events.
Grids:
  • Support for bi-dimensional patterns in rules (which is another reason why an editor dedicated to rules would be desirable).
  • Actually, support for different grid topologies (e.g., hexagonal), and for having border around cells that occupy space and can hold dedicated sprites.
  • Also, diagonal moves.
  • Support for overworlds and open worlds.
  • Support for detection of level borders in the rules.
Rendering:
  • Support for multiple grids in a same game, e.g. a level/world grid and an inventory grid.
  • Support for layers rendered with a constant offset (useful for cast shadows or 3D effects).
Debugging:
  • Add a real rule debugger?
  • Allow to name rules for easier reading of verbose_logging output?
Rigid bodies and complex physics:
  • objects should be able to appear in multiple collision layers for collision detection, but rendered at the highest level only.
  • Get rid of the rigid keyword and replace it with something more useful and understandable. One possibility would be a set of keywords to match intents to move that have been canceled because of collisions, but that is probably not enough.
  • Provide support for linked object, e.g. a button and the door it opens, then two ends of a teleporter, etc.
Others:
  • Have a section for rules that should only be applied once when the level is loaded, and apply these rules in the level editor when the level is edited (so that rules that add graphical details also add them when the level is edited).
  • Provide means to customize the level editor to better fit the game logic (e.g. with special rules and objects).
  • Some games may benefit from more advanced usages of the undo feature (for instance, like the "trial mode" in the puzz.link interface, or commands like "trackback to when I was there").

Code

  • Plugin interface to make it easier to add new features without having to fork the project, and to allow game creators to select the features they need.
  • Rewrite the parser using more generic parsing functions instead of ad hoc ones, so that it would make it easier to add new expressions in the language through forks or plugins.