原文链接
在上面实现的程序中,我们的石头剪刀布会一直运行直到确定最终的赢家为止。在本节中,我们不会改变Reach程序本身。反之,我们将在 reach run 之上建立一个可交互的版本,这个版本将能够在私有开发者测试网络之外(真正以太坊网络)运行。
之前,当我们运行 ./reach run
时,它会为我们的 reach 程序创建一个 Docker 映像,其中包含一个临时的 Node.js 包,将我们的 JavaScript 前端连接到 reach 标准库和一个私有开发者测试网络的新实例。在本节中,我们将对其进行定制,并构建一个非自动化版本的剪刀石头布,并提供连接到真正以太坊网络的选项。
首先我们运行
$ ./reach scaffold
它会自动为我们生成以下文件:
- package.json 一个 Node.js 的包文件,连接 index.mjs 到 Reach 标准库
- Dockerfile 一个 Docker 镜像脚本,高效地建立并运行package包程序
- docker-compose.yml 一个 Docker Compose 脚本,链接 Docker 镜像到Reach私有开发人员测试网络的一个新实例。
- Makefile 一个 Makefile 文件,重新编译并运行 Docker 镜像。
我们将保留前两个文件不变。你可以在tut-8/package.json 和 tut-8/Dockerfile中看到它们。 我们将自定义其他两个文件。
首先,让我们看看 tut-8/docker-compose.yml文件:
tut-8/docker-compose.yml
1 version: '3.4'
2 x-app-base: &app-base
3 image: reachsh/reach-app-tut-7:latest
4 services:
5 ethereum-devnet:
6 image: reachsh/ethereum-devnet:0.1
7 algorand-devnet:
8 image: reachsh/algorand-devnet:0.1
9 depends_on:
10 - algorand-postgres-db
11 environment:
12 - REACH_DEBUG
13 - POSTGRES_HOST=algorand-postgres-db
14 - POSTGRES_USER=algogrand
15 - POSTGRES_PASSWORD=indexer
16 - POSTGRES_DB=pgdb
17 ports:
18 - 9392
19 algorand-postgres-db:
20 image: postgres:11
21 environment:
22 - POSTGRES_USER=algogrand
23 - POSTGRES_PASSWORD=indexer
24 - POSTGRES_DB=pgdb
25 reach-app-tut-7-ETH-live:
26 <<: *app-base
27 environment:
28 - REACH_DEBUG
29 - REACH_CONNECTOR_MODE=ETH-live
30 - ETH_NODE_URI
31 - ETH_NODE_NETWORK
32 reach-app-tut-7-ETH-test-dockerized-geth: &default-app
33 <<: *app-base
34 depends_on:
35 - ethereum-devnet
36 environment:
37 - REACH_DEBUG
38 - REACH_CONNECTOR_MODE=ETH-test-dockerized-geth
39 - ETH_NODE_URI=http://ethereum-devnet:8545
40 reach-app-tut-7-ETH-test-embedded-ganache:
41 <<: *app-base
42 environment:
43 - REACH_DEBUG
44 - REACH_CONNECTOR_MODE=ETH-test-embedded-ganache
45 reach-app-tut-7-FAKE-test-embedded-mock:
46 <<: *app-base
47 environment:
48 - REACH_DEBUG
49 - REACH_CONNECTOR_MODE=FAKE-test-embedded-mock
50 reach-app-tut-7-ALGO-test-dockerized-algod-local:
51 <<: *app-base
52 environment:
53 - REACH_DEBUG
54 - REACH_CONNECTOR_MODE=ALGO-test-dockerized-algod
55 - ALGO_SERVER=http://host.docker.internal
56 - ALGO_PORT=4180
57 - ALGO_INDEXER_SERVER=http://host.docker.internal
58 - ALGO_INDEXER_PORT=8980
59 extra_hosts:
60 - 'host.docker.internal:172.17.0.1'
61 reach-app-tut-7-ALGO-test-dockerized-algod:
62 <<: *app-base
63 depends_on:
64 - algorand-devnet
65 environment:
66 - REACH_DEBUG
67 - REACH_CONNECTOR_MODE=ALGO-test-dockerized-algod
68 - ALGO_SERVER=http://algorand-devnet
69 - ALGO_PORT=4180
70 - ALGO_INDEXER_SERVER=http://algorand-devnet
71 - ALGO_INDEXER_PORT=8980
72 reach-app-tut-7-: *default-app
73 reach-app-tut-7: *default-app
74 # After this is new!
75 player: &player
76 <<: *default-app
77 stdin_open: true
78 alice: *player
79 bob: *player
- 第 2 行和第 3 行定义了启动应用程序的服务。如果您在教程中一直停留在同一个目录中,那么第3行将显示tut,而不是 tut-7 。
- 第 5 行和第 6 行定义了以太坊的 Reach 私有开发者测试网络服务。
- 第 7 至 24 行定义了为Algorand提供的 Reach private developer 测试网络服务。
- 第 25 到 73 行定义了允许应用程序在不同网络上运行的服务;包括第 25 行,它定义了 reach-app-tut-8-ETH-live 连接到一个现有网络。
- 我们还添加了第 73 行到第 77 行来定义一个玩家,它是我们的应用程序,具有开放的标准输入,以及两个名为 alice 和 bob 的实例。
有了这些,我们就可以运行$ docker-compose run WHICH
其中 WHICH 在 live 实例中是 reach-app-tut-8-ETH-live ,而在测试实例中是 Alice 或者 Bob 。如果我们使用live版本,那么我们必须定义环境变量 ETH_NODE_URI 作为我们以太坊节点的 URI 。
我们将修改 tut-8/Makefile ,让程序可以运行:
tut-8/Makefile
..
29 .PHONY: run-live
30 run-live:
31 docker-compose run --rm reach-app-tut-7-ETH-live
32
33 .PHONY: run-alice
34 run-alice:
35 docker-compose run --rm alice
36
37 .PHONY: run-bob
38 run-bob:
39 docker-compose run --rm bob
然而,如果我们尝试运行其中任何一个,它做的事情都一样 : 为每个用户创建测试帐户,并随机模拟游戏。接着我们来修改JavaScript前端,使它们具有交互性。
我们将从头开始,再次展示程序的每一行。您会发现这个版本与先前的版本非常相似,但是为了完整起见,我们会显示每一行。
tut-8/index.mjs
1 import { loadStdlib } from '@reach-sh/stdlib';
2 import * as backend from './build/index.main.mjs';
3 import { ask, yesno, done } from '@reach-sh/stdlib/ask.mjs';
4
5 (async () => {
6 const stdlib = await loadStdlib();
.. // ...
- 第 1 行和第 2 行和之前一样 : 导入标准库和后端。
- 第 3 行是新加入的,为简单的控制台应用程序导入了一个来自 Reach 标准库名为 ask.mjs 的库。下面我们将看到如何使用这三个函数。
.. // ...
7
8 const isAlice = await ask(
9 `Are you Alice?`,
10 yesno
11 );
12 const who = isAlice ? 'Alice' : 'Bob';
.. // ...
- 第 8 行到第 11 行,问用户是否以 Alice 的身份参与,期望得到“是”或“不是”的回答。ask 会显示一个提示并收集一行输入,直到得到的参数没有出错。出现 yes/no , error 就表示没收到 'y' 或 'n'。
.. // ...
13
14 console.log(`Starting Rock, Paper, Scissors! as ${who}`);
15
16 let acc = null;
17 const createAcc = await ask(
18 `Would you like to create an account? (only possible on devnet)`,
19 yesno
20 );
21 if (createAcc) {
22 acc = await stdlib.newTestAccount(stdlib.parseCurrency(1000));
23 } else {
24 const secret = await ask(
25 `What is your account secret?`,
26 (x => x)
27 );
28 acc = await stdlib.newAccountFromSecret(secret);
29 }
.. // ...
- 第 16 行到第 19 行,用户可以选择创建一个测试帐户,或者输入一个密码来导入一个现有的帐户。
- 第 21 行像之前一样创建测试帐户。
- 第 27 行导入现有帐户。
.. // ...
30
31 let ctc = null;
32 const deployCtc = await ask(
33 `Do you want to deploy the contract? (y/n)`,
34 yesno
35 );
36 if (deployCtc) {
37 ctc = acc.deploy(backend);
38 const info = await ctc.getInfo();
39 console.log(`The contract is deployed as = ${JSON.stringify(info)}`);
40 } else {
41 const info = await ask(
42 `Please paste the contract information:`,
43 JSON.parse
44 );
45 ctc = acc.attach(backend, info);
46 }
.. // ...
- 第 31 至 34 行询问参与者是否要部署合约。
- 第 36 到 38 行部署它并打印出可以给其他玩家的公共信息(ctc.getInfo)。
- 第 40 行到第 44 行请求、解析和处理这个信息。
.. // ...
47
48 const fmt = (x) => stdlib.formatCurrency(x, 4);
49 const getBalance = async () => fmt(await stdlib.balanceOf(acc));
50
51 const before = await getBalance();
52 console.log(`Your balance is ${before}`);
53
54 const interact = { ...stdlib.hasRandom };
.. // ...
接下来,我们定义几个辅助函数并启动 participant 交互接口。
tut-8/index.mjs
.. // ...
55
56 interact.informTimeout = () => {
57 console.log(`There was a timeout.`);
58 process.exit(1);
59 };
.. // ...
首先我们定义一个超时处理程序。
tut-8/index.mjs
.. // ...
60
61 if (isAlice) {
62 const amt = await ask(
63 `How much do you want to wager?`,
64 stdlib.parseCurrency
65 );
66 interact.wager = amt;
67 } else {
68 interact.acceptWager = async (amt) => {
69 const accepted = await ask(
70 `Do you accept the wager of ${fmt(amt)}?`,
71 yesno
72 );
73 if (accepted) {
74 return;
75 } else {
76 process.exit(0);
77 }
78 };
79 }
.. // ...
接下来,我们请求赌注金额 (如果是 Alice ) 或定义 acceptWager 方法 (如果是 Bob ) 。
tut-8/index.mjs
.. // ...
80
81 const HAND = ['Rock', 'Paper', 'Scissors'];
82 const HANDS = {
83 'Rock': 0, 'R': 0, 'r': 0,
84 'Paper': 1, 'P': 1, 'p': 1,
85 'Scissors': 2, 'S': 2, 's': 2,
86 };
87 interact.getHand = async () => {
88 const hand = await ask(`What hand will you play?`, (x) => {
89 const hand = HANDS[x];
90 if ( hand == null ) {
91 throw Error(`Not a valid hand ${hand}`);
92 }
93 return hand;
94 });
95 console.log(`You played ${HAND[hand]}`);
96 return hand;
97 };
.. // ...
接下来,我们定义共享的 gethand 方法。
tut-8/index.mjs
.. // ...
98
99 const OUTCOME = ['Bob wins', 'Draw', 'Alice wins'];
100 interact.seeOutcome = async (outcome) => {
101 console.log(`The outcome is: ${OUTCOME[outcome]}`);
102 };
.. // ...
最后是 seeOutcome 方法。
tut-8/index.mjs
.. // ...
103
104 const part = isAlice ? backend.Alice : backend.Bob;
105 await part(ctc, interact);
106
107 const after = await getBalance();
108 console.log(`Your balance is now ${after}`);
109
110 done();
111 })();
最后,我们选择合适的后端功能并等待其完成。
现在我们可以运行
$ make build
然后重建镜像,在该目录的一个终端中
$ make run-alice
在该目录下的另一个终端中:
$ make run-bob
下面是一个运行的例子:
$ make run-alice
Are you Alice?
y
Starting Rock, Paper, Scissors as Alice
Would you like to create an account? (only possible on devnet)
y
Do you want to deploy the contract? (y/n)
y
The contract is deployed as = {"address":"0xc2a875afbdFb39b1341029A7deceC03750519Db6","creation_block":18,"args":[],"value":{"type":"BigNumber","hex":"0x00"},"creator":"0x2486Cf6C788890885D71667BBCCD1A783131547D"}
Your balance is 999.9999
How much do you want to wager?
10
What hand will you play?
r
You played Rock
The outcome is: Bob wins
Your balance is now 989.9999
另一个实例
$ make run-bob
Are you Alice?
n
Starting Rock, Paper, Scissors as Bob
Would you like to create an account? (only possible on devnet)
y
Do you want to deploy the contract? (y/n)
n
Please paste the contract information:
{"address":"0xc2a875afbdFb39b1341029A7deceC03750519Db6","creation_block":18,"args":[],"value":{"type":"BigNumber","hex":"0x00"},"creator":"0x2486Cf6C788890885D71667BBCCD1A783131547D"}
Your balance is 1000
Do you accept the wager of 10?
y
What hand will you play?
p
You played Paper
The outcome is: Bob wins
Your balance is now 1009.9999
当然,运行时准确的数字和地址可能不同。
如果要在 Algorand 上测试和运行我们的应用,而不是在以太坊上运行,我们只要编辑 tut-8/docker-compose.yml,然后将第 24 行中的&default-app
移动到第 51 行,这样就可以了。
现在,我们的剪刀石头布应用完成了,而且我们不会遭受攻击,超时,平手的影响,同时我们可以在非测试网络上交互运行。
在这一步中,我们为 Reach 程序创建了一个命令行界面。在下一步中,我们将进一步做出一个 Web 界面。
如果你的版本不能正常运行,看看完整的版本: tut-8/index.rsh, tut-8/index.mjs, tut-8/package.json, tut-8/Dockerfile, tut-8/docker-compose.yml 和 tut-8/Makefile ,并确保您完整地复制了所有内容!
我们可能还需要更改 tut-8/index.rsh 的第 32 行,将 deadline 定义为更大的数字,比如 30 。这是因为 Algorand 不支持 input-enabled 开发网络,在交易出现时只运行轮次,所以超时可能会意外地发生。这种情况在 CPU 负载过高的机器上经常发生。
您知道了吗? :
是非题 : Reach 帮助你为去中心化的应用程序构建自动化测试,但它不支持构建交互式用户界面
答案 : Reach 不会对添加到 Reach 应用程序的前端类型施加任何限制。
上一篇 : 2.7 平局则继续比赛直至判断胜负
下一篇 : 2.9 网页交互