TutorialsDijkstra’s Algorithm

3  Settle One Cheapest Cell

Goal

By the end of this chapter, the search wakes. Press Step, and Dijkstra’s algorithm settles the camp, then one more cell with every Step, always the cheapest it has reached. Rings show what it has found, the Status line names each cell it settles, and Reset starts it over.

The game as it is at the end of this chapter. Click the game to play.

If your game and this chapter part ways, here is the chapter’s finished project. Open it and compare, or start Chapter 4 from it.

Weighted Maze, Chapter 3 finishedMyth project · 1.8 MB

To open it, start Myth Engine, choose File › Open Project... and pick the downloaded file.

Starting with this chapter? It begins where Chapter 2 ends, and Chapter 2’s Goal has that chapter’s finished project.

3.1 The Idea

The map is painted. The compass waits at the camp. Above the three buttons, the Status line still says Paint terrain. This chapter gives Step something to do.

Press Step in the game above. Inside the orange ring at the camp, a white one appears, and thin cyan rings close around two cells: the road to the camp’s right, and the forest below it. The Status line says Cell 00: cost 0. Press Step again. The white ring moves onto the road, and the line says Cell 01: cost 1.

That’s Dijkstra’s algorithm at work. For every cell it reaches, it answers one question: what’s the cheapest way there from the camp?

The answer so far is the cell’s distance. Here a distance is a cost, never a count of cells. The camp’s is 0, because you start there. The road beside it is 1, because road costs 1 to enter. The forest below is 3.

The cells the search has reached, but isn’t sure of yet, are its frontier: the thin cyan rings. Each Step takes the frontier cell with the smallest distance and makes that distance final. That’s settling the cell. Its ring turns thick, white while it’s the newest, and blue after that.

Final means final. Nothing the search finds later can beat a settled cell. Why that holds is the chapter’s one hard idea, and Cheapest first takes it slowly.

Then the settled cell makes an offer to each of its neighbors: a route through itself, at its own distance plus what the neighbor costs to enter. A neighbor takes the offer only when it’s cheaper than the best it has. That’s relaxing the neighbor, and it’s how cells join the frontier.

You might expect the search to make for the tower. It never does. When it chooses the next cell, it doesn’t ask where the tower is. It settles whatever is cheapest from the camp, whichever way that lies, and the tower’s turn comes when its cost comes.

Most of this chapter is the search’s thinking, and thinking shows nothing. So the screen won’t change at all until One Step, where the Step button wakes the search. In Rings over the map, the last part, you’ll watch it spread across the map.

3.2 The Plan

Here’s how the pieces work together. Read each line from left to right. Most of the names are new, so skim them now and come back after the rings, when you’ve built every one.

  • Step is clicked, or Space is pressed -> Request Step sends stepRequested -> Step Trial makes one Step.
  • Step Trial’s first Step -> Check Maze Input checks the map -> the camp gets distance 0, and Settle Cheapest Cell settles it.
  • Each Step after that -> Settle Cheapest Cell asks Choose Cheapest Cell for the cheapest frontier cell, and settles it.
  • A cell is settled -> cellSettled -> Show Settled Cell writes the Status line.
  • The settled cell isn’t the tower -> Relax Neighbors offers each neighbor a route, through Relax Neighbor.
  • A Step ends -> searchChanged -> every Search Outline colors its ring again.
  • R is pressed, Reset is clicked, or a cell’s cost changes -> Reset Trial calls Clear Trial -> every record is cleared, and Clear Trial sends trialCleared -> Step Trial is Ready again.

3.3 Set Up

This chapter’s Set up card is short.

COMMANDS adds two Button Commands, Step on Space and Reset on R, made in the Input Manager as Paint was in Chapter 2.

GLOBAL VARIABLES adds $currentCell, the cell the last Step settled. It starts at -1: no cell at all. Make it with New Global… on Reset Trial’s Globals card, as before, and type -1 into its Default.

Set upWhat this chapter adds
COMMANDS (Game Settings > Input Manager)
StepButton
KeybindingsSpace
ResetButton
KeybindingsR
GLOBAL VARIABLES
$currentCellNumberstarts at -1

3.4 What the Search Writes Down

First, the search needs somewhere to write.

Everything it finds goes into Maze Cells, in three new columns beside cost. The first, distance, you’ve met. A distance of 1000000 means the search hasn’t reached the cell yet, since that’s far more than any route on this map could cost. The second, parent, holds a cell’s parent: the cell its cheapest route came through. Its -1 means none yet. The third, settled, is 1 once the cell’s distance is final, and 0 until then.

A table’s columns are the fields of its Data Structure. So open Maze Cells Entry and press Add Field for each, as you added rows to Maze Settings Entry in Chapter 2. Give each the Value its card shows.

One thing will look wrong, and isn’t. A new field fills the 120 rows already there with 0, whatever Value you type after it. So your distance and parent columns say 0 where the table’s FIRST ROWS show 1000000 and -1. Leave them. In One Step, Clear Trial writes all three columns on every row each time the game starts. Nothing reads them before then.

Data StructureMaze Cells EntryAddDelete

One row of the "Maze Cells" Data Table.

costNumberdefault 1
distanceNumberdefault 1000000
parentNumberdefault -1
settledNumberdefault 0
Data TableMaze CellsData Structure: Maze Cells EntryAddDelete
IMPORT CSV120 rows, from a file
FIRST ROWS3 of 120, to compare
Row 0Cell 00
cost1
distance1000000
parent-1
settled0
Row 1Cell 01
cost1
distance1000000
parent-1
settled0
Row 2Cell 02
cost1
distance1000000
parent-1
settled0

Maze Settings gets two columns. The first, unreached, is the number the search writes for not reached yet, the same 1000000. The second, maxCost, is the most a cell may cost, 99. A route that entered every cell of the map at 99 would still cost far less than unreached. So no real distance is ever mistaken for not reached.

Add them to Maze Settings Entry the same way. Then type both into the rules row too, as you typed 10 for rows, because a new field gives that row 0 as well.

