以太坊之猜拳小游戏

浏览: 3821

在本文中将实现一个简单的以太坊猜拳小游戏, 就是石头剪刀布游戏。实现的功能很简单,就是在页面上选择石头剪刀布中的一个手势,然后和电脑随机选择的一个手势通过智能合约来返回游戏结果。效果如下:

guess.gif

一、初始化项目

参照上一篇中的步骤新建一个项目,目录结构如下:

.
├── app
│ ├── app.js
│ ├── index.html
│ ├── js
│ │ ├── jquery.min.js
│ │ ├── truffle-contract.js
│ │ └── web3.min.js
│ └── package.json
├── contracts
│ ├── GuessGame.sol
│ └── Migrations.sol
├── migrations
│ ├── 1_initial_migration.js
│ └── 2_deploy_contracts.js
├── test
├── truffle-config.js
└── truffle.js

更新配置文件truffle.js为:

module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 8545,
network_id: "*" // Match any network id
}
}
};

二、实现智能合约

合约的功能是实现输入选项,返回游戏结果,代码如下:

pragma solidity ^0.4.16;


contract GuessGame {

// 定义一个事件,用来发送结果
event GuessResult(uint playerChoice, uint computerChoice, uint result);

// 处理游戏结果并返回
function play(uint playerChoice, uint computerChoice) public returns (bool) {
if (playerChoice > 0 && playerChoice <= 3 && computerChoice > 0 && computerChoice <= 3) {
// 如果两者相同,则代表平手
if (playerChoice == computerChoice) {
GuessResult(playerChoice, computerChoice, 1); // 回调1 代表平手
} else if (playerChoice == (computerChoice + 1) % 3) {
GuessResult(playerChoice, computerChoice, 2); // 回调2 代表电脑赢了
} else {
GuessResult(playerChoice, computerChoice, 3); // 回调3 代表玩家赢了
}
return true; //执行成功返回true
} else {
return false; // 执行错误返回false
}
}
}

这里需要注意的是, web3.js中调用play返回非常量的函数不能直接获取返回值,因为这个函数中会产生交易,所以这个函数直接返回的是产生交易的hash值,这个函数的返回值只能在智能合约中获取到。要在web3.js中获取返回需要通过事件这种方式:

event GuessResult(uint playerChoice, uint computerChoice, uint result);

事件event

在智能合约中,事件(event)是太坊虚拟机提供的一种操作日志的工具,也可以用来实现一些交互功能,比如通知UI,返回函数调用结果等。DApp开发中可以通过事件的方式,回调JavaScript中的监听函数。比如上面的play函数可以通过以下方式进行监听:

var event = guess_contract.GuessResult();
event.watch(function(err, result) {
if (!err) {
console.log(result);
} else {
console.log(err);
}
})

或者直接回调的方法

var event = guess_contract.GuessResult(function(err, result) {
if (!err) {
console.log(result);
} else {
console.log(err);
}
}

这样当智能合约中执行该事件时,上面的监听就会被调用。

实现完成就进行编译和部署.

$ truffle compile
Compiling ./contracts/GuessGame.sol...
Compiling ./contracts/Migrations.sol...
Writing artifacts to ./build/contracts

$ truffle migrate
Using network 'development'.

Running migration: 1_initial_migration.js
Deploying Migrations...
... 0x78c3d80f68b986265437c1cace4eecd7753dcb6195d0eaaebf6327d88eb6536f
Migrations: 0xa6b82506089e630976e6f45713d5b4214daa3217
Saving successful migration to network...
... 0x03d41abd82a95435b6f7b62f2ec9a3b64b73818a7975023b8eb62b4a9d8341bb
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying GuessGame...
... 0xee9295ec1205f149e9c5e89711c74b6e50ea0b4df4062bd5949e038d19153ee2
GuessGame: 0x9363055c516c2d85e890afad79516c18f21cff4f
Saving successful migration to network...
... 0x5d671936768913275ddc4006f03846f4c03a5e612664c324228dbfd1a723b4e0
Saving artifacts...

三、实现DApp

智能合约完成后就是实现一个DApp将之整合进来。先编写一个界面让用户输入,代码如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>DApp猜拳游戏</title>
<style>
*{margin:0; padding: 0; font-weight: 200;}
.player,.computer{
width: 50%;
float: left;
padding-top: 30px;
text-align: center
}
.player,.computer dt{
font-size: 28px;
}
.player img,.computer img{
margin-top: 30px;
width: 30%;
}
.player img{
transform:rotateY(180deg);
}
.select{
text-align: center;
font-size: 18px;
max-width: 800px;
margin: 0 auto;
padding-top: 2%;
}
.select dt{
width: 100%;
overflow: hidden;
line-height: 50px;
}
.select button{
width: 20%;
border:none;
color: #fff;
border-radius: 8px;
line-height: 45px;
margin: 0 5%;
outline: none;
font-size: 18px;
cursor: pointer;
}
#info{
width: 100%;
text-align: center;
overflow: hidden;
font-size: 25px;
line-height: 50px;
color: red;
padding-top: 2%;
opacity: 0;
}
</style>
</head>

