Scripting

Scripts define what happens when players interact with your project.

Quick Reference

Want to... Use Section
Navigate to cell goto "CellName" Navigation
Show/hide object show X / hide X Visibility
Spawn object at runtime spawn "Template" {params} Spawn & Destroy
Remove spawned object destroy obj Spawn & Destroy
Respond to click onClick: Events
Respond to key onKeyDown "Space": Events
Detect overlap onOverlap: Events
Initialize spawned object onSpawn: Events
Cleanup before destroy onDestroy: Events
Reference other object other.name Special References
Wait before next action wait 500ms or wait 2s Timing
Wait for movement to settle wait movement Timing
Restart current level restart Restart
Set variable set score 10 Variables
Set object property set X.opacity 0.5 Properties
Check condition if score > 10: Conditions
Loop over items foreach item in list: Foreach
Find first match first #tag where .x > 0: First
Reference self self.property Special References
Reference parent parent Special References
Target by tag #tagname Tags
Target by type all buttons Type Selectors
Cross-cell reference @Cell.Object Cross-Cell
Send message shout "MSG" Messaging
Define reusable code action name: Custom Actions
Create slideshow set X pageable[A, B, C] Pageable
Access array element arr[i] or arr[i].x Arrays
Get array length length(arr) Array Functions
Generate range range(5) → [0,1,2,3,4] Array Functions
Shuffle array shuffle(arr) Array Functions
Random element pick(arr) Array Functions
Check if cell has object isEmpty(grid, x, y) Grid Functions
Get cells without objects emptyCells(grid) Grid Functions
Store metadata per cell set Grid.cell[x][y].prop value Grid Cell Data
Read metadata per cell Grid.cell[x][y].prop Grid Cell Data
Query cells by metadata cellsWhere(grid, prop, op, val) Grid Functions
Clear cell metadata clear Grid Grid Cell Data
Debug output log "value is" x Log
Animate object shake self 300 Animations
Stop animation stop self Animations
Respond to hover onHover: / onHoverEnd: Events
Make object draggable set X draggable Dynamics - Draggable
Respond to drag onDragStart: / onDrag: / onDragEnd: Events
Detect movement onMove "down": / onStop: Events
Detect jump/land onJump: / onLanding: Events
Sync parallel scripts sync Parallel Execution
Send HTTP request post url {data} HTTP Requests
Fetch external data fetch url into var HTTP Requests
Angle to direction cardinal(angle) Math Functions
AI best move minimax(grid, ...) Grid Functions
Exit action early return Control Flow
Exit loop early break Control Flow
Cell entrance animation transition fade 500 Cell Transitions

Events

Scripts are organized by events - when the code runs:

Event Where When it fires
onEnter Cell, Object Player enters the cell
onExit Cell, Object Player leaves the cell
onClick Object Object is clicked
onKeyDown Cell Key is pressed
onKeyUp Cell Key is released
onMessage Cell, Object Message received via shout
onMessageFrom Cell, Object Message from specific source
onOverlap Object Starts overlapping another object (requires sensor)
onOverlapEnd Object Stops overlapping another object
onCollide Object Collides with a blocking object
onHover Object Mouse enters object bounds
onHoverEnd Object Mouse leaves object bounds
onDragStart Object User starts dragging object (requires draggable)
onDrag Object Each frame while dragging
onDragEnd Object User releases drag
onMove Object Movement direction changes
onStop Object Object stops moving
onJump Object Object performs a jump
onLanding Object Object lands on a surface
onSpawn Object Object is spawned (created at runtime)
onDestroy Object Before spawned object is destroyed
onEnter:
  show WelcomeText

onClick:
  goto "NextRoom"

onKeyDown "Space":
  set player.jumping true

onMessage "KEY_FOUND":
  set hasKey true

onOverlap:
  if other.name == "Coin":
    hide other

onOverlapEnd:
  set inZone false

onCollide:
  if other.name == "Spike":
    goto "GameOver"

onHover:
  set self.opacity 0.8

onHoverEnd:
  set self.opacity 1

onMove "down":
  set self.state "walkDown"

onStop:
  set self.state "idle"

onDragStart:
  set self.opacity 0.7

onDragEnd:
  set self.opacity 1

Combined events - trigger same code from multiple events:

onClick | onKeyDown "Space":
  goto "NextRoom"

