Build a GameCreating Chess

5  The End of the Game

Goal

By the end of this chapter, a game of chess can end. Checkmate, Stalemate and the three Draws by rule each say so under the Board, and the last position stays where it is, with nothing left to move.

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 6 from it.

Chess, Chapter 5 finishedMyth project · 15.7 MB

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

5.1 The Idea

Play your Board from Chapter 4 for a moment. Push White’s f2 Pawn to f3, and Black’s e7 Pawn to e5. Then g2 to g4. Black’s Queen sweeps from d8 all the way to h4, and White’s King on e1 turns red. Check.

Click any White Piece, and its Square turns gold with no dots at all. The King has nowhere to go, and nothing can come between. Chess calls that Checkmate, and the game is over. But your game doesn’t know it. It sits there, saying Check, waiting for a Move that can never come.

This chapter teaches the game to notice the end. After every Move, at the start of each Turn, it asks one question. Can this game go on?

There are five ways it can’t. In Checkmate, the Side to move is in Check with no Legal Move, and it loses. In Stalemate, the Side to move has no Legal Move but isn’t in Check. That’s a Draw, a game nobody wins. The other three Draws are rules about the whole game, not one Move:

  • Insufficient material: neither Side has enough Pieces left to give Checkmate, such as two Kings alone.
  • Threefold repetition: the same position has come round for the third time.
  • The fifty-move rule: fifty Moves by each Side with no Capture and no Pawn Move.

Two of these need a memory. The fifty-move rule needs a count of quiet Moves. Threefold repetition needs every position the game has seen. Think of a photo album. At the start of each Turn, the game takes a picture of the Board and puts it in the album. To ask whether a position came before, it leafs back through the pages.

When the game ends, the sign on the door from Chapter 4 turns one more time. It says Game Over, and the Board stops answering.

5.2 The Plan

Here’s how the pieces work together. Read each line from left to right, and come back after The last word.

  • A Move is made -> Make Move counts it toward the fifty, and asks Update En Passant.
  • Start Turn gives the Turn away -> it sends turnStarted.
  • Look For Game End hears it -> Is Out Of Moves, Is Material Insufficient, Record Position and Is Position Repeated each answer.
  • Record Position -> Can Take En Passant, then a new page in Position History.
  • Is Position Repeated -> Is Same Position compares two pages.
  • Any answer says the game is over -> Show Result picks the words, and the Game State becomes Game Over.

You’ll build the memory first, and then Make Move gets tidier. Next come the questions, one Function each, and last the Logic Component that asks them.

5.3 Set Up

Set upWhat this chapter adds
GAME SETTINGS > General > Game State
Available Game StatesPlaying, Promotion, Game Over

The Game State list grows by one, Game Over. Add it after Promotion, the way The Set up card says. That’s all. Nothing new goes on the screen in this chapter, because the result will show in the Status Line you already have.

5.4 What the Game Rules Remember Now

Board State gets two more fields.

Data StructureBoard StateAddDelete

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), so the end of the game can say who won. fiftyMoveCount: the Moves made since the last Capture or Pawn Move, both Sides counted (the fifty-move rule).

sideToMoveTextdefault White
selectedTextdefault (empty)
lastFromTextdefault (empty)
lastToTextdefault (empty)
checkSquareTextdefault (empty)
enPassantSquareTextdefault (empty)
promotionSquareTextdefault (empty)
playerSideTextdefault White
fiftyMoveCountNumberdefault 0

playerSide is the Side you play, so the end can say who won. For now you’re always White. You’ll choose in Chapter 6. fiftyMoveCount counts the quiet Moves, the ones with no Capture and no Pawn Move. Both Sides’ Moves count.

Its Description changes too. If you typed one, the card gives its new words in full.

Words gets six new rows, one for each ending.

Data TableWordsData Structure: Words EntryAddDelete
Row 0Your Move
textYour move
Row 1Check
textCheck
Row 2Promotion
textPromotion: choose a Piece
Row 3Win By Checkmate
textYou win · Checkmate
Row 4Loss By Checkmate
textYou lose · Checkmate
Row 5Draw By Stalemate
textDraw · Stalemate
Row 6Draw By Insufficient Material
textDraw · Insufficient material
Row 7Draw By Fifty Move Rule
textDraw · Fifty-move rule
Row 8Draw By Threefold Repetition
textDraw · Threefold repetition

Add them in the card’s order, Win By Checkmate to Draw By Threefold Repetition. The middle dot in each text is part of the words, as in You win · Checkmate. Copy it from the card if your keyboard has none.

Last, the photo album.

Data StructurePosition History Entry

One row of the "Position History" Data Table, which follows it. Every position since the last Capture or Pawn Move, recorded at the start of each Turn, so the game can tell when one comes for the third time. Each position takes 64 rows, one per Square in the order of the Position table (a1, b1, ... h8): the first position is rows 0 to 63, the next 64 to 127, and so on. Each row is named by its number. kind and side: the Piece on that Square (empty when none). castlingsAllowed: how many castlings were still allowed then (a castling once ended never comes back, so the same number means the same castlings). The table starts empty.