<body>
<div class="computer">
<dl>
<dt>对手</dt>
<dd><img src="images/2.png" id="computer" alt=""></dd>
</dl>
</div>
<div class="player">
<dl>
<dt></dt>
<dd><img src="images/2.png" id="player" alt=""></dd>
</dl>
</div>
<div id="info">平手</div>
<div class="select">
<dl>
<dt>点击下列图标选择要出的选项:</dt>
<dd>
<button value="1"><img src='images/1.png' style="width:80px"></button>
<button value="2"><img src='images/2.png' style="width:80px"></button>
<button value="3"><img src='images/3.png' style="width:80px"></button>
</dd>
</dl>
</div>
</body>
<script src="js/jquery.min.js"></script>
<script src="js/web3.min.js"></script>
<script src="js/truffle-contract.js"></script>
<script src="app.js"></script>
</html>

接着在app.js中随机生成一个手势,然后调用智能合约,并监督智能合约的返回结果,之所以要用事件的方式获取返回值,是因为在web3.js不能直接获取智能合约中函数的返回值。代码如下:

window.onload = function() {
var web3, provider, guess_contract, guess_result, refresh_timer;

if (typeof window.web3 !== 'undefined') {
// 安装Metamask插件后web3已经定义在window对象下
web3 = new Web3(window.web3.currentProvider);
} else {
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}

// 获取智能合约的ABI(Application Binary Interface)文件
$.getJSON('GuessGame.json', function(data){
var GuessGameArtifact = data;

// 初始化智能合约
GuessGameContract = TruffleContract(GuessGameArtifact);
GuessGameContract.setProvider(web3.currentProvider);

// 通过默认的合约地址获取实例
GuessGameContract.deployed()
.then(function(instance){

guess_contract = instance;
guess_contract.GuessResult(function(err, result) {
if (!err) {
var player_choice = result.args.playerChoice.toNumber();
var computer_choice = result.args.computerChoice.toNumber();
var r = result.args.result.toNumber();
var info = "未知";
if(r == 1){
info = '平手';
}else if(r == 2){
info = '你输了';
}else if(r == 3){
info = '你赢了';
}
update_page(player_choice,computer_choice, info);
} else {
console.log(err);
}
});
}).catch(function(err){
console.log(err.message);
});
})

/*
更新页面显示
*/
function update_page(player,computer, result){
var info = document.getElementById('info');
var playerImg = document.getElementById('player');
var comImg = document.getElementById('computer');
info.style.opacity = '0';
clearInterval(refresh_timer);
playerImg.src = 'images/'+player+'.png';
comImg.src = 'images/'+computer+'.png';
info.style.opacity = 1;
info.innerText = result;

}

/*
猜拳
*/
function guess(player_choice){
// 1:石头、2:剪刀、3:布
var result;
player_choice = parseInt(player_choice);
computer_choice = parseInt(Math.random()*3)+1; // 电脑
document.getElementById('info').innerText = '';
guess_contract.play(player_choice, computer_choice).then(function(result){
if(result) {
var playerImg = document.getElementById('player');
var comImg = document.getElementById('computer');
refresh_timer = setInterval(function(){
this.n?this.n:this.n=1;this.n++
this.n>3?this.n=1:this.n;
playerImg.src = 'images/'+this.n+'.png';
comImg.src = 'images/'+this.n+'.png';
},100);
}
}).catch(function(err){
console.log(err.message);
})
}


// 绑定页面按钮事件
choices = $('button');
for(var i=0; i<choices.length; i++){
choices[i].onclick = function(){
guess(this.value);
}
}
};

完成后启动DApp.

$ npm run dev

访问localhost:3000开始玩游戏。在调用智能合约中的函数时时需要消耗Gas的,如下图:

gas_cost.png

最少输入0.1GWEI, 输入后点击“SUBMIT”按钮就可以看到执行结果。

推荐 3
本文由 101python 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

4 个评论

最后的一张图片没看到
请问哪张?
有一个小问题想请教一下,在链类中,增加区块时,如何绑定当前区块的父节点
请问一下package.json里的代码是什么呢?npm run dev里一直报错ENOENT: no such file or directory, open 'C:\Users\lenovo\GuessGame\package.json'

要回复文章请先登录注册