Skip to content

[SpaceTode] Documentation

Luke Wilson edited this page Aug 23, 2020 · 1 revision

SpaceTode is a language that helps you make elements for the SandPond engine.
It is heavily inspired by SPLAT and shares many principles.

Element

The most important part of SpaceTode is the element expression. It defines a new element:

element Sand

In the SandPond engine, there are two in-built elements:

  • Empty (all spaces are empty by default)
  • Void (the element of any space outside of the universe)

Properties

You can set your element's properties.

element Sand {
    colour "yellow"
}

These are all the in-built properties:

  • colour
  • emissive (emissive colour of the element)
  • opacity
  • visible (if false, the element will not render in the world)
  • hidden (if true, the element will not appear in the menu)
  • category (determines which menu category to appear in)
  • pour (if true, holding down the mouse button will continue to pour more atoms, default is true)
  • default (if true, this element will be picked by the dropper when you load the page)

Custom Properties

You can set your own properties with the prop keyword:

element Water {
    prop state "liquid"
}

Data

Atoms of the element can carry data.
Use the data keyword to shows what data the atom holds, and its default value.

element Sand {
    colour "yellow"
    data isWet false
    data temperature 15
}

Arguments

You can set the arguments you expect when constructing an atom (and their default values).

element Explosion {
    arg size 10
    arg colour "red"
}

When you make an Explosion atom somewhere in your code, you could use those arguments:

new Explosion(20, "yellow")

Note: You would still need to implement what those arguments do in your element's code.

Rules

You can give your element rules by drawing rule diagrams:

element Sand {
   
    colour "yellow"
   
    @ => _
    _    @

}

An element's rules show it how to act in the SandPond world.
It checks its surroundings to see if it matches the left-hand-side of the rule (the inputs).
If it matches, it changes it to look like the right-hand-side of the rule (the outputs).
Rules are checked in order. The first rule to match is the one that gets used.

You can optionally use the rule keyword, which may improve readability in longer elements:

rule {
    @ => _
    _    @
}

Diagram

A rule's diagram shows how things are arranged.
Let's look at a rule more closely:

@ => _
_    @

Inputs:
The @ symbol shows where the atom is.
The _ symbol checks for an empty space.
In other words, the rule checks if there is an empty space below the atom.

Outputs:
The @ symbol shows where to put the atom.
The _ symbol shows where to put an empty space.
In other words, the rule moves the atom down, leaving an empty space behind.

Inputs

Inputs are the symbols you write on the left-hand-side of a rule diagram to check an atom's surroundings.
In the SandPond engine, there are some in-built inputs that you can use straight away:

  • @ The centre of the diagram.
  • _ An empty space.
  • # A non-empty space.
  • . Any space.
  • * Not a space (ie: the edge of the universe).
  • $ This element.

You can also make your own input symbols by using the following keywords:

  • symbol
  • given
  • select
  • check

Outputs

Outputs are the symbols you write on the right-hand-side of a rule diagram to say what to do when a rule has been matched.
In the SandPond engine, some are built-in already:

  • @ Place this atom in the space.
  • _ Empty the space.
  • . Do nothing.

You can make your own output symbols with the following keywords:

  • symbol
  • change
  • keep

Symbol

You can quickly make a symbol with the symbol keyword:

symbol W Water

@ => W
W    @

Any

The any keyword randomly translates rules in specified symmetry:

any(x) {
    @_ => _@
}

The above rule has a x symmetry. It will randomly reflect itself in the x-axis.
In other words, it might move right, or it might move left.
For a full list of symmetries, see below.

Symmetries

NOTE: I'm currently working on this so it's very broken and subject to change.
Symmetries are sets of translations that transform your diagrams into other shapes.
They're useful because it means you don't have to write every possible shape of a diagram.
Symmetries can be any combination of x, y and z in order.
eg: xz
eg: xyz
eg: y

We specify the axes that you don't care about.
For example, xz means that we don't care about how the x and z axes change.
They could swap over, flip around, etc.

Given

All given inputs must be true for a rule to be chosen.

given W (element) => element == Water

W => _
@    @

If there is a water atom above me, delete it.

Change

change outputs say what atom to put in a space.

change W () => new Water()

@ => @
_    W

This makes a water atom and places it below.

Keep

keep outputs don't do anything. They just leave that space how it is.

keep n

@ => n

This does nothing...

Keeps can still have a function like usual.

keep n () => print("I'm doing nothing!")

@ => n

Select

select inputs save data that can be used in an output.

given W (element) => element == Water
select W (atom) => atom
change W (selected) => selected

@ => W
W    @

This selects a water atom below, and swaps places with it.

Check

All check inputs must be true for a rule to be chosen. Only one check function is done per symbol. Unlike given, it is never cached.

given W (element) => element === Water
check W () => Math.random() < 0.2

