TutorialsA* Search

5  New Puzzles and Two Estimates

Goal

By the end of this chapter, New puzzle and Reset join the navy bar, and an Estimate button sits on the card. A line under the buttons says what each Step does. Solve your puzzle with each Estimate, and the count tells you which guess was closer.

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. It’s the whole game, ready to open beside yours.

8-Puzzle Solver, Chapter 5 finishedMyth project · 3.8 MB

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

5.1 The Idea

Try the game above. Press Solve, and the count stops at 20. The Tiles slide home, and the card says Solved.

Now press Reset, or R. The Tiles don’t slide back. They jump, all at once, to the puzzle you started with, and the count reads 0 again.

On the card, under the count, a button reads Estimate: Manhattan. Press it, or E, and now it reads Estimate: Misplaced Tiles. Press Solve once more.

The count runs on past 20 this time. It stops at 84. Then the Tiles slide home, in 12 Moves, the same as before.

Last, press New puzzle, or N. The Tiles jump to a Board you’ve never seen, and to another when you press it again.

Under the navy bar, a line of pale letters has been talking the whole time. Whenever the work changes, it says what a Step does now. That’s the Message Line.

Until now, your solver had one puzzle and one way to guess. That was enough to watch A* Search work. It isn’t enough to ask the question underneath it. Does the guess matter?

Chapter 2’s Estimate is called Manhattan. It asks every Tile how far it is from home, in rows and columns, like counting city blocks. The new one is Misplaced Tiles. It only asks each Tile whether it’s home. A Tile that isn’t counts 1, however far away it sits.

Take Tile 1 on your puzzle. It sits at the bottom, three Slots from its home in the top-left corner. Manhattan counts 3 for it, and Misplaced Tiles counts 1. For your whole puzzle, Manhattan guesses 12 Moves, and Misplaced Tiles guesses 8. The real answer is 12.

Both guesses are honest. A Tile out of place needs at least one Move, and at least as many as its rows and columns. So neither guess ever says too many, and both find a shortest Route. But Misplaced Tiles says less. More Boards look as good as the best one, and the search has to check them before it can be sure. That’s the 84.

The new puzzles need one more new word. A Move slides one Tile. A jump puts a whole Board on the table at once, with no slides. New puzzle and Reset both jump.

5.2 The Plan

Here’s how the new pieces join the game. Read each line once for its shape, and come back to it after the last part.

  • Run Search names a situation -> it copies that row’s explanation into $messageLine -> the Message Line shows it.
  • You press New puzzle, or N -> New Puzzle asks Scramble Board for a Board -> Scramble Board makes 12 random Moves from the Goal with Random Move.
  • New puzzle or Reset puts a Board in $boardState -> New Puzzle sends boardJumped to every Object.
  • Every Tile, Hand Moves and Solve Timer hear boardJumped -> each Tile jumps to its Slot, and the other two stop waiting for a slide.
  • You press the Estimate button, or E -> Choose Estimate moves $estimate to the next row of Estimates -> Estimate Moves Left counts that way from now on.
  • A hand Move, a jump, or a new Estimate -> Run Search sends itself cancelSearch -> the next Step searches again.

5.3 Set Up

This chapter’s Set up card has four blocks.

COMMANDS adds three Buttons: New puzzle, joined to N, Reset, joined to R, and Change Estimate, joined to E. Make them in the Input Manager, as before.

GLOBAL VARIABLES adds two Text Globals. The first, $estimate, is the Estimate the search uses, and it starts at Manhattan. The second, $messageLine, is the sentence under the buttons, and it starts empty. On Start Board’s canvas, press + on the Globals card and choose New Global… for each, as before. Type Manhattan into the Default box of $estimate, with no quote marks. Clear the Default box of $messageLine.

CHANGE THESE PLACEMENTS makes room. The navy bar needs space for four buttons now, so Step moves left, to 209, and Solve follows it, to 403. Those are the two Puzzle Button lines, Step first. On the card, the Status Line moves up to -560 and States Checked to -525, so the Estimate button fits under them. Select each one in the Scene, and type its new numbers. A placement’s own values on the Making page says more.

PLACE THESE waits. Each of its three buttons, and the Message Line, goes in with the part that makes what it shows or rings.

Set upWhat this chapter adds
COMMANDS (Game Settings > Input Manager)
New puzzleButton
KeybindingsN
ResetButton
KeybindingsR
Change EstimateButton
KeybindingsE
GLOBAL VARIABLES
$estimateTextstarts at Manhattan
$messageLineTextstarts at
SCENE Puzzle Table: PLACE THESE
ObjectXY
Puzzle Button597-737
with Text / UI > Text = New puzzle (Edit Object Locally)
with Per Instance Variables > Message = newPressed
Puzzle Button791-737
with Text / UI > Text = Reset (Edit Object Locally)
with Per Instance Variables > Message = resetPressed
Puzzle Button766-610
with Text / UI > Text = Estimate: {$estimate} (Edit Object Locally)
with Transform > Bounding Box > Size = X 260, Y 48 (Edit Object Locally)
with Sprite > Transform > Size = X 260, Y 48 (Edit Object Locally)
with Per Instance Variables > Message = estimatePressed
Message Line500-818
SCENE Puzzle Table: CHANGE THESE PLACEMENTS
Status Linemove to X 766, Y -560
States Checkedmove to X 766, Y -525
Puzzle Buttonmove to X 209, Y -737
Puzzle Buttonmove to X 403, Y -737

5.4 A Line Under the Buttons

First, the game learns to explain itself.

5.4.1 Messages, Changed

Messages gets a third column, explanation, a Text. Each row’s explanation is one sentence about what a Step does in that situation. As in Chapter 4, open Messages Entry, press Add Field, and clear the new field’s Value box. Then fill in each row from the card.