Data StructureMaze Settings EntryAddDelete

One row of the "Maze Settings" Data Table.

columnsNumberdefault 12
rowsNumberdefault 10
startCellNumberdefault 0
goalCellNumberdefault 119
maxCostNumberdefault 99
unreachedNumberdefault 1000000
Data TableMaze SettingsData Structure: Maze Settings EntryAddDelete
Row 0rules
columns12
rows10
startCell0
goalCell119
maxCost99
unreached1000000

Run the game if you like. Nothing has changed, and nothing will for a while.

3.5 Cheapest First

Now the heart of the search: choosing which cell to settle, and settling it.

3.5.1 Choose Cheapest Cell

Choose Cheapest Cell answers one question. Which frontier cell has the smallest distance? It hands back that cell’s number, or -1 when the frontier is empty.

It does what you’d do with a price list and one finger. Read down from the top. Keep your finger on the cheapest price so far, and move it only for a lower one. Here the Repeat reads every row of Maze Cells in order, and as in Lay Out Map, local.index is the cell number. The finger is cheapest, and the price under it is cheapestDistance. The card’s first two steps make them, as Declare Variables that live only while the Function runs.

A row is a candidate only when three tests are true. The If joins them with &&, which means and: it isn’t settled, it isn’t a wall, and its distance is below cheapestDistance. Before the Repeat starts, cheapestDistance is set to unreached, so a cell the search hasn’t reached can never win.

Now look at the third test. It’s <, not <=. A price that only ties never moves your finger, so when two cells have the same distance, the first one met stays chosen. That’s always the lower number, and it decides the fourth Step, as you’ll see.

FunctionChoose Cheapest Cell

The frontier cell with the smallest distance, or -1 when the frontier is empty. The frontier is every cell that is not a Wall, not settled, and already reached (its distance is less than Maze Settings unreached). On equal distances the lowest cell number wins.

RETURN TYPENumber
ACTIONS
1Declare Variable
Name
cheapest
Value
-1
2Declare Variable
Name
cheapestDistance
Value
0
3Change Variable
Variable
cheapest
Value
-1
4Change Variable
Variable
cheapestDistance
Value
data('Maze Settings', 'rules', 'unreached')
5RepeatdataCount('Maze Cells')times
Each time
6Ifdata('Maze Cells', local.index, 'settled') == 0 && data('Maze Cells', local.index, 'cost') != data('Terrains', 'Wall', 'cost') && data('Maze Cells', local.index, 'distance') < cheapestDistance
Then
7Change Variable
Variable
cheapest
Value
local.index
8Change Variable
Variable
cheapestDistance
Value
data('Maze Cells', local.index, 'distance')
9Return
Value
cheapest

3.5.2 Settle Cheapest Cell

Settle Cheapest Cell turns that choice into a certainty. Watch $currentCell. It’s the Global the Function keeps the chosen cell in, so the rest of the game can find it. When the frontier is empty, it’s -1, and the card’s If lets nothing else happen. Otherwise the cell is settled, and the Function sends cellSettled to say so.

Two names on its card need a word. The call’s Action Name is cheapest. That’s the word for the finger in Choose Cheapest Cell, but here it names the call. So cheapest.value is the number the call hands back, the cell under the finger. On the block it shows as cheapest alone. And Set Data Cell finds the row by its name. The formula dataRow('Maze Cells', $currentCell) gives the name of the row at that number, such as Cell 12.

Set Data Cell is back from Chapter 2, in the Action List while Advanced Mode is on.

Visited, down on the sidebar, has said 0 ever since Chapter 1. It was waiting for this. It counts settled cells, and only those. A cell the search has reached but not settled, with only a thin cyan ring, isn’t counted.

Here is the chapter’s one hard idea, so take it slowly. Why can a settled cell’s distance never change again?

Say the cheapest frontier cell has distance 3. Any other way to it, one the search hasn’t found yet, must leave the settled cells somewhere. Where it leaves, it passes through a frontier cell. Every frontier cell’s distance is already 3 or more. That’s what cheapest means.

From there the way on can only add, because no cost on the map is below 0. So no way round comes in under 3. The cell is settled at 3, for good.

That’s why the search never goes back to fix a settled cell. It’s also why every cost must stay at 0 or above, and Check Maze Input, in The map is checked first, sees to that.

Nothing listens for cellSettled yet. In One Step, the Status line will.

FunctionSettle Cheapest Cell

Settles the cheapest frontier cell (Choose Cheapest Cell): marks it settled, adds 1 to Visited, and sends cellSettled, so the Status line can name it. The cell settled becomes the current cell (the Project Global currentCell), which is -1 when the frontier was empty and nothing was settled; cell gives the same number.

RETURN TYPENumber
ACTIONS
1Choose Cheapest CellFunction
Action Name
cheapest
2Change Variable
Variable
$currentCell
Value
cheapest.value
3If$currentCell >= 0
Then
4Set Data Cell
Table
"Maze Cells"
Row
dataRow('Maze Cells', $currentCell)
Column
"settled"
Value
1
5Change Variable
Variable
$visited
Operator
+=
Value
1
6Trigger Custom Event
Custom Event
cellSettled
7Return
Value
$currentCell

3.6 A Cheaper Way In

A settled cell has one more job: it offers each of its neighbors a route through itself.

3.6.1 Relax Neighbor

Relax Neighbor makes one offer, to one neighbor. It asks Find Grid Neighbor, your Function from Chapter 2, which cell lies in its direction. The answer comes back as next.value, and goes into the Variable neighbor.

The offer goes only to a neighbor that can take it: one on the map, not a wall, and not settled. A settled cell is final, so it’s never offered anything. The price comes next, newDistance: this cell’s distance, plus the neighbor’s cost. If that’s below the neighbor’s distance so far, the neighbor takes it, and two Set Data Cells write its new distance, and this cell as its parent.

