尽管玩“石头剪刀布”很有趣,但是加入你的毕生积蓄会更有意思。我们修改程序一下程序,让Alice可以跟Bob打赌下注,最终猜拳的赢家赢得奖金。 这一次,我们从JavaScript前端开始,然后再回到Reach的代码,加入新的方法。
    既然我们将要转移资金,我们就要在游戏开始之前记录每个参与者的余额,以便我们更清楚地显示他们最终赢了多少钱。我们将在帐户创建与合同部署之间添加代码。
    tut-3/index.mjs
    .. // …
    5 const stdlib = await loadStdlib();
    6 const startingBalance = stdlib.parseCurrency(10);
    7 const accAlice = await stdlib.newTestAccount(startingBalance);
    8 const accBob = await stdlib.newTestAccount(startingBalance);
    9
    10 const fmt = (x) => stdlib.formatCurrency(x, 4);
    11 const getBalance = async (who) => fmt(await stdlib.balanceOf(who));
    12 const beforeAlice = await getBalance(accAlice);
    13 const beforeBob = await getBalance(accBob);
    .. // …

    • 第10行利用了一个好用的显示货币金额(最多4个小数位)的函数。
    • 第11行利用了用于获取参与者的余额并显示(最多4个小数位)的函数。
    • 第12和13行在游戏开始前获取Alice和Bob的余额。

    接下来我们更新Alice的用户界面来让她下注。
    tut-3/index.mjs
    .. // …
    32 backend.Alice(ctcAlice, {
    33 …Player(‘Alice’),
    34 wager: stdlib.parseCurrency(5),
    35 }),
    .. // …

    • 第33行将一般玩家界面接到Alice的界面中。
    • 第34行将她的赌注设为5个网络代币,这是在参与者交互界面中使用具体的值而不是函数的例子。

    对于Bob,我们修改他的界面以显示赌注,并通过返回值立即接受赌注。
    tut-3/index.mjs
    .. // …
    36 backend.Bob(ctcBob, {
    37 …Player(‘Bob’),
    38 acceptWager: (amt) => {
    39 console.log(Bob accepts the wager of ${fmt(amt)}.);
    40 },
    41 }),
    .. // …

    • 第38行到第40行定义了接受赌注的函数。

    最后,在计算结束后,我们将会再次获取余额并展示一条总结结果的信息。
    tut-3/index.mjs
    .. // …
    44 const afterAlice = await getBalance(accAlice);
    45 const afterBob = await getBalance(accBob);
    46
    47 console.log(Alice went from ${beforeAlice} to ${afterAlice}.);
    48 console.log(Bob went from ${beforeBob} to ${afterBob}.);
    .. // …

    • 第44和第45行获取之后的余额。
    • 第47和第48行打印结果。

    前端的这些更改仅处理展示和接口的问题,下注和转移资金的实际操作将在Reach的代码中实现。
    让我们接着看看那些代码。
    首先,我们需要更新参与者交互界面。
    tut-3/index.mjs
    1 ‘reach 0.1’;
    2
    3 const Player =
    4 { getHand: Fun([], UInt),
    5 seeOutcome: Fun([UInt], Null) };
    6 const Alice =
    7 { …Player,
    8 wager: UInt };
    9 const Bob =
    10 { …Player,
    11 acceptWager: Fun([UInt], Null) };
    12
    13 export const main =
    14 Reach.App(
    15 {},
    16 [Participant(‘Alice’, Alice), Participant(‘Bob’, Bob)],
    17 (A, B) => {
    .. // …
    42 exit(); });

    • 第6到第8行定义了Alice的接口,加上一个叫做wager(赌注)的整数值。
    • 第9到11行Bob的接口类似,他有一个叫做接受赌注的方法来查看赌注值。
    • 第16行将这些接口和相应的参与者相关联。这行代码的格式是参与者构造函数元组,其中第一个参数是一个名称为后端参与者的字符串,而第二个参数是参与者交互接口,习惯上会用类似的名字来命名。

    该应用程序的三个部分中的每一个都必须修改以实现赌注的功能。让我们先看看Alice的第一步。
    tut-3/index.mjs
    .. // …
    18 A.only(() => {
    19 const wager = declassify(interact.wager);
    20 const handA = declassify(interact.getHand()); });
    21 A.publish(wager, handA)
    22 .pay(wager);
    23 commit();
    .. // …

    • 第19行让Alice将赌注解密以传输。
    • 第21行修改了以便Alice把赌注的金额分享给Bob。
    • 第22行Alice转移这笔赌注作为她广播的一部分。如果wager未出现在21行,而出现在22行,则Reach编译器将引发异常。您可以试试看。这是因为共识网络必须能够验证Alice广播中包含的网络代币的数量是否与共识网络可获取的某些计算相匹配。

    接下来,需要向Bob展示赌注并给予他接受赌注的机会、并转移资产。
    tut-3/index.mjs
    .. // …
    25 B.only(() => {
    26 interact.acceptWager(wager);
    27 const handB = declassify(interact.getHand()); });
    28 B.publish(handB)
    29 .pay(wager);
    .. // …

    • 第26行使Bob接受赌注。如果他不接受,那么他的前端可以不回复这个方法并且DApp将会停止。
    • 第29行同样让Bob支付赌注。

    DApp正在共识步骤中运行,现在合约本身拥有下注金额的两倍。在之前,它先计算结果,然后提交状态。但是现在,它需要查看结果并处理赌注资金。
    tut-3/index.mjs
    .. // …
    31 const outcome = (handA + (4 - handB)) % 3;
    32 const [forA, forB] =
    33 outcome == 2 ? [2, 0] :
    34 outcome == 0 ? [0, 2] :
    35 [1, 1];
    36 transfer(forA wager).to(A);
    37 transfer(forB
    wager).to(B);
    38 commit();
    .. // …

    • 第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原始语句帮你处理这一切。