- Learn
- Tutorials
- Algorithms
- Dijkstra’s Algorithm
- 4 Finish a Route
4 Finish a Route
By the end of this chapter, the search finishes what it started. The tower settles. Cost shows what the cheapest route costs, and a yellow line runs home from the tower to the camp, one cell at a time. Last, the compass walks it.
The game as it is at the end of this chapter. Click the game to play.
Find route comes in Chapter 5, so play this one with Step, or Space.
If your game and this chapter part ways, here is the chapter’s finished project. Open it and compare, or start Chapter 5 from it.
To open it, start Myth Engine, choose File › Open Project... and pick the downloaded file.
Starting with this chapter? It begins where Chapter 3 ends, and Chapter 3’s Goal has that chapter’s finished project.
4.1 The Idea
In Chapter 3 the search stopped at the tower. Press Step in the game above, again and again, and after 78 Steps the tower settles at cost 24. Back then, that was the end. Step did nothing more, and the compass waited at the camp.
Now the tower is a beginning. Cost, which has shown two dashes since Chapter 1, says 24, and the words “Building route…” appear on the Status line. Press Step again. The tower’s ring turns yellow. Again, and the road beside it turns yellow too, with a yellow line between the two. Each Step adds one more cell, back toward the camp.
Here’s the question this chapter answers. The search knows the cheapest route costs 24, but which cells does that route pass through?
Nothing in Chapter 3 wrote a route down. Each cell kept one thing: its parent, the cell its cheapest way in came through. That turns out to be enough.
Think of each parent as a signpost, pointing back the way the cheapest route came. Stand on the tower and follow the signposts, and you walk the route home to the camp, backward. That’s tracing the route.
As each cell is traced, it gets a number, its routeOrder. The tower’s is 0. The cell before it gets 1, the one before that 2, and so on back to the camp. Every other cell keeps -1. So the route is written down backward, and the compass walks it forward, counting down.
Once the camp is traced, “Follow the route.” appears on the Status line. From then on, each Step slides the compass one cell along the yellow line, in a fifth of a second. When it stands on the tower, “Cheapest route.” ends the trial.
On the supplied map, that route is 24 moves. Each move costs 1, because the route keeps to the road the whole way. Straight across the top and down the right side is only 20 moves. It costs 53. The cheapest route isn’t the one with the fewest moves.
It takes 78 Steps to reach the tower, 25 to trace the route home, and 24 more to walk it. For now, your thumb is the clock. In Chapter 5, a button does the pressing for you, a Step every 0.045 seconds.
4.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 when the compass walks.
- The tower settles -> Settle Cheapest Cell sends
cellSettled-> Show Settled Cell writes “Building route…” on the Status line, and Step Trial switches to Tracing. - A Step of the search or the trace ends ->
searchChanged-> Show Route Cost shows the tower’s distance, and the rings and the line show the route so far. - Each Step of Tracing -> Step Trial gives one cell its routeOrder, checks its link with Check Route Link, and moves on to its parent.
- The camp is marked -> Step Trial writes “Follow the route.” and switches to Replaying.
- Each Step of Replaying -> Step Trial moves
$markerCellon and sendsmarkerMoved-> Slide Marker slides the compass ->markerLanded-> Step Trial is ready for the next Step. - The compass reaches the tower -> Step Trial writes “Cheapest route.” and switches to Finished.
- The Scene starts -> Lay Out Route Lines makes a piece of line between every two neighbors ->
placedOnMap-> Show Route Line stands each piece in its place.
4.3 Set Up
This chapter’s Set up card has two blocks.
GLOBAL VARIABLES adds $markerCell, the cell the compass stands on, or is sliding to. Make it with New Global… on Reset Trial’s Globals card, as before. It needs only its name. A new Global is a Number at 0 already.
REMOVED lists Show Marker, and this time it means it. Show Marker stood the compass on the camp, once, as the game began. In this chapter the compass learns to walk, and in the last part Slide Marker takes over its job.
Take Show Marker off the Marker first. Open the Marker, switch to Logic, and press the red trash button on its row in Attached Logic. After that, delete Show Marker from the Resources. The Set up card on the Making page says why the order matters. Taking Show Marker away doesn’t move the compass, because its placement already stands it on the camp’s corner.
4.4 The Tower Settles
First, the moment the search ends gets words of its own, on the Status line and beside Cost.
When the search settles the tower, its work is done. The tower’s distance is final, so it’s the cost of the cheapest route, and nothing found later could beat it. The search stops right there. Every cell settled before the tower offered its neighbors a route through itself, as you saw in Chapter 3. The tower makes no offers.
Messages gets five new rows, the words for the rest of the trial: buildingRoute, followRoute, invalidRoute, noCost and cheapestRoute. The noCost words are the two dashes Cost shows until there’s a route. Add each row with Add Row. Double-click a cell to type in it, as before.
Paint terrain.: costNo route.Fix columns and rows.Check search settings.Check start and goal.Use costs 0 to maxCost.Unblock S and G.Building route...Follow the route.Invalid route.--Cheapest route.4.4.1 Show Settled Cell
In Chapter 3, Show Settled Cell named every cell it heard about. Now it makes one exception. When the cell is the tower, the words “Building route…” go on the Status line instead, because the search is over and the route comes next.
On the card, that’s an If around everything. Its True line writes the new words. Its Else holds your three old steps: the cell’s name, the cellSettled words, and last the distance.
Your three old steps now belong inside the If’s Else, and the If comes first. So the quickest way is to start this line again. Take the three old steps out with Delete, and add the card’s six in order. First the If, then its one Action with Add inside Then. Next comes Add Else. Its three Actions go in with Add inside Else, one at a time. If, Else and Repeat on the Making page shows each move.
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". When the cell settled is the Tower, Status says the Route is being built instead (the Messages buildingRoute words).
Any Object- Variable
$status- Value
dataRow('Maze Cells', $currentCell)
- Variable
$status- Operator
+=- Value
data('Messages', 'cellSettled', 'words')
- Variable
$status- Operator
+=- Value
data('Maze Cells', $currentCell, 'distance')
$currentCell == data('Maze Settings', 'rules', 'goalCell')- Variable
$status- Value
data('Messages', 'buildingRoute', 'words')
- Variable
$status- Value
dataRow('Maze Cells', $currentCell)
- Variable
$status- Operator
+=- Value
data('Messages', 'cellSettled', 'words')
- Variable
$status- Operator
+=- Value
data('Maze Cells', $currentCell, 'distance')
4.4.2 Show Route Cost
Cost has shown two dashes since Chapter 1. Show Route Cost, on Route Cost, finally gives it something to say.
It listens for searchChanged, the message Step Trial sends after each Step of the search and the trace. Clear Trial sends it too, whenever it clears a trial. Each time, Show Route Cost asks one question. Is the tower settled? If it is, Cost shows the tower’s distance, the cost of the cheapest route. If not, it shows the noCost words.
It never keeps a copy of the cost. Every time, it reads the tower’s row, so a Reset brings the dashes back by itself. A Declare Variable, tower, holds the tower’s cell number from Maze Settings.
Its two Change Component Values write self.text.text, as Show Terrain Choice’s did in Chapter 1. So its Components card needs the Text / UI, self.text. It turns up there by itself once the first one is in.
The Cost readout: whenever the search changes, it shows the cost of the cheapest Route, which is the Tower's distance once the Tower is settled, or the Messages noCost words ("--") while it is not.
Any ObjectText / UI- Name
tower- Value
0
- Variable
tower- Value
data('Maze Settings', 'rules', 'goalCell')
data('Maze Cells', tower, 'settled') == 1- Component Value
self.text.text- Value
data('Maze Cells', tower, 'distance')
- Component Value
self.text.text- Value
data('Messages', 'noCost', 'words')
Attach it to Route Cost.
Window / UIUI--21Semibold#203F43CenterVectorRun the game, and press Step until the tower settles. It takes 78 Steps on the supplied map, and Space does the same, one Step for each press. Holding it down still makes only one. Cost says 24, and the words “Building route…” are on the Status line. Press Step again, and nothing happens. For now, Step Trial still ends the trial at the tower.
4.5 Home Along the Signposts
Now Step Trial learns what comes after the tower.
First, the route needs somewhere to be written down, so Maze Cells gets one more column, routeOrder. The tower’s will be 0, and every cell before it one more, back to the camp. A cell that isn’t on the route has -1.
Add it to Maze Cells Entry with Add Field, Value -1, as in Chapter 3. Your 120 rows get 0 again, not -1. This time a 0 means something: on the route, in the tower’s place. So give Clear Trial its two new steps now, before anything reads the column.
The card’s IMPORT CSV block is the one from Chapter 1, and there’s no need to import the file again. It holds only the costs. Your table has those already.
Clear Trial’s step 5 writes -1 on every row whenever a trial is cleared, and the game clears one as it starts. It goes at the end of the Repeat’s panel. Right-click the Repeat, choose Add inside Repeat, and join the block with Connect to sequence.
Its step 9 puts $markerCell back on the camp, for the compass. It goes between two steps you have, so join it with the round handles. Actions, one at a time shows how.
One row of the "Maze Cells" Data Table.
11000000-10-111000000-10-111000000-10-1Puts the trial back to Ready, keeping the terrain: every cell of Maze Cells gets distance unreached (Maze Settings), no parent (-1) and settled 0 and no place on the Route (routeOrder -1), Visited goes back to 0, no cell is current (currentCell -1), the Marker's cell is the Camp (markerCell), 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.
dataCount('Maze Cells')times- Table
"Maze Cells"- Row
dataRow('Maze Cells', local.index)- Column
"distance"- Value
data('Maze Settings', 'rules', 'unreached')
- Table
"Maze Cells"- Row
dataRow('Maze Cells', local.index)- Column
"parent"- Value
-1
- Table
"Maze Cells"- Row
dataRow('Maze Cells', local.index)- Column
"settled"- Value
0
- Table
"Maze Cells"- Row
dataRow('Maze Cells', local.index)- Column
"routeOrder"- Value
-1
- Variable
$visited- Value
0
- Variable
$currentCell- Value
-1
- Variable
$status- Value
data('Messages', 'ready', 'words')
- Variable
$markerCell- Value
data('Maze Settings', 'rules', 'startCell')
- Custom Event
searchChanged
- Applies To
rules- Custom Event
trialCleared
4.5.1 Check Route Link
Each signpost is checked before the route follows it. Check Route Link answers one question about a cell: can its link back to its parent be trusted? It hands back isValid, 1 for yes and 0 for no.
Three things must be true. The parent must be one of the cell’s four neighbors. It must be settled. And its distance, plus what the cell costs to enter, must come to exactly the cell’s distance. That last one says the parent really is the cheapest way in.
The card’s first four steps make its values, as Declare Variables. The first check is a Repeat over Directions. Each time round, it asks Find Grid Neighbor about one of the four ways, the question the arrow keys ask. If an answer is the parent, isNeighbor becomes 1. That If’s condition has a second test after its &&, parent >= 0, because two -1s must never match. Past the edge of the map, Find Grid Neighbor answers -1, and a cell with no parent holds -1 too.
Next, throughParent adds the parent’s distance and the cell’s cost. Last, one If joins three tests with && and asks all three questions at once.
On any map you paint, every link passes, because the search writes no other kind. So why check? Each link that passes says one thing: this cell’s distance is its parent’s, plus what this cell costs to enter. Chain those from the tower back to the camp, whose distance is 0. The costs of the cells the route enters add up to the tower’s distance, the number Cost shows. So the route on the screen is proved to cost exactly what Cost says.
A link that failed would end the trial with “Invalid route.” on the Status line. On your map, that means a slip in your own Logic, and If it didn’t work says where to look.
Its Return Type is Number, and the Return at the bottom hands back isValid.
Whether the link from cell back to its parent can be trusted. isValid is 1 when every check holds and 0 otherwise: the parent is one of cell's four neighbors on the map (Find Grid Neighbor), the parent is settled, and the parent's distance plus the cost of entering cell is exactly cell's distance.
- Name
parent- Value
-1
- Name
isNeighbor- Value
0
- Name
throughParent- Value
0
- Name
isValid- Value
0
- Variable
parent- Value
data('Maze Cells', cell, 'parent')
- Variable
isNeighbor- Value
0
- Variable
isValid- Value
0
dataCount('Directions')times- Action Name
side- cell
cell- direction
dataRow('Directions', local.index)
side.value == parent && parent >= 0- Variable
isNeighbor- Value
1
- Variable
throughParent- Value
data('Maze Cells', parent, 'distance')
- Variable
throughParent- Operator
+=- Value
data('Maze Cells', cell, 'cost')
isNeighbor == 1 && data('Maze Cells', parent, 'settled') == 1 && throughParent == data('Maze Cells', cell, 'distance')- Variable
isValid- Value
1
- Value
isValid
4.5.2 Step Trial
Step Trial’s On Step Requested grows from three Modes to six. Ready and Searching keep the work they did in Chapter 3. When the tower settles, each now switches to Tracing instead of Finished, and Ready gets two new steps to set up the trace. Tracing, Replaying and Waiting For Marker are new, and a third Event arrives, for the message markerLanded.
Its card is the longest in the chapter. So it comes in three parts, each right after the words it needs. Start with what goes in before any Action.
First add the three new Modes with New Mode…, so Switch Mode can pick them by name. The card lists the six in the order a trial meets them. Finished comes last. If yours don’t stand that way, Move Left and Move Right put them there. Ready stays the Starting Mode either way.
The trace takes many Steps, and it has to remember two things from one Step to the next: the cell it marks next, and how many cells it has marked. A Declare Variable is gone as soon as its Event ends. So Step Trial keeps those two in a Data Structure of its own, Step Trial Data. Press + on its Data Structures card and choose New Data Structure. Myth makes Step Trial Data and opens it, ready for the two fields on its card: traceCell and routeLength. Step Trial reads them as self.stepTrialData.traceCell and self.stepTrialData.routeLength, and changes them with Change Component Value. Maze Rules gets it From Logic, as its card shows.
What Step Trial keeps between Steps while it traces the Route: traceCell, the cell the next Tracing Step marks, and routeLength, how many cells of the Route are marked so far.
Step TrialStep Trial DataThe card’s first part holds Ready and Searching. Ready’s steps 4 and 5 are new. They get the trace ready before the search begins, with traceCell on the tower and routeLength at 0. They go inside step 2’s True line, between two steps you have. So add each with step 2’s Add inside Then, which puts it on that line, and join it there with the round handles. Actions, one at a time shows how. In Ready and in Searching, one Switch Mode changes from Finished to Tracing. Pick Tracing in that block’s Mode list.
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. Once the Tower is settled, Tracing marks one cell of the Route per Step, from the Tower back to the Camp, checking each link (Check Route Link); traceCell is the cell the next Step marks and routeLength how many cells are marked. Then Replaying moves the Marker one cell of the Route per Step, toward the Tower: it commits the next cell (markerCell) and waits (Waiting For Marker) until Slide Marker says it has landed (markerLanded). On the Tower the Route is complete. markerOrder is the routeOrder of the Marker's cell. A cleared trial (trialCleared, from Clear Trial) starts again from Ready.
Any Objectself.stepTrialData- Action Name
inputCheck- maxCostLimit
@self.maxCostLimit- unreachedLimit
@self.unreachedLimit
inputCheck.value == ""- Table
"Maze Cells"- Row
dataRow('Maze Cells', data('Maze Settings', 'rules', 'startCell'))- Column
distance- Value
0
- Component Value
self.stepTrialData.traceCell- Value
data('Maze Settings', 'rules', 'goalCell')
- Component Value
self.stepTrialData.routeLength- Value
0
- Action Name
firstSettle
firstSettle.value == data('Maze Settings', 'rules', 'goalCell')- Event
On Step Requested- Mode
Tracing- Mode
Finished
- cell
firstSettle.value
- Event
On Step Requested- Mode
Searching
- Custom Event
searchChanged
- Variable
$status- Value
data('Messages', inputCheck.value, 'words')
- Event
On Step Requested- Mode
Finished
- Action Name
settle
settle.value >= 0settle.value == data('Maze Settings', 'rules', 'goalCell')- Event
On Step Requested- Mode
Tracing- Mode
Finished
- cell
settle.value
- Variable
$status- Value
data('Messages', 'noRoute', 'words')
- Event
On Step Requested- Mode
Finished
- Custom Event
searchChanged
Here is the chapter’s one hard idea, so take it slowly. It’s Tracing, the card’s second part.
Tracing makes one mark per Step. Step Trial Data’s traceCell is the cell this Step marks, and Ready put it on the tower. Its other field, routeLength, counts the cells marked so far. It starts at 0.
Count the first few. On the first Tracing Step, traceCell is the tower. Set Data Cell writes routeLength, still 0, into the tower’s routeOrder, and routeLength becomes 1. The tower isn’t the camp, so Check Route Link looks at the tower’s link to its parent.
The link passes. So traceCell becomes the parent. On the next Step, that cell gets routeOrder 1, its own link is checked, and traceCell moves on again. One cell per Step, the trace walks home along the signposts.
At the camp, the trace is done. Step Trial writes the followRoute words on the Status line and switches to Replaying, ready to walk. A link that fails its check ends the trial instead.
One more guard sits in Tracing’s second If, step 8. The test after its && asks that routeLength stay below the number of rows in Maze Cells. A route can’t pass through more cells than the map has. So the trace always ends.
Step 2 adds 1 to routeLength with the Operator +=. On a Change Component Value, the Operator list shows += as Plus. Pick Plus.
Tracing’s Else holds the call to Check Route Link and that second If, and the If has an Else of its own. With one If inside another, right-click the If or the Else whose line the Action belongs on. Step 13 is on Tracing’s own line, after the first If, so right-click the Tracing block and choose Add unattached Action here.
- Table
"Maze Cells"- Row
dataRow('Maze Cells', self.stepTrialData.traceCell)- Column
routeOrder- Value
self.stepTrialData.routeLength
- Component Value
self.stepTrialData.routeLength- Operator
+=- Value
1
self.stepTrialData.traceCell == data('Maze Settings', 'rules', 'startCell')- Variable
$status- Value
data('Messages', 'followRoute', 'words')
- Event
On Step Requested- Mode
Replaying
- Action Name
link- cell
self.stepTrialData.traceCell
link.value == 1 && self.stepTrialData.routeLength < dataCount('Maze Cells')- Component Value
self.stepTrialData.traceCell- Value
data('Maze Cells', self.stepTrialData.traceCell, 'parent')
- Variable
$status- Value
data('Messages', 'invalidRoute', 'words')
- Event
On Step Requested- Mode
Finished
- Custom Event
searchChanged
The last part is the walk. Replaying moves the compass one cell per Step, toward the tower. The compass stands on $markerCell. Clear Trial put that on the camp. The route was numbered backward, so walking it forward is counting down: the next cell along has a routeOrder one less than the compass’s own. That number is markerOrder, a Declare Variable, since Replaying needs it only while it runs.
To find it, Replaying uses dataFind. It looks down the routeOrder column for that number and hands back the cell. That’s how Cell Picture found a row of Terrains, back in Chapter 1.
The move is decided first, then shown. Replaying writes the next cell into $markerCell, switches to Waiting For Marker, and sends markerMoved. While the compass slides, a Step lands on Waiting For Marker, which has no Actions. So that Step does nothing.
When the compass lands, it sends markerLanded, and Step Trial’s new Event hears it. On the tower, the walk is over. That Event writes “Cheapest route.” on the Status line and switches On Step Requested to Finished, so a Step does nothing more. Anywhere else, it switches back to Replaying, ready for the next Step.
Replaying’s own Else does the same ending. It matters only on a map whose camp and tower are one cell, and only a change to Maze Settings makes one. Build it as the card shows.
Trigger Custom Event takes any name you type, so markerMoved needs nothing more. But markerLanded needs Add Custom Event…, since nothing sends it yet. Its one Mode comes named Step Trial. That can stay.
- Name
markerOrder- Value
-1
- Variable
markerOrder- Value
data('Maze Cells', $markerCell, 'routeOrder')
markerOrder > 0- Variable
$markerCell- Value
dataFind('Maze Cells', 'routeOrder', markerOrder - 1)
- Event
On Step Requested- Mode
Waiting For Marker
- Custom Event
markerMoved
- Variable
$status- Value
data('Messages', 'cheapestRoute', 'words')
- Event
On Step Requested- Mode
Finished
$markerCell == data('Maze Settings', 'rules', 'goalCell')- Variable
$status- Value
data('Messages', 'cheapestRoute', 'words')
- Event
On Step Requested- Mode
Finished
- Event
On Step Requested- Mode
Replaying
- Event
On Step Requested
Run the game now, before anything shows the route. Press Step until the tower settles, and then 25 more times, watching the map. It doesn’t change. The tower’s ring stays white. But after the 25th, “Follow the route.” is on the Status line, because the trace has walked home. The next part makes it show.
4.5.3 Color Search Outline
Now the route shows on the map. Color Search Outline gets a fourth question, asked last, so it wins. Is the cell on the route, with a routeOrder of 0 or more? If it is, the ring takes routeTint, yellow. Every route cell is settled, so the ring is already thick.
Add routeTint to its PARAMETERS, a Color, with the card’s code as its Default. The new If goes at the end of the line, after the If for the current cell. It’s the same shape.
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), and routeTint once it is on the Route (its routeOrder is 0 or more). A later rule wins: Route over 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.
spriteSprite- Applies To
outline- Visible
false
data('Maze Cells', cell, 'distance') < data('Maze Settings', 'rules', 'unreached')- Applies To
outline- Visible
true
- Applies To
outline- Frame
0
- Applies To
outline- Value
frontierTint
data('Maze Cells', cell, 'settled') == 1- Applies To
outline- Visible
true
- Applies To
outline- Frame
1
- Applies To
outline- Value
settledTint
cell == $currentCell- Applies To
outline- Visible
true
- Applies To
outline- Value
currentTint
data('Maze Cells', cell, 'routeOrder') >= 0- Applies To
outline- Visible
true
- Applies To
outline- Value
routeTint
Show Search Outline passes the new color along. Give it a fourth Shared Variable, routeTint, a Color that stays Constant, with the card’s code as its Starting Value. Each of its two calls has a routeTint box now, because the Function has one. Type @self.routeTint into both.
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, currentTint and routeTint are the four colors: a cell on the Route is yellow.
Any Objectself.mapPlaceSprite- Action Name
place- cell
self.mapPlace.cell
- Position Relative To
Screen- X
place.x- Y
place.y
- outline
self- cell
self.mapPlace.cell- frontierTint
@self.frontierTint- settledTint
@self.settledTint- currentTint
@self.currentTint- routeTint
@self.routeTint
- outline
self- cell
self.mapPlace.cell- frontierTint
@self.frontierTint- settledTint
@self.settledTint- currentTint
@self.currentTint- routeTint
@self.routeTint
Run the game. Press Step until the tower settles, and keep going. The tower’s ring turns yellow, then the road beside it, one cell per Step, back toward the camp. After 25 of those Steps, the camp turns yellow, and “Follow the route.” appears on the Status line.
Press Step once more, and nothing seems to happen. Step Trial has moved $markerCell on. It’s waiting for the compass to say it has landed, and the compass doesn’t know how to walk yet. That comes last.
4.6 The Yellow Line
Next comes the line that joins the yellow rings.
It isn’t one long Object. It’s made of short pieces, one between every two neighboring cells of the map. A piece shows only while its two cells follow each other on the route. Each is a yellow bar over a slightly bigger dark one, its backing, so the line stands out on any terrain.
Count them. Each of the ten rows has eleven gaps between its cells, and each of the twelve columns has nine. That’s 110 and 108, so 218 pieces, and 218 backings. The Scene places none of them.
Each piece keeps its two cells in a Data Structure, Route Link. Its fields are cell, and neighbor, the cell on its right or the cell below it. Both start at -1.
The two neighboring cells a Route Line (or its Route Line Backing) joins: cell, and neighbor, the cell on its right or the cell below it. Lay Out Route Lines gives each line its two cells right after creating it.
4.6.1 Draw Route Line
Draw Route Line decides whether one piece shows. It reads both cells’ routeOrder. The piece shows when both are on the route and one apart. Otherwise it hides.
One apart matters. Two cells can both be on a route, side by side on the map, without following each other on it. No line joins those. The card writes the rule with abs(), which gives the size of a difference, whichever way round. So abs(cellOrder - neighborOrder) is 1 for two cells in a row.
Its line is made the way you made Color Search Outline’s outline: an Object Parameter that stays Structural, with the Sprite as a Required Component.
Shows one Route Line (or its backing) between two neighboring cells, cell and neighbor, only while both are on the Route (routeOrder 0 or more) and one comes right after the other (their routeOrder values differ by 1); otherwise hides it. So a line never joins two Route cells that do not follow each other.
spriteSprite- Name
cellOrder- Value
-1
- Name
neighborOrder- Value
-1
- Variable
cellOrder- Value
data('Maze Cells', cell, 'routeOrder')
- Variable
neighborOrder- Value
data('Maze Cells', neighbor, 'routeOrder')
cellOrder >= 0 && neighborOrder >= 0 && abs(cellOrder - neighborOrder) == 1- Applies To
line- Visible
true
- Applies To
line- Visible
false
4.6.2 Show Route Line
Show Route Line is each piece’s own Logic. The line and its backing both use it, and only their color differs.
When placedOnMap arrives, the piece asks Cell Screen Position where its two cells stand. It goes halfway between them, and its Set Position’s Position Relative To says Screen, as Show Search Outline’s does. A piece is drawn lying across. When one cell stands above the other, the two share an x, and Set Rotation turns the piece a quarter turn, 90, to stand it upright.
After that, it takes its color, lineTint, and asks Draw Route Line whether to show. Each time searchChanged arrives, it asks again.
Its Data Structures card takes Route Link, under Existing Data Structures, and its Components card needs the Sprite. The color is a Shared Variable, a Color that stays Constant, with the yellow as its Starting Value. Each call’s line box starts at self, the piece itself, so leave it there.
One piece of the Route Line, the yellow line that joins the Route's cells: whenever the search changes it is shown only while its two cells follow each other on the Route (Draw Route Line). The Route Line and the Route Line Backing both use it. Its two cells are its Route Link cell and neighbor, which Lay Out Route Lines gives it; it stands halfway between them, upright when one is above the other. lineTint is its color: yellow on the Route Line, and dark on the Route Line Backing, each Object's own Shared Variable.
Any Objectself.routeLinkSprite- Action Name
from- cell
self.routeLink.cell
- Action Name
to- cell
self.routeLink.neighbor
- Position Relative To
Screen- X
(from.x + to.x) / 2- Y
(from.y + to.y) / 2
from.x == to.x- Degrees
90
- Value
@self.lineTint
- line
self- cell
self.routeLink.cell- neighbor
self.routeLink.neighbor
- line
self- cell
self.routeLink.cell- neighbor
self.routeLink.neighbor
The Route Line is a Screen UI Object showing the Panel picture at 76 by 12. The Route Line Backing shows it at 82 by 18, and its Scene Depth Draw Order of 2 puts it under the line, which has 3. Remove each one’s UI Interaction, and attach Show Route Line to both. Each gets its Route Link From Logic.
The Panel picture is white, and each piece’s color comes from its Logic. The line keeps the yellow it starts with, but the backing needs a color of its own. That’s the last line of its card. Open the backing, and in its Properties, under Variables, find lineTint, marked From Logic: Show Route Line. Type #101d29 into the box beside its swatch.
Window / UIUIX 76, Y 1233PanelX 76, Y 12Show Route LineRoute LinkWindow / UIUIX 82, Y 1822PanelX 82, Y 18Show Route LineRoute Link#101d294.6.3 Lay Out Route Lines
Lay Out Route Lines makes every piece when the Scene starts. It works like Lay Out Map, but with one Repeat inside another.
The outer Repeat runs once for each cell. The inner one runs once for each row of Directions, and asks Find Grid Neighbor which cell lies that way. Two neighbors are joined once, not twice. So a piece is made only when the neighbor comes after the cell, on its right or below it. Past the edge of the map, Find Grid Neighbor answers -1, which never comes after anything.
Each time, it makes a backing and a line. Each gets its two cells, and the message placedOnMap goes to it alone.
Inside the inner Repeat, local.index counts directions, not cells, so the outer Repeat’s number is kept first, in a Declare Variable, thisCell.
On the card, steps 2 and 3 go in step 1’s panel, and step 3 is the inner Repeat. Steps 4 and 5 go in step 3’s own panel, and steps 6 to 13 on step 5’s True line. Each block’s right-click menu adds only to its own panel. So right-click the block whose panel the Action belongs in: Add inside Repeat on step 1, then on step 3, and Add inside Then on step 5.
Steps 7 and 11 write into the new piece, newBacking.cell and newLine.cell, as newCell.cell did in Lay Out Map. Their Value is thisCell. Steps 8 and 12 hand on next.value, the answer of the Find Grid Neighbor call named next.
It makes its pieces from two Shared Variables, routeLineBacking and routeLine. Both are Objects. Each stays Constant and starts at None, like mapCell and searchOutline.
Lays out the Route Line when the Scene starts: it joins every two neighboring cells with a Route Line over a Route Line Backing, once, from the cell that comes first in reading order to its neighbor on the right or below. Each line gets its two cells (its Route Link) and is told, with placedOnMap, that it is placed; it then stands between its cells and shows itself. routeLineBacking and routeLine are the Objects it makes.
Any ObjectdataCount('Maze Cells')times- Name
thisCell- Value
local.index
dataCount('Directions')times- Action Name
next- cell
thisCell- direction
dataRow('Directions', local.index)
next.value > thisCell- Object to Create
@self.routeLineBacking- X
0- Y
0- Created As
newBacking
- Variable
newBacking.cell- Value
thisCell
- Variable
newBacking.neighbor- Value
next.value
- Applies To
newBacking- Custom Event
placedOnMap
- Object to Create
@self.routeLine- X
0- Y
0- Created As
newLine
- Variable
newLine.cell- Value
thisCell
- Variable
newLine.neighbor- Value
next.value
- Applies To
newLine- Custom Event
placedOnMap
On the Map, attach Lay Out Route Lines beside Lay Out Map. Then set its two Shared Variables the way you set mapCell: under Variables, pick Route Line Backing for routeLineBacking, and Route Line for routeLine.
Map CellSearch OutlineRoute Line BackingRoute LineRun the game, and step until the tower settles. Now watch the trace. Each Step colors one more ring yellow, and a yellow line on its dark backing joins it to the one before. By the time “Follow the route.” appears, the line runs unbroken from the tower to the camp.
4.7 The Compass Walks
Last, the compass walks the route. It has waited at the camp since Chapter 1.
Maze Layout gets one column, slideSeconds, how long each slide takes: 0.2 seconds, a quick glide. Add it to Maze Layout Entry with Add Field, Value 0.2. Add Field puts it last, after towerFrame. The card lists it after spacing, but the order doesn’t matter, as with rows in Chapter 2. Type 0.2 into the board row too, because a new field gives that row 0.
One row of the "Maze Layout" Data Table.
56154640.216454.7.1 Marker Screen Position
Marker Screen Position answers where the compass stands on a cell, starting from the cell’s center, which Cell Screen Position gives. From there it goes right and down by the markerOffset in Maze Layout, 16 pixels each way. Down is a minus on the card. On the screen, y goes below zero as you go down. That’s the sum Show Marker made in Chapter 1, moved into a Function, because two pieces need it now.
It hands back a Marker Screen Position Result, a Data Structure with x and y, as Cell Screen Position hands back its own. So make that Data Structure first, from its card, and then pick it in the Return Type list. Its Return has a box for each.
Where the Marker stands on a cell, as Marker Screen Position returns it: the cell's center (Cell Screen Position's x and y), moved Maze Layout markerOffset to the right and down.
Where the Marker stands on one cell, in screen pixels: on the cell's lower-right corner, Maze Layout markerOffset pixels right of and below the cell's center (Cell Screen Position).
- Name
x- Value
0
- Name
y- Value
0
- Action Name
place- cell
cell
- Variable
x- Value
place.x + data('Maze Layout', 'board', 'markerOffset')
- Variable
y- Value
place.y - data('Maze Layout', 'board', 'markerOffset')
- x
x- y
y
4.7.2 Stand Marker
Stand Marker puts the compass exactly on its cell, $markerCell, at once. First it stops any slide with Control Tween, so nothing pulls the compass away again. After that, it asks Marker Screen Position where to stand.
Its marker is an Object Parameter that names one Object, as Clear Trial’s rules names Maze Rules. So open its Settings, choose Nominal, and pick Marker. Control Tween shows in the Action List while Advanced Mode is on, as Tween does.
Stops the Marker and puts it exactly on the cell markerCell names (Marker Screen Position).
- Applies To
marker
- Action Name
spot- cell
$markerCell
- Applies To
marker- Position Relative To
Screen- X
spot.x- Y
spot.y
4.7.3 Slide Marker
Slide Marker, on the Marker, takes Show Marker’s place. It listens for three things.
When markerMoved arrives, the compass slides. Two Tween Actions run together, one across and one up and down, each over slideSeconds. So the compass glides in a straight line, at an even pace. At the same moment, a timer starts, markerSlide, set to ring once, when the slide is over.
Count the beats. The message arrives. The compass glides for a fifth of a second. The timer rings.
When markerSlide rings, Stand Marker sets the compass exactly on its cell, and Slide Marker sends markerLanded. Step Trial is waiting for that.
When searchChanged arrives, the timer is stopped, and the compass stands on $markerCell at once. After a Reset, that’s the camp. So a Reset in the middle of a slide sends the compass straight home, and no landing comes later. No Step of the walk sends searchChanged, so a Step never cuts a slide short. Only a cleared trial does that.
Type self.transform.position.x into the first Tween’s Value box, and self.transform.position.y into the second’s. Their Ease starts at Linear, the even pace, so leave it. Start Recurring Timer’s Ring Once is a list here too, set to Yes, and the timer’s name goes in bare, as the card has it.
The timer’s own Event is a Recurring Timer named markerSlide. Nothing has used that name yet, so choose Type a timer name…, as Events: the When bars shows. The message markerMoved is listed already. Step Trial sends it.
Slide Marker works on the Marker and nothing else, because Stand Marker’s marker takes only a Marker. So open its Logic Component card, and set Attach to Marker, as you set Reset Trial to Maze Rules. Then both Stand Marker calls’ marker box reads self · Marker. There’s nothing to change.
Shows the Marker on the cell markerCell names. When Step Trial commits the Marker's next cell (markerMoved), the Marker slides there in Maze Layout slideSeconds and sends markerLanded when it is exactly on it. When the search changes or is cleared (searchChanged), the Marker stands on markerCell at once, ending any slide.
Marker- Timer Name
markerSlide
- marker
self
- Action Name
spot- cell
$markerCell
- Value
self.transform.position.x- Target
spot.x- Duration
data('Maze Layout', 'board', 'slideSeconds')
- Value
self.transform.position.y- Target
spot.y- Duration
data('Maze Layout', 'board', 'slideSeconds')
- Timer Name
markerSlide- Repeat Every (s)
data('Maze Layout', 'board', 'slideSeconds')- Ring Once
Yes
- marker
self
- Custom Event
markerLanded
On the Marker, Slide Marker joins the Attached Logic, where Show Marker was.
Window / UIUIX 24, Y 2455CompassX 24, Y 24Run the game, and play it through. Step until the tower settles, then until the route is traced, then once more. The compass glides from the camp onto the road beside it. It stops square on the cell’s corner. Each Step takes it one cell further along the yellow line, and two quick presses still move it one cell. On the tower, “Cheapest route.” ends the trial.
Watch for each of these.
- Before the first Step, Cost shows two dashes, and the compass sits on the camp’s lower-right corner.
- Press Step 77 times. The Status line reads “Cell 115: cost 24”, and Cost still shows two dashes. Cell 115 tied with the tower at 24, and the lower number went first.
- The 78th Step settles the tower. Visited says 78, Cost says 24, and “Building route…” is on the Status line.
- The next Step turns the tower’s ring thick and yellow. Each Step after it turns one more cell yellow, back toward the camp, and a yellow line on a dark backing joins it to the cell before.
- The 25th Step after the tower settled turns the camp yellow too. The line runs unbroken along the road, from the camp to the tower, and “Follow the route.” appears on the Status line.
- Press Step. The compass glides onto the road beside the camp, in a fifth of a second.
- Press Step twice, fast. The compass still moves one cell.
- Keep stepping. 24 moves after the camp, the compass stands on the tower. The words “Cheapest route.” are on the Status line, and Steps change nothing now.
- Count the yellow cells after the camp. There are 24, all road, so the route costs 24, as Cost says.
- Press R. Every ring and every piece of line hides, Cost shows two dashes, and the compass is back on the camp. Press R in the middle of a slide, and it’s back at once.
- Step into the trace, then paint a cell with a new terrain. The search clears, as if you had pressed Reset.
The search settles the tower, and from then on, its distance is the route’s cost, the number Cost shows. Each cell kept its parent. So Step Trial follows the parents home from the tower, one cell per Step, checking each link and numbering each cell. The compass walks the numbers back down, one slide per Step.
Underneath it is one idea. The search never had to remember a whole route. Each cell remembered one thing, where its cheapest way in came from, and that was enough. Follow those signposts from any settled cell, and you walk the cheapest route back to the camp.
That’s Dijkstra’s algorithm, from the first Step to the last. The search settles each cell at its cheapest cost, and the parents it leaves behind turn the tower’s cost into a route. You’ve built all of it.
4.8 If It Didn’t Work
Something may be off the first time, and that’s all right. Three things happen in turn in this chapter: the tower settles, the route is traced, and the compass walks. So first, see how far your game gets, and look for the slip in the stage where it stops.
Cost still shows two dashes after the tower settles? Look for Show Route Cost in Route Cost’s Attached Logic, and at its two Change Component Values, which write self.text.text.
The Status line names Cell 119, not “Building route…”? Show Settled Cell’s If isn’t asking about the tower, because its row should compare $currentCell with goalCell.
The tower settles, and the next Step does nothing? Perhaps Step Trial still switches to Finished there, so look at Searching’s step 4. Its Mode should say Tracing.
Every ring is yellow, before the first Step? The routeOrder column still holds Add Field’s 0s, and a 0 means the tower’s place on the route. Look at Clear Trial’s step 5, inside its Repeat. Is it standing apart from the rest, with no arrow running into it?
Does the trace stop at once, with “Invalid route.” on the Status line? A link failed its check. Perhaps Check Route Link’s Return hands back something other than isValid, or step 13’s Operator is = where it should be +=.
Only the tower’s ring turns yellow, and more Steps add nothing? The trace never leaves the tower. Look at Tracing’s step 9, on the second If’s True line. It moves traceCell on.
The Status line reaches “Follow the route.”, but no ring turns yellow? Look at Color Search Outline’s step 13, the If that asks for a routeOrder of 0 or more. Perhaps it stands apart, with no arrow into it. Or look at the routeTint box in each of Show Search Outline’s two calls. It may be empty.
The rings turn yellow, but no line joins them? Look on the Map for Lay Out Route Lines, with both of its Variables set. After that, look at Show Route Line’s Set Position. It should say Screen.
Every piece of line stands upright, even across the top of the map? Set Rotation belongs inside the If, on its True line.
The line looks white, or wide and all yellow? Each piece’s color comes from Show Route Line: the line keeps the yellow lineTint starts with, and the backing’s lineTint, under its Variables, should be #101d29.
The compass never leaves the camp? Look for Slide Marker on the Marker. And look at the name markerMoved in two places, Step Trial’s Replaying and Slide Marker’s When bar.
The compass stands on each cell’s upper-right corner, not its lower-right? Look at Marker Screen Position’s step 5. It takes markerOffset away from y, because down is a minus.
The compass wanders off the yellow line? Look at Replaying’s step 4. It should look for markerOrder - 1, the next cell along.
The compass jumps one cell and stops, and Step does nothing more? Perhaps Maze Layout’s board row still holds the 0 that Add Field gave slideSeconds. Type 0.2.
The compass slides once, but never again? Step Trial’s On Marker Landed should switch back to Replaying on its Else line. And Slide Marker’s timer Event should send markerLanded. That Event and Start Recurring Timer must name the same timer, markerSlide.
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.
Now the route is yours to bend. Put mud in its way: paint one of the yellow road cells as Mud. Before you press Step, guess where the new route will go, and what it will cost. Run the search and see. Try a wall instead, and the route has to go round it, however costly the way round is. How far can you push the route before the only answer is “No route.” on the Status line?
That’s a lot of pressing. The finished game on the Welcome page runs the same search with Find route, if your thumb needs a rest. In Chapter 5, your game gets that button too.
The route is found, and the compass stands on the tower. In Chapter 5, it walks there again, with Find route pressing Step for you, and stopping whenever you ask.
