原文链接
    玩“石头剪刀布”很有趣,但是加入你的毕生积蓄会更有意思。我们修改程序一下程序,让 Alice 可以跟 Bob 打赌下注,最终猜拳的赢家赢得奖金。 这一次,我们从 JavaScript 前端开始,然后再回到 Reach 的代码,加入新的方法。
    既然我们将要转移资金,我们就要在游戏开始之前记录每个参与者的余额,以便我们更清楚地显示他们最终赢了多少钱。我们将在帐户创建与合同部署之间添加代码。

    tut-4/index.mjs

    1. .. // ...
    2. 5 const stdlib = await loadStdlib();
    3. 6 const startingBalance = stdlib.parseCurrency(10);
    4. 7 const accAlice = await stdlib.newTestAccount(startingBalance);
    5. 8 const accBob = await stdlib.newTestAccount(startingBalance);
    6. 9
    7. 10 const fmt = (x) => stdlib.formatCurrency(x, 4);
    8. 11 const getBalance = async (who) => fmt(await stdlib.balanceOf(who));
    9. 12 const beforeAlice = await getBalance(accAlice);
    10. 13 const beforeBob = await getBalance(accBob);
    11. .. // ...
    • 第 10 行利用了一个好用的显示代币金额(最多4个小数位)的函数。
    • 第 11 行利用了用于获取参与者的余额并显示(最多4个小数位)的函数。
    • 第 12 和 13 行在游戏开始前获取 Alice 和 Bob 的余额。

    接下来我们更新 Alice 的用户界面来让她下注。
    tut-4/index.mjs

    1. .. // ...
    2. 32 backend.Alice(ctcAlice, {
    3. 33 ...Player('Alice'),
    4. 34 wager: stdlib.parseCurrency(5),
    5. 35 }),
    6. .. // ...
    • 第 33 行将一般玩家界面接到 Alice 的界面中。
    • 第 34 行将她的赌注设为 5 个网络代币,这是在参与者交互界面中使用具体的值而不是函数的例子。

    至于 Bob ,我们修改他的界面以显示赌注,并通过返回值立即接受赌注。
    tut-4/index.mjs

    1. .. // ...
    2. 36 backend.Bob(ctcBob, {
    3. 37 ...Player('Bob'),
    4. 38 acceptWager: (amt) => {
    5. 39 console.log(`Bob accepts the wager of ${fmt(amt)}.`);
    6. 40 },
    7. 41 }),
    8. .. // ...
    • 第 38 行到第 40 行定义了接受赌注的函数 acceptWager 。

    最后,在计算完了之后,我们将会再次获取余额并展示一条总结信息。
    tut-4/index.mjs

    1. .. // ...
    2. 44 const afterAlice = await getBalance(accAlice);
    3. 45 const afterBob = await getBalance(accBob);
    4. 46
    5. 47 console.log(`Alice went from ${beforeAlice} to ${afterAlice}.`);
    6. 48 console.log(`Bob went from ${beforeBob} to ${afterBob}.`);
    7. .. // ...
    • 第 44 和第 45 行获取最后的余额。
    • 第 47 和第 48 行打印结果。

    前端的这些更改只处理输出和接口的问题,下注和转移代币的实际操作会在 Reach 的代码中实现。
    让我们接着看看那些代码。
    首先,我们需要更新参与者交互界面。
    tut-4/index.mjs

    1. 1 'reach 0.1';
    2. 2
    3. 3 const Player =
    4. 4 { getHand: Fun([], UInt),
    5. 5 seeOutcome: Fun([UInt], Null) };
    6. 6 const Alice =
    7. 7 { ...Player,
    8. 8 wager: UInt };
    9. 9 const Bob =
    10. 10 { ...Player,
    11. 11 acceptWager: Fun([UInt], Null) };
    12. 12
    13. 13 export const main =
    14. 14 Reach.App(
    15. 15 {},
    16. 16 [Participant('Alice', Alice), Participant('Bob', Bob)],
    17. 17 (A, B) => {
    18. .. // ...
    19. 42 exit(); });
    • 第 6 到第 8 行定义了 Alice 的接口,加上一个叫做 wager (赌注)的整数值。
    • 第 9 到第 11 行 Bob 的接口类似,他有一个叫做 acceptWager (接受赌注)的方法来查看 wager 的值。
    • 第 16 行将这些接口和相应的参与者相关联。这行代码的格式是参与者构造函数元组,其中第一个参数是一个名称为后端参与者的字符串,而第二个参数是参与者交互接口,习惯上会用类似的名字命名它们。

    为了实现赌注的功能,这个应用程序的三个部分中的每一个都必须加以修改。让我们先看看 Alice 的第一步。
    tut-4/index.mjs

    1. .. // ...
    2. 18 A.only(() => {
    3. 19 const wager = declassify(interact.wager);
    4. 20 const handA = declassify(interact.getHand()); });
    5. 21 A.publish(wager, handA)
    6. 22 .pay(wager);
    7. 23 commit();
    8. .. // ...
    • 第 19 行让 Alice 将赌注解密以传输。
    • 第 21 行更新为 Alice 把赌注的金额发送给 Bob 。
    • 第 22 行 Alice 转移这笔赌注作为她广播的一部分。您可以试试看,如果 wager 出现在 22 行而不是 21 行,则 Reach 编译器将引发异常。这是因为共识网络必须能够验证 Alice 广播中包含的网络代币的数量是否与共识网络可获取的一些计算相匹配。

    接下来,需要让 Bob 知道赌注并给他接受赌注的机会、与转移代币的时机。
    tut-4/index.mjs

    1. .. // ...
    2. 25 B.only(() => {
    3. 26 interact.acceptWager(wager);
    4. 27 const handB = declassify(interact.getHand()); });
    5. 28 B.publish(handB)
    6. 29 .pay(wager);
    7. .. // ...
    • 第 26 行让 Bob 接受赌注。如果他不接受,那么他的前端可以不回复这个方法,那么 DApp 将会停止。
    • 第 29 行让 Bob 支付赌注,和之前一样。

    DApp 正在共识步骤中运行,现在合约本身拥有下注金额的两倍。在之前的例子里,它先计算结果,然后提交状态。但是现在,它需要查看结果并处理赌注资金。
    tut-4/index.mjs

    1. .. // ...
    2. 31 const outcome = (handA + (4 - handB)) % 3;
    3. 32 const [forA, forB] =
    4. 33 outcome == 2 ? [2, 0] :
    5. 34 outcome == 0 ? [0, 2] :
    6. 35 [1, 1];
    7. 36 transfer(forA * wager).to(A);
    8. 37 transfer(forB * wager).to(B);
    9. 38 commit();
    10. .. // ...
    • 第 33 行到第 35 行先确定每一方的下注金额,并根据结果计算给每个参与者的金额。如果结果为 2 ,则 Alice 获胜,那么她将获得两倍赌注;如果为 0 ,则 Bob 获胜,那么他将获得两倍赌注;否则,他们每个人都会得到一倍赌注。
    • 第 36 和 37 行用来转移相应的代币。这个转账是从合约到参与者的转移,而不是从参与者到彼此的转移,因为当前所有资金都在合约内部。
    • 第 38 行提交应用程序的状态并允许参与者查看结果并执行。

    现在,我们可以运行这个程序,并查看它的输出结果。
    $ ./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 Paper
    Bob accepts the wager of 5.
    Bob played Rock
    Alice saw outcome Alice wins
    Bob saw outcome Alice wins
    Alice went from 10 to 14.9999.
    Bob went from 10 to 4.9999.
    $ ./reach run
    Alice played Paper
    Bob accepts the wager of 5.
    Bob played Scissors
    Alice saw outcome Bob wins
    Bob saw outcome Bob wins
    Alice went from 10 to 4.9999.
    Bob went from 10 to 14.9999.
    $ ./reach run
    Alice played Rock
    Bob accepts the wager of 5.
    Bob played Scissors
    Alice saw outcome Alice wins
    Bob saw outcome Alice wins
    Alice went from 10 to 14.9999.
    Bob went from 10 to 4.9999.

    Alice和Bob的余额为什么每次都变回 10 呢?这是因为每次我们运行“./reach run”时,它开始了一个测试网络的全新实例并为每个玩家注册账号。 余额为什么不是 10,15 和 5 呢?这是因为运行以太坊的交易会消耗燃气费。 如果显示所有的小数点,看起来就会像这样: — Alice went from 10 to 14.999999999999687163. Bob went from 10 to 4.999999999999978229. … Alice went from 10 to 4.999999999999687163. Bob went from 10 to 14.999999999999978246. — 为什么当 Alice 获胜时,赢得的钱比 Bob 获胜时少呢?因为她得付钱部署这张合约,因为她在前端调用了 acc.deploy 。部署的指南部分讨论了如何避免这种差值。

    Alice的前景一片光明,如果她一直赌下去,她似乎能在“石头剪刀布”中赚上一笔!

    如果你的版本不能正确运行,请查看完整版本的 tut-3/index.rsh tut-3/index.mjs,并确保你正确地复制了所有内容。

    然而这个应用程序还存在一个重大的安全隐患。我们将会在下一步修复它;千万别用这个版本发布,不然 Alice 就要破产了!

    您知道了吗: Reach 程序如何管理代币? 1.他们不管理,你需要在 Reach 程序显式地管理代币; 2.pay 的原始语句可以被添加到 publish 原始语句中来发送资金给 Reach 程序,这样到时候就可以用 transfer 原始语句来把资金发送回参与者和其他地址。 答案: 2; paytransfer 原始语句会帮你搞定这一切。