[Reach教程翻译] 2.4 打赌与下注

在本教程中,我们替石头剪刀布游戏加上了网络代币的转移~

[Reach教程翻译] Reach是安全简单的Dapp开发语言 让用户可以像开发传统App一样开发DApp 目前使用Reach开发的智能合约可以部署在以太坊、Conflux、Algorand Reach官网 Reach官方文挡

2.4 打赌与下注

原文链接

玩“石头剪刀布”很有趣,但是投入你的小金库会更有意思。我们修改一下代码,让 Alice 可以跟 Bob 打赌下注,最终猜拳的赢家赢得奖金。 这一次,我们从 JavaScript 前端开始,然后再回到 Reach 的代码,加入新的方法。

既然要转移资金,我们就要在游戏开始之前记录每个参与者的余额,以便我们更清楚地显示他们最终赢了多少钱。我们将在帐户创建与合约部署之间添加代码。

tut-4/index.mjs

..    // ...
 6    const startingBalance = stdlib.parseCurrency(100);
 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-4/index.mjs

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

至于 Bob ,我们修改他的接口以显示赌注,并通过返回值立即接受赌注。

tut-4/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 行定义了接受赌注的函数 acceptWager

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

tut-4/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-4/index.rsh

 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      wager: UInt,
12    });
13    const Bob   = Participant('Bob', {
14      ...Player,
15      acceptWager: Fun([UInt], Null),
16    });
17    deploy();
18    
19    Alice.only(() => {
..    // ...
  • 第 9 到第 12 行定义 Alice 的接口为一个 Player 接口,加上一个名为 wager (赌注)的整数值。
  • 第 13 到第 16 行 Bob 的接口类似,而他有的是一个叫做 acceptWager (接受赌注)的方法,用来查看 wager 的值。

为了实现赌注的功能,这个应用程序的三个部分中的每一个都必须稍微修改一下。让我们先看看 Alice 的第一步。

tut-4/index.rsh

..    // ...
19    Alice.only(() => {
20      const wager = declassify(interact.wager);
21      const handAlice = declassify(interact.getHand());
22    });
23    Alice.publish(wager, handAlice)
24      .pay(wager);
25    commit();
..    // ...
  • 第 20 行让 Alice 将赌注解密以传输。
  • 第 23 行新加上了 Alice 把赌注的金额
  • 第 24 行 Alice 转移这笔赌注作为她发布的一部分。

您可以试试看,如果 wager 只出现在第 24 行而没有被写在 23 行,则会引发 Reach 编译器异常。这是因为共识网络必须能够验证 Alice 的发布中包含的网络代币的数量是否与共识网络可获取的计算相匹配。

接下来,需要让 Bob 知道赌注并给他接受赌注的机会、与转移代币的时机。

tut-4/index.rsh

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

此时 DApp 正在共识步骤中运行,而现在合约内拥有下注金额两倍的网络代币。在之前的例子里,它只要计算结果后提交状态。但是现在,它还需要根据结果并处理赌注资金。

tut-4/index.rsh

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

现在,我们可以运行这个程序,并查看它的输出结果。

$ ./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 100 to 104.9999.
Bob went from 100 to 94.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 100 to 94.9999.
Bob went from 100 to 104.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 100 to 104.9999.
Bob went from 100 to 94.9999.

Alice和Bob的余额为什么每次都变回 100 呢?这是因为每次我们运行./reach run时,Reach会启动一个测试网络的全新实例并为每个玩家注册账号。

余额为什么不是 100,105 和 95 呢?这是因为在共识网络交易会消耗燃气费。

如果显示所有的小数点,看起来就会像这样:

Alice went from 100 to 104.999999999999687163.
Bob went from 100 to 94.999999999999978229.

Alice went from 10 to 4.999999999999687163.
Bob went from 10 to 14.999999999999978246.

为什么当 Alice 获胜时,赢得的钱比 Bob 获胜时少呢?因为她得付钱部署这份合约,因为她在前端发布了第一个信息 。部署的指南部分讨论了如何避免这种差值。

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

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

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

您知道了吗:

Reach 程序如何管理代币?

1.他们不管理,你需要在 Reach 程序显式地管理代币;

2.pay的原始语句可以被添加到 publish原始语句中来发送资金给 Reach 程序,这样到时候就可以用 transfer 原始语句来把资金发送回参与者和其他地址。

答案:

2; paytransfer 原始语句会帮你搞定这一切。

欢迎关注Reach微信公众号 并在公众号目录 -> 加入群聊 选择加入官方开发者微信群与官方Discord群 与更多Reach开发者一起学习交流!

_.png

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Ivan
Ivan

Reach Developer Evangelists

7 篇文章, 70 学分