Count the camp’s offers. The camp settles at 0, and its neighbors are asked in turn. Up is off the map, so Find Grid Neighbor answers -1, and nothing is offered. Right is road, cost 1. The offer is 0 plus 1, far below 1000000, so the road takes it: distance 1, with the camp as its parent. Below is forest, and it takes 0 plus 3. Left is off the map again, so that’s two thin cyan rings from one settled cell.

An offer that only ties is turned down, because the second If asks <. So a cell keeps the parent it found first. The parent column has no reader yet. In Chapter 4, the route is found by following parents home from the tower.

FunctionRelax Neighbor

Offers one neighbor of a settled cell a route through it. The neighbor is the cell next to cell in direction (a row of Directions). A neighbor on the map that is not a Wall and not settled gets distance (cell's distance plus the neighbor's cost) and cell as its parent, but only when that is strictly smaller than its distance so far, so an equal route keeps the parent it found first.

PARAMETERS
NameTypeStarts at
cellNumber0
directionTextUp
ACTIONS
1Declare Variable
Name
neighbor
Value
-1
2Declare Variable
Name
newDistance
Value
0
3Find Grid NeighborFunction
Action Name
next
cell
cell
direction
direction
4Change Variable
Variable
neighbor
Value
next.value
5Ifneighbor >= 0 && data('Maze Cells', neighbor, 'cost') != data('Terrains', 'Wall', 'cost') && data('Maze Cells', neighbor, 'settled') == 0
Then
6Change Variable
Variable
newDistance
Value
data('Maze Cells', cell, 'distance')
7Change Variable
Variable
newDistance
Operator
+=
Value
data('Maze Cells', neighbor, 'cost')
8IfnewDistance < data('Maze Cells', neighbor, 'distance')
Then
9Set Data Cell
Table
"Maze Cells"
Row
dataRow('Maze Cells', neighbor)
Column
"distance"
Value
newDistance
10Set Data Cell
Table
"Maze Cells"
Row
dataRow('Maze Cells', neighbor)
Column
"parent"
Value
cell

3.6.2 Relax Neighbors

Relax Neighbors makes all four offers, one after another. A Repeat runs once for each row of Directions, and dataRow hands Relax Neighbor that row’s name: Up, Right, Down, then Left.

That’s the order Chapter 2 asked you to keep. Here it is in use. The search looks around each cell up, right, down and left.

FunctionRelax Neighbors

Offers every neighbor of one settled cell a route through it (Relax Neighbor), in the order of Directions: Up, Right, Down, Left.

PARAMETERS
NameTypeStarts at
cellNumber0
ACTIONS
1RepeatdataCount('Directions')times
Each time
2Relax NeighborFunction
cell
cell
direction
dataRow('Directions', local.index)

Nothing calls these yet. Step Trial will, in One Step.

3.7 The Map Is Checked First

One more piece comes before the first Step: a check that the map can be searched at all.

3.7.1 Check Maze Input

A search of a broken map couldn’t be trusted. So Check Maze Input looks at the map and Maze Settings before anything is settled. It hands back one word, problem. That’s the name of a row of Messages, or nothing at all when every rule holds.

It catches your own slips, too. The likeliest is a rules row that still holds Add Field’s 0s, and If it didn’t work shows what the Status line says then.

There are five rules, in the order of the card’s Description. The map must have as many cells as columns times rows, and the settings maxCost and unreached must make sense together. The camp and the tower must be cells of the map. Every cost must be a wall, or a whole number from 0 up to maxCost. Last, the camp and the tower may not be walls. The game’s words call those two S and G, for start and goal, as in Unblock S and G.

The check takes two limits from Step Trial, which calls it. The first, maxCostLimit, is 99. That’s the most maxCost may be, so every cost stays within two digits. The second, unreachedLimit, is the most unreached may be: 1000000000. They’re Step Trial’s Shared Variables, not numbers typed into the check, so you can see them and change them without opening it.

The rule about costs is the one the search rests on. A cost below 0 would let a longer route look cheaper, and a settled distance wouldn’t be final any more. The wall’s -1 is the one exception, because a wall is never offered anything.

The card is long. But most of it is one shape five times: an If with one Change Variable inside. The If for the costs sits in a Repeat, which visits every cell. Each If’s condition joins its tests with ||, which means or, so any true test breaks the rule.

The rules are asked from the last to the first. Each broken rule writes its row’s name over the one before, so when several are broken, the first rule is written last. That’s the one the player reads.

A few pieces are new. Step 8 multiplies: with the Operator *=, the Value multiplies what the Variable held, so mapCells becomes columns times rows. A number that isn’t whole differs from its own floor(), and that’s what each test with floor() asks. And problem is words, so its Declare Variable’s Type is Text. Step 9 empties it with "", two double quote marks with nothing between. The Function’s Return Type is Text too, and the Return at the bottom hands problem back.

FunctionCheck Maze Input

Checks the map and Maze Settings before a search begins. problem is the Messages row of the first rule that is broken, or empty when every rule holds: the map has columns times rows cells (wrongMapSize); maxCost is a whole number from 0 to maxCostLimit, and unreached is larger than any route can cost and at most unreachedLimit (wrongSettings); the Camp and the Tower are cells of the map (wrongEndpoints); every cost is a Wall or a whole number from 0 to maxCost (wrongCost); neither the Camp nor the Tower is a Wall (blockedEndpoint).

