2.8 Interaction and Independence

In the last section, we made our Rock, Paper, Scissors! run until there was a definitive winner. In this section, we won’t be making any changes to the Reach program itself. Instead, we’ll introduce customizations to the JavaScript frontend which facilitate interactivity and provide the option to connect to a real consensus network.

We’ll start from scratch and show every line of the program again. You’ll see a lot of similarity between this and the last version, but for completeness, we’ll show every line.

 1    import { loadStdlib } from '@reach-sh/stdlib';
 2    import * as backend from './build/index.main.mjs';
 3    import { ask, yesno, done } from '@reach-sh/stdlib/ask.mjs';
 4    const stdlib = loadStdlib(process.env);
 5    
 6    (async () => {
..    // ...

..    // ...
 7    const isAlice = await ask(
 8      `Are you Alice?`,
 9      yesno
10    );
11    const who = isAlice ? 'Alice' : 'Bob';
12    
..    // ...

..    // ...
13    console.log(`Starting Rock, Paper, Scissors! as ${who}`);
14    
15    let acc = null;
16    const createAcc = await ask(
17      `Would you like to create an account? (only possible on devnet)`,
18      yesno
19    );
20    if (createAcc) {
21      acc = await stdlib.newTestAccount(stdlib.parseCurrency(1000));
22    } else {
23      const secret = await ask(
24        `What is your account secret?`,
25        (x => x)
26      );
27      acc = await stdlib.newAccountFromSecret(secret);
28    }
29    
..    // ...

..    // ...
30    let ctc = null;
31    const deployCtc = await ask(
32      `Do you want to deploy the contract? (y/n)`,
33      yesno
34    );
35    if (deployCtc) {
36      ctc = acc.deploy(backend);
37      ctc.getInfo().then((info) => {
38        console.log(`The contract is deployed as = ${JSON.stringify(info)}`); });
39    } else {
40      const info = await ask(
41        `Please paste the contract information:`,
42        JSON.parse
43      );
44      ctc = acc.attach(backend, info);
45    }
46    
..    // ...

..    // ...
47    const fmt = (x) => stdlib.formatCurrency(x, 4);
48    const getBalance = async () => fmt(await stdlib.balanceOf(acc));
49    
50    const before = await getBalance();
51    console.log(`Your balance is ${before}`);
52    
53    const interact = { ...stdlib.hasRandom };
54    
..    // ...

Next we define a few helper functions and start the participant interaction interface.

..    // ...
55    interact.informTimeout = () => {
56      console.log(`There was a timeout.`);
57      process.exit(1);
58    };
59    
..    // ...

Then we define a timeout handler.

..    // ...
60    if (isAlice) {
61      const amt = await ask(
62        `How much do you want to wager?`,
63        stdlib.parseCurrency
64      );
65      interact.wager = amt;
66      interact.deadline = { ETH: 100, ALGO: 100, CFX: 1000 }[stdlib.connector];
67    } else {
68      interact.acceptWager = async (amt) => {
69        const accepted = await ask(
70          `Do you accept the wager of ${fmt(amt)}?`,
71          yesno
72        );
73        if (!accepted) {
74          process.exit(0);
75        }
76      };
77    }
78    
..    // ...

Next, we request the wager amount or define the acceptWager method, depending on if we are Alice or not.

..    // ...
79    const HAND = ['Rock', 'Paper', 'Scissors'];
80    const HANDS = {
81      'Rock': 0, 'R': 0, 'r': 0,
82      'Paper': 1, 'P': 1, 'p': 1,
83      'Scissors': 2, 'S': 2, 's': 2,
84    };
85    
86    interact.getHand = async () => {
87      const hand = await ask(`What hand will you play?`, (x) => {
88        const hand = HANDS[x];
89        if ( hand == null ) {
90          throw Error(`Not a valid hand ${hand}`);
91        }
92        return hand;
93      });
94      console.log(`You played ${HAND[hand]}`);
95      return hand;
96    };
97    
..    // ...

Next, we define the shared getHand method.

..    // ...
98    const OUTCOME = ['Bob wins', 'Draw', 'Alice wins'];
99    interact.seeOutcome = async (outcome) => {
100      console.log(`The outcome is: ${OUTCOME[outcome]}`);
101    };
102    
..    // ...

Finally, the seeOutcome method.

..    // ...
103    const part = isAlice ? backend.Alice : backend.Bob;
104    await part(ctc, interact);
105    
106    const after = await getBalance();
107    console.log(`Your balance is now ${after}`);
108    
109    done();
110    })();

Lastly, we choose the appropriate backend function and await its completion.

We can now run

  $ ./reach run

in one terminal in this directory to play as Alice and

  $ ./reach run

in another terminal in this directory to play as Bob.

Here’s an example run:

$ ./reach run

Are you Alice?

y

Starting Rock, Paper, Scissors as Alice

Would you like to create an account? (only possible on devnet)

y

Do you want to deploy the contract? (y/n)

y

The contract is deployed as = "0x132b724e55AEb074C15A5CBb7b8EeE0dBEd45F7b"

Your balance is 999.9999

How much do you want to wager?

10

What hand will you play?

r

You played Rock

The outcome is: Bob wins

Your balance is now 989.9999

and

$ ./reach run

Are you Alice?

n

Starting Rock, Paper, Scissors as Bob

Would you like to create an account? (only possible on devnet)

y

Do you want to deploy the contract? (y/n)

n

Please paste the contract information:

"0x132b724e55AEb074C15A5CBb7b8EeE0dBEd45F7b"

Your balance is 1000

Do you accept the wager of 10?

y

What hand will you play?

p

You played Paper

The outcome is: Bob wins

Your balance is now 1009.9999

Of course, when you run the exact amounts and addresses may be different.

If your version isn’t working, compare with tut-8/index.rsh and tut-8/index.mjs to ensure you’ve copied everything down correctly!

If we were to instead run
  $ REACH_CONNECTOR_MODE=ALGO-devnet ./reach run
in two terminals we’d see equivalent output from running our application on a private Algorand devnet.

Connecting to live consensus networks is similarly easy:
  $ REACH_CONNECTOR_MODE=ETH-live ETH_NODE_URI="http://some.node.fqdn:8545" ./reach run

Now our implementation of Rock, Paper, Scissors! is finished! We are protected against attacks, timeouts, and draws, and we can run interactively on non-test networks.

In this step, we made a command-line interface for our Reach program. In the next step, we’ll replace this with a Web interface for the same Reach program.

Check your understanding: True or false: Reach helps you build automated tests for your decentralized application, but it doesn’t support building interactive user-interfaces.

Answer:

False; Reach does not impose any constraints on what kind of frontend is attached to your Reach application.