kindTextdefault (empty)
sideTextdefault (empty)
castlingsAllowedNumberdefault 0
Data TablePosition HistoryData Structure: Position History Entry

Position History starts with no rows at all. The game fills it while it plays, so you make only its three fields in Position History Entry. The table gets its columns from them.

Each picture takes 64 rows, one for each Square in Position’s order. So the first picture is rows 0 to 63, and the second starts at row 64, right after it. Its kind and side say what stands on each Square. And the last column? It holds how many castlings are still allowed. Why that? A King that has lost its castling isn’t in the same position, even with every Piece where it was.

5.5 A Tidier Make Move

Make Move carries a lot by now. The en passant half moves into a Function of its own, and Make Move gains the count of quiet Moves.

5.5.1 Update En Passant

You’ve built all of this before. Update En Passant is the en passant part of Chapter 4’s Make Move, word for word. It catches the passed Pawn. It empties the old enPassantSquare, and it writes a new one when a Pawn rushes two Squares.

FunctionUpdate En Passant

Keeps en passant up to date for one Move, before the Move is made. A Pawn moving onto the en passant Square takes the enemy Pawn that passed over it. Then the en passant Square is set for the next Move: the Square a Pawn passes over with a two-Square Move, else empty. gameRules: the Game Rules Object, whose Board State holds the en passant Square. from and to: the Squares of the Move (a1 to h8).

PARAMETERS
NameTypeStarts at
gameRulesObjectStructural
Required Components
Slot NameboardState
Component TypeData Structure
Required SchemaBoard State
fromText
toText
ACTIONS
1Declare Variable
Type
Text
Name
kind
2Declare Variable
Name
fromFile
Value
0
3Declare Variable
Name
fromRank
Value
0
4Declare Variable
Name
toFile
Value
0
5Declare Variable
Name
toRank
Value
0
6Declare Variable
Name
passedPawn
Value
0
7Declare Variable
Name
passedRank
Value
0
8Declare Variable
Type
Text
Name
passedSquare
9Change Variable
Variable
kind
Value
data('Position', from, 'kind')
10Change Variable
Variable
fromFile
Value
data('Position', from, 'file')
11Change Variable
Variable
fromRank
Value
data('Position', from, 'rank')
12Change Variable
Variable
toFile
Value
data('Position', to, 'file')
13Change Variable
Variable
toRank
Value
data('Position', to, 'rank')
14Ifkind == "Pawn" && to == gameRules.boardState.enPassantSquare
Then
15Change Variable
Variable
passedPawn
Value
(fromRank - 1) * 8 + (toFile - 1)
16Set Data Cell
Table
Position
Row
passedPawn
Column
'kind'
Value
''
17Set Data Cell
Table
Position
Row
passedPawn
Column
"side"
Value
''
18Change Component Value
Component Value
gameRules.boardState.enPassantSquare
Value
''
19Ifkind == "Pawn" && abs(toRank - fromRank) == 2
Then
20Change Variable
Variable
passedRank
Value
(fromRank + toRank) / 2
21Change Variable
Variable
passedSquare
Value
dataRow('Position', (passedRank - 1) * 8 + (fromFile - 1))
22Change Component Value
Component Value
gameRules.boardState.enPassantSquare
Value
passedSquare

Step 16’s Column is 'kind', in its quote marks, as in Chapter 4.

Build it before you take anything out of Make Move, so your game keeps working all the way through. Its eight Declares are the ones Make Move had, and its Parameters are Make Move’s too. You built these steps once already, in Chapter 4.

5.5.2 Make Move

Now Make Move itself.

FunctionMake Move

Makes one Legal Move on the Position table, with everything it brings: the fifty-move rule's count, the Rook's part of castling (Update Castling), en passant (Update En Passant), and the last Move's Squares. gameRules: the Game Rules Object, whose Board State it updates. from and to: the Squares of the Move (a1 to h8).

PARAMETERS
NameTypeStarts at
gameRulesObjectStructural
Required Components
Slot NameboardState
Component TypeData Structure
Required SchemaBoard State
fromText
toText
ACTIONS
1Ifdata('Position', from, 'kind') == "Pawn" || data('Position', to, 'kind') != ""
Then
2Change Component Value
Component Value
gameRules.boardState.fiftyMoveCount
Value
0
3Otherwise
4Change Component Value
Component Value
gameRules.boardState.fiftyMoveCount
Operator
+=
Value
1
5Update CastlingFunction
from
from
to
to
6Update En PassantFunction
gameRules
gameRules
from
from
to
to
7Move PieceFunction
from
from
to
to
8Change Component Value
Component Value
gameRules.boardState.lastFrom
Value
from
9Change Component Value
Component Value
gameRules.boardState.lastTo
Value
to

Here is Make Move’s whole card as it ends this chapter. Most of Chapter 4’s work now lives in Update En Passant.

Take Make Move apart first. Delete its eight Declare blocks first, then old steps 9 to 13, the five Change Variables. Deleting the old step 15 If takes its three steps with it. So does deleting the step 21 If, with 22 to 24. Old step 20 goes too.

