在本文中将实现一个简单的以太坊猜拳小游戏, 就是石头剪刀布游戏。实现的功能很简单,就是在页面上选择石头剪刀布中的一个手势,然后和电脑随机选择的一个手势通过智能合约来返回游戏结果。效果如下:
一、初始化项目
参照上一篇中的步骤新建一个项目,目录结构如下:
.
├── 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的,如下图:
最少输入0.1GWEI, 输入后点击“SUBMIT”按钮就可以看到执行结果。