PARAMETERS
NameTypeStarts at
maxCostLimitNumber99
unreachedLimitNumber1000000000
RETURN TYPEText
ACTIONS
1Declare Variable
Type
Text
Name
problem
2Declare Variable
Name
camp
Value
0
3Declare Variable
Name
tower
Value
0
4Declare Variable
Name
mapCells
Value
0
5Change Variable
Variable
camp
Value
data('Maze Settings', 'rules', 'startCell')
6Change Variable
Variable
tower
Value
data('Maze Settings', 'rules', 'goalCell')
7Change Variable
Variable
mapCells
Value
data('Maze Settings', 'rules', 'columns')
8Change Variable
Variable
mapCells
Operator
*=
Value
data('Maze Settings', 'rules', 'rows')
9Change Variable
Variable
problem
Value
""
10Ifdata('Maze Cells', camp, 'cost') == data('Terrains', 'Wall', 'cost') || data('Maze Cells', tower, 'cost') == data('Terrains', 'Wall', 'cost')
Then
11Change Variable
Variable
problem
Value
'blockedEndpoint'
12RepeatdataCount('Maze Cells')times
Each time
13Ifdata('Maze Cells', local.index, 'cost') < data('Terrains', 'Wall', 'cost') || data('Maze Cells', local.index, 'cost') > data('Maze Settings', 'rules', 'maxCost') || data('Maze Cells', local.index, 'cost') != floor(data('Maze Cells', local.index, 'cost'))
Then
14Change Variable
Variable
problem
Value
'wrongCost'
15Check Data Row Exists
Action Name
isCampOnMap
Table
"Maze Cells"
Row
camp
16Check Data Row Exists
Action Name
isTowerOnMap
Table
"Maze Cells"
Row
tower
17IfisCampOnMap.value == false || camp != floor(camp) || isTowerOnMap.value == false || tower != floor(tower)
Then
18Change Variable
Variable
problem
Value
'wrongEndpoints'
19Ifdata('Maze Settings', 'rules', 'maxCost') < 0 || data('Maze Settings', 'rules', 'maxCost') > maxCostLimit || data('Maze Settings', 'rules', 'maxCost') != floor(data('Maze Settings', 'rules', 'maxCost')) || data('Maze Settings', 'rules', 'unreached') <= (dataCount('Maze Cells') * data('Maze Settings', 'rules', 'maxCost')) || data('Maze Settings', 'rules', 'unreached') > unreachedLimit
Then
20Change Variable
Variable
problem
Value
'wrongSettings'
21IfmapCells != dataCount('Maze Cells') || data('Maze Settings', 'rules', 'columns') < 1 || data('Maze Settings', 'rules', 'columns') != floor(data('Maze Settings', 'rules', 'columns')) || data('Maze Settings', 'rules', 'rows') != floor(data('Maze Settings', 'rules', 'rows'))
Then
22Change Variable
Variable
problem
Value
'wrongMapSize'
23Return
Value
problem

Its answers are rows of Messages, and Messages gets seven new rows. Six are words for a search that can’t begin or can’t go on: one for each rule, and No route. The seventh, cellSettled, goes between a cell’s name and its distance on the Status line. It starts with a colon and ends with a space after cost.

The card can’t show that space. Type it anyway: without it, the Status line runs the word and the number together.

Data TableMessagesData Structure: Messages EntryAddDelete
Row 0ready
wordsPaint terrain.
Row 1cellSettled
words: cost
Row 2noRoute
wordsNo route.
Row 3wrongMapSize
wordsFix columns and rows.
Row 4wrongSettings
wordsCheck search settings.
Row 5wrongEndpoints
wordsCheck start and goal.
Row 6wrongCost
wordsUse costs 0 to maxCost.
Row 7blockedEndpoint
wordsUnblock S and G.

3.8 One Step

Now the pieces come together, and the search wakes.

3.8.1 Step Trial

Step Trial, on Maze Rules, makes one Step each time stepRequested arrives. What a Step does depends on where the trial is, and Step Trial keeps that in its Event’s Modes. It has three: Ready, Searching and Finished. Only one answers at a time. Switch Mode turns on another one and turns the rest off, ready for the next Step. So one press is always exactly one Step.

Ready answers the first Step of a trial: it calls Check Maze Input, and when problem is empty, the search begins. The camp’s distance becomes 0, and Settle Cheapest Cell settles it, the only cell reached so far. If the camp is also the tower, the search is over at once, and the trial is Finished. Otherwise the camp makes its offers, and Step Trial switches to Searching. When a rule is broken instead, the Status line shows that rule’s words, nothing is settled, and the trial is Finished.

Searching answers every Step after that. It settles the cheapest frontier cell, and that cell makes its offers. When the cell it settles is the tower, though, the search is over: the tower’s distance is final, and that’s the cost of the cheapest route. When there’s no cell left to settle before the tower, nothing more can be reached, and the Status line says No route. Either way, the trial is Finished.

Finished has no Actions. A Step there does nothing, until Reset or a painted cell starts a new trial.

Step Trial has a second Event, for trialCleared. That message comes from Clear Trial, the next piece. When it arrives, Reset Modes puts On Step Requested back to Ready, its Starting Mode.

Build it in this order. Make Step Trial, and give it the card’s two Shared Variables, Numbers that stay Constant. It hands them to Check Maze Input as @self.maxCostLimit and @self.unreachedLimit. Add its Event, a Custom Event, stepRequested. Nothing sends that yet, so make it with Add Custom Event…, as you made paintRequested.

Its one Mode is named Step Trial. Rename it Ready, and add Searching, then Finished, with New Mode…. If they don’t stand in the card’s order, Move Left and Move Right put them there. Ready should be the Starting Mode. Then add the trialCleared Event, with Add Custom Event… again, since nothing sends it yet either. Only now add the Actions. Switch Mode and Reset Modes pick the Event and the Mode from lists, and the lists need them first. Modes on the Making page shows each move.

A few spots need care. Ready’s step 10 sends searchChanged from the end of step 2’s True line, after the If inside it. So right-click step 2’s If, and choose Add inside Then. Searching’s step 10 is different. It’s on the Mode’s own line, after its If, so right-click the Searching block and choose Add unattached Action here. Either way, Connect to sequence joins it at the end of its line.

And three Elses here hold two Actions each. Add Else, on the If’s right-click menu, makes the Else. Then right-click the Else and choose Add inside Else for each of its two Actions, and join each with Connect to sequence.

Ready’s step 2 asks whether inputCheck is empty. Its right side is "", two double quote marks with nothing between them: no problem at all.

On the card, the first Event’s heading and its When bar come three times, one above each Mode. It’s still one Event, with three Modes standing in a row to its right.

Logic ComponentStep TrialAttach to: Maze Rules