Four steps are left: Update Castling, Move Piece, and the two that write the last Move. The new steps go in the middle of that line, in two places.

The four new steps go first, before Update Castling. They count a quiet Move. If the Piece moving is a Pawn, or the Square it lands on holds a Piece, the count goes back to 0. Otherwise it goes up by one. Add the If from the Action List, and use the round handles to join it after Start. It goes ahead of Update Castling. Step 2 goes in its Then, with Add Inside Then. Add its Else, with step 4. A Change Component Value has an Operator list beside its Component Value, as a Change Variable has, and step 4’s is +=. Update Castling stays after the If, on the line from Start.

Step 6, the call to Update En Passant, goes between Update Castling and Move Piece. It’s the same move once more. Add the call apart from the line, and use the round handles to join it after Update Castling. Move Piece stays right behind the call, before the two steps that remember the Move.

Run the game. It plays the way it did. Try the en passant from Chapter 4, e2 to e4, a7 to a6, e4 to e5, and d7 to d5. Your Pawn on e5 can still go to d6, and d5 empties. The count of quiet Moves runs where you can’t see it, and soon something will read it.

5.6 Is There a Move Left?

Now the questions begin. The first one is the most important.

5.6.1 Is Out Of Moves

Is Out Of Moves asks whether the Side to move has any Legal Move at all.

It starts by believing the worst. Its answer, isOutOfMoves, is Yes, and it stays Yes until a single Legal Move proves otherwise. Then it goes down Position, one Square at a time. On each Square that holds a Piece of the Side to move, it asks Find Legal Moves, as a click would. If even one dot appears in Targets, the answer becomes No, and the Squares after that are skipped.

FunctionIs Out Of Moves

Asks whether the Side to move has no Legal Move at all, which ends the game. gameRules: the Game Rules Object, whose Board State says whose Turn it is and where en passant is possible. isOutOfMoves (returned): true when no Piece of the Side to move has a Legal Move. It leaves the Targets table empty.

PARAMETERS
NameTypeStarts at
gameRulesObjectStructural
Required Components
Slot NameboardState
Component TypeData Structure
Required SchemaBoard State
RETURN TYPEBoolean
ACTIONS
1Declare Variable
Type
Text
Name
side
2Declare Variable
Type
Text
Name
enPassant
3Declare Variable
Type
Text
Name
square
4Declare Variable
Type
Boolean
Name
isOutOfMoves
Value
true
5Change Variable
Variable
side
Value
gameRules.boardState.sideToMove
6Change Variable
Variable
enPassant
Value
gameRules.boardState.enPassantSquare
7Clear TargetsFunction
8RepeatdataCount('Position')times
Each time
9Change Variable
Variable
square
Value
dataRow('Position', local.index)
10IfisOutOfMoves == true && data('Position', square, 'side') == side
Then
11Find Legal MovesFunction
from
square
enPassantSquare
enPassant
12IfdataFind('Targets', 'reachable', true) != (-1)
Then
13Change Variable
Variable
isOutOfMoves
Value
false
14Clear TargetsFunction
15Return
Value
isOutOfMoves

Step 10’s If is where the skipping happens. Its first test asks whether isOutOfMoves is still true. Once one Move is found, the If says no on every later Square, so Find Legal Moves doesn’t run again.

Step 12 looks for a dot with dataFind('Targets', 'reachable', true). That finds the first row of Targets whose reachable is Yes, and gives -1 when there’s none. Type the whole condition, including the != (-1) after the search.

Clear Targets wipes the dots it made. Nobody sees them. They were only a question. Its RETURN TYPE is Boolean, and its Return hands back isOutOfMoves. Calls read it as value.

5.6.2 Show Result

Show Result turns the answers into words under the Board.

FunctionShow Result

Shows how the game ended in the status line ($status), once the game has ended. When the Side to move has no Legal Move: Checkmate if its King is in Check (the Player wins or loses), else Stalemate (a Draw). Otherwise the game ended as a Draw by rule: insufficient material, else threefold repetition, else the fifty-move rule. gameRules: the Game Rules Object, whose Board State says whose Turn it is, where Check is, and which Side the Player plays. isOutOfMoves: the Side to move has no Legal Move. isMaterialInsufficient: neither Side can give Checkmate. isRepeated: the same position has come for the third time.