Note: onEnter and onExit fire for ALL objects that have them, not just the cell. They execute in z-order.

Note: onOverlap and onOverlapEnd require at least one of the two overlapping objects to have sensor: true. Use the other keyword inside these handlers to reference the other object in the overlap pair. See Dynamics - Sensor for setup details.

Note: onCollide fires when a movable object collides with a blocking object. The other keyword references the blocker. Unlike onOverlap, this fires for blocking collisions (the mover is stopped), not pass-through overlaps.

Note: onDragStart, onDrag, and onDragEnd require the object to have draggable enabled. See Dynamics - Draggable for setup details.

Note: onMove fires when an object starts moving in a new direction. Use the direction parameter to filter: onMove "down": only triggers for downward movement. Directions are "up", "down", "left", "right".

Note: onStop fires once when an object comes to rest (velocity becomes zero). Use for resetting to idle state.


Referencing Objects

By Name

Reference objects directly by their name:

show Button1
set Text1.opacity 0.5
hide MyShape

Component Children

Access children inside components with dot notation:

set Dialog.Title.content "Hello!"
set TrafficLight.RedLight.visible true
hide MyComponent.Inner

Special References

Reference objects relative to the current context:

Reference Meaning
self The current object
parent The containing component or cell
siblings All objects at the same level
children All objects contained within
other The other object in an overlap pair (only in onOverlap/onOverlapEnd)
set self.color.color "#ff0000"
hide parent
set siblings.visible false
show children

# In onOverlap/onOverlapEnd handlers:
set other.visible false
log other.name

# Check if other has a tag:
if other is #enemy:
  goto "GameOver"

Chaining:

set parent.parent.visible false
hide parent.siblings

Tags

Target all objects with a tag using #tagname:

show #enemies
hide #tutorial with fade
set #buttons.opacity 0.5

Reading tag properties uses "any" semantics - true if ANY object matches:

# Toggle all lights: if any visible, hide all; else show all
if #light.visible:
  hide #light
else:
  show #light

Tag membership check with is:

# Check if an object has a specific tag
if other is #enemy:
  goto "GameOver"

if self is #collectible:
  hide self

See Objects → Tags for how to assign tags.

Type Selectors

Target all objects of a type using all:

show all buttons
hide all circles with fade
set all shapes.color.color "#ff0000"
set all text.opacity 0.5
Selector Matches
all buttons Button objects
all text Text objects
all shapes All shape objects
all lines Line objects
all grids Grid objects
all rectangles Rectangle shapes
all circles Circle shapes
all ellipses Ellipse shapes
all polygons Polygon shapes

Cross-Cell

Access objects in other cells with @:

set @Lobby.Counter.content "10"
show @Inventory.GoldIcon
set @OtherCell.Button1.visible false

Actions

Navigation

goto "Cell Name"                    # Navigate to cell
goto "Cell Name" with fade          # With transition
goto "Cell Name" with slide-left    # Slide transition
goto "Cell Name" with zoom 500      # Custom duration (ms)

Directional navigation - go to adjacent cells on the map:

goto north                          # Go to cell above
goto south                          # Go to cell below
goto east                           # Go to cell right
goto west                           # Go to cell left

Transitions: fade, slide-left, slide-right, slide-up, slide-down, zoom

Note: goto currentCell is a no-op. To re-initialize a cell, use a custom action called from both onEnter and your reset logic.

Visibility

show ObjectName                     # Make visible
show ObjectName with fade           # With transition
hide ObjectName                     # Make hidden
hide ObjectName with scale 200      # Custom duration (ms)

Transitions: fade, slide-up, slide-down, scale

Timing

wait 500ms                          # Wait 500 milliseconds
wait 2s                             # Wait 2 seconds
wait 2                              # Wait 2 seconds (bare number = seconds)
wait movement                       # Wait for all movement to finish

Note: Bare numbers are interpreted as seconds. Use ms suffix for milliseconds.

wait movement pauses until all movable objects have stopped. Use this when reading positions in keyboard handlers:

onKeyDown:
  wait movement                     # Positions are stale until movement settles
  if Player.cellX == 5:
    show WinMessage

Parallel Execution

When onEnter triggers multiple objects' scripts, they run in parallel by default. Use sync to wait for other parallel scripts to reach the same point:

# Object A
onEnter:
  wait 1s
  show MessageA
  sync                              # Wait for Object B to reach sync
  goto "NextCell"