Makes one Step of the trial each time stepRequested arrives (the Step button or the Step Command). What a Step does depends on the trial's phase, the Mode of stepRequested: Ready begins the search and settles the Camp, Searching settles the next cheapest cell, and Finished does nothing. Before the first settle, Ready checks the map and Maze Settings (Check Maze Input); maxCostLimit and unreachedLimit are the largest maxCost and unreached that check allows. A cleared trial (trialCleared, from Clear Trial) starts again from Ready.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
maxCostLimitNumberConstant99
unreachedLimitNumberConstant1000000000
On Step Requested
WhenstepRequestedCustom Event
ModeReady (Starting Mode)
1Check Maze InputFunction
Action Name
inputCheck
maxCostLimit
@self.maxCostLimit
unreachedLimit
@self.unreachedLimit
2IfinputCheck.value == ""
Then
3Set Data Cell
Table
"Maze Cells"
Row
dataRow('Maze Cells', data('Maze Settings', 'rules', 'startCell'))
Column
distance
Value
0
4Settle Cheapest CellFunction
Action Name
firstSettle
5IffirstSettle.value == data('Maze Settings', 'rules', 'goalCell')
Then
6Switch Mode
Event
On Step Requested
Mode
Finished
7Otherwise
8Relax NeighborsFunction
cell
firstSettle.value
9Switch Mode
Event
On Step Requested
Mode
Searching
10Trigger Custom Event
Custom Event
searchChanged
11Otherwise
12Change Variable
Variable
$status
Value
data('Messages', inputCheck.value, 'words')
13Switch Mode
Event
On Step Requested
Mode
Finished
On Step Requested
WhenstepRequestedCustom Event
ModeSearching
1Settle Cheapest CellFunction
Action Name
settle
2Ifsettle.value >= 0
Then
3Ifsettle.value == data('Maze Settings', 'rules', 'goalCell')
Then
4Switch Mode
Event
On Step Requested
Mode
Finished
5Otherwise
6Relax NeighborsFunction
cell
settle.value
7Otherwise
8Change Variable
Variable
$status
Value
data('Messages', 'noRoute', 'words')
9Switch Mode
Event
On Step Requested
Mode
Finished
10Trigger Custom Event
Custom Event
searchChanged
On Step Requested
WhenstepRequestedCustom Event
ModeFinished
(no Actions)
On Trial Cleared
WhentrialClearedCustom Event
1Reset Modes
Event
On Step Requested

The card for Maze Rules adds Step Trial. Yours lands at the bottom of the list. As in Chapter 2, that’s fine: no two of them wait for the same Event.

ObjectMaze RulesAddDelete
Logic
Change Terrain
Restore Defaults
Step Trial
Reset Trial

3.8.2 Clear Trial

Every trial needs a clean start, and Clear Trial gives it one. It wipes the search’s records from every row of Maze Cells, puts the sidebar back to its first words, and hides every ring. Watch its last step. It sends trialCleared to Maze Rules, and Step Trial puts its Event back to Ready. The next Step begins a new search.

Why doesn’t Clear Trial switch the Mode itself? It can’t. Only the Logic whose Event it is can change that Event’s Modes, and Clear Trial is a Function, with no Event at all. So it sends word, and Step Trial does the rest.

It never touches cost. A Reset keeps your terrain.

This is what the 0s in Maze Cells were waiting for. Clear Trial runs as the game starts. Every row is written fresh before the search reads it.

Its one Parameter, rules, is the Maze Rules it tells, and no other kind of Object will do. It’s an Object Parameter, so open its Settings, choose Nominal, and pick Maze Rules. In Trigger Custom Event on Object, the Applies To list then offers rules · Maze Rules.

FunctionClear Trial

Puts the trial back to Ready, keeping the terrain: every cell of Maze Cells gets distance unreached (Maze Settings), no parent (-1) and settled 0, Visited goes back to 0, no cell is current (currentCell -1), Status asks the learner to paint terrain (the Messages ready words), and searchChanged clears every Search Outline. Last, trialCleared tells Step Trial on rules (Maze Rules) that the trial was cleared, so the next Step starts from Ready.

PARAMETERS
NameTypeStarts at
rulesObjectMaze Rules
ACTIONS
1RepeatdataCount('Maze Cells')times
Each time
2Set Data Cell
Table
"Maze Cells"
Row
dataRow('Maze Cells', local.index)
Column
"distance"
Value
data('Maze Settings', 'rules', 'unreached')
3Set Data Cell
Table
"Maze Cells"
Row
dataRow('Maze Cells', local.index)
Column
"parent"
Value
-1
4Set Data Cell
Table
"Maze Cells"
Row
dataRow('Maze Cells', local.index)
Column
"settled"
Value
0
5Change Variable
Variable
$visited
Value
0
6Change Variable
Variable
$currentCell
Value
-1
7Change Variable
Variable
$status
Value
data('Messages', 'ready', 'words')
8Trigger Custom Event
Custom Event
searchChanged
9Trigger Custom Event on Object
Applies To
rules
Custom Event
trialCleared

3.8.3 Reset Trial

In Chapter 1, Reset Trial set the Status line and nothing more. Its name promised more, though. From Chapter 3, a Reset would put the search back where it started, and this would be the piece that does it.

Now four things call for a clean start, and each Event calls Clear Trial. The Scene starts. The Reset Command comes, when R is pressed. The Reset button sends resetRequested when it’s clicked. Or terrainChanged comes after a painted cell, because a search of the old map wouldn’t be true of the new one.

Painting a cell with the cost it already has sends nothing. The search goes on. Any other cost starts the trial over, back at Ready.

Its old step goes. Clear Trial sets the Status line now, along with everything else. Right-click that block and choose Delete. Each call hands Clear Trial self as rules: the Maze Rules that Reset Trial is on. Clear Trial takes nothing else. So Reset Trial must promise to be on a Maze Rules. Open its Logic Component card, and set Attach to Maze Rules, as its card shows. Then each call’s rules box reads self · Maze Rules. There’s nothing to change.

