The original link

In this section, we will continue to write our application so that Alice and Bob can play against each other after a tie until a winner emerges; That is, if the result is a tie, they will proceed. We only need to modify the Reach program without changing the JavaScript front end, but for the next case of a tie fight, we will change the front end, since both punches now require a “timeout” mechanism. Let’s first adjust the getHand method of Player: Tu-7 /index.mjs

.// ...
20    const Player = (Who) = > ({
21. stdlib.hasRandom,22      getHand: async() = > {// <-- async now
23        const hand = Math.floor(Math.random() * 3);
24        console.log(`${Who} played ${HAND[hand]}`);
25        if ( Math.random() <= 0.01 ) {
26          for ( let i = 0; i < 10; i++ ) {
27            console.log(`  ${Who}takes their sweet time sending it back... `);
28            await stdlib.wait(1);
29          }
30        }
31        return hand;
32      },
33      seeOutcome: (outcome) = > {
34        console.log(`${Who} saw outcome ${OUTCOME[outcome]}`);
35      },
36      informTimeout: () = > {
37        console.log(`${Who} observed a timeout`);
38      },
39}); .// ...
Copy the code
  • Lines 25 through 30 move the forced timeout code from Bob’s acceptWager function to this method. We also adjusted the timeout to occur only 1% of the time. Because this behavior is not fun, make it happen much less often.

Because we are testing differently now, we removed the timeout code from Bob’s acceptWager function, essentially reverting to the simpler version. We also removed the timeout code from Bob’s acceptWager function because we now test differently. So let’s go back to the simpler version. tut-7/index.mjs

.// ...
41    await Promise.all([
42      backend.Alice(ctcAlice, {
43. Player('Alice'),
44        wager: stdlib.parseCurrency(5),
45      }),
46      backend.Bob(ctcBob, {
47. Player('Bob'),
48        acceptWager: (amt) = > {
49          console.log(`Bob accepts the wager of ${fmt(amt)}. `);
50        },
51      }),
52]); .// ...
Copy the code
  • Lines 48 through 50 simplify Bob’s acceptWager method.

— Now, let’s look at the Reach application. All the details of the game and the player interface will remain the same. The only difference is the order in which the action takes place. The previous steps were:

  1. Alice sends out her bet.
  2. Bob bets and plays his cards.
  3. Alice plays.
  4. Game over.

However, now because players can submit multiple punches with only one bet, we’ll tweak these steps as follows:

  1. Alice sent out her bet.
  2. Bob bets.
  3. Alice bets.
  4. Bob play.
  5. Alice play.
  6. If it is a tie, return to step 4; Otherwise, the game ends.

Now let’s make these changes: tu-7 /index.rsh

.// ...
42    A.only(() = > {
43      const wager = declassify(interact.wager); });
44    A.publish(wager)
45      .pay(wager);
46commit(); .// ...
Copy the code
  • Line 44 Alice opens and pays the bet.

tut-7/index.rsh

.// ...
48    B.only(() = > {
49      interact.acceptWager(wager); });
50    B.pay(wager)
51      .timeout(DEADLINE, () = > closeTo(A, informTimeout));
52.// ...
Copy the code
  • Line 50 Bob pays the bet.
  • Attention! Line 52 does not commit the consensus step.

— It is now possible to implement a back-and-forth match, where both sides repeatedly throw punches until the result is not a tie. In a normal programming language, this would happen through a while loop, as is the case with Reach. However, the while loop in Reach requires special care, as discussed in the Reach loop guide, so let’s take it slow. Throughout the rest of the Reach program, all identifier binding values are static and immutable, but if this were true throughout Reach, then the while loop would never start or end because the loop condition would never change. Therefore, the while loop in Reach allows the introduction of variable binding values. Next, in order for Reach’s automatic validation engine to work, we must declare which properties of the program are invariant before and after the body of the while loop is executed. This is known as “loop invariants.” Finally, such a loop may only occur at the consensus step. That’s why Bob’s transaction just didn’t commit, because we need to stay inside the consensus to run the while loop. This is so that all participants agree on the direction of control flow in the application. The structure looks like this: tu-7 /index.rsh