# Object B
onEnter:
  wait 2s
  show MessageB
  sync                              # Object A waits here until B catches up

Without sync, Object A would navigate before Object B finishes.

Variables

set score 100                       # Set variable
set score score + 10                # Increment
set game.bestScore 500              # Game scope
set local.name "Bob"                # Local scope

Scopes:

Scope Syntax Persistence
Session variableName Cleared on reset
Game game.variableName Persists across resets
Local local.variableName Permanent storage
Self self.variableName Per-component instance

Properties

Set object properties:

set Button1.color.color "#ff0000"
set Text1.opacity 0.5
set Shape1.visible false
set Door1.x 0.75

With tags:

set #enemies.visible false
set #buttons.opacity 0.5

Inside a first block, .property targets the matched object:

first #square where .state == "NEUTRAL" {
  set .state "RED"
}

Reset

reset session                       # Clear session variables + overrides
reset game                          # Same as session
reset all                           # Clear everything except local
reset visits                        # Reset current cell visit count only
reset "CellName"                    # Reset specific cell's visit count

Restart

Completely restart the current level:

restart

This:

Use for "try again" buttons:

# On a Retry button
onClick:
  restart

Restart vs Reset:

Command Effect
restart Full level reset - positions, properties, re-runs onEnter
reset session Clears variables and overrides, but no re-initialization
reset visits Clears visit count only

Messaging

Send messages project-wide:

# Sender
shout "KEY_PICKED_UP"

# Listener (on any object, any cell)
onMessage "KEY_PICKED_UP":
  set hasKey true

Targeted messages:

shoutTo Player "HEAL"
shoutTo #enemies "FREEZE"

With parameters:

# Sender
shout "DAMAGE" {amount: 50, type: "fire"}

# Receiver - parameters become variables
onMessage "DAMAGE":
  set health health - amount
  if type == "fire":
    show FireEffect

Filtered listening:

onMessageFrom Enemy "DAMAGE":
  set health health - 10

Notes:

HTTP Requests

Send data to external services or load dynamic content:

# Send data to a URL
post "https://api.example.com/scores" {name: playerName, score: finalScore}

# Fetch data from a URL
fetch "https://api.example.com/leaderboard" into highscores
log highscores

Notes:

Animations

Fire-and-forget visual effects that don't block script execution.

Action Syntax Description
shake shake <target> <duration> [intensity] Horizontal oscillation
vibrate vibrate <target> <duration|loop> [intensity] Rapid micro-jitter
pulse pulse <target> <duration|loop> [scale] Scale breathe effect
squeeze squeeze <target> <duration> [direction] Squash & stretch
bounce bounce <target> <duration> [height] Vertical hop with squash
spin spin <target> <duration> [direction] 360° rotation
stop stop <target> [animation] Stop animations

Parameters:

Examples:

onClick:
  shake self 300           # shake for 300ms

onEnter:
  pulse StatusLight loop   # continuous pulse until cell exit
  vibrate Warning loop 1   # subtle continuous vibrate

onMessage "hit":
  squeeze self 200         # impact effect
  shake self 400 10        # strong shake

onClick:
  spin self 500 ccw        # spin counter-clockwise
  bounce self 600 30       # high bounce

onExit:
  stop StatusLight         # stop all animations (or auto-cleared)

Notes:

Cell Transitions

Animate the entire cell on enter or exit:

onEnter:
  transition fade 500              # Fade in over 500ms

onExit:
  transition slide-up 300          # Slide up when leaving

Transition types: fade, slide-up, slide-down, slide-left, slide-right, zoom

Note: Cell transitions override any transition specified on the incoming goto command.

Spawn & Destroy

Create and remove objects at runtime. Use for dynamic game elements like bullets, particles, enemies, or puzzle pieces.

Create a new object from a template:

spawn "PieceTemplate" {col: 3, row: 5, color: "red"}

The template is any existing object in the cell (typically hidden with Build Visible = false). Parameters are passed to the onSpawn handler.

Template object script:

onSpawn:
  set self.snapToGrid "Board"
  set self.cellX col              # Parameters become variables
  set self.cellY row
  set self.color.color color
  show self

Destroy a spawned object:

destroy piece                      # Destroy by variable
destroy self                       # Destroy self

The onDestroy handler runs first, allowing cleanup or death animations:

onDestroy:
  shake self 200
  wait 200                         # Animation completes before removal