The Add Event window lists terrainChanged already, but not resetRequested, since nothing sends it yet. Add Custom Event… makes that one.

Logic ComponentReset TrialAttach to: Maze RulesAddDelete

Puts the trial in its Ready state, keeping the terrain, with the Function Clear Trial: when the Scene starts, when the learner presses Reset (the Reset Command, R in the Input Manager) or the Reset button (resetRequested), and whenever a cell's terrain changes (terrainChanged), so a search never mixes two maps.

Attach toMaze Rules
Attach toAny Object
When the Scene Starts
WhenScene Start
1Change Variable
Variable
$status
Value
data('Messages', 'ready', 'words')
1Clear TrialFunction
rules
self
When Reset Is Pressed
WhenCommand PressedCommand: Reset
1Clear TrialFunction
rules
self
On Reset Requested
WhenresetRequestedCustom Event
1Clear TrialFunction
rules
self
On Terrain Changed
WhenterrainChangedCustom Event
1Clear TrialFunction
rules
self

3.8.4 Show Settled Cell

Remember cellSettled, which Settle Cheapest Cell has been sending with no one listening? This is who it was for.

Show Settled Cell, on Status, hears it each time a cell is settled. It writes the line in three moves: the row’s name for $currentCell, then the cellSettled words, then the cell’s distance. So Cell 12, then the colon and cost, then 3. The second and third moves use the Operator +=, which adds to the end of the words.

Why not let Settle Cheapest Cell write the line itself? It could. But then the search would carry the player’s words as well as the search. Kept apart, Settle Cheapest Cell does Dijkstra’s algorithm and nothing else, and the Status line tells you what happened.

Logic ComponentShow Settled CellAttach to: Status

The Status line while the search settles cells: each time Settle Cheapest Cell settles a cell (cellSettled), Status names the cell by its Maze Cells row and gives its distance, as in "Cell 52: cost 8".

Attach toAny Object
On Cell Settled
WhencellSettledCustom Event
1Change Variable
Variable
$status
Value
dataRow('Maze Cells', $currentCell)
2Change Variable
Variable
$status
Operator
+=
Value
data('Messages', 'cellSettled', 'words')
3Change Variable
Variable
$status
Operator
+=
Value
data('Maze Cells', $currentCell, 'distance')
ObjectStatusAddDelete
Transform
Presentation > SpaceWindow / UI
Presentation > Render LayerUI
Text / UI
Text{$status}
Font Size16
EmphasisSemibold
Color#203F43
AlignmentCenter
RenderingVector
Logic
Show Settled Cell

3.8.5 Request Step

The Step Button has kept its UI Interaction since Chapter 1, waiting for this. Request Step makes a click send stepRequested, and the Step Command does the same for Space. The button and the key only ask. Maze Rules decides what a Step is.

Logic ComponentRequest StepAttach to: Step Button

Lets the learner ask for one Step with a click or a touch on the Step button, or with the Step Command (Space in the Input Manager): each sends stepRequested, which Step Trial on Maze Rules answers.

Attach toAny Object
On Hud Activated
WhenUI Activated
1Trigger Custom Event
Custom Event
stepRequested
When Step Is Pressed
WhenCommand PressedCommand: Step
1Trigger Custom Event
Custom Event
stepRequested
ObjectStep ButtonAddDelete
Transform
Presentation > SpaceWindow / UI
Presentation > Render LayerUI
Bounding Box > SizeX 108, Y 48
Text / UI
TextStep
Font Size18
EmphasisBold
Color#203F43
AlignmentCenter
RenderingVector
Sprite
Z Index Within Object-1
SpritePanel
Transform > SizeX 108, Y 48
Tint (on its placement)#EEE3CC
UI Interaction(as it is added)
Logic
Request Step

3.8.6 Request Reset

Request Reset is Request Step again, for the other button, with one Event: a click sends resetRequested.

Logic ComponentRequest ResetAttach to: Reset Button

Lets the learner ask for a Reset with a click or a touch: activating the Reset button sends resetRequested, which Reset Trial on Maze Rules answers.

Attach toAny Object
On Hud Activated
WhenUI Activated
1Trigger Custom Event
Custom Event
resetRequested
ObjectReset ButtonAddDelete
Transform
Presentation > SpaceWindow / UI
Presentation > Render LayerUI
Bounding Box > SizeX 254, Y 48
Text / UI
TextReset
Font Size18
EmphasisBold
Color#203F43
AlignmentCenter
RenderingVector
Sprite
Z Index Within Object-1
SpritePanel
Transform > SizeX 254, Y 48
Tint (on its placement)#EEE3CC
UI Interaction(as it is added)
Logic
Request Reset

Run the game, and press Step. The Status line says Cell 00: cost 0, Visited says 1, and the search has woken at the camp.

Press Step again: Cell 01: cost 1. Cell 02: cost 2 comes next, then Cell 03: cost 3, and the fifth Step says Cell 12: cost 3. Cells 3 and 12 both had distance 3, and the lower number went first, the price list and the finger.

Press R, and it all goes back to Paint terrain. The map hasn’t shown you any of it yet. That’s the last part’s job.

3.9 Rings Over the Map

Last, the search becomes something you can watch.

Each cell gets a ring of its own, a Search Outline, made by Lay Out Map along with the cell. A ring hides until the search reaches its cell. Reached, it’s a thin cyan ring, on the frontier, and settled, a thick blue one. The cell the last Step settled has a thick white ring, the newest certainty.

Thin means not final yet, and thick means final. So the search reads the same even if cyan and blue look alike to you, and the Status line says it in words as well.

3.9.1 Color Search Outline

Color Search Outline decides one ring’s look. It asks from the weakest look to the strongest, so the last question that’s true wins. First it hides the ring. If the cell is reached, its distance below unreached, it shows the thin ring in frontierTint. If the cell is settled, it shows the thick ring in settledTint. If the cell is $currentCell, the ring takes currentTint, white, on the thick ring it already has.