.// ...
53    var outcome = DRAW;
54    invariant(balance() == 2 * wager && isOutcome(outcome) );
55    while ( outcome == DRAW ) {
..    // ...
Copy the code
  • Line 53 defines the loop variable outcome.
  • Line 54 declares the loop invariants, the balance of the contract account, and the valid outcome outcome.
  • Line 55 begins the loop with the condition that the loop continues as long as the result is a tie.

Now, let’s look at the other steps in the body of the loop, starting with Alice’s promise of a hand card. tut-7/index.rsh

.// ...
56    commit();
57    
58    A.only(() = > {
59      const _handA = interact.getHand();
60      const [_commitA, _saltA] = makeCommitment(interact, _handA);
61      const commitA = declassify(_commitA); });
62    A.publish(commitA)
63      .timeout(DEADLINE, () = > closeTo(B, informTimeout));
64commit(); .// ...
Copy the code
  • Line 56 commits the last transaction, in which the loop begins with Bob accepting the bet, followed by Alice Posting her gesture.
  • Lines 58 through 64 are the same as in the previous version, except that the bet is known and paid.

tut-7/index.rsh

.// ...
66    unknowable(B, A(_handA, _saltA));
67    B.only(() = > {
68      const handB = declassify(interact.getHand()); });
69    B.publish(handB)
70      .timeout(DEADLINE, () = > closeTo(A, informTimeout));
71commit(); .// ...
Copy the code

Similarly, Bob’s code is the same as the previous version, except that the bet is accepted and paid. tut-7/index.rsh

.// ...
73    A.only(() = > {
74      const [saltA, handA] = declassify([_saltA, _handA]); });
75    A.publish(saltA, handA)
76      .timeout(DEADLINE, () = > closeTo(B, informTimeout));
77checkCommitment(commitA, saltA, handA); .// ...
Copy the code

Alice’s next step is actually the same because she still posts her gestures in exactly the same way. Then comes the last part of the cycle. tut-7/index.rsh

.// ...
79    outcome = winner(handA, handB);
80    continue; }..// ...
Copy the code
  • Line 79 updates the loop variable outcome.
  • Line 80 continues the loop. Unlike most programming languages, Reach requires continue to be written explicitly in the body of the loop.

The rest of the program happens outside of the loop, but it can be exactly the same as before, though we’ll simplify it because we know the result will never be a tie. tut-7/index.rsh

.// ...
82    assert(outcome == A_WINS || outcome == B_WINS);
83    transfer(2 * wager).to(outcome == A_WINS ? A : B);
84    commit();
85    
86    each([A, B], () = > {
87      interact.seeOutcome(outcome); });
88    exit(); });
Copy the code
  • Line 82 asserts that the result is not a tie, which is obviously true, or we wouldn’t have exited the while loop.
  • Line 83 transfers the funds to the winner.

— Let’s run the program and see what happens:

$ ./reach run
Bob accepts the wager of 5.
Alice played Paper
Bob played Rock
Bob saw outcome Alice wins
Alice saw outcome Alice wins
Alice went from 10 to 14.9999.
Bob went from 10 to 4.9999.
 
$ ./reach run
Bob accepts the wager of 5.
Alice played Rock
Bob played Rock
Alice played Paper
Bob played Scissors
Bob saw outcome Bob wins
Alice saw outcome Bob wins
Alice went from 10 to 4.9999.
Bob went from 10 to 14.9999.

$ ./reach run
Bob accepts the wager of 5.
Alice played Scissors
Bob played Rock
Bob saw outcome Bob wins
Alice saw outcome Bob wins
Alice went from 10 to 4.9999.
Bob went from 10 to 14.9999.

Copy the code

As before, the results of your runs may vary, but you should also see sometimes a single round with a winner, and sometimes multiple rounds and delays from both sides.

If your version doesn’t work properly, check tu-7 / index.rsh and Tu-7 / index.mj and make sure you copied everything correctly!

Now, our rock-paper-scissors game is definitely a winner, which makes it even more interesting. In the next step, we’ll show how to use Reach to exit “test” mode and turn our JavaScript into a “rock-paper-scissors” game that interacts with real users.

Do you know?

How do you write an application in Reach that runs arbitrarily long, such as a rock-paper-scissors game that is guaranteed not to end in a tie?

  1. This is not possible because all Reach programs are of finite duration;
  2. You can use a while loop that runs until the result of the race is determined.

Answer: 2; Reach supports the while loop.



Do you know?

When you check that a program with a while loop is correct, you need to have a property called a loop invariant. Which of the following statements is true about cyclic invariants?

  1. The program before the while loop partially declares invariants.
  2. Conditions and loop bodies must declare invariants.
  3. The negation and invariants of a condition must declare any properties in the rest of the code.

The answer is: all of the above

2.6 Handling the Timeout problem

Next: 2.8 Interaction and autonomous operation