[Reach教程翻译] 2.10 石头剪刀布的总结!

  • Ivan
  • 更新于 2021-12-10 20:40
  • 阅读 2421

石头剪刀布的教程至此完整啦! 这篇文章提供了完整地总结与更多学习资料的整理。欢迎留言感兴趣的话题,点播后续的文章内容!

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

2.10 石头剪刀布的总结!

原文链接

让我们回顾一下本教程所做的工作:

2.1 安装与初始化 中,我们展示了在不同系统下如何通过令命行安装 Reach,这个过程对多数开发者而言都非常容易。

2.2 创建DApp脚手架 中,我们展示了一个简单的 Reach 框架,可以发现不管要部署在哪个共识网络,这个框架都不用改动。

2.3 石头剪刀布 中,我们可以发现使用 Reach 的开发只要关注DApp的业务逻辑就好了,不用在乎与不同区块链交互和协议设计的麻烦而琐碎的问题。

2.4 打赌与下注 中,我们展示了使用 Reach 处理代币与交易就像简单的发送数据一样。

2.5 信任与约定 中,我们介绍了Reach自动验证引擎,他能替我们的程序发现并避免很大一部份的安全性漏洞。

2.6 超时与不参与 中,我们介绍了该如何使用 Reach 应对参与者迟迟不响应的行为,从而防止资金锁定在合约中。

2.7 平手则继续猜拳直至分出胜负**中,我们展示了如何通过While循环让程序可以不断的交互。

2.8 玩家交互、自主运行游戏 中,我们展示了除了使用 Reach 的测试环境外,我们如何将可交互的 Reach 程序部署到真实共识网络上。

2.9 通过React实现游戏网页交互 中,我们展示了如何将 Reach 程序部署成一个完整的 DApp!

即使已经做了这么多工作,这仅仅只是关于 Reach 功能的一个简单介绍。

上述 9 个小节完成的最终版本其实很简单,让我们看看程序的最终版本。

首先,让我们看看 Reach 程序:

rps-8-interact/index.rsh

'reach 0.1';

const [ isHand, ROCK, PAPER, SCISSORS ] = makeEnum(3);
const [ isOutcome, B_WINS, DRAW, A_WINS ] = makeEnum(3);

const winner = (handAlice, handBob) =>
  ((handAlice + (4 - handBob)) % 3);

assert(winner(ROCK, PAPER) == B_WINS);
assert(winner(PAPER, ROCK) == A_WINS);
assert(winner(ROCK, ROCK) == DRAW);

forall(UInt, handAlice =>
  forall(UInt, handBob =>
    assert(isOutcome(winner(handAlice, handBob)))));

forall(UInt, (hand) =>
  assert(winner(hand, hand) == DRAW));

const Player = {
  ...hasRandom,
  getHand: Fun([], UInt),
  seeOutcome: Fun([UInt], Null),
  informTimeout: Fun([], Null),
};