Its outline is made the way you made Highlight Terrain Choice’s button in Chapter 2: an Object Parameter that stays Structural, with the Sprite as a Required Component. Pick it in each Applies To list. Set Visible is new. In a Function its Visible box is typed, so type true or false, as the card has it. Set Frame picks one of the Cell Outline’s two frames. Frame 0 is the thin ring, and frame 1 the thick one.

FunctionColor Search Outline

Shows one cell's place in the search on its Search Outline: hidden while the search has not reached the cell, a thin ring in frontierTint once it is reached, a thick ring in settledTint once it is settled, and currentTint while it is the current cell (the one the last Step settled). A later rule wins: current over settled over frontier. The thin and the thick ring are frames 0 and 1 of the Cell Outline picture, so a settled cell shows by its shape as well as by its color.

PARAMETERS
NameTypeStarts at
outlineObjectStructural
Required Components
Slot Namesprite
Component TypeSprite
cellNumber0
frontierTintColor#32e9ed
settledTintColor#64afff
currentTintColor#ffffff
ACTIONS
1Set Visible
Applies To
outline
Visible
false
2Ifdata('Maze Cells', cell, 'distance') < data('Maze Settings', 'rules', 'unreached')
Then
3Set Visible
Applies To
outline
Visible
true
4Set Frame
Applies To
outline
Frame
0
5Change Sprite Tint Property
Applies To
outline
Value
frontierTint
6Ifdata('Maze Cells', cell, 'settled') == 1
Then
7Set Visible
Applies To
outline
Visible
true
8Set Frame
Applies To
outline
Frame
1
9Change Sprite Tint Property
Applies To
outline
Value
settledTint
10Ifcell == $currentCell
Then
11Set Visible
Applies To
outline
Visible
true
12Change Sprite Tint Property
Applies To
outline
Value
currentTint

3.9.2 Show Search Outline

Show Search Outline is the ring’s own Logic, and it works like Paint Map Cell, which began in Chapter 1 as Show Map Cell. When placedOnMap arrives, it asks Cell Screen Position where its cell stands. It goes there, Screen and all, and colors itself. When searchChanged arrives, it colors itself again. Step Trial and Clear Trial already send searchChanged, so it’s waiting in the Add Event window.

Its Data Structures card takes Map Place, under Existing Data Structures, as Paint Map Cell’s does. Its Components card needs the Sprite. The three colors are Shared Variables, Colors that stay Constant, with the card’s codes as their Starting Values. Each call’s outline box starts at self, the ring itself, so leave it there.

Logic ComponentShow Search OutlineAttach to: Search Outline

One cell's ring that shows where the search is: whenever the search changes, it is hidden, a thin cyan ring (frontier), a thick blue ring (settled) or a thick white ring (the current cell), with Color Search Outline. Its cell is its Map Place cell, which Lay Out Map gives it. frontierTint, settledTint and currentTint are the three colors.

Attach toAny Object
DATA STRUCTURES
Map Placeself.mapPlace
COMPONENTS
self.spriteSprite
SHARED VARIABLES
NameTypeKindStarting Value
frontierTintColorConstant#32e9ed
settledTintColorConstant#64afff
currentTintColorConstant#ffffff
On Placed on Map
WhenplacedOnMapCustom Event
1Cell Screen PositionFunction
Action Name
place
cell
self.mapPlace.cell
2Set Position
Position Relative To
Screen
X
place.x
Y
place.y
3Color Search OutlineFunction
outline
self
cell
self.mapPlace.cell
frontierTint
@self.frontierTint
settledTint
@self.settledTint
currentTint
@self.currentTint
On Search Changed
WhensearchChangedCustom Event
1Color Search OutlineFunction
outline
self
cell
self.mapPlace.cell
frontierTint
@self.frontierTint
settledTint
@self.settledTint
currentTint
@self.currentTint

The Search Outline is a Screen UI Object showing the Cell Outline at 64 by 64. Remove its UI Interaction. A Scene Depth Draw Order of 1 puts it over the cells and under the orange ring, which has 4. Attach Show Search Outline, and its Map Place arrives From Logic.

ObjectSearch Outline
Transform
Presentation > SpaceWindow / UI
Presentation > Render LayerUI
Bounding Box > SizeX 64, Y 64
Sprite
Scene Depth Draw Order1
Z Index Within Object1
SpriteCell Outline
Transform > SizeX 64, Y 64
Data Structure
From LogicShow Search Outline
Data StructureMap Place
Logic
Show Search Outline

Map Place’s card changes only its Description, which now names the Search Outlines too. A Changes card never marks its Description, so nothing on this one is green or struck. Type the new one in if you like.

Data StructureMap PlaceAddDelete

The cell a piece of the map stands on. cell is its number: 0 is the top-left cell, and the numbers grow to the right, then down (its row of Maze Cells). Lay Out Map gives each Map Cell and each Search Outline its cell right after creating it.

cellNumberdefault -1

3.9.3 Lay Out Map

Lay Out Map makes the rings in the same Repeat that makes the cells. Three new steps follow the old three. They work the same way. Create Instance makes a Search Outline as newOutline, newOutline.cell gets local.index, and placedOnMap goes to newOutline only. Add each with Add inside Repeat, on the Repeat’s right-click menu.

The Object it makes comes from a new Shared Variable, searchOutline, an Object that stays Constant and starts at None, like mapCell.

Logic ComponentLay Out MapAttach to: MapAddDelete

Lays out the map when the Scene starts: for each row of Maze Cells it creates a Map Cell and a Search Outline, gives each the cell number (its Map Place cell) and tells it, with placedOnMap, that it is placed; the piece then stands on its cell and shows itself. mapCell and searchOutline are the Objects it makes.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
mapCellObjectConstantNone
searchOutlineObjectConstantNone
When the Scene Starts
WhenScene Start
1RepeatdataCount('Maze Cells')times
Each time
2Create Instance
Object to Create
@self.mapCell
X
0
Y
0
Created As
newCell
3Change Variable
Variable
newCell.cell
Value
local.index
4Trigger Custom Event on Object
Applies To
newCell
Custom Event
placedOnMap
5Create Instance
Object to Create
@self.searchOutline
X
0
Y
0
Created As
newOutline
6Change Variable
Variable
newOutline.cell
Value
local.index
7Trigger Custom Event on Object
Applies To
newOutline
Custom Event
placedOnMap