PARAMETERS
NameTypeStarts at
gameRulesObjectStructural
Required Components
Slot NameboardState
Component TypeData Structure
Required SchemaBoard State
isOutOfMovesBooleanNo
isMaterialInsufficientBooleanNo
isRepeatedBooleanNo
ACTIONS
1Declare Variable
Type
Text
Name
side
2Declare Variable
Type
Text
Name
checkSquare
3Change Variable
Variable
side
Value
gameRules.boardState.sideToMove
4Change Variable
Variable
checkSquare
Value
gameRules.boardState.checkSquare
5IfisOutOfMoves == true && checkSquare != "" && side == gameRules.boardState.playerSide
Then
6Change Variable
Variable
$status
Value
data('Words', 'Loss By Checkmate', 'text')
7Otherwise
8IfisOutOfMoves == true && checkSquare != ""
Then
9Change Variable
Variable
$status
Value
data('Words', 'Win By Checkmate', 'text')
10Otherwise
11IfisOutOfMoves == true
Then
12Change Variable
Variable
$status
Value
data('Words', 'Draw By Stalemate', 'text')
13Otherwise
14IfisMaterialInsufficient == true
Then
15Change Variable
Variable
$status
Value
data('Words', 'Draw By Insufficient Material', 'text')
16Otherwise
17IfisRepeated == true
Then
18Change Variable
Variable
$status
Value
data('Words', 'Draw By Threefold Repetition', 'text')
19Otherwise
20Change Variable
Variable
$status
Value
data('Words', 'Draw By Fifty Move Rule', 'text')

It’s a staircase of Ifs. Each one sits in the Else of the one before. Read it from the top. Out of Moves, in Check, and it’s your Turn? You lose. Out of Moves and in Check, on the other Side’s Turn? You win. Out of Moves with no Check is Stalemate. After that come the three Draws by rule, in the card’s order.

Each new test is an Else If. Add it with Add Else If, and put the last answer in an Else. The words come from Words. Each row’s name goes in quote marks, as in Look For Check.

5.7 Draws by Rule

Next come the three rules about the whole game. None of them needs a dot.

5.7.1 Is Material Insufficient

Is Material Insufficient counts the Pieces left on the Board that aren’t Kings, and sorts them. It walks Position once, keeping four tallies: every Piece but a King in otherPieces, the Knights, and the Bishops on dark and on light Squares.

FunctionIs Material Insufficient

Asks whether neither Side has enough Pieces left to give Checkmate, which ends the game as a Draw: only the two Kings; a King and one Knight or one Bishop against a King; or Kings and Bishops whose Bishops all stand on Squares of one shade. isInsufficient (returned): true when that is so.

RETURN TYPEBoolean
ACTIONS
1Declare Variable
Type
Text
Name
kind
2Declare Variable
Name
file
Value
0
3Declare Variable
Name
rank
Value
0
4Declare Variable
Name
squareShade
Value
0
5Declare Variable
Name
otherPieces
Value
0
6Declare Variable
Name
knights
Value
0
7Declare Variable
Name
darkBishops
Value
0
8Declare Variable
Name
lightBishops
Value
0
9Declare Variable
Type
Boolean
Name
isInsufficient
Value
false
10RepeatdataCount('Position')times
Each time
11Change Variable
Variable
kind
Value
data('Position', local.index, 'kind')
12Change Variable
Variable
file
Value
data('Position', local.index, 'file')
13Change Variable
Variable
rank
Value
data('Position', local.index, 'rank')
14Change Variable
Variable
squareShade
Value
(file + rank) % 2
15Ifkind != "" && kind != "King"
Then
16Change Variable
Variable
otherPieces
Operator
+=
Value
1
17Ifkind == "Knight"
Then
18Change Variable
Variable
knights
Operator
+=
Value
1
19Ifkind == "Bishop" && squareShade == 0
Then
20Change Variable
Variable
darkBishops
Operator
+=
Value
1
21Ifkind == "Bishop" && squareShade == 1
Then
22Change Variable
Variable
lightBishops
Operator
+=
Value
1
23IfotherPieces == 1 && knights == 1
Then
24Change Variable
Variable
isInsufficient
Value
true
25IfotherPieces == darkBishops || otherPieces == lightBishops
Then
26Change Variable
Variable
isInsufficient
Value
true
27Return
Value
isInsufficient

How does a Square know its shade? Step 14 adds its file and rank and keeps what’s left over after dividing by 2, with %. On a1, 1 and 1 make 2, which leaves 0. So 0 is dark. On b1, 2 and 1 make 3, which leaves 1, and b1 is light.

The two Ifs at the end decide. A lone Knight can’t give Checkmate with only its King to help, and neither can Bishops that all stand on one shade. Look closely at step 25. When only the two Kings are left, otherPieces is 0, and so is darkBishops. 0 equals 0, so bare Kings are a Draw too, with no If of their own.

5.7.2 Can Take En Passant

The photo album has one odd rule. A position where en passant could be played is never the same as one where it can’t. That chance lasts only one Move. So the game asks first: is en passant possible right now?

FunctionCan Take En Passant

Asks whether the Side to move may take en passant right now: an enemy Pawn has just passed over the en passant Square, and a Pawn of the Side to move may take it there without leaving its own King attacked. gameRules: the Game Rules Object, whose Board State says whose Turn it is and where en passant is possible. canTake (returned): true when such a Move is a Legal Move. It leaves the Targets table empty.