export const main = Reach.App(() => {
  const Alice = Participant('Alice', {
    ...Player,
    wager: UInt, // atomic units of currency
    deadline: UInt, // time delta (blocks/rounds)
  });
  const Bob   = Participant('Bob', {
    ...Player,
    acceptWager: Fun([UInt], Null),
  });
  deploy();

  const informTimeout = () => {
    each([Alice, Bob], () => {
      interact.informTimeout();
    });
  };

  Alice.only(() => {
    const wager = declassify(interact.wager);
    const deadline = declassify(interact.deadline);
  });
  Alice.publish(wager, deadline)
    .pay(wager);
  commit();

  Bob.only(() => {
    interact.acceptWager(wager);
  });
  Bob.pay(wager)
    .timeout(relativeTime(deadline), () => closeTo(Alice, informTimeout));

  var outcome = DRAW;
  invariant( balance() == 2 * wager && isOutcome(outcome) );
  while ( outcome == DRAW ) {
    commit();

    Alice.only(() => {
      const _handAlice = interact.getHand();
      const [_commitAlice, _saltAlice] = makeCommitment(interact, _handAlice);
      const commitAlice = declassify(_commitAlice);
    });
    Alice.publish(commitAlice)
      .timeout(relativeTime(deadline), () => closeTo(Bob, informTimeout));
    commit();

    unknowable(Bob, Alice(_handAlice, _saltAlice));
    Bob.only(() => {
      const handBob = declassify(interact.getHand());
    });
    Bob.publish(handBob)
      .timeout(relativeTime(deadline), () => closeTo(Alice, informTimeout));
    commit();

    Alice.only(() => {
      const saltAlice = declassify(_saltAlice);
      const handAlice = declassify(_handAlice);
    });
    Alice.publish(saltAlice, handAlice)
      .timeout(relativeTime(deadline), () => closeTo(Bob, informTimeout));
    checkCommitment(commitAlice, saltAlice, handAlice);

    outcome = winner(handAlice, handBob);
    continue;
  }

  assert(outcome == A_WINS || outcome == B_WINS);
  transfer(2 * wager).to(outcome == A_WINS ? Alice : Bob);
  commit();

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

还有以 Javascript 编写的命令行版本的前端

rps-8-interact/index.mjs

import { loadStdlib } from '@reach-sh/stdlib';
import * as backend from './build/index.main.mjs';
import { ask, yesno, done } from '@reach-sh/stdlib/ask.mjs';
const stdlib = loadStdlib(process.env);

(async () => {
  const isAlice = await ask(
    `Are you Alice?`,
    yesno
  );
  const who = isAlice ? 'Alice' : 'Bob';

  console.log(`Starting Rock, Paper, Scissors! as ${who}`);

  let acc = null;
  const createAcc = await ask(
    `Would you like to create an account? (only possible on devnet)`,
    yesno
  );
  if (createAcc) {
    acc = await stdlib.newTestAccount(stdlib.parseCurrency(1000));
  } else {
    const secret = await ask(
      `What is your account secret?`,
      (x => x)
    );
    acc = await stdlib.newAccountFromSecret(secret);
  }

  let ctc = null;
  if (isAlice) {
    ctc = acc.contract(backend);
    ctc.getInfo().then((info) => {
      console.log(`The contract is deployed as = ${JSON.stringify(info)}`); });
  } else {
    const info = await ask(
      `Please paste the contract information:`,
      JSON.parse
    );
    ctc = acc.contract(backend, info);
  }

  const fmt = (x) => stdlib.formatCurrency(x, 4);
  const getBalance = async () => fmt(await stdlib.balanceOf(acc));

  const before = await getBalance();
  console.log(`Your balance is ${before}`);

  const interact = { ...stdlib.hasRandom };

  interact.informTimeout = () => {
    console.log(`There was a timeout.`);
    process.exit(1);
  };

  if (isAlice) {
    const amt = await ask(
      `How much do you want to wager?`,
      stdlib.parseCurrency
    );
    interact.wager = amt;
    interact.deadline = { ETH: 100, ALGO: 100, CFX: 1000 }[stdlib.connector];
  } else {
    interact.acceptWager = async (amt) => {
      const accepted = await ask(
        `Do you accept the wager of ${fmt(amt)}?`,
        yesno
      );
      if (!accepted) {
        process.exit(0);
      }
    };
  }

  const HAND = ['Rock', 'Paper', 'Scissors'];
  const HANDS = {
    'Rock': 0, 'R': 0, 'r': 0,
    'Paper': 1, 'P': 1, 'p': 1,
    'Scissors': 2, 'S': 2, 's': 2,
  };

  interact.getHand = async () => {
    const hand = await ask(`What hand will you play?`, (x) => {
      const hand = HANDS[x];
      if ( hand == null ) {
        throw Error(`Not a valid hand ${hand}`);
      }
      return hand;
    });
    console.log(`You played ${HAND[hand]}`);
    return hand;
  };

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

  const part = isAlice ? ctc.p.Alice : ctc.p.Bob;
  await part(interact);

  const after = await getBalance();
  console.log(`Your balance is now ${after}`);

  done();
})();

最后是 Web 前端:

rps-9-web/index.js

import React from 'react';
import AppViews from './views/AppViews';
import DeployerViews from './views/DeployerViews';
import AttacherViews from './views/AttacherViews';
import {renderDOM, renderView} from './views/render';
import './index.css';
import * as backend from './build/index.main.mjs';
import {loadStdlib} from '@reach-sh/stdlib';
const reach = loadStdlib(process.env);

const handToInt = {'ROCK': 0, 'PAPER': 1, 'SCISSORS': 2};
const intToOutcome = ['Bob wins!', 'Draw!', 'Alice wins!'];
const {standardUnit} = reach;
const defaults = {defaultFundAmt: '10', defaultWager: '3', standardUnit};

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {view: 'ConnectAccount', ...defaults};
  }
  async componentDidMount() {
    const acc = await reach.getDefaultAccount();
    const balAtomic = await reach.balanceOf(acc);
    const bal = reach.formatCurrency(balAtomic, 4);
    this.setState({acc, bal});
    if (await reach.canFundFromFaucet()) {
      this.setState({view: 'FundAccount'});
    } else {
      this.setState({view: 'DeployerOrAttacher'});
    }
  }
  async fundAccount(fundAmount) {
    await reach.fundFromFaucet(this.state.acc, reach.parseCurrency(fundAmount));
    this.setState({view: 'DeployerOrAttacher'});
  }
  async skipFundAccount() { this.setState({view: 'DeployerOrAttacher'}); }
  selectAttacher() { this.setState({view: 'Wrapper', ContentView: Attacher}); }
  selectDeployer() { this.setState({view: 'Wrapper', ContentView: Deployer}); }
  render() { return renderView(this, AppViews); }
}