Data StructureMessages EntryAddDelete

One row of the "Messages" Data Table.

wordsTextdefault (empty)
pausedWordsTextdefault (empty)
explanationTextdefault (empty)

Look at the ready row. Its words are empty, because the Status Line says nothing when no search is under way. But its explanation is the one you’ll read first: how to begin.

Now look at the searching row. Its sentence ends with g + h. Chapter 2 promised you’d meet those letters once, on a card. Here they are. The g is Moves so far, and the h is the Estimate.

Data TableMessagesData Structure: Messages EntryAddDelete
Row 0solved
wordsSolved
pausedWordsSolved
explanationThe Route has been played: it is a shortest way from the Board the search began on to the Goal.
Row 1searching
wordsSearching...
pausedWordsPaused
explanationEach Step Expands the open Search State with the lowest Moves so far + Estimate (g + h).
Row 2tryNew
wordsTry a new puzzle.
pausedWordsTry a new puzzle.
explanationNo Route: no Search State was left open, or the search grew too large.
Row 3building
wordsBuilding route...
pausedWordsBuilding route...
explanationThe Goal is accepted. Each Step copies one Route record, following the parents back.
Row 4solving
wordsSolving...
pausedWordsPaused
explanationThe Route is complete. Each Step plays its next Move.
Row 5solveButton
wordsSolve
pausedWords
explanation
Row 6pauseButton
wordsPause
pausedWords
explanation
Row 7ready
words
pausedWords
explanationSlide a Tile beside the Blank, or press Step to search from this Board.

5.4.2 Message Line

The Message Line is one sentence in pale cream letters on the wood, under the navy bar. Make it like the Status Line. It’s a Screen UI Object with a Text / UI and nothing else, and its Text is {$messageLine}, braces and all. Remove its UI Interaction.

Its card has no Bounding Box line. Its Text Bounds stays on Fit to Text, so the box grows to fit each sentence.

ObjectMessage Line
Transform
Presentation > SpaceWindow / UI
Presentation > Render LayerUI
Text / UI
Text{$messageLine}
Font Size16
EmphasisSemibold
Color#E0CDA5
AlignmentCenter
RenderingVector

Place it at 500 and -818, from PLACE THESE.

5.4.3 Run Search, Changed

Run Search writes the Message Line. Every time it names a situation with Show Status Row, a new step right after it copies that row’s explanation into $messageLine. Its Value reads the row by $statusRow, which Show Status Row has set a moment before.

That new step goes in four places: Not Started, Building Route, On Goal Accepted and On Search Stopped. In each, it sits between Show Status Row and Switch Mode. Add it, then join it with the round handles. Click the one at the bottom of Show Status Row, then the one at the top of the new block. In Not Started and Building Route, those blocks are inside an If, so add the new one with that If’s Add inside Then. Actions, one at a time on the Making page shows the handles.

Before any of that, the opening needs its sentence. A new Event, When the Scene Starts, puts the ready row’s explanation there. Add it with Add Event, as in Solve Timer.

Logic ComponentRun SearchAttach to: Puzzle RulesAddDelete

Runs the A* solver one Step at a time, from the Board shown. Solve Timer sends takeStep for each Step. The first Step stores that Board as the first open Search State; each Step then Expands the best open Search State (Expand Best State) until the Goal is accepted (goalAccepted). Each Step then copies one record of the Route, back along the parents (Building Route), and then plays one of its Moves (Playing Route). When the search stops (searchStopped) or the Route has been played, Step has nothing more to do (Finished). A hand Move, a New puzzle or Reset, or a change of Estimate cancels the search (cancelSearch). The Status Line and the Message Line say what the solver is doing ($statusRow).

Attach toAny Object
DATA STRUCTURES
Run Search Dataself.runSearchData
SHARED VARIABLES
NameTypeKindStarting Value
puzzleRulesObjectConstantNone
When the Scene Starts
WhenScene Start
1Change Variable
Variable
$messageLine
Value
data('Messages', 'ready', 'explanation')
When Step Is Pressed
WhenCommand PressedCommand: Step
1Trigger Custom Event on Object
Applies To
@self.puzzleRules
Custom Event
stepPressed
On Take Step
WhentakeStepCustom Event
ModeNot Started (Starting Mode)
1If$boardState != data('Puzzle Settings', 'puzzle', 'goalBoard')
Then
2Add Search StateFunction
board
$boardState
movesSoFar
0
parentState
-1
3Show Status RowFunction
row
searching
4Change Variable
Variable
$messageLine
Value
data('Messages', $statusRow, 'explanation')
5Switch Mode
Event
On Take Step
Mode
Searching
6Expand Best StateFunction
puzzleRules
@self.puzzleRules
7Otherwise
8Trigger Custom Event on Object
Applies To
@self.puzzleRules
Custom Event
pauseSolve
On Take Step
WhentakeStepCustom Event
ModeSearching
1Expand Best StateFunction
puzzleRules
@self.puzzleRules

Building Route gets its new step when the Route is complete. Playing Route and Finished don’t change.