PARAMETERS
NameTypeStarts at
gameRulesObjectStructural
Required Components
Slot NameboardState
Component TypeData Structure
Required SchemaBoard State
RETURN TYPEBoolean
ACTIONS
1Declare Variable
Type
Text
Name
side
2Declare Variable
Type
Text
Name
enPassant
3Declare Variable
Type
Text
Name
square
4Declare Variable
Type
Boolean
Name
canTake
Value
false
5Change Variable
Variable
side
Value
gameRules.boardState.sideToMove
6Change Variable
Variable
enPassant
Value
gameRules.boardState.enPassantSquare
7Clear TargetsFunction
8RepeatdataCount('Position')times
Each time
9Change Variable
Variable
square
Value
dataRow('Position', local.index)
10IfenPassant != "" && data('Position', square, 'kind') == "Pawn" && data('Position', square, 'side') == side
Then
11Find En Passant MoveFunction
from
square
enPassantSquare
enPassant
12IfdataFind('Targets', 'reachable', true) != (-1)
Then
13Change Variable
Variable
canTake
Value
true
14Clear TargetsFunction
15Return
Value
canTake

Can Take En Passant works like Is Out Of Moves, smaller. It goes down Position, and for each Pawn of the Side to move, it asks Find En Passant Move. Its step 12 is the one difference in shape. It sits after the Repeat, not inside it, so it waits until the Repeat is done, and only then asks whether any dot appeared. If one did, canTake is true. It clears Targets before it starts and after it ends, so no dot is ever left behind for you to see. The Board never shows its question.

5.7.3 Record Position

Record Position puts one picture in the album, at the start of each Turn.

FunctionRecord Position

Adds the position on the Board to the Position History table at the start of a Turn: for every Square, the Piece on it and how many castlings are still allowed. After a Capture or a Pawn Move (and at the start of a game) the table starts over, because no earlier position can come again. A position in which en passant can be taken is not recorded: en passant is possible only on the very next Move, so that position can never come again either. gameRules: the Game Rules Object, whose Board State holds the fifty-move rule's count, whose Turn it is, and the en passant Square.

PARAMETERS
NameTypeStarts at
gameRulesObjectStructural
Required Components
Slot NameboardState
Component TypeData Structure
Required SchemaBoard State
ACTIONS
1Declare Variable
Name
castlingsStillAllowed
Value
0
2Declare Variable
Type
Text
Name
square
3Declare Variable
Name
lastRow
Value
0
4IfgameRules.boardState.fiftyMoveCount == 0
Then
5Reset Data Table
Table
Position History
6Can Take En PassantFunction
Action Name
enPassantLook
gameRules
gameRules
7RepeatdataCount('Castling')times
Each time
8Ifdata('Castling', local.index, 'isAllowed') == true
Then
9Change Variable
Variable
castlingsStillAllowed
Operator
+=
Value
1
10IfenPassantLook.value != true
Then
11RepeatdataCount('Position')times
Each time
12Change Variable
Variable
square
Value
dataRow('Position', local.index)
13Control Data Row
Action Name
addedRow
Table
Position History
Row
dataCount('Position History')
14Change Variable
Variable
lastRow
Value
dataCount('Position History') - 1
15Set Data Cell
Table
Position History
Row
lastRow
Column
"kind"
Value
data('Position', square, 'kind')
16Set Data Cell
Table
Position History
Row
lastRow
Column
"side"
Value
data('Position', square, 'side')
17Set Data Cell
Table
Position History
Row
lastRow
Column
"castlingsAllowed"
Value
castlingsStillAllowed

It begins with a clean-out. When fiftyMoveCount is 0, the last Move was a Capture or a Pawn Move, or the game has only begun. No position from before it can ever come back, because a Pawn never moves backward and a captured Piece never returns. So Reset Data Table empties the album. It starts fresh.

Next, it asks Can Take En Passant, and counts the castlings still allowed. When en passant is possible, there’s no picture this Turn. Otherwise, for each of the 64 Squares, Control Data Row adds a row at the end of the table. Its Row, dataCount('Position History'), is the number one past the last row. Then three Set Data Cells fill it in.

5.7.4 Is Same Position

Is Same Position lays two pictures from the album side by side. It compares them Square by Square.

FunctionIs Same Position

Asks whether two positions recorded in the Position History table are the same: the same Piece on every Square and the same number of castlings allowed. earlier and later: the two positions' numbers in the table (0 is the first one recorded). isSame (returned): true when they are the same.

PARAMETERS
NameTypeStarts at
earlierNumber0
laterNumber0
RETURN TYPEBoolean
ACTIONS
1Declare Variable
Name
squares
Value
0
2Declare Variable
Name
earlierRow
Value
0
3Declare Variable
Name
laterRow
Value
0
4Declare Variable
Name
sameSquares
Value
0
5Declare Variable
Type
Boolean
Name
isSame
Value
false
6Change Variable
Variable
squares
Value
dataCount('Position')
7Repeatsquarestimes
Each time
8Change Variable
Variable
earlierRow
Value
earlier * squares + local.index
9Change Variable
Variable
laterRow
Value
later * squares + local.index
10Ifdata('Position History', earlierRow, 'kind') == data('Position History', laterRow, 'kind') && data('Position History', earlierRow, 'side') == data('Position History', laterRow, 'side') && data('Position History', earlierRow, 'castlingsAllowed') == data('Position History', laterRow, 'castlingsAllowed')
Then
11Change Variable
Variable
sameSquares
Operator
+=
Value
1
12IfsameSquares == squares
Then
13Change Variable
Variable
isSame
Value
true
14Return
Value
isSame