Complete example - spawning game pieces:

# Cell script - spawn pieces on enter
onEnter:
  set cols range(7)
  foreach col in cols:
    spawn "Piece" {col: col, color: "red"}

# Piece template script
onSpawn:
  set self.snapToGrid "Grid1"
  set self.cellX col
  set self.cellY 0
  set self.color.color color
  set self.blocking true
  show self with scale 200

onDestroy:
  hide self with fade 150
  wait 150

onClick:
  destroy self

Notes:


Control Flow

Conditions

if score > 10:
  show WinMessage
else:
  show TryAgain

if visited("Room1") and hasKey:
  goto "SecretRoom"

# Else-if chains
if health <= 0:
  shout "GAME_OVER"
else if health < 20:
  show LowHealthWarning
else:
  hide LowHealthWarning

Foreach

Iterate over arrays or tagged objects:

set items [Item1, Item2, Item3]
foreach item in items {
  hide item
}

# Iterate over all objects with a tag
foreach piece in #piece {
  set piece.visible true
}

# Access loop variable properties
foreach piece in #piece {
  log piece.name, "at", piece.cellX, piece.cellY
  set piece.color.color "#ff0000"
}

# Show/hide with transitions on loop variables
foreach piece in #piece {
  show piece with scale 200
  wait 100
}

foreach line in ["#row1", "#row2", "#row3"] {
  if all line.state == "BLUE" {
    set winner "BLUE"
  }
}

First

Find the first object matching a condition:

first #square where .state == "NEUTRAL" {
  set .state "RED"
}

# With else clause
first #enemy where .health <= 0 {
  hide self
} else {
  show AllEnemiesAlive
}

Inside a first block, .property refers to the matched object.

Return and Break

Exit early from actions or loops:

# return - exit the current action or event handler
action checkWin:
  if not gameOver:
    return                     # Exit early if game not over
  show WinMessage

onClick:
  if locked:
    return                     # Stop processing this click
  # ... rest of handler

# break - exit the current foreach loop
foreach item in items:
  if item.found:
    set result item
    break                      # Stop iterating
  # This code skipped after break

Notes:

Custom Actions

Define reusable action blocks:

action flash {
  set self.color.color "#ffffff"
  wait 100
  set self.color.color "#ff0000"
}

action toggle {
  if self.state == "on":
    set self.state "off"
  else:
    set self.state "on"
}

onClick:
  self.flash

Call from other objects:

onClick:
  TrafficLight.toggle
  StatusLight.flash

Advanced

Arrays

set lines ["#row1", "#row2", "#row3"]
set items [Item1, Item2, Item3]
set numbers [1, 2, 3, 4, 5]

Array indexing - access elements by position (0-based):

set cells emptyCells("Grid1")
set first cells[0]              # First element
set x cells[0].x                # Property of element
set i 2
set third cells[i]              # Variable index

Aggregates

Check or count objects by tag:

# all - true if ALL match
if all #row1.state == "BLUE":
  set winner "BLUE"

# any - true if ANY matches
if any #enemies.health <= 0:
  show EnemyDefeated

# count - number matching
if count #square.state == "NEUTRAL" == 0:
  show GameOver

set remaining count #square.state == "NEUTRAL"

Capabilities

Capabilities are special behaviors you assign to objects. They extend what an object can do.

Pageable

Cycles through child items or a list of objects:

onEnter:
  set Tutorial pageable[Step1, Step2, Step3]

onClick:  # Next button
  next Tutorial with fade

onClick:  # Prev button
  prev Tutorial with fade

Options:

next Tutorial                       # Go to next
next Tutorial wrap                  # Wrap to first at end
next Tutorial else goto "Done"      # Action at end

prev Tutorial                       # Go to previous
prev Tutorial wrap                  # Wrap to last at start

wrap Tutorial                       # Jump to first

Auto-ordered: Use component children order:

set MyComponent pageable            # Uses children in z-order

Movable (Dynamics)

Makes an object respond to keyboard input for movement:

set Player movable                  # Enable with defaults
set Player movable speed 0.5       # Custom speed
set Player movable style slide     # Smooth grid movement

Jumpable (Dynamics)

Gives an object the ability to jump (Space key by default):

set Player jumpable                 # Enable with defaults
set Player jumpable height 0.8     # Stronger jump
set Player jumpable multijump 2    # Double jump
set Player jumpable key "w"        # Custom jump key

