2.3 Rock, Paper, and Scissors

In this section, we’ll have Alice and Bob actually execute the game of Rock, Paper, Scissors!.

We have to decide how to represent the hands of the game. A simple way is to represent them as the numbers 0, 1, and 2, standing for Rock, Paper, and Scissors. However, Reach does not support unsigned integers of exactly two bits, so it is better to represent them as the equivalence class of integers modulo three, so we won’t distinguish between 0 and 3 as Rock.

We’ll use a similar strategy for representing the three outcomes of the game: B wins, Draw, and A wins.

The first step is to change the Reach program to specify that Alice and Bob’s frontends can be interacted with to get the move that they will play, and later informed of the outcome of the game.

 1    'reach 0.1';
 2    
 3    const Player = {
 4    getHand: Fun([], UInt),
 5    seeOutcome: Fun([UInt], Null),
 6    };
 7    
 8    export const main = Reach.App(() => {
 9    const Alice = Participant('Alice', {
10      ...Player,
11    });
12    const Bob   = Participant('Bob', {
13      ...Player,
14    });
15    deploy();
16    
17    Alice.only(() => {
..    // ...

Before continuing with the Reach application, let’s move over to the JavaScript interface and implement these methods in our frontend.

..    // ...
13    const HAND = ['Rock', 'Paper', 'Scissors'];
14    const OUTCOME = ['Bob wins', 'Draw', 'Alice wins'];
15    const Player = (Who) => ({
16      getHand: () => {
17        const hand = Math.floor(Math.random() * 3);
18        console.log(`${Who} played ${HAND[hand]}`);
19        return hand;
20      },
21      seeOutcome: (outcome) => {
22        console.log(`${Who} saw outcome ${OUTCOME[outcome]}`);
23      },
24    });
25    
26    await Promise.all([
27      backend.Alice(ctcAlice, {
28        ...Player('Alice'),
29      }),
30      backend.Bob(ctcBob, {
31        ...Player('Bob'),
32      }),
33    ]);
..    // ...

There should be nothing interesting or controversial about these implementations; that’s the point of Reach: we get to just write normal business logic without worrying about the details of the consensus network and decentralized application.

Let’s return to the Reach program and look inside of the body of the program for what actions Alice and Bob take.

In a real-life game of Rock, Paper, Scissors!, Alice and Bob simultaneously decide what hand they will play and reveal it at the same time. "Simultaneity" is a complex concept that is hard to realize in practice. For example, if you’ve ever played against a little kid, you may notice them trying to see what you’re going to choose and delaying until the last minute to show their hand so they will win. In a decentralized application, it is not possible to have simultaneity. Instead, we have to select a particular participant who will "go first". In this case, we’ll choose Alice.

Does Alice go first, or do we call the player that goes first "Alice"? This might seem like an unnecessary distinction to make, but it is a very subtle point about the way that Reach works. In our frontend, we explicitly ran backend.Alice and backend.Bob. When we did that, we were committing that particular JavaScript thread to be either Alice or Bob. In our game, whoever chose to run the Alice backend is the one that will go first. In other words, Alice goes first. This will be more obvious at the end of the tutorial when we’ll make the choice interactively about which role to play.

The game proceeds in three steps.

First, the backend for Alice interacts with its frontend, gets Alice’s hand, and publishes it.

..    // ...
17      Alice.only(() => {
18        const handAlice = declassify(interact.getHand());
19      });
20      Alice.publish(handAlice);
21      commit();
..    // ...

The next step is similar, in that Bob publishes his hand; however, we don’t immediately commit the state, instead we compute the outcome of the game.

..    // ...
23      Bob.only(() => {
24        const handBob = declassify(interact.getHand());
25      });
26      Bob.publish(handBob);
27    
28      const outcome = (handAlice + (4 - handBob)) % 3;
29      commit();
..    // ...

Finally, we use the each form to have each of the participants send the final outcome to their frontends.

..    // ...
31      each([Alice, Bob], () => {
32        interact.seeOutcome(outcome);
33      });
..    // ...

At this point, we can run the program and see its output by running

  $ ./reach run

Since the players act randomly, the results will be different every time. When I ran the program three times, this is the output I got:

$ ./reach run

Alice played Scissors

Bob played Paper

Alice saw outcome Alice wins

Bob saw outcome Alice wins

 

$ ./reach run

Alice played Scissors

Bob played Paper

Alice saw outcome Alice wins

Bob saw outcome Alice wins

 

$ ./reach run

Alice played Paper

Bob played Rock

Alice saw outcome Alice wins

Bob saw outcome Alice wins

Alice is pretty good at Rock, Paper, Scissors!!

Consensus networks in general, and Reach specifically, guarantee that all participants agree on the outcome of their decentralized computation. Indeed, this is where the name consensus network comes from, as they enable these distributed, and untrusted, parties to come to a consensus, or agreement, about the intermediate states of a computation; and if they agree on the intermediate states, they will also agree on the output. That’s why every time you run ./reach run, both Alice and Bob will see the same outcome!

If your version isn’t working, look at the complete versions of tut-3/index.rsh and tut-3/index.mjs to make sure you copied everything down correctly!

In the next step, we’ll add some stakes to the game, because Alice needs to take her skills to the bank!

Check your understanding: Reach programs allow interaction with a user interface through which of the following methods?
  1. by forcing you to write a custom backend for the user interface that connects to the generated smart contract,

  2. by allowing the frontends to provide values directly to the Reach application,

  3. by allowing the Reach program to callback to the frontend via the interact object.

Answer:

2 and 3; Reach programs specify a two-way interface between the frontend and the backend via the participant interact interface.

Check your understanding: How do participants in a Reach application share information with each other and find out what others have shared?
  1. Reach generates a smart contract, but you need to implement a process to scan the blockchain for events that correspond to sharing;

  2. The Reach primitive publish allows a participant to share information with all other participants, which happens automatically without the other parties needing to do anything special;

  3. The Reach primitive publish allows a participant to share information with all other participants, but they need to explicitly run the receive primitive to receive published information.

Answer:

2; the publish primitive does everything for you.