eth dapp,前端部分,使用使用truffle 框架,前端部分使用react,eht交互库truffle-contract
合约部分请点击 https://blog.csdn.net/bondsui/article/details/85755186
github代码在文末
github,全球最大的代码库,你想要的基本都有。
TicTacToe,本例中使用react,直接搜索 TicTacToe react,按照starts排序
➜ temp git clone [email protected]:trihargianto/reactjs-tictactoe.git
➜ temp cd reactjs-tictactoe
➜ npm install
➜ npm start
效果图,自带动画,重点查看点击事件
render() {
const indexSquares = [0,1,2,3,4,5,6,7,8];
var squares = indexSquares.map(function(indexSquare, i) {
return (<Square
value={this.state.squares[i]}
key={i}
index={i}
winner={this.state.winner}
xIsNext={this.state.xIsNext}
onClick={this.handleOnClick.bind(this)} />)
},this)
return (
<div>
<h1 style={{textAlign: 'center', fontSize: '46px', color: 'rgba(52, 152, 219,1.0)'}} className="animated flipInY">Tic-Tac-Toe</h1>
<h3 style={{textAlign: 'center'}} id="titlePemenang">{this.state.winner !== null ? <span>Pemenangnya <b>{this.state.winner}</b></span> : ""}</h3>
<div className="container animated fadeInUp">
<div className="row">
<br />
<div className="col-xs-12">{squares}</div>
</div>
</div>
<br />
{this.state.winner !== null || this.state.full === true ? <ResetButton onClick={this.handleResetGame.bind(this)} /> : ""}
</div>
)
}
}
拷贝下载下来的git项目(别忘了css文件一同拷贝),安装包
"bootstrap": "^3.3.7",
"jquery": "^3.2.1",
"react": "^15.4.2",
"react-dom": "^15.4.2",
运行项目,查看页面
略
通过代码,得知,原页面的棋盘的值为0-8,我们改为[[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]],每个value即棋盘的坐标。同时更改Square的handleClick,把值传递到页面。
运行项目,点击第一个模块,显示log 0,1
剩下的就是与合约交互。
class Board extends React.Component {
constructor() {
super();
this.state = {
squares : Array(9).fill(null),
xIsNext : true,
winner : null,
full : false
}
}
handleOnClick(index, turn) {
console.log(index,turn)
}
handleResetGame() {
window.location.reload()
}
render() {
const indexSquares = [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]];
var squares = indexSquares.map(function(indexSquare, i) {
return (<Square
value={this.state.squares[i]}
key={i}
index={i}
winner={this.state.winner}
xIsNext={this.state.xIsNext}
onClick={this.handleOnClick.bind(this)} />)
},this)
return (
<div>
<h1 style={{textAlign: 'center', fontSize: '46px', color: 'rgba(52, 152, 219,1.0)'}} className="animated flipInY">Tic-Tac-Toe</h1>
<h3 style={{textAlign: 'center'}} id="titlePemenang">{this.state.winner !== null ? <span>Pemenangnya <b>{this.state.winner}</b></span> : ""}</h3>
<div className="container animated fadeInUp">
<div className="row">
<br />
<div className="col-xs-12">{squares}</div>
</div>
</div>
<br />
{this.state.winner !== null || this.state.full === true ? <ResetButton onClick={this.handleResetGame.bind(this)} /> : ""}
</div>
)
}
}
"truffle-contract": "^3.0.7",
"web3": "^1.0.0-beta.37",
"xmlhttprequest": "^1.8.0"
补充页面按钮,更新square
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import 'bootstrap/dist/css/bootstrap.min.css'
import './animate.css'
import Square from './Square'
import MenuButtons from './MenuButtons'
import getWeb3 from "./utils/getWeb3"
import contract from 'truffle-contract'
import _TicTacToe from './contracts/TicTacToe.json'
// 棋盘值
const STONES = [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]];
const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000"
const GAME_COST = "1"; //1 eth
let web3 = null
class Board extends React.Component {
state = {
accounts: null, // 账户
// 棋盘相关
instance: null, // 游戏实例
board: [['', '', ''], ['', '', ''], ['', '', '']],
player1: '', // 玩家一,等于创建者
player2: '', // 玩家二,
nextPlayer: '',// 该谁下棋
winner: '',
gameResult: '', // 游戏结果,
}
// 获取合约
getTicTacToe = () => {
let TicTacToe = contract(_TicTacToe)
TicTacToe.setProvider(web3.currentProvider);
return TicTacToe
}
componentDidMount = async () => {
try {
web3 = await getWeb3();
console.log("currentProvider", web3.currentProvider)
const accounts = await web3.eth.getAccounts();
console.log("accounts", accounts)
this.setState({accounts});
} catch (error) {
alert(`web3 加载失败`,);
console.error(error);
}
}
componentWillUnmount() {
this.unRegistNextPlayerEvent()
}
// 点击创建游戏
onCreateGameClick = () => {
console.log("创建游戏")
}
// 加入游戏
onJoinGameClick = async () => {
console.log("加入游戏")
}
// 更新游戏面板
updateBoard = () => {
console.log("更新面板")
}
// 设置棋盘
onStoneClick = (stone) => {
console.log("设置棋盘",stone)
}
// 重置游戏
onResetGameClick = () => {
window.location.reload()
}
// 设置赢家
setWinner = (winner) => {
let gameResult = ""
if (winner === this.state.player1) {
gameResult = "恭喜胜利,再来一局"
} else if (winner === this.state.player2) {
gameResult = "失败,再接再厉"
} else {
gameResult = "旗鼓相当,平局,再来一局"
}
this.setState({gameResult})
}
// 00 01 02
// 10 11 12
// 20 21 22
render() {
let {accounts, board, player1, player2, nextPlayer, instance, winner} = this.state
let gameAddress = instance == null ? "" : instance.address
console.log("state", this.state)
console.log("", instance)
if (winner != ''){
winner = winner == EMPTY_ADDRESS ? "平局,退回赌金" : "赢家为:"+winner
}
let squares = STONES.map((stone, i) => {
return (<Square
key={i}
stone={stone}
board={board}
account={accounts == null ? null : accounts[0]}
nextPlayer={nextPlayer}
onClick={this.onStoneClick}/>)
})
return (
<div style={{textAlign: 'center'}}>
{/*标题*/}
<h2 style={{fontSize: '42px', color: "red"}}
className="animated flipInY">性感荷官 在线发牌
</h2>
{/* 游戏信息展示*/}
<div>
<h5>当前用户:{accounts == null ? "未检测到" : accounts[0]}</h5>
<h5>游戏地址:{gameAddress == "" ? "等待创建" : gameAddress}</h5>
<h5>player1:{player1 == "" ? "等待加入" : player1}</h5>
<h5>player2:{player2 == "" ? "等待加入" : player2}</h5>
<h5>nextPlayer:{player2 == "" ? "游戏未开始" :
<span style={{color: "red"}}>{nextPlayer}</span>}</h5>
<h3 id="gameResult">
<span style={{fontSize: '32px', color: "red"}}>
<b>{winner}</b></span>
</h3>
</div>
{/*棋盘*/}
<div className="container animated fadeInUp">
<div className="row">
<div className="col-xs-12">{squares}</div>
</div>
</div>
<br/>
<MenuButtons
onCreateGameClick={this.onCreateGameClick}
onJoinGameClick={this.onJoinGameClick}
onResetGameClick={this.onResetGameClick}/>
</div>
)
}
}
ReactDOM.render(
<Board/>,
document.getElementById('root')
);
Square.js
es6语法普及,封包与解包
import React from 'react';
const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000"
export default class Square extends React.Component {
constructor(props) {
super(props)
// es6 语法
// 本页面要设置状态,用户点击,提前显示该位置内容,避免等待,如不需要否则可直接使用props或者无状态组件
// 保存所有属性到状态变量
this.state = {...props}
// 等价
// let {key,stone,board,account,nextPlayer,onClick}=props
// this.state = {key,stone,board,account,nextPlayer,onClick}
console.log(this.state)
}
componentWillReceiveProps(nextProps) {
this.state = {...nextProps}
}
// 状态变量未Object,可以不写默认值,
state = {}
// 点击事件
onItemClick(event) {
let errMsg = this.mustCheck()
if (errMsg != "") {
event.target.className += " animated shake";
alert(errMsg)
return
}
// 引用点击
this.state.onClick(this.state.stone);
// 设置 修改
event.target.className += " animated rubberBand square-container-active";
}
// 下棋校验,返回错误信息,没有返回空
mustCheck() {
let {account, nextPlayer} = this.state
console.log(this.state)
if (this.state.account == null) {
return "用户未登录"
}
if (nextPlayer == '') {
return "游戏未开始"
}
if (account.toLowerCase() != nextPlayer.toLowerCase()) {
return "等待对方下子"
}
if (this.getHtmlValue() != '') {
return "这里不能下子"
}
return ""
}
// 获取该位置的文本显示
getHtmlValue() {
let {stone, board, account,nextPlayer} = this.state
// 获取该位置的地址值
let x = stone[0]
let y = stone[1]
let stoneValue = board[x][y]
// 未登录显示空
if (account == null||nextPlayer==""||stoneValue==EMPTY_ADDRESS||stoneValue==""){
return ""
}
console.log(x,y,stoneValue==EMPTY_ADDRESS,stoneValue)
// 自己显示X,否则显示O
return stoneValue.toString().toLowerCase() == account.toString().toLowerCase() ? 'X' : 'O'
}
render() {
let stoneHtmlValue = this.getHtmlValue()
return (
<div onClick={this.onItemClick.bind(this)} className="col-xs-4 square-container">
{stoneHtmlValue}
</div>
)
}
}
第一种方式GameOver.watch(function(error,event){})
第二种方式allEvents(function(error,event){})
第三种方式 调用函数返回值的logs字段
第四中,web3.js 中subscribe
// 点击创建游戏,监听合约事件
// 通过打印instance,可以看到合约中的每个事件,都提供了一个方法
onCreateGameClick = () => {
const {accounts} = this.state
let TicTacToe = this.getTicTacToe()
TicTacToe.new({from: accounts[0], value: web3.utils.toWei(GAME_COST, "ether")})
.then(instance => {
console.log("instance", instance)
this.setState({instance, player1: accounts[0]})
this.registEvent()
alert("游戏创建成功,邀请好友\n" + instance.address)
})
.catch(error => {
console.log("new TicTacToe", error)
})
}
// 注册事件,监听所有(也可以单独监听)
registEvent = () => {
let {instance, accounts} = this.state
instance.allEvents( (err,data)=> {
console.log(err,data)
let {event, args} = data
if (event == "GameStart") {
alert("游戏即将开始\n" + "玩家1: " + args.player1 + "\n玩家2: " + args.player2)
this.setState({...args})
} else if (event == "NextPlayer") {
alert("下一个玩家\n" + args.nextPlayer)
this.updateBoard()
this.setState({...args})
} else if (event == "GameOver") {
this.setState({...args})
alert("GameOver")
} else if (event == "Withdraw") {
alert("游戏结束\n已向" + args.to + "转入赌金" + args.balance)
}
})
}
// 加入游戏
onJoinGameClick = async () => {
let gameAddress = prompt("请输入游戏地址")
if (gameAddress === "") {
return
}
let {accounts} = this.state;
let TicTacToe = this.getTicTacToe()
try {
// 获取合约实例
let instance = await TicTacToe.at(gameAddress)
console.log("at", instance == null, instance)
// 更新状态,监听事件
this.setState({instance})
this.registEvent()
alert("游戏加载成功,加入赌金立即开始游戏")
let result = await instance.joinGame({
from: accounts[0],
value: web3.utils.toWei(GAME_COST, 'ether')
})
} catch (e) {
console.log("join error", e)
this.setState({instance: null})
}
}
// 更新游戏面板
updateBoard = () => {
let {instance, accounts} = this.state
instance.getBoard.call({from: accounts[0]})
.then(board => {
// 更新面板
this.setState({board})
console.log("updateBoard", board)
})
.catch(error => {
console.error("updateBoard", error)
})
}
事件方式3,通过调用函数的返回result,其中的字段logs 封装了该函数调用产生的logs
// 设置棋盘
onStoneClick = (stone) => {
console.log("onStoneClick", stone)
let {instance, accounts} = this.state
instance.setPosition(stone[0], stone[1], {from: accounts[0]})
.then(result => {
console.log("setStone", result)
// this.handleLog(result.logs)
// 通过事件,也可以直接处理log
})
.catch(error => {
console.error("setStone", error)
})
}
// 设置赢家或者平局
setWinner = (winner) => {
let gameResult = ""
if (winner === this.state.player1) {
gameResult = "恭喜胜利,再来一局"
} else if (winner === this.state.player2) {
gameResult = "失败,再接再厉"
} else {
gameResult = "旗鼓相当,平局,再来一局"
}
this.setState({gameResult})
}
4.9 监听event方式2
// 下一个玩家,更新页面
registNextPlayerEvent = () => {
let {instance} = this.state
let nextPlayerEvent = instance.NextPlayer()
// 下一个是一个状态 需保存到state
this.setState(nextPlayerEvent)
event.watch((error, event) => {
console.log("event---NextPlayer")
if (error == null) {
console.log("NextPlayer", event)
} else {
console.error("event", error)
}
})
}
// 取消下一个玩家监听
unRegistNextPlayerEvent = () => {
let {nextPlayerEvent} = this.state
if (nextPlayerEvent != null) {
nextPlayerEvent.stopWatching()
}
}
// 游戏结束事件,只需监听一次
registGameOverEvent = () => {
let {instance} = this.state
let event = instance.GameOver()
event.watch((error, event) => {
console.log("GameOverEvent", event)
if (error == null) {
event.stopWatching()
this.unRegistNextPlayerEvent()
} else {
console.error("event", error)
}
})
}
// 游戏结束提现事件 ,只需一次监听
registWithdrawEvent = () => {
let {instance} = this.state
let event = instance.WithdrawEvent()
event.watch((error, event) => {
console.log("WithdrawEvent")
if (error == null) {
alert("有钱到账了")
event.stopWatching()
console.log("WithDrawEvent", event)
} else {
console.error("event", error)
}
})
}
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import 'bootstrap/dist/css/bootstrap.min.css'
import './animate.css'
import Square from './Square'
import MenuButtons from './MenuButtons'
import getWeb3 from "./utils/getWeb3"
import contract from 'truffle-contract'
import _TicTacToe from './contracts/TicTacToe.json'
// 棋盘值
const STONES = [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]];
const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000"
const GAME_COST = "1"; //1 eth
let web3 = null
class Board extends React.Component {
state = {
accounts: null, // 账户
// 棋盘相关
instance: null, // 游戏实例
board: [['', '', ''], ['', '', ''], ['', '', '']],
player1: '', // 玩家一,等于创建者
player2: '', // 玩家二,
nextPlayer: '',// 该谁下棋
winner: '',
gameResult: '', // 游戏结果,
}
// 获取合约
getTicTacToe = () => {
let TicTacToe = contract(_TicTacToe)
TicTacToe.setProvider(web3.currentProvider);
return TicTacToe
}
componentDidMount = async () => {
try {
web3 = await getWeb3();
console.log("currentProvider", web3.currentProvider)
const accounts = await web3.eth.getAccounts();
console.log("accounts", accounts)
this.setState({accounts});
} catch (error) {
alert(`web3 加载失败`,);
console.error(error);
}
}
componentWillUnmount() {
this.unRegistNextPlayerEvent()
}
// 点击创建游戏
onCreateGameClick = () => {
const {accounts} = this.state
let TicTacToe = this.getTicTacToe()
TicTacToe.new({from: accounts[0], value: web3.utils.toWei(GAME_COST, "ether")})
.then(instance => {
console.log("instance", instance)
this.setState({instance, player1: accounts[0]})
this.registEvent()
alert("游戏创建成功,邀请好友\n" + instance.address)
})
.catch(error => {
console.log("new TicTacToe", error)
})
}
// 加入游戏
onJoinGameClick = async () => {
let gameAddress = prompt("请输入游戏地址")
if (gameAddress === "") {
return
}
let {accounts} = this.state;
let TicTacToe = this.getTicTacToe()
try {
// 获取合约实例
let instance = await TicTacToe.at(gameAddress)
console.log("at", instance == null, instance)
// 更新状态,监听事件
this.setState({instance})
this.registEvent()
alert("游戏加载成功,加入赌金立即开始游戏")
let result = await instance.joinGame({
from: accounts[0],
value: web3.utils.toWei(GAME_COST, 'ether')
})
} catch (e) {
console.log("join error", e)
this.setState({instance: null})
}
}
// 更新游戏面板
updateBoard = () => {
let {instance, accounts} = this.state
instance.getBoard.call({from: accounts[0]})
.then(board => {
// 更新面板
this.setState({board})
console.log("updateBoard", board)
})
.catch(error => {
console.error("updateBoard", error)
})
}
// 设置棋盘
onStoneClick = (stone) => {
console.log("onStoneClick", stone)
let {instance, accounts} = this.state
instance.setPosition(stone[0], stone[1], {from: accounts[0]})
.then(result => {
console.log("setStone", result)
// this.handleLog(result.logs)
// 通过事件,也可以直接处理log
})
.catch(error => {
console.error("setStone", error)
})
}
// 重置游戏
onResetGameClick = () => {
window.location.reload()
}
// 设置赢家
setWinner = (winner) => {
let gameResult = ""
if (winner === this.state.player1) {
gameResult = "恭喜胜利,再来一局"
} else if (winner === this.state.player2) {
gameResult = "失败,再接再厉"
} else {
gameResult = "旗鼓相当,平局,再来一局"
}
this.setState({gameResult})
}
// 注册事件,监听所有(也可以单独监听)
registEvent = () => {
let {instance, accounts} = this.state
instance.allEvents( (err,data)=> {
console.log(err,data)
let {event, args} = data
if (event == "GameStart") {
alert("游戏即将开始\n" + "玩家1: " + args.player1 + "\n玩家2: " + args.player2)
this.setState({...args})
} else if (event == "NextPlayer") {
alert("下一个玩家\n" + args.nextPlayer)
this.updateBoard()
this.setState({...args})
} else if (event == "GameOver") {
this.setState({...args})
alert("GameOver")
} else if (event == "Withdraw") {
alert("游戏结束\n已向" + args.to + "转入赌金" + args.balance)
}
})
}
// 下一个玩家,更新页面
registNextPlayerEvent = () => {
let {instance} = this.state
let nextPlayerEvent = instance.NextPlayer()
// 下一个是一个状态 需保存到state
this.setState(nextPlayerEvent)
event.watch((error, event) => {
console.log("event---NextPlayer")
if (error == null) {
console.log("NextPlayer", event)
} else {
console.error("event", error)
}
})
}
// 取消下一个玩家监听
unRegistNextPlayerEvent = () => {
let {nextPlayerEvent} = this.state
if (nextPlayerEvent != null) {
nextPlayerEvent.stopWatching()
}
}
// 游戏结束事件,只需监听一次
registGameOverEvent = () => {
let {instance} = this.state
let event = instance.GameOver()
event.watch((error, event) => {
console.log("GameOverEvent", event)
if (error == null) {
event.stopWatching()
this.unRegistNextPlayerEvent()
} else {
console.error("event", error)
}
})
}
// 游戏结束提现事件 ,只需一次监听
registWithdrawEvent = () => {
let {instance} = this.state
let event = instance.WithdrawEvent()
event.watch((error, event) => {
console.log("WithdrawEvent")
if (error == null) {
alert("有钱到账了")
event.stopWatching()
console.log("WithDrawEvent", event)
} else {
console.error("event", error)
}
})
}
// 00 01 02
// 10 11 12
// 20 21 22
render() {
let {accounts, board, player1, player2, nextPlayer, instance, winner} = this.state
let gameAddress = instance == null ? "" : instance.address
console.log("state", this.state)
console.log("", instance)
if (winner != ''){
winner = winner == EMPTY_ADDRESS ? "平局,退回赌金" : "赢家为:"+winner
}
let squares = STONES.map((stone, i) => {
return (<Square
key={i}
stone={stone}
board={board}
account={accounts == null ? null : accounts[0]}
nextPlayer={nextPlayer}
onClick={this.onStoneClick}/>)
})
return (
<div style={{textAlign: 'center'}}>
{/*标题*/}
<h2 style={{fontSize: '42px', color: "red"}}
className="animated flipInY">性感荷官 在线发牌
</h2>
{/* 游戏信息展示*/}
<div>
<h5>当前用户:{accounts == null ? "未检测到" : accounts[0]}</h5>
<h5>游戏地址:{gameAddress == "" ? "等待创建" : gameAddress}</h5>
<h5>player1:{player1 == "" ? "等待加入" : player1}</h5>
<h5>player2:{player2 == "" ? "等待加入" : player2}</h5>
<h5>nextPlayer:{player2 == "" ? "游戏未开始" :
<span style={{color: "red"}}>{nextPlayer}</span>}</h5>
<h3 id="gameResult">
<span style={{fontSize: '32px', color: "red"}}>
<b>{winner}</b></span>
</h3>
</div>
{/*棋盘*/}
<div className="container animated fadeInUp">
<div className="row">
<div className="col-xs-12">{squares}</div>
</div>
</div>
<br/>
<MenuButtons
onCreateGameClick={this.onCreateGameClick}
onJoinGameClick={this.onJoinGameClick}
onResetGameClick={this.onResetGameClick}/>
</div>
)
}
}
ReactDOM.render(
<Board/>,
document.getElementById('root')
);
剩余时间显示
玩家超时(或者故意退出)处理
页面刷新,加载游戏状态。
2名玩家,在一个10*10的棋盘,猜对方部署的飞机,优先全部猜中的获胜
eth游戏平台,玩家玩游戏,开发社申请发布游戏。
由于真实以太坊的延迟,并不适合实时性要求较强类的游戏
转载请说明出处
代码地址:https://github.com/bigsui/eth-game-tictactoe
联系邮箱:[email protected]