Its Parameters earlier and later are page numbers in the album, starting at 0. Page 2’s rows start at 2 times 64, so step 8 finds each Square’s row on one page with earlier * squares + local.index, and step 9 on the other. Every Square that matches in kind, side and castlings adds one to sameSquares. All 64 match? Then the two pictures are the same.

5.7.5 Is Position Repeated

Is Position Repeated takes the newest picture and leafs back through the album.

FunctionIs Position Repeated

Asks whether the position recorded last in the Position History table has now been seen timesForDraw times, which ends the game as a Draw. Only every other position before it can be the same, because the Sides take turns: those had the same Side to move. timesForDraw: how many times the same position must be seen (3, threefold repetition). isRepeated (returned): true when it has been seen that often.

PARAMETERS
NameTypeStarts at
timesForDrawNumber3
RETURN TYPEBoolean
ACTIONS
1Declare Variable
Name
lastPosition
Value
0
2Declare Variable
Name
earlier
Value
0
3Declare Variable
Name
timesSeen
Value
1
4Declare Variable
Type
Boolean
Name
isRepeated
Value
false
5Change Variable
Variable
lastPosition
Value
dataCount('Position History') / dataCount('Position') - 1
6Repeatfloor(lastPosition / 2)times
Each time
7Change Variable
Variable
earlier
Value
lastPosition - 2 * (local.index + 1)
8Is Same PositionFunction
Action Name
positionLook
earlier
earlier
later
lastPosition
9IfpositionLook.value == true
Then
10Change Variable
Variable
timesSeen
Operator
+=
Value
1
11IftimesSeen >= timesForDraw
Then
12Change Variable
Variable
isRepeated
Value
true
13Return
Value
isRepeated

It leafs back two pages at a time. Why skip one? The Sides take turns, so the page right before has the other Side to move. That’s never the same position, even with every Piece in place.

Step 5 finds the newest page’s number: the album’s rows, divided by 64, minus 1. The Repeat then runs once for every second page before it. timesSeen starts at 1, for the newest picture itself, and each match adds one. When it reaches timesForDraw, 3, the answer is yes. That’s the Draw.

5.8 The Last Word

Now one Logic Component asks all the questions, at the start of every Turn.

5.8.1 Start Turn

Start Turn tells the rest of the game when a Turn begins, with a new Custom Event, turnStarted.

Logic ComponentStart TurnAttach to: Game RulesAddDelete

Starts each Turn: the first one (White's) when the game begins, and the next one after every Move (Select and Move or Choose Promotion sends "moveMade"). It gives the Turn to the other Side with nothing selected and no Moves marked, and asks whether that Side is in Check (Look For Check), which sets the Check mark and the status line. Then it tells the Game Rules "turnStarted" (Look For Game End listens).

Attach toAny Object
DATA STRUCTURES
Board Stateself.boardState
When Created
WhenCreate
1Look For CheckFunction
gameRules
self
2Trigger Custom Event on Object
Custom Event
turnStarted
3Trigger Custom Event
Custom Event
positionChanged
On Move Made
WhenmoveMadeCustom Event
1Declare Variable
Type
Text
Name
side
2Change Variable
Variable
side
Value
self.boardState.sideToMove
3Change Component Value
Component Value
self.boardState.sideToMove
Value
data('Sides', side, 'opponent')
4Change Component Value
Component Value
self.boardState.selected
Value
''
5Clear TargetsFunction
6Look For CheckFunction
gameRules
self
7Trigger Custom Event on Object
Custom Event
turnStarted

In When Created, step 2 goes between Look For Check and positionChanged. That’s the middle of a line once more. Add the new block apart from the line, and join it after Look For Check with the round handles. In On Move Made, step 7 goes at the end, after Look For Check. Both are Trigger Custom Event on Object, with no Applies to line, so they ring the bell for the Game Rules alone.

5.8.2 Look For Game End

Look For Game End is the one that listens. It hears turnStarted and asks its questions, one after another. If any answer says the game is over, two things happen inside one If, in its Then: Show Result writes the words, and the sign turns to Game Over. Nothing else happens on an ordinary Turn.

Logic ComponentLook For Game EndAttach to: Game Rules

Ends the game at the start of every Turn (Start Turn sends "turnStarted") when the Side to move has no Legal Move (Is Out Of Moves), when neither Side can give Checkmate (Is Material Insufficient), when the same position has come timesSeenForDraw times (Record Position, Is Position Repeated), or when fiftyMoveCountForDraw Moves in a row have had no Capture and no Pawn Move (the fifty-move rule; Make Move counts them). The status line shows the result (Show Result), and the game is over (Game State Game Over), so the Board no longer answers and the final position stays. timesSeenForDraw: 3, threefold repetition. fiftyMoveCountForDraw: 100, fifty Moves by each Side.

Attach toAny Object
DATA STRUCTURES
Board Stateself.boardState
SHARED VARIABLES
NameTypeKindStarting Value
timesSeenForDrawNumberConstant3
fiftyMoveCountForDrawNumberConstant100
On Turn Started
WhenturnStartedCustom Event
1Is Out Of MovesFunction
Action Name
outOfMoves
gameRules
self
2Is Material InsufficientFunction
Action Name
material
3Record PositionFunction
gameRules
self
4Is Position RepeatedFunction
Action Name
repetition
timesForDraw
@self.timesSeenForDraw
5IfoutOfMoves.value == true || material.value == true || repetition.value == true || self.boardState.fiftyMoveCount >= @self.fiftyMoveCountForDraw
Then
6Show ResultFunction
gameRules
self
isOutOfMoves
outOfMoves.value
isMaterialInsufficient
material.value
isRepeated
repetition.value
7Set Game State
Game State
Game Over

It asks its four questions in a row. Each call has its own Action Name: outOfMoves, material and repetition. Step 5’s If joins four tests with ||, because any one answer is enough. Each call’s one answer is read as value: outOfMoves.value, material.value and repetition.value. The last test asks whether fiftyMoveCount is at fiftyMoveCountForDraw yet.

Steps 6 and 7 both go in the If’s Then. Step 6, the call to Show Result, hands it all four: gameRules and the three answers, in the card’s order. Step 7, Set Game State, turns the sign to Game Over.

Its Shared Variables are two constant Numbers, so neither changes during play. The first, timesSeenForDraw, is 3, for threefold. The second, fiftyMoveCountForDraw, is 100. The rule counts fifty Moves by each Side, and Make Move counts both Sides’ Moves.

ObjectGame RulesAddDelete
Data Structure
Data StructureBoard State
Data Structure
From LogicSelect and Move
Data StructureSelect and Move Data
Data Structure
From LogicChoose Promotion
Data StructureChoose Promotion Data
Logic
Select and Move
Start Turn
Choose Promotion
Look For Game End

Attach Look For Game End to the Game Rules. Every Turn now starts with a look at the whole game, before anyone moves.

5.8.3 Report Click

Only the Description changes on this card. There’s nothing to build.

Logic ComponentReport ClickAttach to: SquareAddDelete

When the Player clicks this Square while the game is being played (Game State Playing), it tells whoever listens (the Game Rules) with "squareClicked"; the listener reads which Square it was as Other. While the Promotion choice is open, and after the game has ended, the Board does not answer.

Attach toAny Object
When Left Is Clicked
WhenMouse Pressed on ObjectMouse button: left
1Check Game State
Action Name
isPlaying
Game State
Playing
2IfisPlaying.value == true
Then
3Trigger Custom Event
Custom Event
squareClicked

Its steps already send squareClicked only while the sign says Playing. That was Chapter 4’s promise for the Promotion panel, and it keeps a finished game still too. If you typed its Description, add the new words about the end of the game.

5.8.4 Choose Promotion

One last change, for a rare moment. What if a Pawn promotes and gives Checkmate in the same Move?

Logic ComponentChoose PromotionAttach to: Game RulesAddDelete

Closes the Promotion choice when the Player presses one of its buttons ("promotionChosen"; the button is its Other): the panel is hidden and the game is played again (Game State Playing). A Kind then makes the waiting Pawn Move (Make Move), puts the chosen Kind on the Square, and tells the Game Rules "moveMade"; Cancel makes no Move, and the Pawn stays selected. Then everyone hears "positionChanged".

Attach toAny Object
DATA STRUCTURES
Board Stateself.boardState
Choose Promotion Dataself.choosePromotionData
On Promotion Chosen
WhenpromotionChosenCustom Event
1Check Game State
Action Name
isChoosing
Game State
Promotion
2IfisChoosing.value == true
Then
3Change Component Value
Component Value
self.choosePromotionData.chosenKind
Value
other.promotionChoice.kind
4Change Component Value
Component Value
self.choosePromotionData.landingSquare
Value
self.boardState.promotionSquare
5Change Component Value
Component Value
self.boardState.promotionSquare
Value
''
6Set UI Group Visibility
UI Group
"Promotion"
Visible
No
7Set Game State
Game State
Playing
8Ifself.choosePromotionData.chosenKind != ""
Then
9Make MoveFunction
gameRules
self
from
self.boardState.selected
to
self.choosePromotionData.landingSquare
10Set Data Cell
Table
Position
Row
self.choosePromotionData.landingSquare
Column
"kind"
Value
self.choosePromotionData.chosenKind
11Trigger Custom Event on Object
Custom Event
moveMade
12Otherwise
13Look For CheckFunction
gameRules
self
11Change Component Value
Component Value
self.boardState.promotionSquare
Value
''
12Set UI Group Visibility
UI Group
"Promotion"
Visible
No
13Set Game State
Game State
Playing
14Trigger Custom Event
Custom Event
positionChanged

Choose Promotion used to close the panel last, after the Move. But the new Turn starts inside the Move, when moveMade is sent, and Look For Game End may turn the sign to Game Over right then. A Set Game State to Playing after that would turn it back, and the game would go on after its end. So the three closing steps move up, before the Move is made. Here, the order is everything.

Delete the three struck steps from the bottom of the line. Then put them in again as steps 5 to 7, between step 4 and the If, with Add Inside Then on the outer If. They join the end of its line first, after the inner If. Use the round handles to join them after step 4, in order, ahead of that inner If. Step 7’s list shows Playing as Playing · Initial, as in Chapter 4.

Run the game. Play f2 to f3, e7 to e5, g2 to g4, and the Queen from d8 to h4.

The Queen lands, and at once the words under the Board change to You lose · Checkmate, while White’s King sits on its red Square. Click a Piece, any Piece. Nothing answers.

The game is over, and the Board keeps it exactly as it ended.

Play it!

Watch for each of these.

  • Play f2 to f3, e7 to e5, g2 to g4, and the Queen from d8 to h4. You lose · Checkmate, and no Piece answers a click.
  • In a new game, play e2 to e4 and e7 to e5. The Queen goes from d1 to h5, and Black’s Knight from b8 to c6. Next, the Bishop goes from f1 to c4, and Black’s other Knight comes out from g8 to f6, too late. Last, the Queen captures on f7. You win · Checkmate, with Black’s King red on e8.
  • In a new game, move the Knights out and home again, twice: g1 to f3, g8 to f6, f3 to g1, f6 to g8, then all four once more. After the eighth Move: Draw · Threefold repetition.
  • Stalemate takes a famous short game, ten Moves for White and nine for Black. In a new game, play them in pairs, White first:
    1. e2 to e3, a7 to a5.
    2. Queen d1 to h5, Rook a8 to a6.
    3. The Queen captures the Pawn on a5, and Black’s h-Pawn goes from h7 to h5.
    4. h2 to h4, Rook a6 to h6.
    5. The Queen captures on c7, and f7 goes to f6.
    6. The Queen captures on d7. Black’s King steps to f7.
    7. The Queen captures on b7, and Black’s Queen comes out from d8 to d3.
    8. The Queen captures on b8. Queen d3 to h7.
    9. The Queen captures the Bishop on c8, and Black’s King steps from f7 to g6.
    10. Last, the Queen goes from c8 to e6.
    Draw · Stalemate. Black isn’t in Check, and has no Move at all.
  • Promote a Pawn, as in Chapter 4. The panel closes, and the game goes on as before.
  • Insufficient material and the fifty-move rule take a long game to reach by hand. They’re built, and a long game will meet them.
How it works

Every Turn now starts with one question. Look For Game End asks whether there’s a Legal Move left, whether either Side could still give Checkmate, and whether the game has been here twice before. The album of positions answers the last one, and a count of quiet Moves answers the fifty-move rule. Show Result says how it ended, and the sign says Game Over.

Underneath, the end of the game is only one more state. Nobody told the Squares. They were already listening to the sign from Chapter 4, so when the sign changed to Game Over, they stopped.

5.9 If It Didn’t Work

This chapter has many small Functions, and a slip in one can hide in a long game. Watch what the game does, and think it through.

A block drawn in red, with a number on the Errors bar? Open the bar. It names the block and what it misses. Compare it with the card.

The game says Check after the Queen reaches h4, but never Checkmate? Look For Game End may not be hearing turnStarted. Perhaps it isn’t attached to the Game Rules. Perhaps Start Turn’s new steps say something a letter off: both must read turnStarted, with a capital S.

The game ends after the very first Move? Look first at Look For Game End’s step 7, which belongs inside the If, right under Show Result. Next, Is Out Of Moves may never find a dot. Look at its step 10: && joins both tests. Or step 12’s If may sit inside step 10’s If instead of after it.

You lose · Checkmate when you won? Show Result’s step 5 asks three things, and the last compares side with gameRules.boardState.playerSide. Perhaps playerSide in Board State has no default, White.

The words say a result, but the Board still answers clicks? Look at Look For Game End’s step 7, and the Game State list in the Set up. Game Over has to be there to be picked.

The Knights dance back and forth, and no Draw comes? Look at Record Position. Its Control Data Row adds a row only when the Row is dataCount('Position History'), one past the end. Or look at Is Position Repeated’s step 5, which divides by dataCount('Position'). Are its Table names spelled the same as yours?

After an en passant capture, is a strange Piece left behind on the Board again? Look at Update En Passant’s step 16, whose Column needs 'kind' with its quote marks, as Make Move’s did.

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.

Challenge

Every ending is one If in Look For Game End and one row in Words. What ending would you add? Some players like a race: the first King to reach one of the four middle Squares, d4, d5, e4 or e5, wins. Or a game that ends after a set number of Moves, with the most Pieces left winning. Your game knows how to stop now, and what it stops for is up to you.

Still stuck? Search the manual, or ask in the Myth Engine Discord (opens a new tab).

A game can end now, and the Board remembers how. In Chapter 6, a row of buttons arrives, and the first one starts a new game.