WWW   ...
 @ =>  _

Origin

origin symbols show where the centre of the diagram is.

origin O

O => _
_    @

For

The for keyword loops through all translations of a symmetry (in a random order):

for(xyz) {
    @_ => @@
}

The above rule will loop through all possible translations of xyz in a random order, until it finds a match.

All

The all keyword works similarly to the for keyword, but it doesn't randomise the order of translations.

all(xyz) {
    @_ => @@
}

Symmetry Subsets

You can also use certain subsets of symmetries.
Symmetries have these properties, which let you access subsets:

  • directions (only points your diagram in different directions - does not rotate)
  • rotations (only rotates your diagram - does not reflect)
  • shifts (points your diagram in different directions and then rotates around those directions - does not do any other rotations or reflections)
  • swaps (only swaps the axes with each other)

For example, this element would move in a random xyz direction (6 possible directions):

any(xyz.directions) @_ => _@

This would have 12 transformations:

any(xyz.shifts) {
    @  => _
     _     @
}

Named Parameters

When you write functions for given, change, keep or select, you can name what parameters you want to use. You can choose from these parameters:

  • space
  • atom
  • element
  • origin (the space that is calling the event)
  • self (the atom that is calling the event)
  • Self (the element that is calling the event)
  • selected (a value that was returned by a select function of the same symbol)
  • x (the relative x-value of the space)
  • y (the relative y-value of the space)
  • z (the relative z-value of the space)
  • sites (an array containing all spaces in the origin's event window)
  • siteNumber (the site index of this space)
  • time (the number of event cycles that have happened since page load)

The parameters below are used for internal purposes, but are still available:

  • transformationNumber (when using a symmetry block, the current transformation number)
  • possibleSiteNumbers (when using an any block, an array containing possible site numbers for this space)
  • possibleXs (when using an any block, an array containing possible relative x-values for this space)
  • possibleYs (when using an any block, an array containing possible relative y-values for this space)
  • possibleZs (when using an any block, an array containing possible relative z-values for this space)
  • transformationNumbers (when using a for block, an array containing transformation numbers)

Maybe

The maybe keyword specifies the chance of rules happening:

maybe(1/2) {
    @ => _
    _    @
}

The above rule only happens 50% of the time.

Point of View

By default, the rule diagram is positioned as if you are looking from the front.
You can use the pov keyword to make the diagram represent another point of view.

pov(top) {
    @ => _
    _    @
}

The above rule's diagram is shown from a bird's eye view instead of from the front.
Valid points of view:

  • front (default)
  • back
  • top
  • bottom
  • right
  • left

Action

Use the action keyword to give actions to an element.
Actions don't end an atom's behaviour when they match (like rules do).

element Dropper {
    change S () => new Sand()
    
    // Drop some sand below me
    action {
        @ => @
        _    S
    }
    
    // Afterwards... move into space
    any(xyz) @_ => _@
}

Mimic

Elements can copy another element's rules (and actions) with the mimic keyword.

element Snow {
    colour "white"
    change W () => new Water()
    maybe(1/100) @ => W
    mimic(Sand)
}

This snow element melts 1% of the time. The rest of the time, it behaves like Sand.

Sub-Elements

You can create elements inside elements. This can help you to organise your code. Sub-elements do not appear in the SandPond menu.

element Snake {

    element Head {
        // Snake.Head code goes here...
    }

    element Tail {
        // Snake.Tail code goes here...
    }
}

Blocks

When you write SpaceTode, you write blocks of code. There are three ways to write a block.
Multi-line block:

element Forkbomb {
    @_ => .$
}

Single-line block:

element Forkbomb { @_ => .$ }

Naked block:

element Forkbomb @_ => .$

Scoping

Symbols that you declare are block-scoped. They only exist inside the block that they are declared in.
This code causes an Unidentified Symbol error because the E symbol doesn't exist in the scope of the diagram:

element Sand {
    {
        symbol E Empty
    }
    
    @ => E
    E    @
}

It can be useful to scope different parts of your code so that you can keep track of what different symbols mean:

element Sand {
    {
        symbol W Water
        @ => W
        W    @
    }
    {
        symbol W Wind
        W@_ => ._@
    }
}

JavaScript

All values and functions are written in embedded JavaScript in SpaceTode. For example, all the symbol functions are written in JavaScript:

given W (element) => element === Water

The JavaScript above is (element) => element === Water.
You can extend your JavaScript over multiple lines by ending a line with a bracket:

given W (element) => {
    return element === Water
}

You can also place your JavaScript within brace brackets if you want to make a closure:

given W { return (element) => element === Water }

Behave

You need to know how the SandPond engine works on the backend to use this effectively.
The behave keyword lets you write plain JavaScript to manually define what to do.

element Vanisher {
    behave (origin) => SPACE.setAtom(origin, new Empty(), Empty)
}