class Player extends React.Component {
  random() { return reach.hasRandom.random(); }
  async getHand() { // Fun([], UInt)
    const hand = await new Promise(resolveHandP => {
      this.setState({view: 'GetHand', playable: true, resolveHandP});
    });
    this.setState({view: 'WaitingForResults', hand});
    return handToInt[hand];
  }
  seeOutcome(i) { this.setState({view: 'Done', outcome: intToOutcome[i]}); }
  informTimeout() { this.setState({view: 'Timeout'}); }
  playHand(hand) { this.state.resolveHandP(hand); }
}

class Deployer extends Player {
  constructor(props) {
    super(props);
    this.state = {view: 'SetWager'};
  }
  setWager(wager) { this.setState({view: 'Deploy', wager}); }
  async deploy() {
    const ctc = this.props.acc.deploy(backend);
    this.setState({view: 'Deploying', ctc});
    this.wager = reach.parseCurrency(this.state.wager); // UInt
    this.deadline = {ETH: 10, ALGO: 100, CFX: 1000}[reach.connector]; // UInt
    backend.Alice(ctc, this);
    const ctcInfoStr = JSON.stringify(await ctc.getInfo(), null, 2);
    this.setState({view: 'WaitingForAttacher', ctcInfoStr});
  }
  render() { return renderView(this, DeployerViews); }
}
class Attacher extends Player {
  constructor(props) {
    super(props);
    this.state = {view: 'Attach'};
  }
  attach(ctcInfoStr) {
    const ctc = this.props.acc.attach(backend, JSON.parse(ctcInfoStr));
    this.setState({view: 'Attaching'});
    backend.Bob(ctc, this);
  }
  async acceptWager(wagerAtomic) { // Fun([UInt], Null)
    const wager = reach.formatCurrency(wagerAtomic, 4);
    return await new Promise(resolveAcceptedP => {
      this.setState({view: 'AcceptTerms', wager, resolveAcceptedP});
    });
  }
  termsAccepted() {
    this.state.resolveAcceptedP();
    this.setState({view: 'WaitingForTurn'});
  }
  render() { return renderView(this, AttacherViews); }
}

renderDOM(<App />);

我们总共写了约 100 行的 Reach 代码,并且有两个不同的前端,分别是命令行版本和网页版本的,他们各自也都大约只有 100 行。

在这背后,Reach 生成了几百行的 Solidity 代码(可以查看文件:rps-8-interact/build/index.main.sol),近 2000 行的 TEAL 代码(可以查看文件:rps-8-interact/build/index.main.appApproval.teal),以及超过 1000 行的 JavaScript 代码(可以查看文件:rps-8-interact/build/index.main.mjs)。如果我们不是使用 Reach,那么我们就必须自己编写这几千行代码,并确保它们在每次改动时彼此同步、相容,那将是一个很复杂的任务!

现在我们已经从头到尾的带着你写出一个完整的 Reach 应用程序了,该是你开发自己的应用程序的时候啦!

无论如何,我们非常希望你能加入我们的 Discord 社区。一旦你加入 Discord 社区,发信息@team, I just completed the tutorial! 我们就会赋予你tutorial veteran的角色,这样你就可以帮助其他人学习本教程!

感谢您和我们一起度过学习这个教程的美好时光!

文挡内还有很多值得阅读的部份, 比如: 工作坊内有更多的应用案例,并且不断持续更新。 指南部份有关于各种不同主题与概念的详细讨论。 参考资料中则可以找到全部语法与概念仔细的说明。

我们也会持续与大家分享,也欢迎各位读者加入微信群与更多开发者一起讨论、学习。 有想要了解的主题也欢迎留言点播!!!

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

_.png

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

0 条评论

请先 登录 后评论
Ivan
Ivan
江湖只有他的大名,没有他的介绍。