On the Map, set the new searchOutline the way you set mapCell: under Variables, pick Search Outline as its Value. The Map’s card comes whole this time, with no marks. Its new line is the last one.

ObjectMap
Logic
Lay Out Map
Shared Variables
mapCellMap Cell
searchOutlineSearch Outline

Run the game, and press Step. Inside the orange ring at the camp, a white one appears, and thin cyan rings close around the road to its right and the forest below. Step again. The white ring moves onto the road, and the camp’s turns blue.

Keep going. The thick blue spreads out from the camp like a ripple, quick along the road and slow into the forest. The thin cyan edge stays a cell ahead of it. Each white ring is the newest certainty, and none is ever taken back.

Play it!

Watch for each of these.

  • Before the first Step, no ring shows. Visited says 0, and the Status line says Paint terrain.
  • Press Step. Visited says 1, and the Status line says Cell 00: cost 0. Inside the orange ring at the camp is a thick white ring. Thin cyan rings stand on the road to its right and the forest below it.
  • Press Step again: Cell 01: cost 1, and Visited 2. The road beside the camp has the thick white ring now, and the camp’s is thick and blue.
  • The third Step settles Cell 02 at cost 2, and the fourth Cell 03 at cost 3. The fifth says Cell 12: cost 3. Cells 3 and 12 tied at 3, and the lower number went first.
  • Press R, or click Reset. Every ring hides, Visited goes back to 0, and the Status line says Paint terrain.
  • Step a few times, then paint a cell with a new terrain. The search clears, as if you had pressed Reset. Paint a cell with the terrain it already has, and the search goes on.
  • Wall the camp in: paint walls on the road to its right and the forest below it. The first Step settles the camp alone. The second says “No route.” Visited stays at 1, and Steps after that change nothing.
  • Press D to bring the map back. If you have the patience, keep pressing Step: after 78 Steps the tower settles, Cell 119: cost 24, and Step does nothing after that.
  • Cost still shows two dashes. It learns the route’s cost in Chapter 4.
How it works

Each Step settles the cheapest frontier cell, and its distance is final. The cell offers its neighbors a route, and a neighbor takes it only when it’s cheaper. The rings show it all: thin cyan for the frontier, thick blue for settled cells, thick white for the newest. Reset, or a painted cell, clears the search. The next Step begins again at the camp.

Underneath all of it is one plain fact about the map you painted: no cost on it is below 0. A route never gets cheaper as it goes, so the cheapest cell on the frontier can’t be beaten by any way round. That’s why the search never goes back. It settles cells in order of their cost from the camp, each one once, and each one for good.

Did you know?

Edsger Dijkstra described this search in 1959, in a paper called “A note on two problems in connexion with graphs.” The game’s title, Weighted maze (Dijkstra), names him for it.

3.10 If It Didn’t Work

Something may be off the first time, and that’s all right. This chapter has more moving parts than the last two, so there’s more to check. Run the game, watch the Status line, and follow the chain one piece at a time.

Step does nothing at all? Follow the message. Request Step, on the Step Button, sends stepRequested. Step Trial’s Event must name it the same way, capital R and all. Is Step Trial on Maze Rules? Then open it, and look along Ready’s line for a block standing apart, with no arrow into it. Join it with Connect to sequence.

Every cell says cost 0, or every cell has a thin cyan ring before the first Step? The distances are still the 0s from Add Field. Clear Trial never ran. Look at Reset Trial’s When the Scene Starts: it should call Clear Trial, with self as its rules.

Does the first Step say “Check search settings.”? The rules row of Maze Settings still holds 0 under maxCost and unreached. Type 99 and 1000000, and step again. If it says “Use costs 0 to maxCost.” instead, it’s the same row: maxCost is still 0.

Does the second Step already say “No route.”? The camp’s offers never landed, then, and perhaps Relax Neighbors never runs at all. Look at Ready’s step 8, under the Else of step 5’s If. Or look in Relax Neighbor for a block standing apart, since both Set Data Cells sit inside its second If.

The Status line runs the word into the number, as in cost3? The cellSettled words need a space after cost. Double-click that cell in Messages, and type it.

After R, does the next Step say “No route.” at once, or do nothing at all? Then Step Trial never went back to Ready. Clear Trial’s last step should send trialCleared to rules. And Step Trial’s On Trial Cleared should Reset Modes on On Step Requested.

Cell 12 comes before Cell 03? Look at the third test in Choose Cheapest Cell’s If. It should say <. With <=, a tie moves your finger, and the higher number wins.

Status names the cells, but no ring shows? Perhaps the rings were never made. Look at Lay Out Map’s three new steps, inside the Repeat. Then open the Map. Under Variables, searchOutline should say Search Outline. Or perhaps they never hear searchChanged: look at Show Search Outline’s second Event. Or perhaps they stand off the screen: in its first Event, Set Position should say Screen.

The rings show, but never turn thick? Look at Color Search Outline’s step 8, whose Frame should be 1.

R does nothing? In the Input Manager, the Reset Command needs its key. And Reset Trial needs its Command Pressed Event, set to Reset.

Each of these is one small fix. Make it, run the game again, and you’re back with the chapter. The catch-up download in the Goal is this chapter’s game, finished, to compare with yours.

Challenge

The search is yours to test now. Play a game against it. Before each Step, put your finger on the screen, on the cyan cell you think it settles next. Then press Step and see. The rule is the one Choose Cheapest Cell follows: the smallest distance, and the lower number on a tie. Paint a few cells of your own first, maybe a road through the forest below the camp, and see whether your finger keeps up.

The search is awake, and it stops when the tower settles. The compass still waits at the camp. In Chapter 4, a yellow line runs home from the tower, and the compass walks it.