eth实战项目游戏开发 TICTACTOE 二 https://blog.csdn.net/bondsui/article/details/85755404
github代码在文末
猜棋牌类游戏,将棋盘设置为3*3,率先连城直线的玩家获胜,每局1eth赌金,赢家赢得所有赌金,平局退回赌金
游戏创建成功,对方输入地址加入游戏
游戏开始,提示下一个玩家下棋
错误处理:等待对方下子、已经有棋子位置不可以下子
率先完成直连,游戏结束
游戏demo仅供参考学习,该类游戏并不能进行商用,游戏涉及到的未实现部分及其他功能自行扩展
truffle unbox react
在contracts目录下新建 TicTacToe.sol
创建棋盘,使用二维数组【3】【3】表示,值为玩家1、2的地址
添加获取棋盘状态函数
pragma solidity ^0.4.24;
contract TicTacToe {
// 每局1eth赌金
uint constant GAME_COST = 1 ether;
uint8 constant GAME_BOARD_SIZE = 3;
address[GAME_BOARD_SIZE][GAME_BOARD_SIZE] public board; //游戏面板
address public player1;
address public player2;
constructor() public payable{
require(msg.value == GAME_COST);
player1 = msg.sender;
}
//获取游戏面板
function getBoard() public view returns (address[GAME_BOARD_SIZE][GAME_BOARD_SIZE]) {
return board;
}
}
加入游戏函数,添加关键字payable接受转账
限制条件:金额,玩家2!=0的时候
游戏开始,添加变量gameActive=true
设置谁先开始游戏,随机函数
address public activePlayer; //当前玩家
// 加入游戏
function joinGame() public payable {
require(msg.value == GAME_COST);
require(player1 != msg.sender);
require(player2 == address(0));
player2 = msg.sender;
gameActive = true;
//设置随机玩家
if(block.number % 2 == 0) {
activePlayer = player2;
} else {
activePlayer = player1;
}
}
以坐标设置棋子,将棋盘xy位置设置为 msg.sender
条件判断:
function setPosition(uint8 x, uint8 y) public {
// 空位置
require(board[x][y] == address(0));
//当前玩家
require(msg.sender == activePlayer);
// 正确的位置
assert(x < GAME_BOARD_SIZE && y < GAME_BOARD_SIZE);
// 游戏开始中
assert(gameActive);
board[x][y] = msg.sender;
}
每次下棋子,都应判断是否获胜、或者平局
获胜判断:率先直连,分步骤判断。行,列,对角线,反对角线
平局判断规则:总步数等于棋盘长度
function setPosition(uint8 x, uint8 y) public {
require(board[x][y] == address(0));
assert(x < GAME_BOARD_SIZE && y < GAME_BOARD_SIZE);
// 正确的位置
assert(gameActive);
board[x][y] = msg.sender;
steps++;
// 规则判断
// 行 00,01,02
for (uint8 i = 0; i < GAME_BOARD_SIZE; i++) {
// 如果不是activePlayer,说明未获胜
if (board[x][i] != activePlayer) {
break;
}
// 如果本行都是当前玩家 00 01 02值都为player
if (i == GAME_BOARD_SIZE - 1) {
setWinner(activePlayer);
return;
}
}
// 列规则和行一样
// 列 01,11,21
for (i = 0; i < GAME_BOARD_SIZE; i++) {
if (board[i][y] != activePlayer) {
break;
}
if (i == GAME_BOARD_SIZE - 1) {
setWinner(activePlayer);
return;
}
}
// 对角线 00,11,22
if (x == y) {
for (i = 0; i < GAME_BOARD_SIZE; i++) {
if (board[i][i] != activePlayer) {
break;
}
// win 如果22 = player,获胜
if (i == GAME_BOARD_SIZE - 1) {
setWinner(activePlayer);
return;
}
}
}
// 反对角线 02,11,20
if (x + y == GAME_BOARD_SIZE - 1) {
for (i = 0; i < GAME_BOARD_SIZE; i++) {
if (board[i][GAME_BOARD_SIZE - i - 1] != activePlayer) {
break;
}
// win 如果20等于player获胜
if (i == GAME_BOARD_SIZE - 1) {
setWinner(activePlayer);
return;
}
}
}
// 平局
if (steps == GAME_BOARD_SIZE * GAME_BOARD_SIZE) {
// 提现
setDraw();
return ;
}
// 设置下一个玩家
if (msg.sender == player1) {
activePlayer = player2;
} else {
activePlayer = player1;
}
}
function setWinner(address player) private {
gameActive = false;
uint payBalance = address(this).balance;
player.transfer(payBalance);
}
英文翻译解释
someAddress.send()
and someAddress.transfer()
are considered safe against reentrancy. While these methods still trigger code execution, the called contract is only given a stipend of 2,300 gas which is currently only enough to log an event.x.transfer(y)
is equivalent to require(x.send(y))
, it will automatically revert if the send fails.someAddress.call.value(y)()
will send the provided ether and trigger code execution. The executed code is given all available gas for execution making this type of value transfer unsafe against reentrancy.用户已经赢得胜利,如果因网络等原因失败,用户本应该赢得100eth赌金,因transfer交易失败导致回滚,使用send,并处理失败后的处理。
function setWinner(address player) private {
gameActive = false;
// 转账 如果发送失败,允许用户提现
if (!player.send(this.balance)) {
if (player == player1) {
withDrawBalance1 = this.balance;
} else {
withDrawBalance2 = this.balance;
}
}
}
// 设置平局
function setDraw() private {
gameActive = false;
if (!player1.send(GAME_COST)) {
withDrawBalance1 = GAME_COST;
}
if (!player2.send(GAME_COST)) {
withDrawBalance2 = GAME_COST;
}
}
条件:balance>0,这里可以使用transfer。思考为什么?
// 允许用户提现
function withdraw() public {
if (msg.sender == player1) {
// 先修改状态,在transfer
require(withDrawBalance1 > 0);
withDrawBalance1 = 0;
player1.transfer(withDrawBalance1);
} else if (msg.sender == player2) {
require(withDrawBalance2 > 0);
withDrawBalance2 = 0;
player2.transfer(withDrawBalance2);
}
}
如果玩家2即将要输,玩家2估计不执行下子操作或者玩家2离开游戏,怎么办?
考虑到网络的延迟 ,可自定义延迟时长
// 超时时间,3分钟不处理,判断该玩家失败,每次设置棋子后,都应该更新该时间
uint constant TIME_INTERVAL = 3 minutes;
uint timeValid;
constructor() public payable{
require(msg.value == GAME_COST);
player1 = msg.sender;
// 超时时间判断
timeValid = now + TIME_INTERVAL;
}
// 加入游戏
function joinGame() public payable {
require(msg.value == GAME_COST);
require(player1 != msg.sender);
require(player2 == address(0));
player2 = msg.sender;
gameActive = true;
}
function setPosition(uint8 x, uint8 y) public {
//时间判断
require(timeValid > now);
timeValid = now + TIME_INTERVAL;
...
}
合约和前端页面进行交互,游戏开始结束通知,下一个玩家通知等。
通过合约发送事件,在前端监听(watch),处理不同的事件进行页面交互
定义事件
event GameStart(address player1,address player2); // 玩家加入事件
event NextPlayer(address nextPlayer); // 下一个玩家
event GameOver(address winner); // 游戏结束事件
event Withdraw(address to, uint balance); // 支付成功
在游戏玩家2加入时,发送游戏开始事件和下一个玩家事件
// 加入游戏
function joinGame() public payable {
require(msg.value == GAME_COST);
require(player1 != msg.sender);
require(player2 == address(0));
player2 = msg.sender;
gameActive = true;
// 发送消息
emit GameStart(player1,msg.sender);
//时间判断
timeValid = now + TIME_INTERVAL;
//设置随机玩家
if(block.number % 2 == 0) {
activePlayer = player2;
} else {
activePlayer = player1;
}
// 发送下个玩家事件
emit NextPlayer(activePlayer);
}
设置棋子最后发送下一个玩家事件
function setPosition(uint8 x, uint8 y) public {
...
emit NextPlayer(activePlayer);
}
设置winner时,发送游戏结束事件
function setWinner(address player) private {
gameActive = false;
// 发送消息
emit GameOver(player);
...
}
// 设置平局
function setDraw() private {
gameActive = false;
emit GameOver(0);
...
}
如果player1 即将要输,player1停止玩游戏。player2 无法赢得赌金。
function drawback() public {
require(timeValid < now); // 超时之后可以提现
if (!gameActive) {
// 如果游戏没开始,退款给创建者
setWinner(player1);
}else{
//如果已经开始, 平局退款流程
setDraw();
// TODO 恶意退出游戏,应该退全部赌给该赢的玩家或者自定义
}
}
pragma solidity ^0.4.24;
contract TicTacToe {
uint constant GAME_COST = 1 ether;
uint8 constant GAME_BOARD_SIZE = 3;
uint constant TIME_INTERVAL = 1 minutes;
address[GAME_BOARD_SIZE][GAME_BOARD_SIZE] public board; //游戏面板
address public player1;
address public player2;
address public activePlayer; //当前玩家
bool gameActive = false; // 游戏是否开始
uint steps = 0; //游戏步数
uint withDrawBalance1; //用户1余额
uint withDrawBalance2; //用户2余额
uint timeValid;
event GameStart(address player1,address player2); // 玩家加入事件
event NextPlayer(address nextPlayer); // 下一个玩家
event GameOver(address winner); // 游戏结束事件
event Withdraw(address to, uint balance); // 支付成功
constructor() public payable{
require(msg.value == GAME_COST);
player1 = msg.sender;
// 超时时间判断
timeValid = now + TIME_INTERVAL;
}
// 加入游戏
function joinGame() public payable {
require(msg.value == GAME_COST);
require(player1 != msg.sender);
require(player2 == address(0));
player2 = msg.sender;
gameActive = true;
// 发送消息
emit GameStart(player1,msg.sender);
//时间判断
timeValid = now + TIME_INTERVAL;
//设置随机玩家
if(block.number % 2 == 0) {
activePlayer = player2;
} else {
activePlayer = player1;
}
emit NextPlayer(activePlayer);
}
//获取游戏面板
function getBoard() public view returns (address[GAME_BOARD_SIZE][GAME_BOARD_SIZE]) {
return board;
}
function setPosition(uint8 x, uint8 y) public {
// 该位置未设置过
require(board[x][y] == address(0));
// 时间判断
require(timeValid > now);
//当前玩家
require(msg.sender == activePlayer);
// 正确的位置
assert(x < GAME_BOARD_SIZE && y < GAME_BOARD_SIZE);
// 游戏开始中
assert(gameActive);
board[x][y] = msg.sender;
steps++;
//时间判断
timeValid = now + TIME_INTERVAL;
// 行 00,01,02
for (uint8 i = 0; i < GAME_BOARD_SIZE; i++) {
if (board[x][i] != activePlayer) {
break;
}
if (i == GAME_BOARD_SIZE - 1) {
setWinner(activePlayer);
return;
}
}
// 列 01,11,21
for (i = 0; i < GAME_BOARD_SIZE; i++) {
if (board[i][y] != activePlayer) {
break;
}
if (i == GAME_BOARD_SIZE - 1) {
setWinner(activePlayer);
return;
}
}
// 对角线 00,11,22
if (x == y) {
for (i = 0; i < GAME_BOARD_SIZE; i++) {
if (board[i][i] != activePlayer) {
break;
}
// win
if (i == GAME_BOARD_SIZE - 1) {
setWinner(activePlayer);
return;
}
}
}
// 反对角线 02,11,20
if (x + y == GAME_BOARD_SIZE - 1) {
for (i = 0; i < GAME_BOARD_SIZE; i++) {
if (board[i][GAME_BOARD_SIZE - i - 1] != activePlayer) {
break;
}
// win
if (i == GAME_BOARD_SIZE - 1) {
setWinner(activePlayer);
return;
}
}
}
// 平局
if (steps == GAME_BOARD_SIZE * GAME_BOARD_SIZE) {
// 提现
setDraw();
return ;
}
if (msg.sender == player1) {
activePlayer = player2;
} else {
activePlayer = player1;
}
emit NextPlayer(activePlayer);
}
function setWinner(address player) private {
gameActive = false;
// 发送消息
emit GameOver(player);
// 转账 如果发送失败,允许用户提现
uint payBalance = address(this).balance;
if (!player.send(payBalance)) {
if (player == player1) {
withDrawBalance1 = payBalance;
} else {
withDrawBalance2 = payBalance;
}
} else {
emit Withdraw(player, payBalance);
}
}
// 设置平局
function setDraw() private {
gameActive = false;
emit GameOver(0);
uint payBalance = address(this).balance / 2;
// 用户1提现
if (!player1.send(GAME_COST)) {
withDrawBalance1 = payBalance;
} else {
emit Withdraw(player1, payBalance);
}
// 用户2 提现
if (!player2.send(GAME_COST)) {
withDrawBalance2 = payBalance;
} else {
emit Withdraw(player2, payBalance);
}
}
// 允许用户提现
function withdraw() public {
if (msg.sender == player1) {
// 先修改状态,在transfer
require(withDrawBalance1 > 0);
withDrawBalance1 = 0;
player1.transfer(withDrawBalance1);
// 提现消息
emit Withdraw(player1, withDrawBalance1);
} else if (msg.sender == player2) {
require(withDrawBalance2 > 0);
withDrawBalance2 = 0;
player2.transfer(withDrawBalance2);
emit Withdraw(player2, withDrawBalance2);
}
}
function drawback() public {
require(timeValid < now); // 超时之后可以提现
if (!gameActive) {
// 如果游戏没开始,退款给创建者
setWinner(player1);
}else{
//如果已经开始, 平局退款流程
setDraw();
// TODO 恶意退出游戏,应该退全部赌给该赢的玩家
}
}
}
转载请说明出处
代码地址:https://github.com/bigsui/eth-game-tictactoe
联系邮箱:[email protected]