未验证 提交 38e7a078 编写于 作者: K Kevin Heifner 提交者: GitHub

Merge pull request #4130 from EOSIO/tic-tac-toe

Update tic tac toe contract to follow latest eosio framework
{
"____comment": "This file was generated by eosio-abigen. DO NOT EDIT - 2018-07-06T13:38:01",
"version": "eosio::abi/1.0",
"types": [{
"new_type_name": "account_name",
"type": "name"
}],
"types": [],
"structs": [{
"name": "game",
"base": "",
"fields": [
{"name":"challenger", "type":"account_name"},
{"name":"host", "type":"account_name"},
{"name":"turn", "type":"account_name"},
{"name":"winner", "type":"account_name"},
{"name":"board", "type":"uint8[]"}
"fields": [{
"name": "challenger",
"type": "name"
},{
"name": "host",
"type": "name"
},{
"name": "turn",
"type": "name"
},{
"name": "winner",
"type": "name"
},{
"name": "board",
"type": "uint8[]"
}
]
},{
"name": "create",
"base": "",
"fields": [
{"name":"challenger", "type":"account_name"},
{"name":"host", "type":"account_name"}
"fields": [{
"name": "challenger",
"type": "name"
},{
"name": "host",
"type": "name"
}
]
},{
"name": "restart",
"base": "",
"fields": [
{"name":"challenger", "type":"account_name"},
{"name":"host", "type":"account_name"},
{"name":"by", "type":"account_name"}
"fields": [{
"name": "challenger",
"type": "name"
},{
"name": "host",
"type": "name"
},{
"name": "by",
"type": "name"
}
]
},{
"name": "close",
"base": "",
"fields": [
{"name":"challenger", "type":"account_name"},
{"name":"host", "type":"account_name"}
]
},{
"name": "movement",
"base": "",
"fields": [
{"name":"row", "type":"uint32"},
{"name":"column", "type":"uint32"}
"fields": [{
"name": "challenger",
"type": "name"
},{
"name": "host",
"type": "name"
}
]
},{
"name": "move",
"base": "",
"fields": [
{"name":"challenger", "type":"account_name"},
{"name":"host", "type":"account_name"},
{"name":"by", "type":"account_name"},
{"name":"mvt", "type":"movement"}
"fields": [{
"name": "challenger",
"type": "name"
},{
"name": "host",
"type": "name"
},{
"name": "by",
"type": "name"
},{
"name": "row",
"type": "uint16"
},{
"name": "column",
"type": "uint16"
}
]
}
],
......@@ -73,13 +99,18 @@
}
],
"tables": [{
"name": "games",
"type": "game",
"index_type": "i64",
"key_names" : ["challenger"],
"key_types" : ["account_name"]
}
"name": "games",
"index_type": "i64",
"key_names": [
"challenger"
],
"key_types": [
"name"
],
"type": "game"
}
],
"ricardian_clauses": [],
"error_messages": [],
"abi_extensions": []
}
}
\ No newline at end of file
......@@ -5,206 +5,167 @@
#include "tic_tac_toe.hpp"
using namespace eosio;
namespace tic_tac_toe {
struct impl {
/**
* @brief Check if cell is empty
* @param cell - value of the cell (should be either 0, 1, or 2)
* @return true if cell is empty
*/
bool is_empty_cell(const uint8_t& cell) {
return cell == 0;
}
/**
* @brief Check for valid movement
* @detail Movement is considered valid if it is inside the board and done on empty cell
* @param mvt - the movement made by the player
* @param game_for_movement - the game on which the movement is being made
* @return true if movement is valid
*/
bool is_valid_movement(const movement& mvt, const game& game_for_movement) {
uint32_t movement_location = mvt.row * 3 + mvt.column;
bool is_valid = movement_location < board_len && is_empty_cell(game_for_movement.board[movement_location]);
return is_valid;
}
/**
* @brief Check if cell is empty
* @param cell - value of the cell (should be either 0, 1, or 2)
* @return true if cell is empty
*/
bool is_empty_cell(const uint8_t& cell) {
return cell == 0;
}
/**
* @brief Check for valid movement
* @detail Movement is considered valid if it is inside the board and done on empty cell
* @param row - the row of movement made by the player
* @param column - the column of movement made by the player
* @param board - the board on which the movement is being made
* @return true if movement is valid
*/
bool is_valid_movement(const uint16_t& row, const uint16_t& column, const vector<uint8_t>& board) {
uint32_t movement_location = row * tic_tac_toe::game::board_width + column;
bool is_valid = movement_location < board.size() && is_empty_cell(board[movement_location]);
return is_valid;
}
/**
* @brief Get winner of the game
* @detail Winner of the game is the first player who made three consecutive aligned movement
* @param current_game - the game which we want to determine the winner of
* @return winner of the game (can be either none/ draw/ account name of host/ account name of challenger)
*/
account_name get_winner(const game& current_game) {
if((current_game.board[0] == current_game.board[4] && current_game.board[4] == current_game.board[8]) ||
(current_game.board[1] == current_game.board[4] && current_game.board[4] == current_game.board[7]) ||
(current_game.board[2] == current_game.board[4] && current_game.board[4] == current_game.board[6]) ||
(current_game.board[3] == current_game.board[4] && current_game.board[4] == current_game.board[5])) {
// x | - | - - | x | - - | - | x - | - | -
// - | x | - - | x | - - | x | - x | x | x
// - | - | x - | x | - x | - | - - | - | -
if (current_game.board[4] == 1) {
return current_game.host;
} else if (current_game.board[4] == 2) {
return current_game.challenger;
}
} else if ((current_game.board[0] == current_game.board[1] && current_game.board[1] == current_game.board[2]) ||
(current_game.board[0] == current_game.board[3] && current_game.board[3] == current_game.board[6])) {
// x | x | x x | - | -
// - | - | - x | - | -
// - | - | - x | - | -
if (current_game.board[0] == 1) {
return current_game.host;
} else if (current_game.board[0] == 2) {
return current_game.challenger;
}
} else if ((current_game.board[2] == current_game.board[5] && current_game.board[5] == current_game.board[8]) ||
(current_game.board[6] == current_game.board[7] && current_game.board[7] == current_game.board[8])) {
// - | - | x - | - | -
// - | - | x - | - | -
// - | - | x x | x | x
if (current_game.board[8] == 1) {
return current_game.host;
} else if (current_game.board[8] == 2) {
return current_game.challenger;
}
} else {
bool is_board_full = true;
for (uint8_t i = 0; i < board_len; i++) {
if (is_empty_cell(current_game.board[i])) {
is_board_full = false;
break;
}
}
if (is_board_full) {
return N(draw);
}
/**
* @brief Get winner of the game
* @detail Winner of the game is the first player who made three consecutive aligned movement
* @param current_game - the game which we want to determine the winner of
* @return winner of the game (can be either none/ draw/ account name of host/ account name of challenger)
*/
account_name get_winner(const tic_tac_toe::game& current_game) {
auto& board = current_game.board;
bool is_board_full = true;
// Use bitwise AND operator to determine the consecutive values of each column, row and diagonal
// Since 3 == 0b11, 2 == 0b10, 1 = 0b01, 0 = 0b00
vector<uint32_t> consecutive_column(tic_tac_toe::game::board_width, 3 );
vector<uint32_t> consecutive_row(tic_tac_toe::game::board_height, 3 );
uint32_t consecutive_diagonal_backslash = 3;
uint32_t consecutive_diagonal_slash = 3;
for (uint32_t i = 0; i < board.size(); i++) {
is_board_full &= is_empty_cell(board[i]);
uint16_t row = uint16_t(i / tic_tac_toe::game::board_width);
uint16_t column = uint16_t(i % tic_tac_toe::game::board_width);
// Calculate consecutive row and column value
consecutive_row[column] = consecutive_row[column] & board[i];
consecutive_column[row] = consecutive_column[row] & board[i];
// Calculate consecutive diagonal \ value
if (row == column) {
consecutive_diagonal_backslash = consecutive_diagonal_backslash & board[i];
}
// Calculate consecutive diagonal / value
if ( row + column == tic_tac_toe::game::board_width - 1) {
consecutive_diagonal_slash = consecutive_diagonal_slash & board[i];
}
return N(none);
}
/**
* @brief Apply create action
* @param c - action to be applied
*/
void on(const create& c) {
require_auth(c.host);
eosio_assert(c.challenger != c.host, "challenger shouldn't be the same as host");
// Check if game already exists
games existing_host_games(code_account, c.host);
auto itr = existing_host_games.find( c.challenger );
eosio_assert(itr == existing_host_games.end(), "game already exists");
existing_host_games.emplace(c.host, [&]( auto& g ) {
g.challenger = c.challenger;
g.host = c.host;
g.turn = c.host;
});
// Inspect the value of all consecutive row, column, and diagonal and determine winner
vector<uint32_t> aggregate = { consecutive_diagonal_backslash, consecutive_diagonal_slash };
aggregate.insert(aggregate.end(), consecutive_column.begin(), consecutive_column.end());
aggregate.insert(aggregate.end(), consecutive_row.begin(), consecutive_row.end());
for (auto value: aggregate) {
if (value == 1) {
return current_game.host;
} else if (value == 2) {
return current_game.challenger;
}
}
// Draw if the board is full, otherwise the winner is not determined yet
return is_board_full ? N(draw) : N(none);
}
/**
* @brief Apply restart action
* @param r - action to be applied
*/
void on(const restart& r) {
require_auth(r.by);
// Check if game exists
games existing_host_games(code_account, r.host);
auto itr = existing_host_games.find( r.challenger );
eosio_assert(itr != existing_host_games.end(), "game doesn't exists");
// Check if this game belongs to the action sender
eosio_assert(r.by == itr->host || r.by == itr->challenger, "this is not your game!");
// Reset game
existing_host_games.modify(itr, itr->host, []( auto& g ) {
g.reset_game();
});
}
/**
* @brief Apply create action
*/
void tic_tac_toe::create(const account_name& challenger, const account_name& host) {
require_auth(host);
eosio_assert(challenger != host, "challenger shouldn't be the same as host");
// Check if game already exists
games existing_host_games(_self, host);
auto itr = existing_host_games.find( challenger );
eosio_assert(itr == existing_host_games.end(), "game already exists");
existing_host_games.emplace(host, [&]( auto& g ) {
g.challenger = challenger;
g.host = host;
g.turn = host;
});
}
/**
* @brief Apply close action
* @param c - action to be applied
*/
void on(const close& c) {
require_auth(c.host);
/**
* @brief Apply restart action
*/
void tic_tac_toe::restart(const account_name& challenger, const account_name& host, const account_name& by) {
require_auth(by);
// Check if game exists
games existing_host_games(code_account, c.host);
auto itr = existing_host_games.find( c.challenger );
eosio_assert(itr != existing_host_games.end(), "game doesn't exists");
// Check if game exists
games existing_host_games(_self, host);
auto itr = existing_host_games.find( challenger );
eosio_assert(itr != existing_host_games.end(), "game doesn't exists");
// Remove game
existing_host_games.erase(itr);
}
// Check if this game belongs to the action sender
eosio_assert(by == itr->host || by == itr->challenger, "this is not your game!");
/**
* @brief Apply move action
* @param m - action to be applied
*/
void on(const move& m) {
require_auth(m.by);
// Check if game exists
games existing_host_games(code_account, m.host);
auto itr = existing_host_games.find( m.challenger );
eosio_assert(itr != existing_host_games.end(), "game doesn't exists");
// Check if this game hasn't ended yet
eosio_assert(itr->winner == N(none), "the game has ended!");
// Check if this game belongs to the action sender
eosio_assert(m.by == itr->host || m.by == itr->challenger, "this is not your game!");
// Check if this is the action sender's turn
eosio_assert(m.by == itr->turn, "it's not your turn yet!");
// Check if user makes a valid movement
eosio_assert(is_valid_movement(m.mvt, *itr), "not a valid movement!");
// Fill the cell, 1 for host, 2 for challenger
const uint8_t cell_value = itr->turn == itr->host ? 1 : 2;
const auto turn = itr->turn == itr->host ? itr->challenger : itr->host;
existing_host_games.modify(itr, itr->host, [&]( auto& g ) {
g.board[m.mvt.row * 3 + m.mvt.column] = cell_value;
g.turn = turn;
//check to see if we have a winner
g.winner = get_winner(g);
});
}
// Reset game
existing_host_games.modify(itr, itr->host, []( auto& g ) {
g.reset_game();
});
}
/// The apply method implements the dispatch of events to this contract
void apply( uint64_t /*receiver*/, uint64_t code, uint64_t action ) {
if (code == code_account) {
if (action == N(create)) {
impl::on(eosio::unpack_action_data<tic_tac_toe::create>());
} else if (action == N(restart)) {
impl::on(eosio::unpack_action_data<tic_tac_toe::restart>());
} else if (action == N(close)) {
impl::on(eosio::unpack_action_data<tic_tac_toe::close>());
} else if (action == N(move)) {
impl::on(eosio::unpack_action_data<tic_tac_toe::move>());
}
}
}
/**
* @brief Apply close action
*/
void tic_tac_toe::close(const account_name& challenger, const account_name& host) {
require_auth(host);
};
// Check if game exists
games existing_host_games(_self, host);
auto itr = existing_host_games.find( challenger );
eosio_assert(itr != existing_host_games.end(), "game doesn't exists");
// Remove game
existing_host_games.erase(itr);
}
/**
* The apply() methods must have C calling convention so that the blockchain can lookup and
* call these methods.
*/
extern "C" {
using namespace tic_tac_toe;
/// The apply method implements the dispatch of events to this contract
void apply( uint64_t receiver, uint64_t code, uint64_t action ) {
impl().apply(receiver, code, action);
}
* @brief Apply move action
*/
void tic_tac_toe::move(const account_name& challenger, const account_name& host, const account_name& by, const uint16_t& row, const uint16_t& column ) {
require_auth(by);
// Check if game exists
games existing_host_games(_self, host);
auto itr = existing_host_games.find( challenger );
eosio_assert(itr != existing_host_games.end(), "game doesn't exists");
// Check if this game hasn't ended yet
eosio_assert(itr->winner == N(none), "the game has ended!");
// Check if this game belongs to the action sender
eosio_assert(by == itr->host || by == itr->challenger, "this is not your game!");
// Check if this is the action sender's turn
eosio_assert(by == itr->turn, "it's not your turn yet!");
// Check if user makes a valid movement
eosio_assert(is_valid_movement(row, column, itr->board), "not a valid movement!");
// Fill the cell, 1 for host, 2 for challenger
const uint8_t cell_value = itr->turn == itr->host ? 1 : 2;
const auto turn = itr->turn == itr->host ? itr->challenger : itr->host;
existing_host_games.modify(itr, itr->host, [&]( auto& g ) {
g.board[row * tic_tac_toe::game::board_width + column] = cell_value;
g.turn = turn;
g.winner = get_winner(g);
});
}
} // extern "C"
EOSIO_ABI( tic_tac_toe, (create)(restart)(close)(move))
......@@ -42,102 +42,63 @@
* @{
*/
namespace tic_tac_toe {
static const account_name games_account = N(games);
static const account_name code_account = N(tic.tac.toe);
/**
* @brief Data structure to hold game information
*/
static const uint32_t board_len = 9;
struct game {
game() { initialize_board(); }
game(account_name challenger_account, account_name host_account)
: challenger(challenger_account), host(host_account), turn(host_account) {
// Initialize board
initialize_board();
}
account_name challenger;
account_name host;
account_name turn; // = account name of host/ challenger
account_name winner = N(none); // = none/ draw/ account name of host/ challenger
uint8_t board[board_len];
// Initialize board with empty cell
void initialize_board() {
for (uint8_t i = 0; i < board_len ; i++) {
board[i] = 0;
class tic_tac_toe : public eosio::contract {
public:
tic_tac_toe( account_name self ):contract(self){}
/**
* @brief Information related to a game
* @abi table games i64
*/
struct game {
static const uint16_t board_width = 3;
static const uint16_t board_height = board_width;
game() {
initialize_board();
}
account_name challenger;
account_name host;
account_name turn; // = account name of host/ challenger
account_name winner = N(none); // = none/ draw/ name of host/ name of challenger
std::vector<uint8_t> board;
// Initialize board with empty cell
void initialize_board() {
board = std::vector<uint8_t>(board_width * board_height, 0);
}
}
// Reset game
void reset_game() {
initialize_board();
turn = host;
winner = N(none);
}
auto primary_key() const { return challenger; }
EOSLIB_SERIALIZE( game, (challenger)(host)(turn)(winner)(board) )
};
/**
* @brief Action to create new game
*/
struct create {
account_name challenger;
account_name host;
EOSLIB_SERIALIZE( create, (challenger)(host) )
};
/**
* @brief Action to restart new game
*/
struct restart {
account_name challenger;
account_name host;
account_name by; // the account who wants to restart the game
EOSLIB_SERIALIZE( restart, (challenger)(host)(by) )
};
/**
* @brief Action to close new game
*/
struct close {
account_name challenger;
account_name host;
EOSLIB_SERIALIZE( close, (challenger)(host) )
};
/**
* @brief Data structure for movement
*/
struct movement {
uint32_t row;
uint32_t column;
EOSLIB_SERIALIZE( movement, (row)(column) )
};
/**
* @brief Action to make movement
*/
struct move {
account_name challenger;
account_name host;
account_name by; // the account who wants to make the move
movement mvt;
EOSLIB_SERIALIZE( move, (challenger)(host)(by)(mvt) )
};
// Reset game
void reset_game() {
initialize_board();
turn = host;
winner = N(none);
}
/**
* @brief table definition, used to store existing games and their current state
*/
typedef eosio::multi_index< games_account, game> games;
}
auto primary_key() const { return challenger; }
EOSLIB_SERIALIZE( game, (challenger)(host)(turn)(winner)(board))
};
/**
* @brief The table definition, used to store existing games and their current state
*/
typedef eosio::multi_index< N(games), game> games;
/// @abi action
/// Create a new game
void create(const account_name& challenger, const account_name& host);
/// @abi action
/// Restart a game
/// @param by the account who wants to restart the game
void restart(const account_name& challenger, const account_name& host, const account_name& by);
/// @abi action
/// Close an existing game, and remove it from storage
void close(const account_name& challenger, const account_name& host);
/// @abi action
/// Make movement
/// @param by the account who wants to make the move
void move(const account_name& challenger, const account_name& host, const account_name& by, const uint16_t& row, const uint16_t& column);
};
/// @}
......@@ -41,12 +41,6 @@ struct game {
FC_REFLECT(game, (challenger)(host)(turn)(winner)(board));
struct movement {
uint32_t row;
uint32_t column;
};
FC_REFLECT(movement, (row)(column));
struct ttt_tester : TESTER {
void get_game(game& game, account_name scope, account_name key) {
auto* maybe_tid = find_table(N(tic.tac.toe), scope, N(games));
......@@ -84,84 +78,68 @@ BOOST_AUTO_TEST_CASE( tic_tac_toe_game ) try {
chain.produce_blocks();
auto mvt = mutable_variant_object()
("row", 1)
("column", 1);
chain.push_action(N(tic.tac.toe), N(move), N(player1), mutable_variant_object()
("challenger", "player2")
("host", "player1")
("by", "player1")
("mvt", mvt)
("row", 1)
("column", 1)
);
mvt = mutable_variant_object()
("row", 0)
("column", 1);
BOOST_CHECK_EXCEPTION(chain.push_action(N(tic.tac.toe), N(move), N(player1), mutable_variant_object()
("challenger", "player2")
("host", "player1")
("by", "player1")
("mvt", mvt)
("row", 0)
("column", 1)
), eosio_assert_message_exception, eosio_assert_message_starts_with("it's not your turn yet"));
mvt = mutable_variant_object()
("row", 1)
("column", 1);
BOOST_CHECK_EXCEPTION(chain.push_action(N(tic.tac.toe), N(move), N(player2), mutable_variant_object()
("challenger", "player2")
("host", "player1")
("by", "player2")
("mvt", mvt)
("row", 1)
("column", 1)
), eosio_assert_message_exception, eosio_assert_message_starts_with("not a valid movement"));
mvt = mutable_variant_object()
("row", 0)
("column", 1);
chain.push_action(N(tic.tac.toe), N(move), N(player2), mutable_variant_object()
("challenger", "player2")
("host", "player1")
("by", "player2")
("mvt", mvt)
("row", 0)
("column", 1)
);
mvt = mutable_variant_object()
("row", 0)
("column", 0 );
chain.push_action(N(tic.tac.toe), N(move), N(player1), mutable_variant_object()
("challenger", "player2")
("host", "player1")
("by", "player1")
("mvt", mvt)
("row", 0)
("column", 0 )
);
mvt = mutable_variant_object()
("row", 0)
("column", 2);
chain.push_action(N(tic.tac.toe), N(move), N(player2), mutable_variant_object()
("challenger", "player2")
("host", "player1")
("by", "player2")
("mvt", mvt)
("row", 0)
("column", 2)
);
mvt = mutable_variant_object()
("row", 2)
("column", 2);
chain.push_action(N(tic.tac.toe), N(move), N(player1), mutable_variant_object()
("challenger", "player2")
("host", "player1")
("by", "player1")
("mvt", mvt)
("row", 2)
("column", 2)
);
mvt = mutable_variant_object()
("row", 2)
("column", 0);
BOOST_CHECK_EXCEPTION(chain.push_action(N(tic.tac.toe), N(move), N(player2), mutable_variant_object()
("challenger", "player2")
("host", "player1")
("by", "player2")
("mvt", mvt)
("row", 2)
("column", 0)
), eosio_assert_message_exception, eosio_assert_message_starts_with("the game has ended"));
game current;
......@@ -173,14 +151,12 @@ BOOST_AUTO_TEST_CASE( tic_tac_toe_game ) try {
("host", "player1")
);
mvt = mutable_variant_object()
("row", 2)
("column", 0);
BOOST_CHECK_EXCEPTION(chain.push_action(N(tic.tac.toe), N(move), N(player2), mutable_variant_object()
("challenger", "player2")
("host", "player1")
("by", "player2")
("mvt", mvt)
("row", 2)
("column", 0)
), eosio_assert_message_exception, eosio_assert_message_starts_with("game doesn't exists"));
BOOST_CHECK_EXCEPTION(chain.push_action(N(tic.tac.toe), N(restart), N(player2), mutable_variant_object()
......@@ -201,25 +177,21 @@ BOOST_AUTO_TEST_CASE( tic_tac_toe_game ) try {
);
// making a move and ...
mvt = mutable_variant_object()
("row", 1)
("column", 1);
chain.push_action(N(tic.tac.toe), N(move), N(player2), mutable_variant_object()
("challenger", "player1")
("host", "player2")
("by", "player2")
("mvt", mvt)
("row", 1)
("column", 1)
);
// ... repeating to get exception to ensure restart above actually did something
mvt = mutable_variant_object()
("row", 0)
("column", 1);
BOOST_CHECK_EXCEPTION(chain.push_action(N(tic.tac.toe), N(move), N(player2), mutable_variant_object()
("challenger", "player1")
("host", "player2")
("by", "player2")
("mvt", mvt)
("row", 0)
("column", 1)
), eosio_assert_message_exception, eosio_assert_message_starts_with("it's not your turn yet"));
} FC_LOG_AND_RETHROW()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册