Cell Properties

Set cell-level properties for physics and appearance:

# Physics
set Cell.gravity 0.5               # Downward gravity
set Cell.wind 0.3                  # Wind magnitude
set Cell.windAngle 90              # Wind direction (degrees: 0=right, 90=down)

# Appearance
set Cell.backgroundColor "#001122"
set Cell.backgroundPattern "grid"
set Cell.patternColor "rgba(255,255,255,0.1)"
set Cell.patternScale 1.5

See Dynamics for full documentation on:


Patterns & Debugging

Sequential Clicks

Multiple onClick blocks execute in sequence:

onClick:
  show Step1
onClick:
  show Step2
onClick:
  show Step3
  resetscript   # Next click goes back to Step1

Log

Debug output to console:

log "Hello"
log "Score is" score
log turn, winner, "game state"

Open Console from View menu to see output.


Reference

Operators

Operator Meaning
==, != Equals, not equals
>, <, >=, <= Comparisons
and, or, not Logic
+, -, *, / Math
%, mod Modulo (remainder)

Precedence (highest to lowest):

  1. () parentheses
  2. - (unary), not
  3. *, /, %, mod
  4. +, -
  5. >, <, >=, <=, ==, !=
  6. and
  7. or

Built-in Values

Value Description
random Random number 0-1 (fresh each access)
currentCell Current cell's label
clicks Current click count (for sequential onClick)

Built-in Functions

Function Returns
visited("CellName") True if cell was visited
visits("CellName") Number of times visited
has-object("Name") True if cell has named object
get("varName") Get variable value by name

Math Functions

Function Returns
floor(n) Rounds down to integer
ceil(n) Rounds up to integer
round(n) Rounds to nearest integer
abs(n) Absolute value
min(a, b) Smaller of two values
max(a, b) Larger of two values
cardinal(angle) Direction from angle: "right", "down", "left", "up"
set gridX floor(x * 5)
set clamped min(max(value, 0), 100)
set distance abs(target - position)
set facing cardinal(Player.moveAngle)  # "right", "down", "left", "up"

Array Functions

Function Returns
length(arr) Number of elements in array
range(n) Array [0, 1, 2, ..., n-1]
shuffle(arr) New array with elements randomly reordered
pick(arr) Random element from array
set count length(items)
set indices range(5)              # [0, 1, 2, 3, 4]
set randomOrder shuffle(items)
set randomItem pick(items)

Grid Functions

Grids have two separate systems - see Dynamics - Grid Systems for full documentation.

System Purpose Functions
Occupancy Track where objects are isEmpty(), emptyCells()
Cell Data Store metadata per cell Grid.cell[x][y].prop, cellsWhere()

Occupancy functions (check for objects):

Function Returns
isEmpty(grid, x, y) True if no object at (x, y)
emptyCells(grid) Array of {x, y} for cells without objects
# Check if a cell has no object
if isEmpty("Grid1", 2, 3):
  set piece.cellX 2
  set piece.cellY 3

# Get all empty cells and pick randomly
set empty emptyCells("Grid1")
if length(empty) > 0:
  set cell pick(empty)
  set piece.cellX cell.x
  set piece.cellY cell.y

Cell data functions (query stored data):

Function Returns
cellsWhere(grid, prop, op, val) Array of {x, y} matching condition
minimax(grid, prop, ai, human, depth, ...) Best column for AI move
# Find cells with specific stored data
set redCells cellsWhere("Board", "owner", "==", "RED")
set revealed cellsWhere("Board", "revealed", "==", true)

# AI for Connect Four / Tic-Tac-Toe
set bestCol minimax("Board", "owner", "YELLOW", "RED", 5, 0, 4)

Grid Cell Data

Store arbitrary metadata per cell. This is separate from object occupancy - a cell can have data even with no object in it.

# Write data to cells
set Board.cell[0][0].owner "RED"
set Board.cell[x][y].revealed true
set Board.cell[col][row].mineCount 3

# Read data from cells
if Board.cell[0][0].owner == "RED":
  show RedWins
set value Board.cell[x][y].mineCount

# Clear data
clear Board                    # Clear entire grid
clear Board.cell[2][3]         # Clear one cell

Common mistake - confusing occupancy and data:

# WRONG: This just stores data, doesn't move any object!
set Board.cell[3][4].hasPiece true

