- Learn
- Build a Game
- Chess
- 10 Pin Lines and Arrows
10 Pin Lines and Arrows
By the end of this chapter, Threats mode draws on the Board. A dashed amber line runs from each pinning Piece to the King behind. Click any Piece, of either Side, and arrows show what it attacks and what attacks it. The Threats button looks pressed while the mode is on.
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 11 from it.
To open it, right-click the downloaded file, choose Extract All... and then Extract. Start Myth Engine, choose File › Open Project... and pick the .mythproj file in the new folder. CREDITS.md and the licenses folder beside it come with the game.
10.1 The Idea
Play your game from Chapter 9, with Threats on. Play e2 to e4, and after Black’s reply, your Queen from d1 to h5. Unless Black has moved its f-Pawn or g-Pawn, an amber bar appears under its Pawn on f7. It’s pinned. But by what? You trace the diagonal with a finger, from your Queen to Black’s King, and there it is. The Board knew all along. It kept the answer in pinnedBy, and Chapter 9 promised that something would read it.
This chapter reads it, and draws it. A dashed amber line runs from the pinning Piece to the King it threatens, across the pinned Piece in between.
A red outline leaves the same question open. It says a Piece could be captured, not by whom. So this chapter adds arrows. Click any Piece in Threats mode, yours or the Computer’s, and you inspect it. Its Square turns teal, and arrows fan out from it. Arrows run out along every line it could capture on, red where they reach an enemy Piece and teal otherwise. A red arrow comes in from every enemy Piece that could capture it.
The hard part is the drawing. Lines and arrows aren’t Objects, so they don’t stay on the Board. They come from a Draw Event, an Event that runs every frame. What it draws lasts one frame. It works like a flip book: each page is drawn fresh, and the last page is already gone. So the Draw Event has to be quick. The arrows are worked out once, each time the Board changes, into a table. Every frame, the Draw Event only reads that table.
10.2 The Plan
Here’s how the pieces work together. Read each line from left to right, once now for the shape, and again after the lines first show.
- A click on a Piece in Threats mode -> Select and Move -> Inspect Piece names it in Board State’s inspected -> positionChanged -> Square Marks tints its Square teal.
- The Board changes -> positionChanged -> Draw Threat Lines calls Find Arrows -> one row per arrow in the Arrows table.
- Every frame -> Draw Threat Lines’ Draw Event -> Draw Arrow for each row of Arrows, Draw Dashed Line for each pin in Threats.
- The same positionChanged -> Show Threats Button Pressed draws the Threats button pressed, or plain.
10.3 Set Up
There’s nothing new to place in the Scene. Threats Button gains its own Show Threats Button Pressed Logic in the chapter’s last part. Every placement of that variant gains it too.
10.4 Looking at Any Piece
First, the Board learns to look at a Piece without moving it.
10.4.1 Board State
What the Game Rules remember about the game besides the Position table. sideToMove: the Side whose Turn it is (White moves first). selected: the Square of the selected Piece (empty when none is selected). lastFrom and lastTo: the two Squares of the last Move (empty before the first Move). checkSquare: the Square of the King in Check (empty when the Side to move is not in Check). enPassantSquare: the Square a Pawn has just passed over with a two-Square Move, where an enemy Pawn may take it en passant on the very next Move (empty otherwise). promotionSquare: while the Promotion choice is open, the Square the selected Pawn will be promoted on (empty otherwise). playerSide: the Side the Player plays (White unless chosen otherwise), so the end of the game can say who won. fiftyMoveCount: the Moves made since the last Capture or Pawn Move, both Sides counted, for the fifty-move rule. isFlipped: whether the Board is drawn turned over, Black at the bottom (false: White at the bottom, a1 at the bottom-left). strength: the Strength the Computer plays at (Gentle, Casual, Club, Strong, or Expert; Casual unless chosen otherwise). chosenSide and chosenStrength: while the New Game panel is open, the Side and the Strength it shows as chosen. isShowingThreats: whether Threats mode is on (the Threats button; kept on this device). inspected: in Threats mode, the Square of the Piece the Player last clicked to look at it, of either Side (empty when none is inspected).
Board State gets one more field at the end, inspected, for the Square of the inspected Piece.
10.4.2 Inspect Piece
In Threats mode, inspects the Piece standing on a Square the Player clicked (Board State's inspected), whichever Side it belongs to, so its arrows can be shown without selecting it for a Move. A click on an empty Square, or any click outside Threats mode, inspects nothing. gameRules: the Game Rules Object. square: the Square that was clicked.
boardStateData StructureBoard StategameRules.boardState.isShowingThreats == true && data('Position', square, 'kind') != ""- Component Value
gameRules.boardState.inspected- Value
square
- Component Value
gameRules.boardState.inspected- Value
''
Inspect Piece asks two things. Is Threats mode on? Is there a Piece on the clicked Square, of either Side? Then that Square is inspected. Any other click inspects nothing, and the empty '' clears it.
10.4.3 Select and Move
Decides what a click on a Square means. A click on a Square the selected Piece may move to makes that Move (Make Move: castling and en passant included) and tells the Game Rules "moveMade" (Start Turn then starts the other Side's Turn); when the Move takes a Pawn to its last Rank, it opens the Promotion choice instead (Game State Promotion), and Choose Promotion makes the Move. A click on a Piece of the Side to move selects it and marks its Legal Moves (Find Legal Moves); any other click clears the selection. In Threats mode a click that is not a Move also inspects the Piece clicked, of either Side (Inspect Piece). Then it tells everyone "positionChanged". Its Other is the clicked Square. Every click forgets the Hint (Forget The Hint).
Any Objectself.boardStateself.selectAndMoveData- Type
Text- Name
selectedSquare
- Type
Text- Name
clickedSquare
- Variable
clickedSquare- Value
other.squareInfo.name
- Variable
selectedSquare- Value
self.boardState.selected
selectedSquare != "" && data('Targets', clickedSquare, 'reachable') == true- Component Value
self.selectAndMoveData.side- Value
self.boardState.sideToMove
data('Position', selectedSquare, 'kind') == "Pawn" && data('Position', clickedSquare, 'rank') == data('Sides', self.selectAndMoveData.side, 'promotionRank')- gameRules
self- square
clickedSquare
- gameRules
self- from
selectedSquare- to
clickedSquare
- Custom Event
moveMade
data('Position', clickedSquare, 'side') == self.boardState.sideToMove && clickedSquare != selectedSquare- Component Value
self.boardState.selected- Value
clickedSquare
- from
clickedSquare- enPassantSquare
self.boardState.enPassantSquare
- Component Value
self.boardState.selected- Value
''
- gameRules
self- square
clickedSquare
- Custom Event
positionChanged
Select and Move calls Inspect Piece on every click that isn’t a Move. Those clicks go down the outer Else, and the new step goes last on its line. Add Inspect Piece apart from the line. Use the round handles to join it after step 14’s If, inside the outer Else. It follows that If and its own Else, on the same line.
So a click on your own Piece does two things: it selects the Piece and inspects it. Click it again, and step 18 clears the selection. The Piece stays inspected.
10.4.4 Start Turn, Go Back To The Start and Show Threats
Starts each Turn: the first one (White's) when the game begins, and the next one after every Move, which it first writes down in the Move History (Record Move) before giving the Turn to the other Side (Pass The Turn). Each Turn starts with Start The Turn: it asks whether the Side to move is in Check, then tells the Game Rules that the Turn has started ("turnStarted").
Any Objectself.boardState- gameRules
self
- Custom Event
positionChanged
- gameRules
self
- gameRules
self
- Component Value
self.boardState.selected- Value
''
- Component Value
self.boardState.inspected- Value
''
- gameRules
self
Puts the game back as it is when a game starts: the starting position, every castling allowed again, no position seen yet, and nothing marked (the tables as they were made), and Board State as a game starts: White to move, no en passant Square, no Moves counted for the fifty-move rule, no last Move, and nothing selected or inspected. gameRules: the Game Rules Object.
boardStateData StructureBoard State- Table
Position
- Table
Castling
- Table
Position History
- Component Value
gameRules.boardState.sideToMove- Value
'White'
- Component Value
gameRules.boardState.enPassantSquare- Value
''
- Component Value
gameRules.boardState.fiftyMoveCount- Value
0
- Component Value
gameRules.boardState.lastFrom- Value
''
- Component Value
gameRules.boardState.lastTo- Value
''
- Component Value
gameRules.boardState.selected- Value
''
- Component Value
gameRules.boardState.inspected- Value
''
Turns Threats mode on and off, and keeps its marks up to date. When the game begins (Game Start) it takes the choice kept on this device (the Persistent Value "threats"), and shows the legend (the UI Group "Threats Legend") when Threats mode is on. At the start of every Turn ("turnStarted") it fills the Threats table again (Find Threats) while Threats mode is on. When the Threats button is pressed ("threatsPressed") on the Player's Turn or after the game has ended, it turns Threats mode over (Board State's isShowingThreats) and ends any inspecting, fills or empties the Threats table, shows or hides the legend, keeps the choice on this device, and every Square redraws ("positionChanged").
Any Objectself.boardState- Action Name
savedThreats- Key
"threats"- Default
self.boardState.isShowingThreats
savedThreats.value == true- Component Value
self.boardState.isShowingThreats- Value
true
self.boardState.isShowingThreats == true- UI Group
"Threats Legend"
self.boardState.isShowingThreats == true- gameRules
self
- Action Name
threatsLook
threatsLook.value == true- Component Value
self.boardState.inspected- Value
''
self.boardState.isShowingThreats == true- Component Value
self.boardState.isShowingThreats- Value
false
- UI Group
"Threats Legend"- Visible
No
- Component Value
self.boardState.isShowingThreats- Value
true
- gameRules
self
- UI Group
"Threats Legend"
self.boardState.selected != ""- from
self.boardState.selected- enPassantSquare
self.boardState.enPassantSquare
- Action Name
persistentWriteResult- Key
"threats"- Value
self.boardState.isShowingThreats
- Custom Event
positionChanged
Three places forget the inspected Piece, each with one Change Component Value and an empty ''. A Move forgets it, because the Board is new. So does Undo, through Go Back To The Start, and so does turning Threats mode on or off.
In Start Turn, step 4 goes between two old steps. Add it apart from the line, then join it after step 3 with the round handles. Clear Targets stays behind it. In Go Back To The Start, step 11 comes last.
Show Threats’ step 3 goes first inside step 2’s If. Its Add Inside Then adds Change Component Value, apart from the old steps. Use the round handles to join it to the If’s True handle. The old If follows it.
10.4.5 Show Highlight
Shows a Square's highlight in one color: tints the Square and sets how strongly the tint shows. square: the Square Object to highlight. color: the color. alpha: how strongly it shows, from 0 (not at all) to 1 (solid).
- Applies to
square- Value
color
- Applies to
square- Alpha
alpha
Square Marks is about to tint its Square six different ways, and each one takes two steps: a tint, then how strongly it shows. That’s twelve steps that differ only in their two values. This Function holds the pair once, in two steps of its own. Its square is an Object with nothing listed under it, so leave it as it comes. It only tints that Object and sets its Alpha.
10.4.6 Square Marks
Shows the highlight on this Square after every change (the Game Rules send "positionChanged" and are its Other): a tint when it was part of the last Move, when its Piece is attacked in Threats mode (the Threats table; a stronger red when it is unprotected), when its Piece is inspected in Threats mode, when its Piece is selected, when it is one of the Hint's two Squares (the Chosen Moves table, row Hint), or when its King is in Check (the most important one shows). The colors, and how strongly each shows, are the Square's Shared Variables (Show Highlight shows them). While the Computer's Move slides ("slideStarted"), no highlight is shown.
Any Objectself.squareInfo- Alpha
0
self.squareInfo.name == other.boardState.lastFrom || self.squareInfo.name == other.boardState.lastTo- Value
@self.lastMoveColor
- Alpha
@self.lastMoveAlpha
- square
self- color
@self.lastMoveColor- alpha
@self.lastMoveAlpha
data('Threats', self.squareInfo.name, 'isAttacked') == true- Value
@self.attackedColor
- Alpha
@self.attackedAlpha
- square
self- color
@self.attackedColor- alpha
@self.attackedAlpha
data('Threats', self.squareInfo.name, 'isUnprotected') == true- Alpha
@self.unprotectedAlpha
self.squareInfo.name == other.boardState.inspected- square
self- color
@self.inspectedColor- alpha
@self.inspectedAlpha
self.squareInfo.name == other.boardState.selected- Value
@self.selectedColor
- Alpha
@self.selectedAlpha
- square
self- color
@self.selectedColor- alpha
@self.selectedAlpha
self.squareInfo.name == data('Chosen Moves', 'Hint', 'from') || self.squareInfo.name == data('Chosen Moves', 'Hint', 'to')- Value
@self.hintColor
- Alpha
@self.hintAlpha
- square
self- color
@self.hintColor- alpha
@self.hintAlpha
self.squareInfo.name == other.boardState.checkSquare- Value
@self.checkColor
- Alpha
@self.checkAlpha
- square
self- color
@self.checkColor- alpha
@self.checkAlpha
- Alpha
0
Square Marks hands each pair of steps to the new Function. In each If, delete the two struck steps, so its True line is empty. Add Inside Then adds the Function. Join it there, with self, the color that If used before, and its alpha. Step 7, the kept Set Alpha in the unprotected If, stays as it was. It only makes the red a little stronger.
One If is new, step 8, for the teal, and it goes before the selected Square’s If. Add the new If apart from the line. Join it after step 6’s If with the round handles. The selected Square’s If follows. Add Inside Then adds the Function to the new If’s True line, ready to join.
Why there? Each If paints over the one before. So a Piece that’s selected and inspected at once shows its gold, and the teal shows only on a Piece that isn’t selected. Its inspectedAlpha is 0.27, a soft wash, lighter than any other mark.
Run the game, and press Threats. Click Black’s Knight on b8, and its Square turns teal. Now click your Pawn on e2. It turns gold, with its dots. Click the same Pawn a second time, and the dots go while its Square turns teal.
10.5 Arrows, Worked Out Once
Now the arrows. This part fills a table, and nothing draws it yet. The game plays as before until the next part, where the arrows appear.
10.5.1 Arrows
One row of the "Arrows" Data Table, which follows it. The arrows Threats mode shows for the selected or inspected Piece, one row per arrow (each row is named by its number). from and to: the Squares the arrow runs from and to (a1 to h8). color: the arrow's color, with how strongly it shows (#rrggbbaa). Find Arrows fills it after every change; Draw Threat Lines draws it. Empty when no arrow is shown.
Arrows has one row for each arrow: the Square it starts from, the Square it points to, and its color. Its columns come from Arrows Entry. It starts with no rows. Find Arrows fills it each time the Board changes.
10.5.2 Find Attacker Along
What Find Attacker Along returns. attackers: 1 when the first Piece in the way belongs to the attacking Side and is of the direction's Kind, else 0. attacker: the Square of that Piece, or empty.
Looks back from one Square along one direction of the Directions table for the first Piece in the way (Find First Piece Along). attackers (returned): 1 when that Piece belongs to bySide and is of the direction's Kind, so it could capture onto the Square, else 0 (for a Pawn, the direction is turned round by its Side's forward). attacker (returned): the Square of that attacking Piece, or empty when there is none. square: the Square looked from (a1 to h8). direction: the row (0 = first) of the Directions table. bySide: the attacking Side.
- Type
Text- Name
piece
- Name
attackers- Value
0
- Type
Text- Name
attacker
- Action Name
pieceLook- square
square- direction
direction- bySide
bySide
- Variable
piece- Value
pieceLook.value
piece != "" && data('Position', piece, 'side') == bySide && data('Position', piece, 'kind') == data('Directions', direction, 'kind')- Variable
attackers- Value
1
- Variable
attacker- Value
piece
- attackers
attackers- attacker
attacker
The answer has grown. Find Attacker Along now returns a Data Structure, Find Attacker Along Result. It always knew whether some Piece could capture onto the Square. Now it names that Piece’s Square too. The Return has two fields, attackers and attacker, and step 8 writes the new name.
Change its Return Type to that Data Structure, and add the new Declare, attacker. The full card shows the line with both returned fields.
Is Square Attacked reads this answer too. Its step 6 now takes attackerLook.attackers in place of attackerLook.value. The count stays the same.
Answers whether any Piece of one Side could capture onto a Square. square: the Square (a1 to h8). bySide: the attacking Side (White or Black). attacked (returned): yes when at least one of its Pieces attacks the Square. attackers (returned): how many do.
- Name
attackerCount- Value
0
- Type
Boolean- Name
attacked- Value
false
dataCount('Directions')timesdata('Directions', local.index, 'canCapture') == true- Action Name
attackerLook- square
square- direction
local.index- bySide
bySide
- Variable
attackerCount- Operator
+=- Value
attackerLook.attackers- Value
attackerLook.value
attackerCount > 0- Variable
attacked- Value
true
- attacked
attacked- attackers
attackerCount
10.5.3 Find Farthest Square Along
Follows one direction of the Directions table from a Piece, Square by Square, as far as the Piece reaches that way: up to the first Piece in the way (of either Side), the edge of the Board, or the end of the direction's steps. from: the Square of the Piece (a1 to h8). direction: the row (0 = first) of the Directions table (a Pawn's direction is turned round by its Side's forward). farthest (returned): the last Square reached, or empty when the first step leaves the Board.
- Type
Text- Name
side
- Name
forward- Value
1
- Name
file- Value
0
- Name
rank- Value
0
- Name
stepsLeft- Value
0
- Type
Boolean- Name
isOnBoard- Value
false
- Name
index- Value
0
- Type
Boolean- Name
keepGoing- Value
true
- Type
Text- Name
farthest
- Variable
side- Value
data('Position', from, 'side')
- Variable
forward- Value
data('Sides', side, 'forward')
- Variable
file- Value
data('Position', from, 'file')
- Variable
rank- Value
data('Position', from, 'rank')
- Variable
stepsLeft- Value
data('Directions', direction, 'steps')
keepGoing == true && stepsLeft > 0- Variable
file- Operator
+=- Value
data('Directions', direction, 'fileStep')
- Variable
rank- Operator
+=- Value
data('Directions', direction, 'rankStep') * forward
- Variable
stepsLeft- Operator
-=- Value
1
- Variable
isOnBoard- Value
false
file >= 1 && file <= 8 && rank >= 1 && rank <= 8- Variable
isOnBoard- Value
true
- Variable
index- Value
(rank - 1) * 8 + (file - 1)
- Variable
keepGoing- Value
false
isOnBoard == true- Variable
farthest- Value
dataRow('Position', index)
isOnBoard == true && data('Position', index, 'kind') == ""- Variable
keepGoing- Value
true
- Value
farthest
An arrow along a line should run as far as the Piece reaches. Find Farthest Square Along follows one direction from a Piece, Square by Square. It stops at the first Piece in the way, of either Side, or at the edge, or when the direction’s steps run out. The last Square it reached is farthest, returned as Text.
Its walk is close to Find First Piece Along’s, from Chapter 9, with one change. It goes forward, the way the Piece moves, so file and rank step with +=. The old walk looked back, with -=.
Steps 25 and 26 read Position by a row number, index, which step 22 works out. So a1 is row 0, b1 is row 1, and so on, up to h8, the last. dataRow gives that row’s name, which is the Square’s own.
10.5.4 Add Arrow
Adds one arrow at the end of the Arrows table. from and to: the Squares it runs from and to (a1 to h8). color: its color, with how strongly it shows.
- Name
lastRow- Value
0
- Action Name
addedRow- Table
Arrows- Row
dataCount('Arrows')
- Variable
lastRow- Value
dataCount('Arrows') - 1
- Table
Arrows- Row
lastRow- Column
'from'- Value
from
- Table
Arrows- Row
lastRow- Column
'to'- Value
to
- Table
Arrows- Row
lastRow- Column
'color'- Value
color
Add Arrow writes one arrow. Control Data Row adds a row at the end of Arrows. Its Operation stays on adding the row. Its Row, dataCount(‘Arrows’), is one past the last, so the row is always new. lastRow is its number, and three Set Data Cells fill it in. The color column is Color. It holds the color’s code, eight digits and all, and Set Draw Color reads it later.
10.5.5 Add Arrow Along
Adds the arrow a Piece shows along one of its directions, to the farthest Square it reaches that way (Find Farthest Square Along): in toEnemyColor when an enemy Piece stands there, else in otherColor (an empty Square, or a Piece of its own Side that it protects). No arrow when the direction leaves the Board at once. from: the Square of the Piece. direction: the row (0 = first) of the Directions table.
- Type
Text- Name
enemy
- Type
Text- Name
farthest
- Type
Color- Name
color- Value
'#ffffff'
- Variable
enemy- Value
data('Sides', data('Position', from, 'side'), 'opponent')
- Action Name
reachLook- from
from- direction
direction
- Variable
farthest- Value
reachLook.value
- Variable
color- Value
otherColor
data('Position', farthest, 'side') == enemy- Variable
color- Value
toEnemyColor
farthest != ""- from
from- to
farthest- color
color
Add Arrow Along draws a Piece’s arrow along one direction, out to the Square Find Farthest Square Along names. An enemy Piece there makes it red, toEnemyColor. Anything else makes it teal, otherColor: an empty Square, or a Piece of its own Side that it protects. If the first step leaves the Board, farthest is empty, and there’s no arrow at all.
A Knight, a King or a Pawn reaches only one Square each way, so it gets one short arrow for each Square it attacks. A Queen, a Rook or a Bishop gets one long arrow for each direction.
10.5.6 Add Threat Arrow
Adds an arrow from an enemy Piece that attacks a Piece, when it could really capture it: the Capture is a Legal Move (Can Capture), or the Piece attacked is a King. from: the Square of the attacking Piece (empty: no arrow). to: the Square it would move to. taken: the Square of the Piece it would take (not to for en passant). enPassantSquare: the en passant Square, for an en passant Capture, else empty. color: the arrow's color.
- Type
Boolean- Name
isThreat- Value
false
from != ""- Action Name
captureLook- from
from- to
to- taken
taken- enPassantSquare
enPassantSquare
- Variable
isThreat- Value
captureLook.value
data('Position', taken, 'kind') == "King"- Variable
isThreat- Value
true
isThreat == true- from
from- to
to- color
colorInput
Add Threat Arrow adds an arrow coming in, from an enemy Piece. It only counts if the Capture is a Legal Move, so Can Capture from Chapter 9 decides.
Here’s the one exception. An enemy Piece that attacks a King is giving Check, even when it’s pinned to its own King and Can Capture says no. That’s the arrow you’d most want to see. So step 6 makes isThreat true for a King, whatever Can Capture said.
10.5.7 Find Arrows Toward
Adds an arrow from every enemy Piece that can capture a Piece (Add Threat Arrow): each direction a Piece captures along is looked along backwards from it for an enemy attacker (Find Attacker Along). A Pawn that can be taken en passant also gets an arrow from each enemy Pawn that can take it, to the en passant Square. gameRules: the Game Rules Object. target: the Square of the Piece. color: the arrows' color.
boardStateData StructureBoard State- Type
Text- Name
enemy
- Type
Text- Name
enPassant
- Variable
enemy- Value
data('Sides', data('Position', target, 'side'), 'opponent')
- Action Name
passedLook- gameRules
gameRules
passedLook.value == target- Variable
enPassant- Value
gameRules.boardState.enPassantSquare
dataCount('Directions')timesdata('Directions', local.index, 'canCapture') == true- Action Name
attackerLook- square
target- direction
local.index- bySide
enemy
- from
attackerLook.attacker- to
target- taken
target- enPassantSquare
''- color
colorInput
data('Directions', local.index, 'canCapture') == true && data('Directions', local.index, 'kind') == "Pawn" && enPassant != ""- Action Name
enPassantLook- square
enPassant- direction
local.index- bySide
enemy
- from
enPassantLook.attacker- to
enPassant- taken
target- enPassantSquare
enPassant- color
colorInput
Find Arrows Toward finds the arrows that come in, trying every direction a Piece can capture along. For each one, Find Attacker Along looks back from the target, as it does for Check, and its new attacker names the enemy at the far end. Add Threat Arrow adds that arrow.
Steps 4 to 6 and step 11 are en passant, again. A Pawn that can be taken en passant is taken from another Square, the en passant Square. Steps 4 to 6 ask Find Passed Pawn whether this Piece is that Pawn, and if so, enPassant names the Square. Step 11 then gives each enemy Pawn that attacks it an arrow there too.
10.5.8 Find Arrows
Fills the Arrows table for the position on the Board. In Threats mode the selected Piece, or, when none is selected, the inspected Piece, shows an arrow along each direction it captures along, to the farthest Square it reaches (Add Arrow Along), and an arrow from every enemy Piece that can capture it (Find Arrows Toward). Outside Threats mode, or with no such Piece, the table stays empty. gameRules: the Game Rules Object. toEnemyColor, otherColor: the colors of its own arrows to an enemy Piece and to anything else. attackerColor: the color of the arrows from enemy Pieces.
boardStateData StructureBoard State- Type
Text- Name
focus
- Table
Arrows
- Variable
focus- Value
gameRules.boardState.selected
focus == ""- Variable
focus- Value
gameRules.boardState.inspected
gameRules.boardState.isShowingThreats != true- Variable
focus- Value
''
dataCount('Directions')timesfocus != "" && data('Directions', local.index, 'kind') == data('Position', focus, 'kind') && data('Directions', local.index, 'canCapture') == true- from
focus- direction
local.index- toEnemyColor
toEnemyColor- otherColor
otherColor
focus != ""- gameRules
gameRules- target
focus- color
attackerColor
gameRules.boardState.selected != ""- from
gameRules.boardState.selected- enPassantSquare
gameRules.boardState.enPassantSquare
Find Arrows is the one that runs on each change. It empties Arrows first. Which Piece shows its arrows? The selected one, or, with none selected, the inspected one. Outside Threats mode, none at all, and the table stays empty.
Next, the Repeat walks the Directions table. Each row of that Piece’s Kind that captures gets Add Arrow Along. After that, Find Arrows Toward adds the arrows coming in.
The last If cleans up after itself. Can Capture tried its Captures in the Targets table, where your dots come from. So the selected Piece’s Legal Moves are marked again. With no Piece selected, Clear Targets empties the table, so no stray dots show.
10.6 Drawing Every Frame
Now the table becomes lines on the screen. Draw counts in screen pixels, so first the Board’s meters need turning into pixels.
10.6.1 Square Screen Point
What Square Screen Point returns: x and y, the center of the Square on the game screen, in screen pixels from the screen's bottom-left corner.
Answers where the center of a Square is on the game screen, in screen pixels counted from the bottom-left corner of the screen, as the Draw Actions count them. It asks Square Position where the Square is in the world, in meters, and turns meters into pixels: the camera never moves, so the world's (0, 0) is the screen's bottom-left corner. square: the Square (a1 to h8). isFlipped, squareMeters, boardLeft, boardBottom: as for Square Position. pixelsPerMeter: the project's Pixels Per Unit (32). It returns x and y: the Square's center on the screen.
- Name
x- Value
0
- Name
y- Value
0
- Action Name
worldPoint- square
square- isFlipped
isFlipped- squareMeters
squareMeters- boardLeft
boardLeft- boardBottom
boardBottom
- Variable
x- Value
worldPoint.x * pixelsPerMeter
- Variable
y- Value
worldPoint.y * pixelsPerMeter
- x
x- y
y
Square Screen Point answers where a Square’s center is on the screen. Square Position already knows where that is in the world. It answers in meters. This Function returns its own x and y in Square Screen Point Result. Its pixelsPerMeter is your project’s Pixels Per Unit, 32. Multiply the meters by it, and they become pixels on the screen.
Why does a plain multiplication work? The camera never moves. So the world’s corner at 0, 0 is the screen’s bottom-left corner, and that’s where Draw counts from.
10.6.2 Draw Dashed Line
Draws one dashed line, in the current Draw Color, from one point on the game screen toward another. Use it in a Draw Event. fromX, fromY: where the line starts; toX, toY: where it points to (screen pixels, from the bottom-left corner). startGap: how far after the start the first dash begins; endGap: how far before the end the last dash stops. dashLength: how long each dash is; dashSpacing: how far one dash starts after the one before (more than dashLength leaves a gap). width: how thick the line is. All in screen pixels.
- Name
distance- Value
0
- Name
stepX- Value
0
- Name
stepY- Value
0
- Name
along- Value
0
- Name
lastPoint- Value
0
- Name
dashEnd- Value
0
- Variable
distance- Value
hypot(toX - fromX, toY - fromY)
- Variable
along- Value
startGap
- Variable
lastPoint- Value
distance - endGap
distance > 0- Variable
stepX- Value
(toX - fromX) / distance
- Variable
stepY- Value
(toY - fromY) / distance
along < lastPoint- Variable
dashEnd- Value
along + dashLength
dashEnd > lastPoint- Variable
dashEnd- Value
lastPoint
- X 1
fromX + stepX * along- Y 1
fromY + stepY * along- X 2
fromX + stepX * dashEnd- Y 2
fromY + stepY * dashEnd- Line Width
width
- Variable
along- Operator
+=- Value
dashSpacing
Draw Dashed Line draws one dashed line between two points. First it finds the distance, with hypot. Next come stepX and stepY: how far one pixel along the line moves across, and how far up.
A Draw Line is one straight stroke, from X 1, Y 1 to X 2, Y 2, in the Draw Color of the moment. It’s in Advanced Mode, with Set Draw Color and the Draw Event.
The While draws dash after dash. Its Variable along says how far down the line it has got. Each time round, Draw Line draws one dash, from along to along plus dashLength, and along moves on by dashSpacing. A dash that would run past lastPoint is cut short there. When along reaches lastPoint, the line is done.
10.6.3 Draw Arrow
Draws one arrow, in the current Draw Color, from one point on the game screen toward another: a straight shaft and a head of two short lines at its end. Use it in a Draw Event. fromX, fromY: where the arrow starts; toX, toY: where it points to (screen pixels, from the bottom-left corner). startGap: how far after the start the shaft begins; endGap: how far before the end the head's tip is. headLength: how far back the head reaches along the shaft. headSpread: how far each side of the head reaches out, as a share of headLength. width: how thick the lines are. All but headSpread in screen pixels.
- Name
distance- Value
0
- Name
stepX- Value
0
- Name
stepY- Value
0
- Name
tipX- Value
0
- Name
tipY- Value
0
- Name
backX- Value
0
- Name
backY- Value
0
- Name
sideX- Value
0
- Name
sideY- Value
0
- Variable
distance- Value
hypot(toX - fromX, toY - fromY)
distance > 0- Variable
stepX- Value
(toX - fromX) / distance
- Variable
stepY- Value
(toY - fromY) / distance
- Variable
tipX- Value
toX - stepX * endGap
- Variable
tipY- Value
toY - stepY * endGap
- Variable
backX- Value
tipX - stepX * headLength
- Variable
backY- Value
tipY - stepY * headLength
- Variable
sideX- Value
stepY * headLength * headSpread
- Variable
sideY- Value
stepX * headLength * headSpread
- X 1
fromX + stepX * startGap- Y 1
fromY + stepY * startGap- X 2
tipX- Y 2
tipY- Line Width
width
- X 1
tipX- Y 1
tipY- X 2
backX - sideX- Y 2
backY + sideY- Line Width
width
- X 1
tipX- Y 1
tipY- X 2
backX + sideX- Y 2
backY - sideY- Line Width
width
Draw Arrow is the same idea, with three strokes in place of dashes. It finds the distance and the steps across and up, as Draw Dashed Line does. The tip stops endGap short of the end. That keeps the arrow off the Piece it points at. The head’s two strokes start at the tip and lean back, one to each side.
The two side steps swap stepX and stepY. That’s what turns a step along the line into a step across it.
10.6.4 Draw Threat Lines
What Draw Threat Lines remembers on the Game Rules while it draws. pinned and pinner: the Squares of a pinned Piece and of the Piece that pins it. arrowFrom and arrowTo: the Squares of the arrow being drawn.
Draws the lines of Threats mode over the Board: the arrows of the selected or inspected Piece (the Arrows table, which Find Arrows fills on every "positionChanged": red to an enemy Piece it attacks and teal to any other Square it reaches, and red from each enemy Piece that can capture it), and a dashed amber line from every Piece that pins an enemy Piece to its King, to that King (the Threats table's pinnedBy, which Find Threats fills only while Threats mode is on). The Draw Event runs every frame; nothing is drawn while the Computer's Move slides ("slideStarted") or the Promotion choice is open (Game State Promotion), until the next "positionChanged". arrowToEnemyColor, arrowToOtherColor, arrowFromAttackerColor: the arrows' colors. arrowWidth, arrowGapAtStart, arrowGapAtEnd, arrowHeadLength: the arrows' sizes, in Squares (0.035, 0.22, 0.27, 0.11); arrowHeadSpread: how far each side of a head reaches out, as a share of its length (0.6). pinColor: the pin lines' color. pinLineWidth, pinDashLength, pinDashSpacing: the dashes, in Squares (0.03, 0.12, 0.22). pinGapAtStart, pinGapAtKing: how far from the pinning Piece's and the King's centers the line starts and stops, in Squares. squareMeters, boardLeft, boardBottom: where the Squares are (as for Place On Board). pixelsPerMeter: the project's Pixels Per Unit.
Any Objectself.boardStateself.drawThreatLinesData- Name
squarePixels- Value
0
- Variable
squarePixels- Value
@self.squareMeters * @self.pixelsPerMeter
dataCount('Arrows')times- Component Value
self.drawThreatLinesData.arrowFrom- Value
data('Arrows', local.index, 'from')
- Component Value
self.drawThreatLinesData.arrowTo- Value
data('Arrows', local.index, 'to')
- Color
data('Arrows', local.index, 'color')
- Action Name
arrowStart- square
self.drawThreatLinesData.arrowFrom- isFlipped
self.boardState.isFlipped- squareMeters
@self.squareMeters- boardLeft
@self.boardLeft- boardBottom
@self.boardBottom- pixelsPerMeter
@self.pixelsPerMeter
- Action Name
arrowEnd- square
self.drawThreatLinesData.arrowTo- isFlipped
self.boardState.isFlipped- squareMeters
@self.squareMeters- boardLeft
@self.boardLeft- boardBottom
@self.boardBottom- pixelsPerMeter
@self.pixelsPerMeter
- fromX
arrowStart.x- fromY
arrowStart.y- toX
arrowEnd.x- toY
arrowEnd.y- startGap
@self.arrowGapAtStart * squarePixels- endGap
@self.arrowGapAtEnd * squarePixels- headLength
@self.arrowHeadLength * squarePixels- headSpread
@self.arrowHeadSpread- width
@self.arrowWidth * squarePixels
- Color
@self.pinColor
dataCount('Threats')times- Component Value
self.drawThreatLinesData.pinned- Value
dataRow('Threats', local.index)
data('Threats', self.drawThreatLinesData.pinned, 'pinnedBy') != ""- Component Value
self.drawThreatLinesData.pinner- Value
data('Threats', self.drawThreatLinesData.pinned, 'pinnedBy')
- Action Name
kingLook- side
data('Position', self.drawThreatLinesData.pinned, 'side')
- Action Name
pinnerPoint- square
self.drawThreatLinesData.pinner- isFlipped
self.boardState.isFlipped- squareMeters
@self.squareMeters- boardLeft
@self.boardLeft- boardBottom
@self.boardBottom- pixelsPerMeter
@self.pixelsPerMeter
- Action Name
kingPoint- square
kingLook.value- isFlipped
self.boardState.isFlipped- squareMeters
@self.squareMeters- boardLeft
@self.boardLeft- boardBottom
@self.boardBottom- pixelsPerMeter
@self.pixelsPerMeter
- fromX
pinnerPoint.x- fromY
pinnerPoint.y- toX
kingPoint.x- toY
kingPoint.y- startGap
@self.pinGapAtStart * squarePixels- endGap
@self.pinGapAtKing * squarePixels- dashLength
@self.pinDashLength * squarePixels- dashSpacing
@self.pinDashSpacing * squarePixels- width
@self.pinLineWidth * squarePixels
- Event
When Drawn- Mode
Draw Nothing
- gameRules
self- toEnemyColor
@self.arrowToEnemyColor- otherColor
@self.arrowToOtherColor- attackerColor
@self.arrowFromAttackerColor
- Action Name
isChoosing- Game State
Promotion
isChoosing.value == true- Event
When Drawn- Mode
Draw Nothing
- Event
When Drawn- Mode
Draw The Lines
Draw Threat Lines is where the drawing happens, on the Game Rules. It’s the chapter’s biggest card. Its colors and sizes are constant Shared Variables. The Squares being drawn wait in Draw Threat Lines Data. Add that Data Structure to the Logic, with drawThreatLinesData as its slot name.
The four colors let the Board show through, and each has eight digits after the #. The first six are the color. The last two are how strongly it shows. So cc is about four fifths, and a lower pair lets more of the Board through. Type all eight into the Value’s HEX box, and Myth Engine keeps them.
The sizes are in Squares, so 1 is one Square. A dash, pinDashLength, is 0.12 of one, and a new dash starts every 0.22, pinDashSpacing. Step 2 turns Squares into pixels: squareMeters times pixelsPerMeter.
Build the Draw Event first, named When Drawn, with two Modes: Draw The Lines and Draw Nothing. It’s one Draw Event. The card shows its bar once for each Mode. The two Switch Modes further down pick that Event and its Mode from lists, as Slide Piece’s did in Chapter 7. Modes shows how to make the pair. In the Add Event window, the Draw Event is Draw, and it asks nothing more.
Draw The Lines has two Repeats. The first walks Arrows. For each row, Set Draw Color takes the row’s color, Square Screen Point finds both ends, and Draw Arrow draws it. The second walks Threats, the table Chapter 9 filled. Remember pinnedBy? This is who it was for. A row whose pinnedBy names a Square is a pinned Piece. Find King, from Chapter 3, finds its King. Draw Dashed Line runs from the pinning Piece to that King, across the pinned one.
The arrows come first. So where an arrow and a pin line cross, the dashes lie on top.
Draw Nothing has no Actions. While the Computer’s Piece slides, slideStarted switches the Draw Event to it, because the tables still describe the Board before the Move. The Promotion choice switches it to Draw Nothing too, while its panel lies over the Board.
On Position Changed is the one that does the work. Find Arrows fills Arrows for the Board as it is now, with the three arrow colors. Check Game State asks next whether the Promotion choice is open. If it is, Draw Nothing. If not, Draw The Lines again.
So the work happens once, on each change, and every frame after that the Draw Event reads two tables and draws what’s there. Nothing in it decides anything.
10.6.5 Game Rules
Board StateSelect and MoveSelect and Move DataChoose PromotionChoose Promotion DataMake Computer MoveMake Computer Move DataUndoUndo DataGive A HintGive A Hint DataDraw Threat LinesDraw Threat Lines DataAttach Draw Threat Lines to the Game Rules.
Run the game, press Threats, and play e2 to e4. Wait for the reply, and then move your Queen from d1 to h5, along the diagonal.
Your Queen lands, and a dashed amber line runs from it to Black’s King, across the Pawn on f7. Click your Queen. Red arrows reach out to the Black Pawns it could take. Teal arrows run the other ways, to the edge or to a Piece of your own. If a Black Piece could take your Queen, a red arrow comes in from it.
10.8 If It Didn’t Work
This chapter draws, and drawing has a surprise of its own. A line in the wrong place turns nothing red. The game draws exactly what you told it, even when that’s off the Board. So watch where things land.
A block drawn in red, with a number on the Errors bar? Open the bar. It names the block and what it misses. Compare that block with its card.
No lines or arrows at all, though the red marks show? Is Draw Threat Lines attached? Perhaps the Draw Event starts on Draw Nothing, when the card’s Draw The Lines is the one it should start with.
The lines show once, then never again after the Computer moves? Look at On Position Changed. Its Else needs its own Switch Mode, back to Draw The Lines.
Arrows land in the wrong spot, or off the Board? Look at Square Screen Point, and the Board’s Shared Variables. squareMeters, boardLeft and boardBottom must match Place On Board’s, and pixelsPerMeter is 32. Perhaps x is multiplied by pixelsPerMeter and y isn’t.
An arrow’s head looks bent, with both strokes leaning one way? Look at Draw Arrow’s last two Draw Lines, and the signs on sideX and sideY. One head stroke takes minus sideX and plus sideY, the other the other way round.
The lines are solid, not dashed? Look at Draw Dashed Line’s step 18. Its along should move on by dashSpacing, and dashSpacing must be more than dashLength.
The game freezes when Threats is on? A While that never ends looks like that, so look at Draw Dashed Line’s step 18. Is it inside the While, at the end of its line?
Dots appear for a Piece you didn’t select? Find Arrows’ last If may be missing, or its Clear Targets Unattached.
The teal never shows? Look at Square Marks’ step 8, and whether it comes before the selected Square’s If. Is Inspect Piece on the outer Else’s line in Select and Move?
The button never turns pale green? Look at Threats Button’s Logic. It needs Show Threats Button Pressed alongside Report Threats. Its If reads other.boardState, the Game Rules that sent the message.
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, if you’d like to compare.
Your Board draws two things now, and the Draw Event can draw anything you can describe as lines. What else would help a player read the Board? Perhaps a faint line along the last Move, from its first Square to its second. Or a ring of short strokes around the King in Check. Draw Arrow and Draw Dashed Line can draw any arrow and any dashed line you hand them. The rest is up to you.
Still stuck? Search the manual, or ask in the Myth Engine Discord (opens a new tab).
Your Board shows the lines between the Pieces now. In Chapter 11, the game gets new looks to choose from, and a memory that keeps your game when you close it.