Logic ComponentRun SearchcontinuedAddDelete
On Take Step
WhentakeStepCustom Event
ModeBuilding Route
1Copy Route RecordFunction
Action Name
copied
board
self.runSearchData.routeBoard
2Ifcopied.value == (-1)
Then
3Change Component Value
Component Value
self.runSearchData.movesPlayed
Value
0
4Show Status RowFunction
row
solving
5Change Variable
Variable
$messageLine
Value
data('Messages', $statusRow, 'explanation')
6Switch Mode
Event
On Take Step
Mode
Playing Route
7Otherwise
8Change Component Value
Component Value
self.runSearchData.routeBoard
Value
copied.value
On Take Step
WhentakeStepCustom Event
ModePlaying Route
1Change Component Value
Component Value
self.runSearchData.routeNextBoard
Value
data('Solution Route', text(self.runSearchData.movesPlayed + 1), 'state')
2Tile For MoveFunction
Action Name
routeTile
board
$boardState
nextBoard
self.runSearchData.routeNextBoard
3Choose TileFunction
puzzleRules
@self.puzzleRules
tile
routeTile.value
On Take Step
WhentakeStepCustom Event
ModeFinished
1Trigger Custom Event on Object
Applies To
@self.puzzleRules
Custom Event
pauseSolve

On Goal Accepted and On Search Stopped get theirs, each right after Show Status Row.

Logic ComponentRun SearchcontinuedAddDelete
On Goal Accepted
WhengoalAcceptedCustom Event
1Change Component Value
Component Value
self.runSearchData.routeBoard
Value
data('Puzzle Settings', 'puzzle', 'goalBoard')
2Show Status RowFunction
row
building
3Change Variable
Variable
$messageLine
Value
data('Messages', $statusRow, 'explanation')
4Switch Mode
Event
On Take Step
Mode
Building Route
On Search Stopped
WhensearchStoppedCustom Event
1Show Status RowFunction
row
tryNew
2Change Variable
Variable
$messageLine
Value
data('Messages', $statusRow, 'explanation')
3Switch Mode
Event
On Take Step
Mode
Finished

Run the game. Under the navy bar, a line reads Slide a Tile beside the Blank, or press Step to search from this Board. Press Step. The line changes to the searching row’s sentence, the one that ends with g + h.

5.5 New Puzzles

Next, the table gets new puzzles.

Chapter 1’s Challenge said it first. Any Board you reach by sliding Tiles away from the Goal can be solved, because every Move can be undone. A New puzzle is made exactly that way. Start from the Goal, make random Moves, and wherever you stop is the new puzzle.

5.5.1 Puzzle Settings, Changed

Puzzle Settings gets a third column, scrambleMoves, a Number that starts at 12, and its one row, puzzle, holds 12 too. That’s how many random Moves a New puzzle makes. Like explanation, it’s a new field of the table’s Data Structure, Puzzle Settings Entry, made with Add Field.

Data StructurePuzzle Settings EntryAddDelete

One row of the "Puzzle Settings" Data Table.

startBoardNumberdefault 542703816
goalBoardNumberdefault 123456780
scrambleMovesNumberdefault 12
Data TablePuzzle SettingsData Structure: Puzzle Settings EntryAddDelete
Row 0puzzle
startBoard542703816
goalBoard123456780
scrambleMoves12

5.5.2 Random Move

Random Move makes one random Move on a Board. It’s handed a board, and notFromSlot, a Slot whose Tile may not move. It gives back the new board, and movedTo, the Slot the Tile slid into.

Two answers make a small Data Structure, Random Move Result, as they did for Pick Best Open State in Chapter 2. Make it first. Then it’s on the Return Type list.

Data StructureRandom Move Result

What Random Move gives back: board, the Board after the random Move, and movedTo, the Slot the Tile slid into (where the Blank was).

boardNumberdefault 0
movedToNumberdefault 0

Picking at random takes two walks over the Slots. Think of a teacher who has to choose one raised hand, fairly. First she counts the hands. She picks a number, and counts again in the same order, stopping at that number.

Random Move does the same. Three Declare Variables come first: choices, counted and fromSlot. The last starts at -1, before the first Slot. Its Repeat counts every Tile beside the Blank into choices, all but the one in notFromSlot. Then Generate Random Integer picks a number from 1 to choices, as pick.value.

The second count is a While. Each time round, fromSlot moves on one Slot, and counted counts the same Tiles again. When counted reaches pick.value, the loop ends. The Tile in fromSlot is the one that slides, and Board After Move, from Chapter 1, makes the Move. Last, the Return fills in both answers.

Every Tile beside the Blank has the same chance. And the While always ends, because pick is never more than the Tiles there are to count. If, Else, Repeat and While on the Making page shows a While, and Actions, one at a time shows Generate Random Integer.

FunctionRandom Move

Makes one random legal Move on a Board: one of the Tiles beside the Blank, each with the same chance, slides into it. The Tile in notFromSlot is never chosen, so the Move just made is not undone (-1 lets every Tile beside the Blank be chosen). movedTo is the Slot the Tile slid into, where the Blank was.

PARAMETERS
NameTypeStarts at
boardNumber0
notFromSlotNumber-1
RETURN TYPERandom Move Result (a Data Structure)
ACTIONS
1Declare Variable
Name
choices
Value
0
2Declare Variable
Name
counted
Value
0
3Declare Variable
Name
fromSlot
Value
-1
4Slot Of TileFunction
Action Name
blank
board
board
tile
0
5Repeat9times
Each time
6Slot DistanceFunction
Action Name
apart
fromSlot
local.index
toSlot
blank.value
7Ifapart.value == 1 && local.index != notFromSlot
Then
8Change Variable
Variable
choices
Operator
+=
Value
1
9Generate Random Integer
Action Name
pick
Minimum
1
Maximum
choices
10Whilecounted < pick.value
Each time
11Change Variable
Variable
fromSlot
Operator
+=
Value
1
12Slot DistanceFunction
Action Name
apartAgain
fromSlot
fromSlot
toSlot
blank.value
13IfapartAgain.value == 1 && fromSlot != notFromSlot
Then
14Change Variable
Variable
counted
Operator
+=
Value
1
15Board After MoveFunction
Action Name
afterMove
board
board
fromSlot
fromSlot
toSlot
blank.value
16Return
board
afterMove.value
movedTo
blank.value

5.5.3 Scramble Board

Scramble Board makes the whole puzzle. It declares two Variables, board and notFromSlot, and puts the goalBoard in board. Then it calls Random Move as many times as scrambleMoves says, each time on the Board the last Move made. It reads the answers as scramble.board and scramble.movedTo.

Each time, it hands over the last movedTo as notFromSlot. The Tile that slid last sits there now, where the Blank was a moment ago. Sliding it straight back would undo the Move, so that’s the one Tile that may not go.

Random Moves can wander back to the Goal. A New puzzle that’s already solved is no puzzle at all. So if that happens, the If at step 8 makes one more Move.

It has no PARAMETERS. Its Return hands back board, a Number.

FunctionScramble Board

Makes a New puzzle: starts from the Goal and makes Puzzle Settings / scrambleMoves random legal Moves (Random Move), never undoing the Move just made. If that lands on the Goal, one more Move is made. A Board made by legal Moves from the Goal can always be solved.

RETURN TYPENumber
ACTIONS
1Declare Variable
Name
board
Value
0
2Declare Variable
Name
notFromSlot
Value
-1
3Change Variable
Variable
board
Value
data('Puzzle Settings', 'puzzle', 'goalBoard')
4Repeatdata('Puzzle Settings', 'puzzle', 'scrambleMoves')times
Each time
5Random MoveFunction
Action Name
scramble
board
board
notFromSlot
notFromSlot
6Change Variable
Variable
board
Value
scramble.board
7Change Variable
Variable
notFromSlot
Value
scramble.movedTo
8Ifboard == data('Puzzle Settings', 'puzzle', 'goalBoard')
Then
9Random MoveFunction
Action Name
oneMore
board
board
notFromSlot
notFromSlot
10Change Variable
Variable
board
Value
oneMore.board
11Return
Value
board

5.5.4 New Puzzle

New Puzzle is the Logic behind two buttons. New puzzle asks Scramble Board for a Board and puts it in $boardState. Reset puts back the Board the last New puzzle made.

So it remembers that Board, in resetBoard. That field lives in a Data Structure of New Puzzle’s own, New Puzzle Data. On its Data Structures card, press + and choose New Data Structure, as you did for Run Search Data in Chapter 3. New Puzzle reads the field as self.newPuzzleData.resetBoard, and Change Component Value changes it.

Data StructureNew Puzzle Data

What New Puzzle remembers on Puzzle Rules: resetBoard, the Board Reset puts back (the Board of the last New puzzle, or the Board the game opened with).

resetBoardNumberdefault 0

When Created stores the first Board: the one the game opened with. Create runs after Scene Start, where Start Board has set up the Board, so it’s there to remember. After that, each New puzzle stores its own.

N and R ring newPressed and resetPressed, the messages the buttons send, the way A does for Solve.

Reset has one more job, for a puzzle that was refused. Remember Chapter 2, when you typed a Board into startBoard to see a refusal? A refused puzzle opens as the Goal. So when Reset puts back the Goal, it’s putting back a refused puzzle. Show Status Row names tryNew again, as it did when the game first opened that refused puzzle. New puzzle never makes the Goal, so nothing else brings it back.

Both buttons end the same way. They send boardJumped, with Trigger Custom Event, so every Object hears it. Nothing listens yet. Four pieces will: every Tile, Hand Moves, Solve Timer and Run Search.

Attach New Puzzle to Puzzle Rules once its Events are made. New Puzzle Data arrives on Puzzle Rules with it, From Logic, the way Run Search Data did.

Logic ComponentNew PuzzleAttach to: Puzzle Rules

Puts a whole Board on the table at once, without slides. New puzzle makes a Board by random legal Moves from the Goal (Scramble Board); Reset puts back the Board of the last New puzzle, or the Board the game opened with. Either way it broadcasts boardJumped, so the Tiles jump and the search starts over. When Reset puts back a Start Board that was refused (the Goal), the Status Line asks for a new puzzle again, as when the puzzle opened.

Attach toAny Object
DATA STRUCTURES
New Puzzle Dataself.newPuzzleData
When Created
WhenCreate
1Change Component Value
Component Value
self.newPuzzleData.resetBoard
Value
$boardState
When New Is Pressed
WhenCommand PressedCommand: New puzzle
1Trigger Custom Event on Object
Custom Event
newPressed
When Reset Is Pressed
WhenCommand PressedCommand: Reset
1Trigger Custom Event on Object
Custom Event
resetPressed
On New Pressed
WhennewPressedCustom Event
1Scramble BoardFunction
Action Name
scrambled
2Change Variable
Variable
$boardState
Value
scrambled.value
3Change Component Value
Component Value
self.newPuzzleData.resetBoard
Value
$boardState
4Trigger Custom Event
Custom Event
boardJumped
On Reset Pressed
WhenresetPressedCustom Event
1Change Variable
Variable
$boardState
Value
self.newPuzzleData.resetBoard
2If$boardState == data('Puzzle Settings', 'puzzle', 'goalBoard')
Then
3Show Status RowFunction
row
tryNew
4Trigger Custom Event
Custom Event
boardJumped

5.5.5 Slide To Slot, Changed

Now the Tiles learn to jump. Slide To Slot gets an Event for boardJumped, and it does what When Created used to do. It finds the Tile’s Slot and sets its position there at once.

First, though, it stops any slide under way, the slide timer and the glide both. So no Tile is left halfway.

When Created’s four steps all go. One new step takes their place, and it sends boardJumped to the Tile itself, with no Applies To. So a new Tile jumps to its Slot the same way the whole Board does. On the card, the four struck steps sit above the new step 1.

Logic ComponentSlide To SlotAttach to: TileAddDelete

Keeps this Tile on the Slot the Board State gives it. It jumps there when it is created and when New puzzle or Reset puts a whole Board at once (boardJumped), cutting short any slide. When a Move moves it, it slides there in a straight line at an even speed, taking slideSeconds, lands exactly on the Slot, and tells Puzzle Rules that the slide is over.

Attach toAny Object
DATA STRUCTURES
Numbered Tileself.numberedTile
Slide To Slot Dataself.slideToSlotData
SHARED VARIABLES
NameTypeKindStarting Value
puzzleRulesObjectConstantNone
slideSecondsNumberConstant0.22
When Created
WhenCreate
1Slot Of TileFunction
Action Name
startSlot
board
$boardState
tile
self.numberedTile.number
2Slot PositionFunction
Action Name
startPlace
slot
startSlot.value
layout
board
3Set Position
Position Relative To
Screen
X
startPlace.x
Y
startPlace.y
4Change Component Value
Component Value
self.slideToSlotData.shownSlot
Value
startSlot.value
1Trigger Custom Event on Object
Custom Event
boardJumped
On Board Jumped
WhenboardJumpedCustom Event
1Stop Recurring Timer
Timer Name
slide
2Control Tween
3Slot Of TileFunction
Action Name
jumpSlot
board
$boardState
tile
self.numberedTile.number
4Slot PositionFunction
Action Name
jumpPlace
slot
jumpSlot.value
layout
board
5Set Position
Position Relative To
Screen
X
jumpPlace.x
Y
jumpPlace.y
6Change Component Value
Component Value
self.slideToSlotData.shownSlot
Value
jumpSlot.value
On Board Changed
WhenboardChangedCustom Event
1Slot Of TileFunction
Action Name
mySlot
board
$boardState
tile
self.numberedTile.number
2IfmySlot.value != self.slideToSlotData.shownSlot
Then
3Change Component Value
Component Value
self.slideToSlotData.shownSlot
Value
mySlot.value
4Slot PositionFunction
Action Name
place
slot
mySlot.value
layout
board
5Tween
Value
self.transform.position.x
Target
place.x
Duration
@self.slideSeconds
6Tween
Value
self.transform.position.y
Target
place.y
Duration
@self.slideSeconds
7Start Recurring Timer
Timer Name
slide
Repeat Every (s)
@self.slideSeconds
Ring Once
Yes
On the Slide Timer
WhenRecurring TimerTimer name: slide
1Control Tween
2Slot PositionFunction
Action Name
restingPlace
slot
self.slideToSlotData.shownSlot
layout
board
3Set Position
Position Relative To
Screen
X
restingPlace.x
Y
restingPlace.y
4Trigger Custom Event on Object
Applies To
@self.puzzleRules
Custom Event
slideFinished

5.5.6 Hand Moves and Solve Timer, Changed

Two more listeners come next. A slide that’s cut short never finishes, so its slideFinished never comes. Hand Moves would wait for it forever in While Sliding, and Solve Timer would wait in Tile Sliding.

So each gets an On Board Jumped, and each switches back at once. Hand Moves goes back to Ready, where a click counts again.

Logic ComponentHand MovesAttach to: Puzzle RulesAddDelete

Makes each Move on the Board State, one at a time. When a Tile beside the Blank is chosen (by the player, or by the Route), the Move is stored and every Tile is told (boardChanged); while that Tile slides, other choices are ignored. When New puzzle or Reset puts a whole Board at once (boardJumped), any slide is cut short, so choices count again.

Attach toAny Object
On Tile Chosen
WhentileChosenCustom Event
ModeReady (Starting Mode)
1Slot Of TileFunction
Action Name
chosenSlot
board
$boardState
tile
$chosenTile
2Slot Of TileFunction
Action Name
blankSlot
board
$boardState
tile
0
3Slot DistanceFunction
Action Name
apart
fromSlot
chosenSlot.value
toSlot
blankSlot.value
4Ifapart.value == 1
Then
5Board After MoveFunction
Action Name
afterMove
board
$boardState
fromSlot
chosenSlot.value
toSlot
blankSlot.value
6Change Variable
Variable
$boardState
Value
afterMove.value
7Switch Mode
Event
On Tile Chosen
Mode
While Sliding
8Trigger Custom Event
Custom Event
boardChanged
On Tile Chosen
WhentileChosenCustom Event
ModeWhile Sliding
(no Actions)
On Slide Finished
WhenslideFinishedCustom Event
1Switch Mode
Event
On Tile Chosen
Mode
Ready
On Board Jumped
WhenboardJumpedCustom Event
1Switch Mode
Event
On Tile Chosen
Mode
Ready

Solve Timer goes to No Tile Sliding, so the Steps that Solve asks for go through again.

Logic ComponentSolve TimerAttach to: Puzzle RulesAddDelete

Decides when the solver takes a Step (takeStep): once each time the player presses Step, and, while Solve runs, up to stepsPerTick Steps every solveSeconds. No Step is taken while a Tile slides, so the Route plays one slide at a time and a Step pressed during a slide does nothing. The Solve button and the A key start Solve or pause it; Escape and Step pause it, and so does the search when it has nothing more to do. Pausing keeps all progress. The Status Line shows the words of $statusRow while Solve runs, and its pausedWords while it is paused.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
solveSecondsNumberConstant0.05
stepsPerTickNumberConstant8
When the Scene Starts
WhenScene Start
1Change Variable
Variable
$solveButtonWords
Value
data('Messages', 'solveButton', 'words')
When Solve Is Pressed
WhenCommand PressedCommand: Solve
1Trigger Custom Event on Object
Custom Event
solvePressed
When Pause Is Pressed
WhenCommand PressedCommand: Pause
1Trigger Custom Event on Object
Custom Event
pauseSolve
On Solve Pressed
WhensolvePressedCustom Event
ModePaused (Starting Mode)
1Start Recurring Timer
Timer Name
solveTick
Repeat Every (s)
@self.solveSeconds
2Switch Mode
Event
On Solve Pressed
Mode
Running
3Change Variable
Variable
$solveButtonWords
Value
data('Messages', 'pauseButton', 'words')
4Change Variable
Variable
$statusLine
Value
data('Messages', $statusRow, 'words')
On Solve Pressed
WhensolvePressedCustom Event
ModeRunning
1Trigger Custom Event on Object
Custom Event
pauseSolve
On Step Pressed
WhenstepPressedCustom Event
1Trigger Custom Event on Object
Custom Event
stepDue
2Trigger Custom Event on Object
Custom Event
pauseSolve
On the Solve Tick Timer
WhenRecurring TimerTimer name: solveTick
1Repeat@self.stepsPerTicktimes
Each time
2Trigger Custom Event on Object
Custom Event
stepDue
On Step Due
WhenstepDueCustom Event
ModeNo Tile Sliding (Starting Mode)
1Trigger Custom Event on Object
Custom Event
takeStep
On Step Due
WhenstepDueCustom Event
ModeTile Sliding
(no Actions)
On Board Changed
WhenboardChangedCustom Event
1Switch Mode
Event
On Step Due
Mode
Tile Sliding
On Slide Finished
WhenslideFinishedCustom Event
1Switch Mode
Event
On Step Due
Mode
No Tile Sliding
On Board Jumped
WhenboardJumpedCustom Event
1Switch Mode
Event
On Step Due
Mode
No Tile Sliding
On Pause Solve
WhenpauseSolveCustom Event
1Stop Recurring Timer
Timer Name
solveTick
2Switch Mode
Event
On Solve Pressed
Mode
Paused
3Change Variable
Variable
$solveButtonWords
Value
data('Messages', 'solveButton', 'words')
4Change Variable
Variable
$statusLine
Value
data('Messages', $statusRow, 'pausedWords')

Nobody will ever see these two Events work. Without them, a New puzzle pressed mid-slide would leave every click on a Tile doing nothing at all.

Two Puzzle Buttons go in the navy bar now, from PLACE THESE: New puzzle at 597 and -737, and Reset at 791 and -737. Give each its Tint, #F1E8D3, as you did for Step. PLACE THESE doesn’t list it, because it’s on the Puzzle Button’s own card, as Tint (on its placement). Each one’s words go in through Edit Object Locally, in its Text / UI: New puzzle, and Reset. Their messages are newPressed and resetPressed. Each goes in the Message box under Send When Pressed, as Solve’s did.

Run the game and press New puzzle. The Tiles jump, all at once, to a new Board. Press it again, and again. Then press Reset, and the last one comes back.

The search doesn’t hear boardJumped yet. Until the last part, jump before you press Step, not after.

5.6 Two Ways to Guess

Now the second guess.

5.6.1 Estimates

Estimates is a small table, with one row for each Estimate. Each row is named by the words the button will show: Manhattan, and Misplaced Tiles.

Its countsSlotsAway column is a Boolean. Yes means a Tile out of place counts as many Slots as it is from home. No means it counts 1. The next column names the Estimate that comes after this one. Manhattan’s next is Misplaced Tiles, and Misplaced Tiles’ next is Manhattan, so the two go round in a ring.

Data TableEstimatesData Structure: Estimates Entry
Row 0Manhattan
countsSlotsAwayYes
nextMisplaced Tiles
Row 1Misplaced Tiles
countsSlotsAwayNo
nextManhattan

Both columns are fields of Estimates Entry, made with Add Field. The card gives countsSlotsAway’s default as true, which is Yes written the other way. Clear the Value box of next.

Data StructureEstimates Entry

One row of the "Estimates" Data Table.

countsSlotsAwayBooleandefault true
nextTextdefault (empty)

5.6.2 Estimate Moves Left, Changed

Estimate Moves Left, from Chapter 2, learns the second way. First it reads countsSlotsAway from the row $estimate names, and keeps it in a Variable of the same name.

That Variable is a Boolean, like isFound in Chapter 2. It’s made by one of three new steps at the top, between the first Declare Variable and the Repeat. Steps 2 and 3 declare tileMoves and countsSlotsAway. For countsSlotsAway, pick Boolean in the block’s Type list, and give it true. Step 4 reads the table. Join each one in its place with the round handles.

For each Tile, it still finds how far the Tile is from home. Now that number goes into tileMoves first. If this Estimate doesn’t count Slots, and the Tile isn’t home, tileMoves becomes 1. Then tileMoves goes onto the total.

On the card, the old last step becomes step 10. Its Variable is tileMoves now, and its Operator goes back to =. So it keeps away.value in tileMoves. Steps 11, 12 and 13 are new, and they follow it, inside step 7’s If. Right-click that If and choose Add inside Then for 11 and 13. Step 11 is an If whose box joins two tests with &&, and step 12 goes inside it. Step 13 adds tileMoves onto total.

FunctionEstimate Moves LeftAddDelete

The Estimate of the Moves still needed from a Board to the Goal, by the Estimate chosen in $estimate (a row of the Estimates table). Manhattan distance adds, for each Tile, the rows plus the columns between its Slot and its Slot in the Goal; misplaced Tiles adds 1 for each Tile out of place. The Blank is not counted. Neither guesses more Moves than are really needed, which is what lets A* find the shortest Route.

PARAMETERS
NameTypeStarts at
boardNumber0
RETURN TYPENumber
ACTIONS
1Declare Variable
Name
total
Value
0
2Declare Variable
Name
tileMoves
Value
0
3Declare Variable
Type
Boolean
Name
countsSlotsAway
Value
true
4Change Variable
Variable
countsSlotsAway
Value
data('Estimates', $estimate, 'countsSlotsAway')
5Repeat9times
Each time
6Tile In SlotFunction
Action Name
here
board
board
slot
local.index
7Ifhere.value > 0
Then
8Slot Of TileFunction
Action Name
home
board
data('Puzzle Settings', 'puzzle', 'goalBoard')
tile
here.value
9Slot DistanceFunction
Action Name
away
fromSlot
local.index
toSlot
home.value
10Change Variable
Variable
tileMoves
Value
away.value
11IfcountsSlotsAway == false && tileMoves > 0
Then
12Change Variable
Variable
tileMoves
Value
1
13Change Variable
Variable
total
Operator
+=
Value
tileMoves
Value
away.value
14Return
Value
total

5.6.3 Choose Estimate

Choose Estimate is the button’s Logic, and it’s short: E rings estimatePressed, as the button does.

Each press moves $estimate one step round the ring. While it’s Manhattan, the next column of Manhattan’s row says Misplaced Tiles, so that’s what it becomes. The next press reads Misplaced Tiles’ row, and Manhattan comes back. One look-up on the card does it all.

Then Choose Estimate sends estimateChanged, to tell Puzzle Rules that the guess the search uses has changed. Nothing listens for that yet either.

Logic ComponentChoose EstimateAttach to: Puzzle Rules

The Estimate button and the E key change the Estimate the search uses ($estimate) to the next row of the Estimates table. The search made with the old Estimate is cancelled, so both Estimates can be tried on the same Board.

Attach toAny Object
When Estimate Is Pressed
WhenCommand PressedCommand: Change Estimate
1Trigger Custom Event on Object
Custom Event
estimatePressed
On Estimate Pressed
WhenestimatePressedCustom Event
1Change Variable
Variable
$estimate
Value
data('Estimates', $estimate, 'next')
2Trigger Custom Event on Object
Custom Event
estimateChanged

Puzzle Rules’ card shows New Puzzle and Choose Estimate attached, below Solve Timer.

ObjectPuzzle RulesAddDelete
Data Structure
From LogicRun Search
Data StructureRun Search Data
Logic
Start Board
Hand Moves
Check Goal
Tile Keys
Run Search
Solve Timer
New Puzzle
Choose Estimate
Shared Variables
puzzleRulesPuzzle Rules

The last button goes on the card itself. Place a Puzzle Button at 766 and -610, under the count, with the same Tint, #F1E8D3, and estimatePressed in its Message box. It’s wider than the others, so three of its values go in through Edit Object Locally. In its Text / UI, the Text is Estimate: {$estimate}, braces and all. Its Transform’s Bounding Box Size and its Sprite’s Transform Size are both 260 by 48.

Run the game. The button on the card reads Estimate: Manhattan. Press it, or E, and it reads Estimate: Misplaced Tiles. Press it again, and it’s back.

The search doesn’t hear the change yet, and that’s the last part.

5.7 Every Change Starts Over

Last, the search learns to begin again.

Three things now make the search’s notebook wrong. A hand Move changes the Board it was searching, so every line in it is about some other Board. So does a jump. And a new Estimate makes every guess in it the wrong kind of guess. Each time, the answer is the same. Empty the notebook, and start over.

5.7.1 Run Search, Changed Again

Chapter 4’s On Board Changed did that work itself, in its Else. Now the work moves into an Event of its own, On Cancel Search, and three Events ask for it with one message, cancelSearch. On Board Changed sends it for your own Move. On Board Jumped sends it for a jump, and On Estimate Changed for a new guess. Each sends it to Puzzle Rules, with no Applies To.

So On Board Changed keeps one step in its Else, the one that sends cancelSearch. Its inner If gets a new step 4 as well. When the Route’s last Move makes the Goal, the Message Line says the Route has been played.

Here is the finished Event, whole, from Run Search’s own card. First, delete every step in its old Else. The If there has a step inside it, and deleting the If deletes that step as well. You’ll build those steps again in a moment, in On Cancel Search. Its card shows each one new.

Then build step 7 in the empty Else, with Add inside Else. Step 4 goes in the inner If, step 3. Right-click it and choose Add inside Then, and the new block joins that If’s line after Switch Mode. The card has step 4 first, but neither step reads what the other writes. So on your Event, Switch Mode stays step 4 and the new step is 5. Either order plays the same.

Logic ComponentRun Searchcontinued
On Board Changed
WhenboardChangedCustom Event
1If$boardState == self.runSearchData.routeNextBoard
Then
2Change Component Value
Component Value
self.runSearchData.movesPlayed
Operator
+=
Value
1
3If$boardState == data('Puzzle Settings', 'puzzle', 'goalBoard')
Then
4Change Variable
Variable
$messageLine
Value
data('Messages', 'solved', 'explanation')
5Switch Mode
Event
On Take Step
Mode
Finished
6Otherwise
7Trigger Custom Event on Object
Custom Event
cancelSearch

On Cancel Search is Chapter 4’s old Else, with one step more: step 7 puts the ready row’s sentence on the Message Line. As before, the Status Line keeps its words on the Goal. So Solved stays Solved. A refused puzzle still says Try a new puzzle.

The last two Events are one step each, and each sends cancelSearch.

Logic ComponentRun SearchcontinuedAddDelete
On Cancel Search
WhencancelSearchCustom Event
1Reset Data Table
Table
"Search States"
2Reset Data Table
Table
"Solution Route"
3Change Component Value
Component Value
self.runSearchData.routeNextBoard
Value
-1
4Change Variable
Variable
$statesChecked
Value
0
5If$boardState != data('Puzzle Settings', 'puzzle', 'goalBoard')
Then
6Show Status RowFunction
row
ready
7Change Variable
Variable
$messageLine
Value
data('Messages', 'ready', 'explanation')
8Switch Mode
Event
On Take Step
Mode
Not Started
9Trigger Custom Event on Object
Applies To
@self.puzzleRules
Custom Event
pauseSolve
On Board Jumped
WhenboardJumpedCustom Event
1Trigger Custom Event on Object
Custom Event
cancelSearch
On Estimate Changed
WhenestimateChangedCustom Event
1Trigger Custom Event on Object
Custom Event
cancelSearch

Run the game. Press Solve, and let it finish. Then press Reset, press E, and press Solve again.

The count runs on past 20, all the way to 84, and the Tiles slide home.

Play it!

Here is what to watch for.

  • When the game opens, the line under the buttons reads Slide a Tile beside the Blank, or press Step to search from this Board.
  • Press Step once. The count reads 1, and the card says Paused. The line now says each Step Expands the open Search State with the lowest Moves so far + Estimate (g + h).
  • Press Solve. The count stops at 20, the Tiles slide home, and the card says Solved. The line says the Route has been played.
  • Press Reset, or R. Every Tile jumps back to your puzzle at once. The count reads 0, the Status Line is empty, and the line under the buttons tells you how to begin.
  • Press E. The button reads Estimate: Misplaced Tiles. Press Solve. This time the count stops at 84, and the Tiles slide home in 12 Moves, as before.
  • Press E again, and then New puzzle. The Tiles jump to a Board you haven’t seen, and to another when you press New puzzle again.
  • Click a Tile beside the Blank, then press Reset. The new puzzle comes back as New puzzle made it, not the puzzle you started with.
  • Press Solve. The search solves the new puzzle too.
How it works

New puzzle makes a Board by random Moves from the Goal, so it can always be solved. Reset puts back the last one. Both jump, and boardJumped reaches every Tile, Hand Moves, Solve Timer and Run Search. Estimate Moves Left counts each Tile out of place as its Slots from home, or as 1, by the row in $estimate. A hand Move, a jump or a new Estimate empties the notebook through On Cancel Search. The Message Line says what each Step does now.

Underneath all of it is one idea. A guess that never says too many keeps A* Search honest. Manhattan and Misplaced Tiles both pass, so both find a shortest Route, 12 Moves from your puzzle. But the closer guess tells the search more. Manhattan checks 20 Boards, and Misplaced Tiles checks 84.

One more thing is true of this search, and you built it. Pick Best Open State reads every line of the notebook, one by one, on every Step. A search thousands of lines long would want a quicker way to find the best line. This one stays small enough to follow.

5.8 If It Didn’t Work

This chapter adds new ways to start over, so a slip often shows as something that doesn’t start over. That’s normal. Press a button, and watch the count and the line under the buttons.

New puzzle does nothing? Look at the button’s message, newPressed, and at New Puzzle on Puzzle Rules. Last, look at On New Pressed’s step 4. It’s a Trigger Custom Event, which rings for every Object, not the kind with on Object.

Some Tiles jump and some don’t? Perhaps Slide To Slot’s On Board Jumped asks about the wrong Tile, so look at its step 3, Slot Of Tile, whose tile should read self.numberedTile.number.

After a jump, clicks on the Tiles do nothing? Hand Moves may be stuck in While Sliding. Look at its On Board Jumped, which switches On Tile Chosen to Ready.

The count doesn’t go back to 0 after New puzzle, Reset or E? Look at Run Search’s On Board Jumped and On Estimate Changed, and at each one’s Custom Event. It should be cancelSearch, spelled the same as the On Cancel Search Event.

The Estimate button reads Estimate: and nothing after it? Look at $estimate’s Default, which should be Manhattan with no quote marks, and at the button’s Text, Estimate: {$estimate}, braces and all.

The button’s words change, but the count is 20 both ways? Look at Estimate Moves Left’s step 4, whose Value reads ‘countsSlotsAway’. The row names in Estimates, and the words in its next column, have to match letter for letter.

Misplaced Tiles doesn’t stop at 84? Perhaps step 11 lost its second test, tileMoves > 0, and without it a Tile already home counts 1 too.

The line under the buttons stays empty? Its Text should be {$messageLine}. After that, look at Run Search’s When the Scene Starts, and the explanation column, spelled the same in Messages and on every Value that reads it.

Each of these is one small fix. Make it, run the game again, and you’re back with the chapter. The catch-up download at the top of this chapter is the finished game’s project, if you’d like to compare.

Challenge

Make the puzzles harder, and watch the two guesses part ways. In Puzzle Settings, set scrambleMoves to 30. Press New puzzle, and solve it with Manhattan. Then press Reset, press E, and solve it again with Misplaced Tiles. Write down both counts. Try a few puzzles. Which guess checks fewer Boards, and by how much?

If the card ever says Try a new puzzle., read the line under the buttons. The search grew too large, and that’s a real answer too.

Estimate Moves Left knows two ways to count, and a third is yours to invent. When you’re done, put scrambleMoves back to 12.

In Chapter 1, the navy bar under the Board was empty, and only your hands could move a Tile. Now it holds four buttons, and the card keeps a count, a guess and a reason. Every Tile, word and number on that screen is one you put there.

The lantern still burns at the corner of the table. Press New puzzle. See what your solver makes of it.