# CORRECT: Move the actual object
set Piece.cellX 3
set Piece.cellY 4
# Now isEmpty("Board", 3, 4) is false

Often you need BOTH:

# Connect Four: piece falls (occupancy) AND we track owner (data)
onSpawn:
  set self.snapToGrid "Board"
  set self.cellX col                           # Occupancy
  set Board.cell[col][row].owner currentPlayer  # Data for win checking

See Dynamics - Grid Systems for complete documentation with examples.

Scriptable Properties

Property Type Description
x, y number Position (0-1)
width, height number Size (0-1)
visible boolean Visibility
clickable boolean Accepts clicks (false = pass through)
opacity number Transparency (0-1)
rotation number Rotation angle (0-360)
flipX, flipY boolean Horizontal/vertical flip
color.color color Fill color (color layer)
strokeColor color Border color
strokeWidth number Border width
content string Text content
textColor color Text color
fontSize number Font size (rem)
cornerRadius number Corner radius
shadowX, shadowY number Shadow offset
shadowBlur number Shadow blur radius
shadowColor color Shadow color
zIndex number Layer order
state string Component state
mass number Object mass (affects wind/input, default: area)

Movement Properties (Read-only)

These properties can be read but not set - they reflect the object's current dynamics state:

Property Type Description
cellX, cellY number Grid position (0-based)
moving boolean Is object currently moving
direction string Current direction: "up", "down", "left", "right", "none"
velocityX, velocityY number Current velocity
moveAngle number Movement angle in degrees (0=right, 90=down, -1=stopped)
moveSpeed number Movement speed magnitude
# Rotate sprite to face movement direction
onMove:
  set self.state cardinal(self.moveAngle)

# Check if player is moving
if Player.moving:
  show DustParticle

Grid Properties

For objects attached to a grid. See Dynamics for full documentation.

Property Type Description
snapToGrid string Grid object name to snap to
cellX number Grid column position (0-based)
cellY number Grid row position (0-based)
blocking boolean/string Blocks other objects (can be expression)
sensor boolean Detects overlaps without blocking movement
set Piece.snapToGrid "Grid1"
set Piece.cellX 2
set Piece.cellY 3

Fill Layer Properties

Objects and cells support multi-layer fills. Access layer properties by type:

# Gradient layer
set Sun.gradient.angle 180          # Rotation angle (0-360)
set Sky.gradient.angle Sky.gradient.angle + 1   # Animate rotation

# Pattern layer
set Floor.pattern.scale 2           # Pattern scale
set Grid.pattern.color "#ff0000"    # Pattern color

# Noise layer
set Clouds.noise.scale 1.5          # Noise scale
set Clouds.noise.intensity 0.8      # Noise intensity (0-1)

# Color layer
set Box.color.color "#00ff00"       # Base color
set Box.color.opacity 0.5           # Layer opacity
Layer Type Properties
color color, opacity
gradient angle, gradientType, opacity
pattern pattern, color, scale, opacity
noise noiseType, scale, intensity, monochrome, opacity
image mode, scale, opacity

Note: When multiple layers of the same type exist, the property targets the first matching layer.

Block Styles

Both colon-style and brace-style blocks work:

# Colon style (indentation-based)
onClick:
  show Panel
  wait 500
  hide Panel

# Brace style
onClick {
  show Panel
  wait 500
  hide Panel
}

# Single-line
onClick: show Panel
onClick { show Panel }

Examples

Tic-Tac-Toe

A classic tic-tac-toe game against a simple AI opponent. Click squares to place your mark (blue) and try to get three in a row before the computer (red).

Key features demonstrated:

Download project file - Load this in Purl to explore the full implementation.

Chromastack (2048-style Puzzle)

A matching game where pieces on a grid move together in cardinal directions. When same-colored pieces collide, they merge and change color.

Cell script - animated scatter on enter:

onEnter:
  hide #piece                           # Start invisible
  set empty emptyCells("Grid1")
  set shuffled shuffle(empty)
  set i 0
  foreach piece in #piece {
    set piece.snapToGrid "Grid1"
    set piece.movable true
    set piece.blocking true
    set cell shuffled[i]
    set piece.cellX cell.x
    set piece.cellY cell.y
    show piece with scale 150           # Animated reveal
    wait 100ms                          # Stagger each piece
    set i i + 1
  }

Key features demonstrated: