6. Decisions
Goal
Make the game stop by itself after ten rounds.
New in this chapter
true,falseform value-bool spec/04 §4.1if (n.Round is 10)form if spec/02 §2.2elseform else spec/02 §2.3a is bform is spec/04 §4.5a is not bform is-not spec/04 §4.5a less than bform less-than spec/04 §4.5a more than bform more-than spec/04 §4.5a at least bform at-least spec/04 §4.5a at most bform at-most spec/04 §4.5a and bform and spec/04 §4.5a or bform or spec/04 §4.5not aform not spec/04 §4.5truetype bool spec/04 §4.1
Steps
The game of chapter 5 never ends on its own; we stopped it from outside with sim.StopAt. The players should decide for themselves. That takes a condition: a value that is true or false, and a block that runs only when it is true.
if takes a condition and a block. else gives the block for the other case; it sits on its own line, at the same indentation as the if, right after its block. A condition with more than one word is in parentheses, like every argument. The simplest condition is a comparison: less than compares two numbers and gives a bool, a value of type bool, either true or false. A bool can also be kept: Done is a node field that starts as false.
In step 1 the node that gets a Pong checks the round. Below ten it pings again; at ten it has had enough and sets Done to true. Without sim.StopAt the run ends when nothing is left to happen, so the demo no longer needs it:
Step 1
# package main
use example.com/tutorial/pingpong
use std/net
use std/sim
define main
set a to (sim.Spawn pingpong)
set b to (sim.Spawn pingpong)
set a.pingpong.Peer to b
net.Latency between 50ms and 150ms
deliver (message pingpong.Start with Round 1) to a
# package pingpong
use std/net
use std/sim
Start has
set Round to 0
Ping has
set round to 0
Pong has
set round to 0
Node has
set Peer to sim.Nobody
set Rounds to 0
set Done to false
# The first node serves
when [n: Node] (sim.Delivered Start)
net.Send (message Ping with round Start.Round) from n to n.Peer
# Whoever is pinged answers
when [n: Node] (net.Received Ping from sender)
set n.Rounds to (n.Rounds plus 1)
net.Send (message Pong with round Ping.round) from n to sender
# Whoever hears the answer pings again, until round ten
when [n: Node] (net.Received Pong from sender)
set n.Rounds to (n.Rounds plus 1)
if (Pong.round less than 10)
net.Send (message Ping with round (Pong.round plus 1)) from n to sender
else
set n.Done to true
The game ends after 2 seconds, with ten rounds counted by both nodes. But only the first node knows it is done. Step 2 teaches the other one the same, and makes the first node careful before serving. The comparison words are:
a is b a is not b
a less than b a more than b
a at least b a at most b
is and is not compare any two values of the same kind: numbers, texts, nodes, bools. The other four compare numbers (or durations). Conditions combine with and, or and not, which work on bools:
(n.Peer is not sim.Nobody) and (not n.Done)
Each side of and is one argument, so each comparison has its own parentheses; and there is no precedence to remember. In step 2 the serving node starts only if it has a partner, the Start names a real round (more than 0) and it is not done, and a node that gets a Ping of round ten, or more, also marks itself done:
Step 2
# package pingpong
use std/net
use std/sim
Start has
set Round to 0
Ping has
set round to 0
Pong has
set round to 0
Node has
set Peer to sim.Nobody
set Rounds to 0
set Done to false
# The first node serves, if it has a partner and is not done
when [n: Node] (sim.Delivered Start)
if (((n.Peer is not sim.Nobody) and (Start.Round more than 0)) and (not n.Done))
net.Send (message Ping with round Start.Round) from n to n.Peer
# Whoever is pinged answers
when [n: Node] (net.Received Ping from sender)
set n.Rounds to (n.Rounds plus 1)
net.Send (message Pong with round Ping.round) from n to sender
if (Ping.round at least 10)
set n.Done to true
# Whoever hears the answer pings again, until round ten
when [n: Node] (net.Received Pong from sender)
set n.Rounds to (n.Rounds plus 1)
if (Pong.round less than 10)
net.Send (message Ping with round (Pong.round plus 1)) from n to sender
else
set n.Done to true
In step 3 the demo checks the result after five seconds and reports it with captions. The first if uses or to join two comparisons: the game never started if either node played no round. The second compares two bools with a plain is: if the nodes are not both done, either neither is or only one is. The else block holds another if, because else if does not exist: you nest.
Step 3
# package main
use example.com/tutorial/pingpong
use std/lesson
use std/net
use std/sim
define main
set a to (sim.Spawn pingpong)
set b to (sim.Spawn pingpong)
set a.pingpong.Peer to b
net.Latency between 50ms and 150ms
deliver (message pingpong.Start with Round 1) to a
after 5s
if ((a.pingpong.Rounds at most 0) or (b.pingpong.Rounds at most 0))
lesson.Note "Somebody never played."
if (a.pingpong.Done and b.pingpong.Done)
lesson.Note "Both nodes stopped after ten rounds."
else
if (a.pingpong.Done is b.pingpong.Done)
lesson.Note "Neither node is done yet."
else
lesson.Note "Only one node is done."
Notice that the demo can read a.pingpong.Done and a.pingpong.Rounds: the demo sees the state of any node (it is the director of the show), nodes do not.
What to watch
The messages go back and forth ten times and then the animation stops with nothing in flight. The final state shows Done true on both cards, and after five seconds the caption says so. In step 1 the second card still says false: only the one that heard the last answer is done.
Why it looks like this
You may want to write "is not equal" as not around an equality. It parses, but it is not canonical:
pingpong/pingpong.synod:21:10: error[non-canonical]: line is not canonical
|
21 | if (((not (n.Peer is sim.Nobody)) and (Start.Round more than 0)) and (not n.Done))
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: write the canonical line (synod fmt does this)
|
21 - if (((not (n.Peer is sim.Nobody)) and (Start.Round more than 0)) and (not n.Done))
21 + if (((n.Peer is not sim.Nobody) and (Start.Round more than 0)) and (not n.Done))
|
non-canonicalsynod check says error[non-canonical] and the hint shows the one spelling: (n.Peer is not sim.Nobody). Synod has one way to say each thing. synod fmt rewrites not (a is b) into a is not b, and not (a less than b) into a at least b, so you never need to do it by hand. not stays for bools, like not n.Done.
Exercises
- Change the limit from 10 to 3 in both places. What does the final state show?
Expected
Three rounds: both nodes count 3 and both say Done true. (In step 1 only the first.)
- Replace
at most 0in the demo byless than 1. Is anything different?
Expected
Nothing: for whole numbers at most 0 and less than 1 mean the same. Both are canonical, so pick the one that reads best.
- Use
more thanto say the same asat least 10in thePinghandler.
Expected
Ping.round more than 9 is the same for whole rounds. at least reads better here.