From 50a9f913ce0d5dfe15ad0b1752d730b3940f8770 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Tue, 4 Oct 2016 10:41:26 -0400 Subject: [PATCH 1/8] Move game object to its own file --- libraries/chain/CMakeLists.txt | 1 + libraries/chain/db_init.cpp | 1 + libraries/chain/game_object.cpp | 260 ++++++++++++++++++ .../include/graphene/chain/game_object.hpp | 125 +++++++++ .../graphene/chain/tournament_object.hpp | 27 -- libraries/chain/match_object.cpp | 1 + 6 files changed, 388 insertions(+), 27 deletions(-) create mode 100644 libraries/chain/game_object.cpp create mode 100644 libraries/chain/include/graphene/chain/game_object.hpp diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 5edb63fe..e56d0126 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -77,6 +77,7 @@ add_library( graphene_chain tournament_evaluator.cpp tournament_object.cpp match_object.cpp + game_object.cpp withdraw_permission_evaluator.cpp worker_evaluator.cpp confidential_evaluator.cpp diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 070ec618..80005410 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include diff --git a/libraries/chain/game_object.cpp b/libraries/chain/game_object.cpp new file mode 100644 index 00000000..b13e9410 --- /dev/null +++ b/libraries/chain/game_object.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace graphene { namespace chain { + + namespace msm = boost::msm; + namespace mpl = boost::mpl; + + namespace + { + // Events + struct initiate_game + { + database& db; + vector players; + initiate_game(database& db, const vector& players) : + db(db), players(players) + {} + }; + + struct game_complete + { + database& db; + game_id_type game_id; + game_complete(database& db, game_id_type game_id) : db(db), game_id(game_id) {}; + }; + + struct game_state_machine_ : public msm::front::state_machine_def + { + // disable a few state machine features we don't use for performance + typedef int no_exception_thrown; + typedef int no_message_queue; + + // States + struct waiting_on_previous_gamees : public msm::front::state<>{}; + struct game_in_progress : public msm::front::state<> + { + void on_entry(const initiate_game& event, game_state_machine_& fsm) + { + game_object& game = *fsm.game_obj; + + fc_ilog(fc::logger::get("tournament"), + "game ${id} is now in progress", + ("id", game.id)); + } + }; + struct game_complete : public msm::front::state<> + { + void on_entry(const game_complete& event, game_state_machine_& fsm) + { + fc_ilog(fc::logger::get("tournament"), + "game ${id} is complete", + ("id", fsm.game_obj->id)); + } + void on_entry(const initiate_game& event, game_state_machine_& fsm) + { + game_object& game = *fsm.game_obj; + fc_ilog(fc::logger::get("tournament"), + "game ${id} is complete, it was a buy", + ("id", game)); + } + }; + typedef waiting_on_previous_gamees initial_state; + + typedef game_state_machine_ x; // makes transition table cleaner + + // Guards + bool was_final_game(const game_complete& event) + { + fc_ilog(fc::logger::get("tournament"), + "In was_final_game guard, returning ${value}", + ("value", false));// game_obj->registered_players == game_obj->options.number_of_players - 1)); + return false; + //return game_obj->registered_players == game_obj->options.number_of_players - 1; + } + + bool game_is_a_buy(const initiate_game& event) + { + return event.players.size() < 2; + } + + void start_next_game(const game_complete& event) + { + fc_ilog(fc::logger::get("tournament"), + "In start_next_game action"); + } + + // Transition table for tournament + struct transition_table : mpl::vector< + // Start Event Next Action Guard + // +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+ + _row < waiting_on_previous_gamees, initiate_game, game_in_progress >, + g_row < waiting_on_previous_gamees, initiate_game, game_complete, &x::game_is_a_buy >, + // +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+ + a_row < game_in_progress, game_complete, game_in_progress, &x::start_next_game >, + g_row < game_in_progress, game_complete, game_complete, &x::was_final_game > + // +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+ + > {}; + + + game_object* game_obj; + game_state_machine_(game_object* game_obj) : game_obj(game_obj) {} + }; + typedef msm::back::state_machine game_state_machine; + } + + class game_object::impl { + public: + game_state_machine state_machine; + + impl(game_object* self) : state_machine(self) {} + }; + + game_object::game_object() : + my(new impl(this)) + { + } + + game_object::game_object(const game_object& rhs) : + graphene::db::abstract_object(rhs), + players(rhs.players), + winners(rhs.winners), + game_details(rhs.game_details), + my(new impl(this)) + { + my->state_machine = rhs.my->state_machine; + my->state_machine.game_obj = this; + } + + game_object& game_object::operator=(const game_object& rhs) + { + //graphene::db::abstract_object::operator=(rhs); + id = rhs.id; + players = rhs.players; + winners = rhs.winners; + game_details = rhs.game_details; + my->state_machine = rhs.my->state_machine; + my->state_machine.game_obj = this; + + return *this; + } + + game_object::~game_object() + { + } + + bool verify_game_state_constants() + { + unsigned error_count = 0; + typedef msm::back::generate_state_set::type all_states; + static char const* filled_state_names[mpl::size::value]; + mpl::for_each > + (msm::back::fill_state_names(filled_state_names)); + for (unsigned i = 0; i < mpl::size::value; ++i) + { + try + { + // this is an approximate test, the state name provided by typeinfo will be mangled, but should + // at least contain the string we're looking for + const char* fc_reflected_value_name = fc::reflector::to_string((game_state)i); + if (!strcmp(fc_reflected_value_name, filled_state_names[i])) + fc_elog(fc::logger::get("game"), + "Error, state string misgame between fc and boost::msm for int value ${int_value}: boost::msm -> ${boost_string}, fc::reflect -> ${fc_string}", + ("int_value", i)("boost_string", filled_state_names[i])("fc_string", fc_reflected_value_name)); + } + catch (const fc::bad_cast_exception&) + { + fc_elog(fc::logger::get("game"), + "Error, no reflection for value ${int_value} in enum game_state", + ("int_value", i)); + ++error_count; + } + } + + return error_count == 0; + } + + game_state game_object::get_state() const + { + static bool state_constants_are_correct = verify_game_state_constants(); + (void)&state_constants_are_correct; + game_state state = (game_state)my->state_machine.current_state()[0]; + + return state; + } + + void game_object::pack_impl(std::ostream& stream) const + { + boost::archive::binary_oarchive oa(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking); + oa << my->state_machine; + } + + void game_object::unpack_impl(std::istream& stream) + { + boost::archive::binary_iarchive ia(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking); + ia >> my->state_machine; + } + +} } // graphene::chain + +namespace fc { + // Manually reflect game_object to variant to properly reflect "state" + void to_variant(const graphene::chain::game_object& game_obj, fc::variant& v) + { + fc_elog(fc::logger::get("tournament"), "In game_obj to_variant"); + elog("In game_obj to_variant"); + fc::mutable_variant_object o; + o("id", game_obj.id) + ("players", game_obj.players) + ("winners", game_obj.winners) + ("game_details", game_obj.game_details) + ("state", game_obj.get_state()); + + v = o; + } + + // Manually reflect game_object to variant to properly reflect "state" + void from_variant(const fc::variant& v, graphene::chain::game_object& game_obj) + { + fc_elog(fc::logger::get("tournament"), "In game_obj from_variant"); + game_obj.id = v["id"].as(); + game_obj.players = v["players"].as >(); + game_obj.winners = v["winners"].as >(); + game_obj.game_details = v["game_details"].as(); + graphene::chain::game_state state = v["state"].as(); + const_cast(game_obj.my->state_machine.current_state())[0] = (int)state; + } +} //end namespace fc + + diff --git a/libraries/chain/include/graphene/chain/game_object.hpp b/libraries/chain/include/graphene/chain/game_object.hpp new file mode 100644 index 00000000..ed4aea0d --- /dev/null +++ b/libraries/chain/include/graphene/chain/game_object.hpp @@ -0,0 +1,125 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace graphene { namespace chain { + class game_object; +} } + +namespace fc { + void to_variant(const graphene::chain::game_object& game_obj, fc::variant& v); + void from_variant(const fc::variant& v, graphene::chain::game_object& game_obj); +} //end namespace fc + +namespace graphene { namespace chain { + class database; + using namespace graphene::db; + + enum class game_state + { + game_in_progress, + game_complete + }; + + class game_object : public graphene::db::abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = game_object_type; + + match_id_type match_id; + + vector players; + + flat_set winners; + + game_specific_details game_details; + + game_state get_state() const; + + game_object(); + game_object(const game_object& rhs); + ~game_object(); + game_object& operator=(const game_object& rhs); + + // serialization functions: + // for serializing to raw, go through a temporary sstream object to avoid + // having to implement serialization in the header file + template + friend Stream& operator<<( Stream& s, const game_object& game_obj ); + + template + friend Stream& operator>>( Stream& s, game_object& game_obj ); + + friend void ::fc::to_variant(const graphene::chain::game_object& game_obj, fc::variant& v); + friend void ::fc::from_variant(const fc::variant& v, graphene::chain::game_object& game_obj); + + void pack_impl(std::ostream& stream) const; + void unpack_impl(std::istream& stream); + + class impl; + std::unique_ptr my; + }; + + typedef multi_index_container< + game_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > > > + > game_object_multi_index_type; + typedef generic_index game_index; + + template + inline Stream& operator<<( Stream& s, const game_object& game_obj ) + { + // pack all fields exposed in the header in the usual way + // instead of calling the derived pack, just serialize the one field in the base class + // fc::raw::pack >(s, game_obj); + fc::raw::pack(s, game_obj.id); + fc::raw::pack(s, game_obj.match_id); + fc::raw::pack(s, game_obj.players); + fc::raw::pack(s, game_obj.winners); + fc::raw::pack(s, game_obj.game_details); + + // fc::raw::pack the contents hidden in the impl class + std::ostringstream stream; + game_obj.pack_impl(stream); + std::string stringified_stream(stream.str()); + fc::raw::pack(s, stream.str()); + + return s; + } + + template + inline Stream& operator>>( Stream& s, game_object& game_obj ) + { + // unpack all fields exposed in the header in the usual way + //fc::raw::unpack >(s, game_obj); + fc::raw::unpack(s, game_obj.id); + fc::raw::unpack(s, game_obj.match_id); + fc::raw::unpack(s, game_obj.players); + fc::raw::unpack(s, game_obj.winners); + fc::raw::unpack(s, game_obj.game_details); + + // fc::raw::unpack the contents hidden in the impl class + std::string stringified_stream; + fc::raw::unpack(s, stringified_stream); + std::istringstream stream(stringified_stream); + game_obj.unpack_impl(stream); + + return s; + } + +} } + +FC_REFLECT_ENUM(graphene::chain::game_state, + (game_in_progress) + (game_complete)) + +FC_REFLECT_TYPENAME(graphene::chain::game_object) // manually serialized + + diff --git a/libraries/chain/include/graphene/chain/tournament_object.hpp b/libraries/chain/include/graphene/chain/tournament_object.hpp index d9250789..f8a04a97 100644 --- a/libraries/chain/include/graphene/chain/tournament_object.hpp +++ b/libraries/chain/include/graphene/chain/tournament_object.hpp @@ -119,21 +119,6 @@ namespace graphene { namespace chain { std::unique_ptr my; }; - class game_object : public graphene::db::abstract_object - { - public: - static const uint8_t space_id = protocol_ids; - static const uint8_t type_id = game_object_type; - - match_id_type match_id; - - vector players; - - flat_set winners; - - game_specific_details game_details; - }; - struct by_registration_deadline {}; struct by_start_time {}; typedef multi_index_container< @@ -159,12 +144,6 @@ namespace graphene { namespace chain { > tournament_details_object_multi_index_type; typedef generic_index tournament_details_index; - typedef multi_index_container< - game_object, - indexed_by< - ordered_unique< tag, member< object, object_id_type, &object::id > > > - > game_object_multi_index_type; - typedef generic_index game_index; template inline Stream& operator<<( Stream& s, const tournament_object& tournament_obj ) @@ -232,9 +211,3 @@ FC_REFLECT_ENUM(graphene::chain::tournament_state, (registration_period_expired) (concluded)) -FC_REFLECT_DERIVED(graphene::chain::game_object, (graphene::db::object), - (match_id) - (players) - (winners) - (game_details)) - diff --git a/libraries/chain/match_object.cpp b/libraries/chain/match_object.cpp index 37a60d07..4a146b88 100644 --- a/libraries/chain/match_object.cpp +++ b/libraries/chain/match_object.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include From 7c30da6b6d65d7a6fc4a0da4747bc3567abff0ac Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Fri, 14 Oct 2016 19:43:38 -0400 Subject: [PATCH 2/8] Simple two-player tournaments are working if you don't do anything fancy --- libraries/app/database_api.cpp | 3 +- libraries/app/impacted.cpp | 4 + libraries/chain/db_init.cpp | 1 + libraries/chain/game_object.cpp | 242 ++++++++++++++--- .../include/graphene/chain/game_object.hpp | 8 + .../include/graphene/chain/match_object.hpp | 13 + .../graphene/chain/protocol/operations.hpp | 3 +- .../chain/protocol/rock_paper_scissors.hpp | 61 ++++- .../graphene/chain/protocol/tournament.hpp | 33 +++ .../graphene/chain/rock_paper_scissors.hpp | 55 +--- .../graphene/chain/tournament_evaluator.hpp | 12 + libraries/chain/match_object.cpp | 100 +++++-- libraries/chain/protocol/tournament.cpp | 9 + libraries/chain/tournament_evaluator.cpp | 18 +- libraries/chain/tournament_object.cpp | 7 + .../wallet/include/graphene/wallet/wallet.hpp | 15 ++ libraries/wallet/wallet.cpp | 245 ++++++++++++++++-- 17 files changed, 710 insertions(+), 119 deletions(-) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 811aa446..b8c0c280 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -1932,7 +1932,8 @@ void database_api_impl::on_objects_changed(const vector& ids) /// if a connection hangs then this could get backed up and result in /// a failure to exit cleanly. fc::async([capture_this,this,updates,market_broadcast_queue](){ - if( _subscribe_callback ) _subscribe_callback( updates ); + if( _subscribe_callback ) + _subscribe_callback( updates ); for( const auto& item : market_broadcast_queue ) { diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index b7e3a3d6..7cfbafe8 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -212,6 +212,10 @@ struct get_impacted_account_visitor _impacted.insert( op.payer_account_id ); _impacted.insert( op.player_account_id ); } + void operator()( const game_move_operation& op ) + { + _impacted.insert( op.player_account_id ); + } }; diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 80005410..2f9c3b8f 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -177,6 +177,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); } void database::initialize_indexes() diff --git a/libraries/chain/game_object.cpp b/libraries/chain/game_object.cpp index b13e9410..4031890f 100644 --- a/libraries/chain/game_object.cpp +++ b/libraries/chain/game_object.cpp @@ -23,6 +23,8 @@ */ #include #include +#include +#include #include #include @@ -48,11 +50,13 @@ namespace graphene { namespace chain { {} }; - struct game_complete + struct game_move { database& db; - game_id_type game_id; - game_complete(database& db, game_id_type game_id) : db(db), game_id(game_id) {}; + const game_move_operation& move; + game_move(database& db, const game_move_operation& move) : + db(db), move(move) + {} }; struct game_state_machine_ : public msm::front::state_machine_def @@ -62,53 +66,129 @@ namespace graphene { namespace chain { typedef int no_message_queue; // States - struct waiting_on_previous_gamees : public msm::front::state<>{}; - struct game_in_progress : public msm::front::state<> + struct waiting_for_game_to_start : public msm::front::state<> {}; + struct expecting_commit_moves : public msm::front::state<> { void on_entry(const initiate_game& event, game_state_machine_& fsm) { game_object& game = *fsm.game_obj; fc_ilog(fc::logger::get("tournament"), - "game ${id} is now in progress", + "game ${id} is now in progress, expecting commit moves", ("id", game.id)); + fc_ilog(fc::logger::get("tournament"), + "game ${id} is associtated with match ${match_id}", + ("id", game.id) + ("match_id", game.match_id)); + } + void on_entry(const game_move& event, game_state_machine_& fsm) + { + game_object& game = *fsm.game_obj; + + fc_ilog(fc::logger::get("tournament"), + "game ${id} received a commit move, still expecting another commit move", + ("id", game.id)); + } + }; + struct expecting_reveal_moves : public msm::front::state<> + { + void on_entry(const game_move& event, game_state_machine_& fsm) + { + game_object& game = *fsm.game_obj; + + if (event.move.move.which() == game_specific_moves::tag::value) + fc_ilog(fc::logger::get("tournament"), + "game ${id} received a commit move, now expecting reveal moves", + ("id", game.id)); + else + fc_ilog(fc::logger::get("tournament"), + "game ${id} received a reveal move, still expecting reveal moves", + ("id", game.id)); } }; struct game_complete : public msm::front::state<> { - void on_entry(const game_complete& event, game_state_machine_& fsm) - { - fc_ilog(fc::logger::get("tournament"), - "game ${id} is complete", - ("id", fsm.game_obj->id)); - } - void on_entry(const initiate_game& event, game_state_machine_& fsm) + void on_entry(const game_move& event, game_state_machine_& fsm) { game_object& game = *fsm.game_obj; fc_ilog(fc::logger::get("tournament"), - "game ${id} is complete, it was a buy", - ("id", game)); + "received a reveal move, game ${id} is complete", + ("id", fsm.game_obj->id)); + + // we now know who played what, figure out if we have a winner + const rock_paper_scissors_game_details& game_details = game.game_details.get(); + if (game_details.reveal_moves[0]->gesture == game_details.reveal_moves[1]->gesture) + ilog("The game was a tie, both players threw ${gesture}", ("gesture", game_details.reveal_moves[0]->gesture)); + else + { + const match_object& match_obj = game.match_id(event.db); + const tournament_object& tournament_obj = match_obj.tournament_id(event.db); + const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get(); + + unsigned winner = ((((int)game_details.reveal_moves[0]->gesture - + (int)game_details.reveal_moves[1]->gesture + + game_options.number_of_gestures) % game_options.number_of_gestures) + 1) % 2; + ilog("${gesture1} vs ${gesture2}, ${winner} wins", + ("gesture1", game_details.reveal_moves[1]->gesture) + ("gesture2", game_details.reveal_moves[0]->gesture) + ("winner", game_details.reveal_moves[winner]->gesture)); + game.winners.insert(game.players[winner]); + } + + + const match_object& match_obj = game.match_id(event.db); + event.db.modify(match_obj, [&](match_object& match) { + match.on_game_complete(event.db, game); + }); } }; - typedef waiting_on_previous_gamees initial_state; + typedef waiting_for_game_to_start initial_state; typedef game_state_machine_ x; // makes transition table cleaner - + // Guards - bool was_final_game(const game_complete& event) + bool already_have_other_commit(const game_move& event) { - fc_ilog(fc::logger::get("tournament"), - "In was_final_game guard, returning ${value}", - ("value", false));// game_obj->registered_players == game_obj->options.number_of_players - 1)); - return false; - //return game_obj->registered_players == game_obj->options.number_of_players - 1; + auto iter = std::find(game_obj->players.begin(), game_obj->players.end(), + event.move.player_account_id); + unsigned player_index = std::distance(game_obj->players.begin(), iter); + // hard-coded here for two-player games + unsigned other_player_index = player_index == 0 ? 1 : 0; + const rock_paper_scissors_game_details& game_details = game_obj->game_details.get(); + return game_details.commit_moves.at(other_player_index).valid(); } - bool game_is_a_buy(const initiate_game& event) + bool already_have_other_reveal(const game_move& event) { - return event.players.size() < 2; + auto iter = std::find(game_obj->players.begin(), game_obj->players.end(), + event.move.player_account_id); + unsigned player_index = std::distance(game_obj->players.begin(), iter); + // hard-coded here for two-player games + unsigned other_player_index = player_index == 0 ? 1 : 0; + const rock_paper_scissors_game_details& game_details = game_obj->game_details.get(); + return game_details.reveal_moves.at(other_player_index).valid(); } - + + void apply_commit_move(const game_move& event) + { + auto iter = std::find(game_obj->players.begin(), game_obj->players.end(), + event.move.player_account_id); + unsigned player_index = std::distance(game_obj->players.begin(), iter); + + rock_paper_scissors_game_details& details = game_obj->game_details.get(); + details.commit_moves[player_index] = event.move.move.get(); + } + + void apply_reveal_move(const game_move& event) + { + auto iter = std::find(game_obj->players.begin(), game_obj->players.end(), + event.move.player_account_id); + unsigned player_index = std::distance(game_obj->players.begin(), iter); + + rock_paper_scissors_game_details& details = game_obj->game_details.get(); + details.reveal_moves[player_index] = event.move.move.get(); + } + void start_next_game(const game_complete& event) { fc_ilog(fc::logger::get("tournament"), @@ -118,12 +198,17 @@ namespace graphene { namespace chain { // Transition table for tournament struct transition_table : mpl::vector< // Start Event Next Action Guard + // +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+ + _row < waiting_for_game_to_start, initiate_game, expecting_commit_moves >, // +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+ - _row < waiting_on_previous_gamees, initiate_game, game_in_progress >, - g_row < waiting_on_previous_gamees, initiate_game, game_complete, &x::game_is_a_buy >, + a_row < expecting_commit_moves, game_move, expecting_commit_moves, &x::apply_commit_move >, + row < expecting_commit_moves, game_move, expecting_reveal_moves, &x::apply_commit_move, &x::already_have_other_commit >, // +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+ - a_row < game_in_progress, game_complete, game_in_progress, &x::start_next_game >, - g_row < game_in_progress, game_complete, game_complete, &x::was_final_game > + a_row < expecting_reveal_moves, game_move, expecting_reveal_moves, &x::apply_reveal_move >, + row < expecting_reveal_moves, game_move, game_complete, &x::apply_reveal_move, &x::already_have_other_reveal > + // +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+ + //a_row < game_in_progress, game_complete, game_in_progress, &x::start_next_game >, + //g_row < game_in_progress, game_complete, game_complete, &x::was_final_game > // +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+ > {}; @@ -148,6 +233,7 @@ namespace graphene { namespace chain { game_object::game_object(const game_object& rhs) : graphene::db::abstract_object(rhs), + match_id(rhs.match_id), players(rhs.players), winners(rhs.winners), game_details(rhs.game_details), @@ -161,6 +247,7 @@ namespace graphene { namespace chain { { //graphene::db::abstract_object::operator=(rhs); id = rhs.id; + match_id = rhs.match_id; players = rhs.players; winners = rhs.winners; game_details = rhs.game_details; @@ -214,6 +301,99 @@ namespace graphene { namespace chain { return state; } + void game_object::evaluate_move_operation(const database& db, const game_move_operation& op) const + { + const match_object& match_obj = match_id(db); + + if (game_details.which() == game_specific_details::tag::value) + { + if (op.move.which() == game_specific_moves::tag::value) + { + // Is this move made by a player in the match + auto iter = std::find(players.begin(), players.end(), + op.player_account_id); + if (iter == players.end()) + FC_THROW("Player ${account_id} is not a player in game ${game}", + ("account_id", op.player_account_id) + ("game", id)); + unsigned player_index = std::distance(players.begin(), iter); + + //const rock_paper_scissors_throw_commit& commit = op.move.get(); + + // are we expecting commits? + if (get_state() != game_state::expecting_commit_moves) + FC_THROW("Game ${game} is not accepting any commit moves", ("game", id)); + + // has this player committed already? + const rock_paper_scissors_game_details& details = game_details.get(); + if (details.commit_moves.at(player_index)) + FC_THROW("Player ${account_id} has already committed their move for game ${game}", + ("account_id", op.player_account_id) + ("game", id)); + // if all the above checks pass, then the move is accepted + } + else if (op.move.which() == game_specific_moves::tag::value) + { + // Is this move made by a player in the match + auto iter = std::find(players.begin(), players.end(), + op.player_account_id); + if (iter == players.end()) + FC_THROW("Player ${account_id} is not a player in game ${game}", + ("account_id", op.player_account_id) + ("game", id)); + unsigned player_index = std::distance(players.begin(), iter); + + // has this player committed already? + const rock_paper_scissors_game_details& details = game_details.get(); + if (!details.commit_moves.at(player_index)) + FC_THROW("Player ${account_id} cannot reveal a move which they did not commit in game ${game}", + ("account_id", op.player_account_id) + ("game", id)); + + // are we expecting reveals? + if (get_state() != game_state::expecting_reveal_moves) + FC_THROW("Game ${game} is not accepting any reveal moves", ("game", id)); + + const rock_paper_scissors_throw_commit& commit = *details.commit_moves.at(player_index); + const rock_paper_scissors_throw_reveal& reveal = op.move.get(); + + // does the reveal match the commit? + rock_paper_scissors_throw reconstructed_throw; + reconstructed_throw.nonce1 = commit.nonce1; + reconstructed_throw.nonce2 = reveal.nonce2; + reconstructed_throw.gesture = reveal.gesture; + fc::sha256 reconstructed_hash = reconstructed_throw.calculate_hash(); + + if (commit.throw_hash != reconstructed_hash) + FC_THROW("Reveal does not match commit's hash of ${commit_hash}", + ("commit_hash", commit.throw_hash)); + + // is the throw valid for this game + const match_object& match_obj = match_id(db); + const tournament_object& tournament_obj = match_obj.tournament_id(db); + const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get(); + if ((unsigned)reveal.gesture >= game_options.number_of_gestures) + FC_THROW("Gesture ${gesture_int} is not valid for this game", ("gesture", (unsigned)reveal.gesture)); + // if all the above checks pass, then the move is accepted + } + else + FC_THROW("The only valid moves in a rock-paper-scissors game are commit and reveal, not ${type}", + ("type", op.move.which())); + } + else + FC_THROW("Game of type ${type} not supported", ("type", game_details.which())); + } + + void game_object::on_move(database& db, const game_move_operation& op) + { + my->state_machine.process_event(game_move(db, op)); + } + + void game_object::start_game(database& db, const std::vector& players) + { + my->state_machine.process_event(initiate_game(db, players)); + } + void game_object::pack_impl(std::ostream& stream) const { boost::archive::binary_oarchive oa(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking); @@ -236,6 +416,7 @@ namespace fc { elog("In game_obj to_variant"); fc::mutable_variant_object o; o("id", game_obj.id) + ("match_id", game_obj.match_id) ("players", game_obj.players) ("winners", game_obj.winners) ("game_details", game_obj.game_details) @@ -249,6 +430,7 @@ namespace fc { { fc_elog(fc::logger::get("tournament"), "In game_obj from_variant"); game_obj.id = v["id"].as(); + game_obj.match_id = v["match_id"].as(); game_obj.players = v["players"].as >(); game_obj.winners = v["winners"].as >(); game_obj.game_details = v["game_details"].as(); diff --git a/libraries/chain/include/graphene/chain/game_object.hpp b/libraries/chain/include/graphene/chain/game_object.hpp index ed4aea0d..6f8ba82f 100644 --- a/libraries/chain/include/graphene/chain/game_object.hpp +++ b/libraries/chain/include/graphene/chain/game_object.hpp @@ -23,6 +23,8 @@ namespace graphene { namespace chain { enum class game_state { game_in_progress, + expecting_commit_moves, + expecting_reveal_moves, game_complete }; @@ -46,6 +48,10 @@ namespace graphene { namespace chain { game_object(const game_object& rhs); ~game_object(); game_object& operator=(const game_object& rhs); + + void evaluate_move_operation(const database& db, const game_move_operation& op) const; + void on_move(database& db, const game_move_operation& op); + void start_game(database& db, const std::vector& players); // serialization functions: // for serializing to raw, go through a temporary sstream object to avoid @@ -118,6 +124,8 @@ namespace graphene { namespace chain { FC_REFLECT_ENUM(graphene::chain::game_state, (game_in_progress) + (expecting_commit_moves) + (expecting_reveal_moves) (game_complete)) FC_REFLECT_TYPENAME(graphene::chain::game_object) // manually serialized diff --git a/libraries/chain/include/graphene/chain/match_object.hpp b/libraries/chain/include/graphene/chain/match_object.hpp index 65d17135..6f8423f4 100644 --- a/libraries/chain/include/graphene/chain/match_object.hpp +++ b/libraries/chain/include/graphene/chain/match_object.hpp @@ -49,6 +49,12 @@ namespace graphene { namespace chain { /// information about a match without having to request all game objects vector > game_winners; + /// A count of the number of wins for each player + vector number_of_wins; + + /// the total number of games that ended up in a tie/draw/stalemate + uint32_t number_of_ties; + // If the match is not yet complete, this will be empty // If the match is in the "match_complete" state, it will contain the // list of winners. @@ -84,6 +90,7 @@ namespace graphene { namespace chain { void pack_impl(std::ostream& stream) const; void unpack_impl(std::istream& stream); void on_initiate_match(database& db, const vector& players); + void on_game_complete(database& db, const game_object& game); game_id_type start_next_game(database& db, match_id_type match_id); class impl; @@ -104,9 +111,12 @@ namespace graphene { namespace chain { // instead of calling the derived pack, just serialize the one field in the base class // fc::raw::pack >(s, match_obj); fc::raw::pack(s, match_obj.id); + fc::raw::pack(s, match_obj.tournament_id); fc::raw::pack(s, match_obj.players); fc::raw::pack(s, match_obj.games); fc::raw::pack(s, match_obj.game_winners); + fc::raw::pack(s, match_obj.number_of_wins); + fc::raw::pack(s, match_obj.number_of_ties); fc::raw::pack(s, match_obj.match_winners); fc::raw::pack(s, match_obj.start_time); fc::raw::pack(s, match_obj.end_time); @@ -126,9 +136,12 @@ namespace graphene { namespace chain { // unpack all fields exposed in the header in the usual way //fc::raw::unpack >(s, match_obj); fc::raw::unpack(s, match_obj.id); + fc::raw::unpack(s, match_obj.tournament_id); fc::raw::unpack(s, match_obj.players); fc::raw::unpack(s, match_obj.games); fc::raw::unpack(s, match_obj.game_winners); + fc::raw::unpack(s, match_obj.number_of_wins); + fc::raw::unpack(s, match_obj.number_of_ties); fc::raw::unpack(s, match_obj.match_winners); fc::raw::unpack(s, match_obj.start_time); fc::raw::unpack(s, match_obj.end_time); diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index c5d1a084..31c0553d 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -94,7 +94,8 @@ namespace graphene { namespace chain { asset_claim_fees_operation, fba_distribute_operation, // VIRTUAL tournament_create_operation, - tournament_join_operation + tournament_join_operation, + game_move_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/rock_paper_scissors.hpp b/libraries/chain/include/graphene/chain/protocol/rock_paper_scissors.hpp index 1271757c..1d377e0f 100644 --- a/libraries/chain/include/graphene/chain/protocol/rock_paper_scissors.hpp +++ b/libraries/chain/include/graphene/chain/protocol/rock_paper_scissors.hpp @@ -48,10 +48,69 @@ namespace graphene { namespace chain { /// The number of seconds users are given to reveal their move, counted from the time of the /// block containing the second commit or the where the time_per_commit_move expired uint32_t time_per_reveal_move; + + /// The number of allowed gestures, must be either 3 or 5. If 3, the game is + /// standard rock-paper-scissors, if 5, it's + /// rock-paper-scissors-lizard-spock. + uint8_t number_of_gestures; + }; + + enum class rock_paper_scissors_gesture + { + rock, + paper, + scissors, + spock, + lizard + }; + + struct rock_paper_scissors_throw + { + uint64_t nonce1; + uint64_t nonce2; + rock_paper_scissors_gesture gesture; + fc::sha256 calculate_hash() const; + }; + + struct rock_paper_scissors_throw_commit + { + uint64_t nonce1; + fc::sha256 throw_hash; + bool operator<(const graphene::chain::rock_paper_scissors_throw_commit& rhs) const + { + return std::tie(nonce1, throw_hash) < std::tie(rhs.nonce1, rhs.throw_hash); + } + }; + + + + struct rock_paper_scissors_throw_reveal + { + uint64_t nonce2; + rock_paper_scissors_gesture gesture; }; } } -FC_REFLECT( graphene::chain::rock_paper_scissors_game_options, (insurance_enabled)(time_per_commit_move)(time_per_reveal_move) ) +FC_REFLECT( graphene::chain::rock_paper_scissors_game_options, (insurance_enabled)(time_per_commit_move)(time_per_reveal_move)(number_of_gestures) ) +FC_REFLECT_TYPENAME( graphene::chain::rock_paper_scissors_gesture) +FC_REFLECT_ENUM( graphene::chain::rock_paper_scissors_gesture, + (rock) + (paper) + (scissors) + (spock) + (lizard)) + +FC_REFLECT( graphene::chain::rock_paper_scissors_throw, + (nonce1) + (nonce2) + (gesture) ) + +FC_REFLECT( graphene::chain::rock_paper_scissors_throw_commit, + (nonce1) + (throw_hash) ) + +FC_REFLECT( graphene::chain::rock_paper_scissors_throw_reveal, + (nonce2)(gesture) ) diff --git a/libraries/chain/include/graphene/chain/protocol/tournament.hpp b/libraries/chain/include/graphene/chain/protocol/tournament.hpp index 2d167f1f..2c390fac 100644 --- a/libraries/chain/include/graphene/chain/protocol/tournament.hpp +++ b/libraries/chain/include/graphene/chain/protocol/tournament.hpp @@ -148,10 +148,36 @@ namespace graphene { namespace chain { void validate()const; }; + typedef fc::static_variant game_specific_moves; + + struct game_move_operation : public base_operation + { + struct fee_parameters_type { + share_type fee = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + asset fee; + + /// the id of the game + game_id_type game_id; + + /// The account of the player making this move + account_id_type player_account_id; + + /// the move itself + game_specific_moves move; + + extensions_type extensions; + + account_id_type fee_payer()const { return player_account_id; } + share_type calculate_fee(const fee_parameters_type& k)const; + void validate()const; + }; + } } FC_REFLECT_ENUM( graphene::chain::game_type, (rock_paper_scissors)(GAME_TYPE_COUNT) ) FC_REFLECT_TYPENAME( graphene::chain::game_specific_options ) +FC_REFLECT_TYPENAME( graphene::chain::game_specific_moves ) FC_REFLECT( graphene::chain::tournament_options, (type_of_game) (registration_deadline) @@ -176,6 +202,13 @@ FC_REFLECT( graphene::chain::tournament_join_operation, (tournament_id) (buy_in) (extensions)) +FC_REFLECT( graphene::chain::game_move_operation, + (fee) + (game_id) + (player_account_id) + (move) + (extensions)) FC_REFLECT( graphene::chain::tournament_create_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::tournament_join_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::game_move_operation::fee_parameters_type, (fee) ) diff --git a/libraries/chain/include/graphene/chain/rock_paper_scissors.hpp b/libraries/chain/include/graphene/chain/rock_paper_scissors.hpp index abfa6678..23015e3e 100644 --- a/libraries/chain/include/graphene/chain/rock_paper_scissors.hpp +++ b/libraries/chain/include/graphene/chain/rock_paper_scissors.hpp @@ -33,55 +33,24 @@ #include namespace graphene { namespace chain { - enum class rock_paper_scissors_throw - { - rock, - paper, - scissors - }; - struct rock_paper_scissors_move - { - uint64_t nonce1; - uint64_t nonce2; - rock_paper_scissors_throw move; - }; - struct rock_paper_scissors_commit - { - uint64_t nonce1; - fc::sha256 move_hash; - }; - struct rock_paper_scissors_reveal - { - uint64_t nonce2; - rock_paper_scissors_throw move; - }; - struct rock_paper_scissors_game_details { - fc::array, 2> commit_moves; - fc::array, 2> reveal_moves; + // note: I wanted to declare these as fixed arrays, but they don't serialize properly + //fc::array, 2> commit_moves; + //fc::array, 2> reveal_moves; + std::vector > commit_moves; + std::vector > reveal_moves; + rock_paper_scissors_game_details() : + commit_moves(2), + reveal_moves(2) + { + } }; typedef fc::static_variant game_specific_details; - } } -FC_REFLECT_ENUM( graphene::chain::rock_paper_scissors_throw, - (rock) - (paper) - (scissors)) - -FC_REFLECT( graphene::chain::rock_paper_scissors_move, - (nonce1) - (nonce2) - (move) ) - -FC_REFLECT( graphene::chain::rock_paper_scissors_commit, - (nonce1) - (move_hash) ) - -FC_REFLECT( graphene::chain::rock_paper_scissors_reveal, - (nonce2)(move) ) - FC_REFLECT( graphene::chain::rock_paper_scissors_game_details, (commit_moves)(reveal_moves) ) +FC_REFLECT_TYPENAME( graphene::chain::game_specific_details ) + diff --git a/libraries/chain/include/graphene/chain/tournament_evaluator.hpp b/libraries/chain/include/graphene/chain/tournament_evaluator.hpp index d5b70d04..ff70a52e 100644 --- a/libraries/chain/include/graphene/chain/tournament_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/tournament_evaluator.hpp @@ -28,4 +28,16 @@ namespace graphene { namespace chain { void_result do_apply( const tournament_join_operation& o ); }; + class game_move_evaluator : public evaluator + { + private: + const game_object* _game_obj = nullptr; + public: + typedef game_move_operation operation_type; + + void_result do_evaluate( const game_move_operation& o ); + void_result do_apply( const game_move_operation& o ); + }; + + } } diff --git a/libraries/chain/match_object.cpp b/libraries/chain/match_object.cpp index 4a146b88..5627965c 100644 --- a/libraries/chain/match_object.cpp +++ b/libraries/chain/match_object.cpp @@ -53,8 +53,8 @@ namespace graphene { namespace chain { struct game_complete { database& db; - game_id_type game_id; - game_complete(database& db, game_id_type game_id) : db(db), game_id(game_id) {}; + const game_object& game; + game_complete(database& db, const game_object& game) : db(db), game(game) {}; }; struct match_state_machine_ : public msm::front::state_machine_def @@ -67,24 +67,40 @@ namespace graphene { namespace chain { struct waiting_on_previous_matches : public msm::front::state<>{}; struct match_in_progress : public msm::front::state<> { + void on_entry(const game_complete& event, match_state_machine_& fsm) + { + fc_ilog(fc::logger::get("tournament"), + "Game ${game_id} in match ${id} is complete", + ("game_id", event.game.id)("id", fsm.match_obj->id)); + } void on_entry(const initiate_match& event, match_state_machine_& fsm) { match_object& match = *fsm.match_obj; - match.players = event.players; - match.start_time = event.db.head_block_time(); fc_ilog(fc::logger::get("tournament"), "Match ${id} is now in progress", ("id", match.id)); + match.players = event.players; + match.number_of_wins.resize(match.players.size()); + match.start_time = event.db.head_block_time(); + + fsm.start_next_game(event.db); } }; struct match_complete : public msm::front::state<> { void on_entry(const game_complete& event, match_state_machine_& fsm) { + match_object& match = *fsm.match_obj; fc_ilog(fc::logger::get("tournament"), "Match ${id} is complete", - ("id", fsm.match_obj->id)); + ("id", match.id)); + + const tournament_object& tournament_obj = match.tournament_id(event.db); + event.db.modify(tournament_obj, [&](tournament_object& tournament) { + tournament.on_final_game_completed(); + }); + } void on_entry(const initiate_match& event, match_state_machine_& fsm) { @@ -93,6 +109,7 @@ namespace graphene { namespace chain { "Match ${id} is complete, it was a buy", ("id", match)); match.players = event.players; + match.number_of_wins.resize(match.players.size()); boost::copy(event.players, std::inserter(match.match_winners, match.match_winners.end())); match.start_time = event.db.head_block_time(); match.end_time = event.db.head_block_time(); @@ -105,11 +122,17 @@ namespace graphene { namespace chain { // Guards bool was_final_game(const game_complete& event) { - fc_ilog(fc::logger::get("tournament"), - "In was_final_game guard, returning ${value}", - ("value", false));// match_obj->registered_players == match_obj->options.number_of_players - 1)); + const tournament_object& tournament_obj = match_obj->tournament_id(event.db); + + for (unsigned i = 0; i < match_obj->players.size(); ++i) + { + // this guard is called before the winner of the current game factored in to our running totals, + // so we must add the current game to our count + uint32_t win_for_this_game = event.game.winners.find(match_obj->players[i]) != event.game.winners.end() ? 1 : 0; + if (match_obj->number_of_wins[i] + win_for_this_game >= tournament_obj.options.number_of_wins) + return true; + } return false; - //return match_obj->registered_players == match_obj->options.number_of_players - 1; } bool match_is_a_buy(const initiate_match& event) @@ -117,10 +140,35 @@ namespace graphene { namespace chain { return event.players.size() < 2; } - void start_next_game(const game_complete& event) + void record_completed_game(const game_complete& event) + { + if (event.game.winners.empty()) + ++match_obj->number_of_ties; + else + for (unsigned i = 0; i < match_obj->players.size(); ++i) + if (event.game.winners.find(match_obj->players[i]) != event.game.winners.end()) + ++match_obj->number_of_wins[i]; + match_obj->game_winners.emplace_back(event.game.winners); + } + + void start_next_game(database& db) { fc_ilog(fc::logger::get("tournament"), - "In start_next_game action"); + "In start_next_game"); + const game_object& game = + db.create( [&]( game_object& game ) { + game.match_id = match_obj->id; + game.players = match_obj->players; + game.game_details = rock_paper_scissors_game_details(); + game.start_game(db, game.players); + }); + match_obj->games.push_back(game.id); + } + + void record_and_start_next_game(const game_complete& event) + { + record_completed_game(event); + start_next_game(event.db); } // Transition table for tournament @@ -130,8 +178,8 @@ namespace graphene { namespace chain { _row < waiting_on_previous_matches, initiate_match, match_in_progress >, g_row < waiting_on_previous_matches, initiate_match, match_complete, &x::match_is_a_buy >, // +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+ - a_row < match_in_progress, game_complete, match_in_progress, &x::start_next_game >, - g_row < match_in_progress, game_complete, match_complete, &x::was_final_game > + a_row < match_in_progress, game_complete, match_in_progress, &x::record_and_start_next_game >, + row < match_in_progress, game_complete, match_complete, &x::record_completed_game, &x::was_final_game > // +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+ > {}; @@ -156,9 +204,12 @@ namespace graphene { namespace chain { match_object::match_object(const match_object& rhs) : graphene::db::abstract_object(rhs), + tournament_id(rhs.tournament_id), players(rhs.players), games(rhs.games), game_winners(rhs.game_winners), + number_of_wins(rhs.number_of_wins), + number_of_ties(rhs.number_of_ties), match_winners(rhs.match_winners), start_time(rhs.start_time), end_time(rhs.end_time), @@ -172,9 +223,12 @@ namespace graphene { namespace chain { { //graphene::db::abstract_object::operator=(rhs); id = rhs.id; + tournament_id = rhs.tournament_id; players = rhs.players; games = rhs.games; game_winners = rhs.game_winners; + number_of_wins = rhs.number_of_wins; + number_of_ties = rhs.number_of_ties; match_winners = rhs.match_winners; start_time = rhs.start_time; end_time = rhs.end_time; @@ -245,6 +299,11 @@ namespace graphene { namespace chain { my->state_machine.process_event(initiate_match(db, players)); } + void match_object::on_game_complete(database& db, const game_object& game) + { + my->state_machine.process_event(game_complete(db, game)); + } +#if 0 game_id_type match_object::start_next_game(database& db, match_id_type match_id) { const game_object& game = @@ -254,42 +313,49 @@ namespace graphene { namespace chain { }); return game.id; } +#endif } } // graphene::chain namespace fc { // Manually reflect match_object to variant to properly reflect "state" void to_variant(const graphene::chain::match_object& match_obj, fc::variant& v) - { + { try { fc_elog(fc::logger::get("tournament"), "In match_obj to_variant"); elog("In match_obj to_variant"); fc::mutable_variant_object o; o("id", match_obj.id) + ("tournament_id", match_obj.tournament_id) ("players", match_obj.players) ("games", match_obj.games) ("game_winners", match_obj.game_winners) + ("number_of_wins", match_obj.number_of_wins) + ("number_of_ties", match_obj.number_of_ties) ("match_winners", match_obj.match_winners) ("start_time", match_obj.start_time) ("end_time", match_obj.end_time) ("state", match_obj.get_state()); v = o; - } + } FC_RETHROW_EXCEPTIONS(warn, "") } // Manually reflect match_object to variant to properly reflect "state" void from_variant(const fc::variant& v, graphene::chain::match_object& match_obj) - { + { try { fc_elog(fc::logger::get("tournament"), "In match_obj from_variant"); match_obj.id = v["id"].as(); + match_obj.tournament_id = v["tournament_id"].as(); match_obj.players = v["players"].as >(); match_obj.games = v["games"].as >(); match_obj.game_winners = v["game_winners"].as > >(); + match_obj.number_of_wins = v["number_of_wins"].as >(); + match_obj.number_of_ties = v["number_of_ties"].as(); match_obj.match_winners = v["match_winners"].as >(); match_obj.start_time = v["start_time"].as(); match_obj.end_time = v["end_time"].as >(); graphene::chain::match_state state = v["state"].as(); const_cast(match_obj.my->state_machine.current_state())[0] = (int)state; - } + } FC_RETHROW_EXCEPTIONS(warn, "") } } //end namespace fc diff --git a/libraries/chain/protocol/tournament.cpp b/libraries/chain/protocol/tournament.cpp index 9a5cf074..f7faa93b 100644 --- a/libraries/chain/protocol/tournament.cpp +++ b/libraries/chain/protocol/tournament.cpp @@ -52,5 +52,14 @@ void tournament_join_operation::validate()const FC_ASSERT( fee.amount >= 0 ); } +share_type game_move_operation::calculate_fee(const fee_parameters_type& k)const +{ + return k.fee; +} + +void game_move_operation::validate()const +{ +} + } } // namespace graphene::chain diff --git a/libraries/chain/tournament_evaluator.cpp b/libraries/chain/tournament_evaluator.cpp index 8ba4d663..6bb62866 100644 --- a/libraries/chain/tournament_evaluator.cpp +++ b/libraries/chain/tournament_evaluator.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -101,7 +102,7 @@ namespace graphene { namespace chain { //const account_object& player_account = op.player_account_id(d); _buy_in_asset_type = &op.buy_in.asset_id(d); - // TODO FC_ASSERT(_tournament_obj->state == tournament_state::accepting_registrations); + FC_ASSERT(_tournament_obj->get_state() == tournament_state::accepting_registrations); FC_ASSERT(_tournament_details_obj->registered_players.size() < _tournament_obj->options.number_of_players, "Tournament is already full"); FC_ASSERT(d.head_block_time() <= _tournament_obj->options.registration_deadline, @@ -143,6 +144,21 @@ namespace graphene { namespace chain { return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } + void_result game_move_evaluator::do_evaluate( const game_move_operation& o ) + { try { + const database& d = db(); + _game_obj = &o.game_id(d); + _game_obj->evaluate_move_operation(d, o); + return void_result(); + } FC_CAPTURE_AND_RETHROW( (o) ) } + + void_result game_move_evaluator::do_apply( const game_move_operation& o ) + { try { + db().modify(*_game_obj, [&](game_object& game_obj){ + game_obj.on_move(db(), o); + }); + return void_result(); + } FC_CAPTURE_AND_RETHROW( (o) ) } } } diff --git a/libraries/chain/tournament_object.cpp b/libraries/chain/tournament_object.cpp index 85434e87..0ee5be92 100644 --- a/libraries/chain/tournament_object.cpp +++ b/libraries/chain/tournament_object.cpp @@ -102,6 +102,7 @@ namespace graphene { namespace chain { db.create( [&]( match_object& match ) { match.tournament_id = tournament_id; match.players = players; + match.number_of_wins.resize(match.players.size()); match.start_time = db.head_block_time(); if (match.players.size() == 1) { @@ -445,6 +446,12 @@ namespace graphene { namespace chain { } } + fc::sha256 rock_paper_scissors_throw::calculate_hash() const + { + std::vector full_throw_packed(fc::raw::pack(*this)); + return fc::sha256::hash(full_throw_packed.data(), full_throw_packed.size()); + } + } } // graphene::chain namespace fc { diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 07a16f4a..e2b04909 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -191,6 +191,8 @@ struct wallet_data key_label_index_type labeled_keys; blind_receipt_index_type blind_receipts; + std::map committed_game_moves; + string ws_server = "ws://localhost:8090"; string ws_user; string ws_password; @@ -1430,6 +1432,17 @@ class wallet_api */ tournament_object get_tournament(tournament_id_type id); + /** Play a move in the rock-paper-scissors game + * @param game_id the id of the game + * @param player_account the name of the player + * @param gesture rock, paper, or scissors + * @return the signed version of the transaction + */ + signed_transaction rps_throw(game_id_type game_id, + string player_account, + rock_paper_scissors_gesture gesture, + bool broadcast); + void dbg_make_uia(string creator, string symbol); void dbg_make_mia(string creator, string symbol); void flood_network(string prefix, uint32_t number_of_transactions); @@ -1472,6 +1485,7 @@ FC_REFLECT( graphene::wallet::wallet_data, (pending_account_registrations)(pending_witness_registrations) (labeled_keys) (blind_receipts) + (committed_game_moves) (ws_server) (ws_user) (ws_password) @@ -1619,6 +1633,7 @@ FC_API( graphene::wallet::wallet_api, (receive_blind_transfer) (tournament_create) (tournament_join) + (rps_throw) (get_upcoming_tournaments) (get_tournament) ) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 93e5e591..604a99f7 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -60,11 +60,17 @@ #include #include #include +#include #include #include + #include #include +#include +#include +#include + #include #include #include @@ -339,6 +345,16 @@ private: } } + // return true if any of my_accounts are players in this tournament + bool tournament_is_relevant_to_my_accounts(const tournament_object& tournament_obj) + { + tournament_details_object tournament_details = get_object(tournament_obj.tournament_details_id); + for (const account_object& account_obj : _wallet.my_accounts) + if (tournament_details.registered_players.find(account_obj.id) != tournament_details.registered_players.end()) + return true; + return false; + } + fc::mutex _subscribed_object_changed_mutex; void subscribed_object_changed(const variant& changed_objects_variant) { @@ -368,6 +384,14 @@ private: } tournament_cache.modify(tournament_cache_iter, [&](tournament_object& obj) { obj = current_tournament_obj; }); } + else if (tournament_is_relevant_to_my_accounts(current_tournament_obj)) + { + ilog ("We were just notified about an in-progress tournament ${id} relevant to our accounts", + ("id", current_tournament_obj.id)); + tournament_cache.insert(current_tournament_obj); + if (current_tournament_obj.get_state() == tournament_state::in_progress) + monitor_matches_in_tournament(current_tournament_obj); + } continue; } catch (const fc::exception& e) @@ -398,6 +422,30 @@ private: { // idump((e)); } + try + { + object_id_type id = changed_object_variant["id"].as(); + game_object current_game_obj = changed_object_variant.as(); + auto game_cache_iter = game_cache.find(id); + if (game_cache_iter != game_cache.end()) + { + const game_object& cached_game_obj = *game_cache_iter; + if (cached_game_obj.get_state() != current_game_obj.get_state()) + { + ilog("game ${id} changed state from ${old} to ${new}", + ("id", id) + ("old", cached_game_obj.get_state()) + ("new", current_game_obj.get_state())); + game_in_new_state(current_game_obj); + } + game_cache.modify(game_cache_iter, [&](game_object& obj) { obj = current_game_obj; }); + } + continue; + } + catch (const fc::exception& e) + { + // idump((e)); + } } } } @@ -787,8 +835,77 @@ public: vector< signed_transaction > import_balance( string name_or_id, const vector& wif_keys, bool broadcast ); + void game_in_new_state(const game_object& game_obj) + { try { + if (game_obj.get_state() == game_state::expecting_commit_moves) + { + if (game_obj.players.size() != 2) // we only support RPS, a 2 player game + return; + const rock_paper_scissors_game_details& rps_details = game_obj.game_details.get(); + for (unsigned i = 0; i < 2; ++i) + { + if (!rps_details.commit_moves.at(i)) // if this player hasn't committed their move + { + const account_id_type& account_id = game_obj.players[i]; + if (_wallet.my_accounts.find(account_id) != _wallet.my_accounts.end()) // and they're us + { + ilog("Game ${game_id}: it is ${account_name}'s turn to commit their move", + ("game_id", game_obj.id) + ("account_name", get_account(account_id).name)); + } + } + } + } + else if (game_obj.get_state() == game_state::expecting_reveal_moves) + { + if (game_obj.players.size() != 2) // we only support RPS, a 2 player game + return; + const rock_paper_scissors_game_details& rps_details = game_obj.game_details.get(); + for (unsigned i = 0; i < 2; ++i) + { + if (rps_details.commit_moves.at(i) && + !rps_details.reveal_moves.at(i)) // if this player has committed but not revealed + { + const account_id_type& account_id = game_obj.players[i]; + if (_wallet.my_accounts.find(account_id) != _wallet.my_accounts.end()) // and they're us + { + if (self.is_locked()) + ilog("Game ${game_id}: unable to broadcast ${account_name}'s reveal because the wallet is locked", + ("game_id", game_obj.id) + ("account_name", get_account(account_id).name)); + else + { + ilog("Game ${game_id}: it is ${account_name}'s turn to reveal their move", + ("game_id", game_obj.id) + ("account_name", get_account(account_id).name)); + + auto iter = _wallet.committed_game_moves.find(*rps_details.commit_moves.at(i)); + if (iter != _wallet.committed_game_moves.end()) + { + const rock_paper_scissors_throw_reveal& reveal = iter->second; + + game_move_operation move_operation; + move_operation.game_id = game_obj.id; + move_operation.player_account_id = account_id; + move_operation.move = reveal; + + signed_transaction trx; + trx.operations = {move_operation}; + set_operation_fees( trx, _remote_db->get_global_properties().parameters.current_fees); + trx.validate(); + ilog("Broadcasting reveal..."); + trx = sign_transaction(trx, true); + ilog("Reveal broadcast, transaction id is ${id}", ("id", trx.id())); + } + } + } + } + } + } + } FC_RETHROW_EXCEPTIONS(warn, "") } + void match_in_new_state(const match_object& match_obj) - { + { try { if (match_obj.get_state() == match_state::match_in_progress) { for (const account_id_type& account_id : match_obj.players) @@ -797,15 +914,22 @@ public: { ilog("Match ${match} is now in progress for player ${account}", ("match", match_obj.id)("account", get_account(account_id).name)); + for (const game_id_type& game_id : match_obj.games) + { + game_object game_obj = get_object(game_id); + auto insert_result = game_cache.insert(game_obj); + if (insert_result.second) + game_in_new_state(game_obj); + } } } } - } + } FC_RETHROW_EXCEPTIONS(warn, "") } // Cache all matches in the tournament, which will also register us for // updates on those matches void monitor_matches_in_tournament(const tournament_object& tournament_obj) - { + { try { tournament_details_object tournament_details = get_object(tournament_obj.tournament_details_id); for (const match_id_type& match_id : tournament_details.matches) { @@ -814,6 +938,42 @@ public: if (insert_result.second) match_in_new_state(match_obj); } + } FC_RETHROW_EXCEPTIONS(warn, "") } + + void resync_active_tournaments() + { + // check to see if any of our accounts are registered for tournaments + // the real purpose of this is to ensure that we are subscribed for callbacks on these tournaments + ilog("Checking my accounts for active tournaments",); + tournament_cache.clear(); + match_cache.clear(); + game_cache.clear(); + for (const account_object& my_account : _wallet.my_accounts) + { + std::vector tournaments = _remote_db->get_active_tournaments(my_account.id, 100); + std::vector tournament_ids; + for (const tournament_object& tournament : tournaments) + { + try + { + auto insert_result = tournament_cache.insert(tournament); + if (insert_result.second) + { + // then this is the first time we've seen this tournament + monitor_matches_in_tournament(tournament); + } + tournament_ids.push_back(tournament.id); + } + catch (const fc::exception& e) + { + edump((e)(tournament)); + } + } + if (!tournaments.empty()) + ilog("Account ${my_account} is registered for tournaments: ${tournaments}", ("my_account", my_account.name)("tournaments", tournament_ids)); + else + ilog("Account ${my_account} is not registered for any tournaments", ("my_account", my_account.name)); + } } bool load_wallet_file(string wallet_filename = "") @@ -876,28 +1036,7 @@ public: } } - // check to see if any of our accounts are registered for tournaments - // the real purpose of this is to ensure that we are subscribed for callbacks on these tournaments - ilog("Checking my accounts for active tournaments",); - for (const account_object& my_account : _wallet.my_accounts) - { - std::vector tournaments = _remote_db->get_active_tournaments(my_account.id, 100); - std::vector tournament_ids; - for (const tournament_object& tournament : tournaments) - { - auto insert_result = tournament_cache.insert(tournament); - if (insert_result.second) - { - // then this is the first time we've seen this tournament - monitor_matches_in_tournament(tournament); - } - tournament_ids.push_back(tournament.id); - } - if (!tournaments.empty()) - ilog("Account ${my_account} is registered for tournaments: ${tournaments}", ("my_account", my_account.name)("tournaments", tournament_ids)); - else - ilog("Account ${my_account} is not registered for any tournaments", ("my_account", my_account.name)); - } + resync_active_tournaments(); return true; } @@ -2723,6 +2862,12 @@ public: ordered_unique< tag, member< object, object_id_type, &object::id > > > > match_index_type; match_index_type match_cache; + typedef multi_index_container< + game_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > > > > game_index_type; + game_index_type game_cache; + #ifdef __unix__ mode_t _old_umask; #endif @@ -3666,6 +3811,7 @@ void wallet_api::unlock(string password) my->_keys = std::move(pk.keys); my->_checksum = pk.checksum; my->self.lock_changed(false); + my->resync_active_tournaments(); } FC_CAPTURE_AND_RETHROW() } void wallet_api::set_password( string password ) @@ -4419,6 +4565,55 @@ tournament_object wallet_api::get_tournament(tournament_id_type id) return my->_remote_db->get_objects({id})[0].as(); } +signed_transaction wallet_api::rps_throw(game_id_type game_id, + string player_account, + rock_paper_scissors_gesture gesture, + bool broadcast) +{ + FC_ASSERT( !is_locked() ); + + // check whether the gesture is appropriate for the game we're playing + graphene::chain::game_object game_obj = my->get_object(game_id); + graphene::chain::match_object match_obj = my->get_object(game_obj.match_id); + graphene::chain::tournament_object tournament_obj = my->get_object(match_obj.tournament_id); + graphene::chain::rock_paper_scissors_game_options game_options = + tournament_obj.options.game_options.get(); + if ((int)gesture >= game_options.number_of_gestures) + FC_THROW("Gesture ${gesture} not supported in this game", ("gesture", gesture)); + + account_object player_account_obj = get_account(player_account); + + // construct the complete throw, the commit, and reveal + rock_paper_scissors_throw full_throw; + fc::rand_bytes((char*)&full_throw.nonce1, sizeof(full_throw.nonce1)); + fc::rand_bytes((char*)&full_throw.nonce2, sizeof(full_throw.nonce2)); + full_throw.gesture = gesture; + + rock_paper_scissors_throw_commit commit_throw; + commit_throw.nonce1 = full_throw.nonce1; + std::vector full_throw_packed(fc::raw::pack(full_throw)); + commit_throw.throw_hash = fc::sha256::hash(full_throw_packed.data(), full_throw_packed.size()); + + rock_paper_scissors_throw_reveal reveal_throw; + reveal_throw.nonce2 = full_throw.nonce2; + reveal_throw.gesture = full_throw.gesture; + + // store off the reveal for transmitting after both players commit + my->_wallet.committed_game_moves[commit_throw] = reveal_throw; + + // broadcast the commit + signed_transaction tx; + game_move_operation move_operation; + move_operation.game_id = game_id; + move_operation.player_account_id = player_account_obj.id; + move_operation.move = commit_throw; + tx.operations = {move_operation}; + my->set_operation_fees( tx, my->_remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return my->sign_transaction( tx, broadcast ); +} + // default ctor necessary for FC_REFLECT signed_block_with_info::signed_block_with_info() { From c4ad9000266d0cf2dd3c998ccdfab13543c08783 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Tue, 18 Oct 2016 15:56:10 -0400 Subject: [PATCH 3/8] Fix build error in member_enumerator when an operation includes an enum. Implement the commit/reveal timeouts in RPS games, and generate automatic ("insurance") moves. Make the CLI wallet watch for new games in tournaments you're registered for. --- libraries/chain/db_management.cpp | 3 +- libraries/chain/db_update.cpp | 26 +++ libraries/chain/game_object.cpp | 195 +++++++++++++++--- .../chain/include/graphene/chain/database.hpp | 5 + .../include/graphene/chain/game_object.hpp | 15 +- .../graphene/chain/tournament_object.hpp | 2 +- libraries/chain/match_object.cpp | 2 +- libraries/chain/tournament_object.cpp | 51 +++-- libraries/wallet/wallet.cpp | 3 +- programs/build_helpers/member_enumerator.cpp | 43 +++- programs/js_operation_serializer/main.cpp | 55 +++-- 11 files changed, 323 insertions(+), 77 deletions(-) diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index e27d03c4..6371c61f 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -35,7 +35,8 @@ namespace graphene { namespace chain { -database::database() +database::database() : + _random_number_generator(fc::ripemd160().data()) { initialize_indexes(); initialize_evaluators(); diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 4dbf531f..fbbecb4a 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include @@ -70,6 +71,8 @@ void database::update_global_dynamic_data( const signed_block& b ) fc::raw::pack( enc, b.previous_secret ); dgp.random = enc.result(); + _random_number_generator = fc::hash_ctr_rng(dgp.random.data()); + if( BOOST_UNLIKELY( b.block_num() == 1 ) ) dgp.recently_missed_count = 0; else if( _checkpoints.size() && _checkpoints.rbegin()->first >= b.block_num() ) @@ -476,6 +479,11 @@ void database::update_withdraw_permissions() remove(*permit_index.begin()); } +uint64_t database::get_random_bits( uint64_t bound ) +{ + return _random_number_generator(bound); +} + void process_finished_games(database& db) { //auto& games_index = db.get_index_type().indices().get(); @@ -546,6 +554,24 @@ void initiate_next_round_of_matches(database& db) void initiate_next_games(database& db) { + // Next, trigger timeouts on any games which have been waiting too long for commit or + // reveal moves + auto& next_timeout_index = db.get_index_type().indices().get(); + while (1) + { + // empty time_points are sorted to the beginning, so upper_bound takes us to the first + // non-empty time_point + auto start_iter = next_timeout_index.upper_bound(boost::make_tuple(optional())); + if (start_iter != next_timeout_index.end() && + *start_iter->next_timeout <= db.head_block_time()) + { + db.modify(*start_iter, [&](game_object& game) { + game.on_timeout(db); + }); + } + else + break; + } } void database::update_tournaments() diff --git a/libraries/chain/game_object.cpp b/libraries/chain/game_object.cpp index 4031890f..bc205f20 100644 --- a/libraries/chain/game_object.cpp +++ b/libraries/chain/game_object.cpp @@ -33,6 +33,8 @@ #include #include +#include + namespace graphene { namespace chain { namespace msm = boost::msm; @@ -59,6 +61,14 @@ namespace graphene { namespace chain { {} }; + struct timeout + { + database& db; + timeout(database& db) : + db(db) + {} + }; + struct game_state_machine_ : public msm::front::state_machine_def { // disable a few state machine features we don't use for performance @@ -69,6 +79,13 @@ namespace graphene { namespace chain { struct waiting_for_game_to_start : public msm::front::state<> {}; struct expecting_commit_moves : public msm::front::state<> { + void set_next_timeout(database& db, game_object& game) + { + const match_object& match_obj = game.match_id(db); + const tournament_object& tournament_obj = match_obj.tournament_id(db); + const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get(); + game.next_timeout = db.head_block_time() + game_options.time_per_commit_move; + } void on_entry(const initiate_game& event, game_state_machine_& fsm) { game_object& game = *fsm.game_obj; @@ -80,6 +97,7 @@ namespace graphene { namespace chain { "game ${id} is associtated with match ${match_id}", ("id", game.id) ("match_id", game.match_id)); + set_next_timeout(event.db, game); } void on_entry(const game_move& event, game_state_machine_& fsm) { @@ -88,10 +106,26 @@ namespace graphene { namespace chain { fc_ilog(fc::logger::get("tournament"), "game ${id} received a commit move, still expecting another commit move", ("id", game.id)); + set_next_timeout(event.db, game); } }; struct expecting_reveal_moves : public msm::front::state<> { + void set_next_timeout(database& db, game_object& game) + { + const match_object& match_obj = game.match_id(db); + const tournament_object& tournament_obj = match_obj.tournament_id(db); + const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get(); + game.next_timeout = db.head_block_time() + game_options.time_per_reveal_move; + } + void on_entry(const timeout& event, game_state_machine_& fsm) + { + game_object& game = *fsm.game_obj; + fc_ilog(fc::logger::get("tournament"), + "game ${id} timed out waiting for commit moves, now expecting reveal move", + ("id", game.id)); + set_next_timeout(event.db, game); + } void on_entry(const game_move& event, game_state_machine_& fsm) { game_object& game = *fsm.game_obj; @@ -104,10 +138,31 @@ namespace graphene { namespace chain { fc_ilog(fc::logger::get("tournament"), "game ${id} received a reveal move, still expecting reveal moves", ("id", game.id)); + set_next_timeout(event.db, game); } }; + struct game_complete : public msm::front::state<> { + void clear_next_timeout(database& db, game_object& game) + { + const match_object& match_obj = game.match_id(db); + const tournament_object& tournament_obj = match_obj.tournament_id(db); + const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get(); + game.next_timeout = fc::optional(); + } + void on_entry(const timeout& event, game_state_machine_& fsm) + { + game_object& game = *fsm.game_obj; + fc_ilog(fc::logger::get("tournament"), + "timed out waiting for commits or reveals, game ${id} is complete", + ("id", game.id)); + + game.make_automatic_moves(event.db); + game.determine_winner(event.db); + clear_next_timeout(event.db, game); + } + void on_entry(const game_move& event, game_state_machine_& fsm) { game_object& game = *fsm.game_obj; @@ -115,31 +170,10 @@ namespace graphene { namespace chain { "received a reveal move, game ${id} is complete", ("id", fsm.game_obj->id)); - // we now know who played what, figure out if we have a winner - const rock_paper_scissors_game_details& game_details = game.game_details.get(); - if (game_details.reveal_moves[0]->gesture == game_details.reveal_moves[1]->gesture) - ilog("The game was a tie, both players threw ${gesture}", ("gesture", game_details.reveal_moves[0]->gesture)); - else - { - const match_object& match_obj = game.match_id(event.db); - const tournament_object& tournament_obj = match_obj.tournament_id(event.db); - const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get(); - - unsigned winner = ((((int)game_details.reveal_moves[0]->gesture - - (int)game_details.reveal_moves[1]->gesture + - game_options.number_of_gestures) % game_options.number_of_gestures) + 1) % 2; - ilog("${gesture1} vs ${gesture2}, ${winner} wins", - ("gesture1", game_details.reveal_moves[1]->gesture) - ("gesture2", game_details.reveal_moves[0]->gesture) - ("winner", game_details.reveal_moves[winner]->gesture)); - game.winners.insert(game.players[winner]); - } - - - const match_object& match_obj = game.match_id(event.db); - event.db.modify(match_obj, [&](match_object& match) { - match.on_game_complete(event.db, game); - }); + // if one player didn't commit a move we might need to make their "insurance" move now + game.make_automatic_moves(event.db); + game.determine_winner(event.db); + clear_next_timeout(event.db, game); } }; typedef waiting_for_game_to_start initial_state; @@ -158,15 +192,23 @@ namespace graphene { namespace chain { return game_details.commit_moves.at(other_player_index).valid(); } - bool already_have_other_reveal(const game_move& event) + bool now_have_reveals_for_all_commits(const game_move& event) { auto iter = std::find(game_obj->players.begin(), game_obj->players.end(), event.move.player_account_id); - unsigned player_index = std::distance(game_obj->players.begin(), iter); - // hard-coded here for two-player games - unsigned other_player_index = player_index == 0 ? 1 : 0; + unsigned this_reveal_index = std::distance(game_obj->players.begin(), iter); + const rock_paper_scissors_game_details& game_details = game_obj->game_details.get(); - return game_details.reveal_moves.at(other_player_index).valid(); + for (unsigned i = 0; i < game_details.commit_moves.size(); ++i) + if (!game_details.reveal_moves[i] && i != this_reveal_index) + return false; + return true; + } + + bool have_at_least_one_commit_move(const timeout& event) + { + const rock_paper_scissors_game_details& game_details = game_obj->game_details.get(); + return game_details.commit_moves[0] || game_details.commit_moves[1]; } void apply_commit_move(const game_move& event) @@ -203,9 +245,12 @@ namespace graphene { namespace chain { // +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+ a_row < expecting_commit_moves, game_move, expecting_commit_moves, &x::apply_commit_move >, row < expecting_commit_moves, game_move, expecting_reveal_moves, &x::apply_commit_move, &x::already_have_other_commit >, + _row < expecting_commit_moves, timeout, game_complete >, + g_row < expecting_commit_moves, timeout, expecting_reveal_moves, &x::have_at_least_one_commit_move >, // +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+ + _row < expecting_reveal_moves, timeout, game_complete >, a_row < expecting_reveal_moves, game_move, expecting_reveal_moves, &x::apply_reveal_move >, - row < expecting_reveal_moves, game_move, game_complete, &x::apply_reveal_move, &x::already_have_other_reveal > + row < expecting_reveal_moves, game_move, game_complete, &x::apply_reveal_move, &x::now_have_reveals_for_all_commits > // +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+ //a_row < game_in_progress, game_complete, game_in_progress, &x::start_next_game >, //g_row < game_in_progress, game_complete, game_complete, &x::was_final_game > @@ -237,6 +282,7 @@ namespace graphene { namespace chain { players(rhs.players), winners(rhs.winners), game_details(rhs.game_details), + next_timeout(rhs.next_timeout), my(new impl(this)) { my->state_machine = rhs.my->state_machine; @@ -251,6 +297,7 @@ namespace graphene { namespace chain { players = rhs.players; winners = rhs.winners; game_details = rhs.game_details; + next_timeout = rhs.next_timeout; my->state_machine = rhs.my->state_machine; my->state_machine.game_obj = this; @@ -384,11 +431,95 @@ namespace graphene { namespace chain { FC_THROW("Game of type ${type} not supported", ("type", game_details.which())); } + void game_object::make_automatic_moves(database& db) + { + rock_paper_scissors_game_details& rps_game_details = game_details.get(); + + unsigned players_without_commit_moves = 0; + bool no_player_has_reveal_move = true; + for (unsigned i = 0; i < 2; ++i) + { + if (!rps_game_details.commit_moves[i]) + ++players_without_commit_moves; + if (rps_game_details.reveal_moves[i]) + no_player_has_reveal_move = false; + } + + if (players_without_commit_moves || no_player_has_reveal_move) + { + const match_object& match_obj = match_id(db); + const tournament_object& tournament_obj = match_obj.tournament_id(db); + const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get(); + for (unsigned i = 0; i < 2; ++i) + { + if (!rps_game_details.commit_moves[i] || + no_player_has_reveal_move) + { + struct rock_paper_scissors_throw_reveal reveal; + reveal.nonce2 = 0; + reveal.gesture = (rock_paper_scissors_gesture)db.get_random_bits(game_options.number_of_gestures); + rps_game_details.reveal_moves[i] = reveal; + ilog("Player ${player} failed to commit a move, generating a random move for them: ${gesture}", + ("player", i)("gesture", reveal.gesture)); + } + } + } + } + + void game_object::determine_winner(database& db) + { + // we now know who played what, figure out if we have a winner + const rock_paper_scissors_game_details& rps_game_details = game_details.get(); + if (rps_game_details.reveal_moves[0]->gesture == rps_game_details.reveal_moves[1]->gesture) + ilog("The game was a tie, both players threw ${gesture}", ("gesture", rps_game_details.reveal_moves[0]->gesture)); + else + { + const match_object& match_obj = match_id(db); + const tournament_object& tournament_obj = match_obj.tournament_id(db); + const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get(); + + if (rps_game_details.reveal_moves[0] && rps_game_details.reveal_moves[1]) + { + unsigned winner = ((((int)rps_game_details.reveal_moves[0]->gesture - + (int)rps_game_details.reveal_moves[1]->gesture + + game_options.number_of_gestures) % game_options.number_of_gestures) + 1) % 2; + ilog("${gesture1} vs ${gesture2}, ${winner} wins", + ("gesture1", rps_game_details.reveal_moves[1]->gesture) + ("gesture2", rps_game_details.reveal_moves[0]->gesture) + ("winner", rps_game_details.reveal_moves[winner]->gesture)); + winners.insert(players[winner]); + } + else if (rps_game_details.reveal_moves[0]) + { + ilog("Player 1 didn't commit or reveal their move, player 0 wins"); + winners.insert(players[0]); + } + else if (rps_game_details.reveal_moves[1]) + { + ilog("Player 0 didn't commit or reveal their move, player 1 wins"); + winners.insert(players[1]); + } + else if (rps_game_details.reveal_moves[1]) + ilog("Neither player made a move, both players lose"); + } + + + const match_object& match_obj = match_id(db); + db.modify(match_obj, [&](match_object& match) { + match.on_game_complete(db, *this); + }); + } + void game_object::on_move(database& db, const game_move_operation& op) { my->state_machine.process_event(game_move(db, op)); } + void game_object::on_timeout(database& db) + { + my->state_machine.process_event(timeout(db)); + } + void game_object::start_game(database& db, const std::vector& players) { my->state_machine.process_event(initiate_game(db, players)); @@ -420,6 +551,7 @@ namespace fc { ("players", game_obj.players) ("winners", game_obj.winners) ("game_details", game_obj.game_details) + ("next_timeout", game_obj.next_timeout) ("state", game_obj.get_state()); v = o; @@ -434,6 +566,7 @@ namespace fc { game_obj.players = v["players"].as >(); game_obj.winners = v["winners"].as >(); game_obj.game_details = v["game_details"].as(); + game_obj.next_timeout = v["next_timeout"].as >(); graphene::chain::game_state state = v["state"].as(); const_cast(game_obj.my->state_machine.current_state())[0] = (int)state; } diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 36f7f622..0f8102a0 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -36,6 +36,8 @@ #include #include +#include + #include #include @@ -250,6 +252,8 @@ namespace graphene { namespace chain { const node_property_object& get_node_properties()const; const fee_schedule& current_fee_schedule()const; + uint64_t get_random_bits( uint64_t bound ); + time_point_sec head_block_time()const; uint32_t head_block_num()const; block_id_type head_block_id()const; @@ -488,6 +492,7 @@ namespace graphene { namespace chain { flat_map _checkpoints; node_property_object _node_property_object; + fc::hash_ctr_rng _random_number_generator; }; namespace detail diff --git a/libraries/chain/include/graphene/chain/game_object.hpp b/libraries/chain/include/graphene/chain/game_object.hpp index 6f8ba82f..a6c3232f 100644 --- a/libraries/chain/include/graphene/chain/game_object.hpp +++ b/libraries/chain/include/graphene/chain/game_object.hpp @@ -41,6 +41,8 @@ namespace graphene { namespace chain { flat_set winners; game_specific_details game_details; + + fc::optional next_timeout; game_state get_state() const; @@ -50,7 +52,11 @@ namespace graphene { namespace chain { game_object& operator=(const game_object& rhs); void evaluate_move_operation(const database& db, const game_move_operation& op) const; + void make_automatic_moves(database& db); + void determine_winner(database& db); + void on_move(database& db, const game_move_operation& op); + void on_timeout(database& db); void start_game(database& db, const std::vector& players); // serialization functions: @@ -72,10 +78,15 @@ namespace graphene { namespace chain { std::unique_ptr my; }; + struct by_next_timeout {}; typedef multi_index_container< game_object, indexed_by< - ordered_unique< tag, member< object, object_id_type, &object::id > > > + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, + composite_key, &game_object::next_timeout>, + member > > > > game_object_multi_index_type; typedef generic_index game_index; @@ -90,6 +101,7 @@ namespace graphene { namespace chain { fc::raw::pack(s, game_obj.players); fc::raw::pack(s, game_obj.winners); fc::raw::pack(s, game_obj.game_details); + fc::raw::pack(s, game_obj.next_timeout); // fc::raw::pack the contents hidden in the impl class std::ostringstream stream; @@ -110,6 +122,7 @@ namespace graphene { namespace chain { fc::raw::unpack(s, game_obj.players); fc::raw::unpack(s, game_obj.winners); fc::raw::unpack(s, game_obj.game_details); + fc::raw::unpack(s, game_obj.next_timeout); // fc::raw::unpack the contents hidden in the impl class std::string stringified_stream; diff --git a/libraries/chain/include/graphene/chain/tournament_object.hpp b/libraries/chain/include/graphene/chain/tournament_object.hpp index f8a04a97..df144f7d 100644 --- a/libraries/chain/include/graphene/chain/tournament_object.hpp +++ b/libraries/chain/include/graphene/chain/tournament_object.hpp @@ -111,7 +111,7 @@ namespace graphene { namespace chain { void on_registration_deadline_passed(database& db); void on_player_registered(database& db, account_id_type payer_id, account_id_type player_id); void on_start_time_arrived(database& db); - void on_final_game_completed(); + void on_match_completed(database& db, const match_object& match); void check_for_new_matches_to_start(database& db) const; private: diff --git a/libraries/chain/match_object.cpp b/libraries/chain/match_object.cpp index 5627965c..8c39c1b3 100644 --- a/libraries/chain/match_object.cpp +++ b/libraries/chain/match_object.cpp @@ -98,7 +98,7 @@ namespace graphene { namespace chain { const tournament_object& tournament_obj = match.tournament_id(event.db); event.db.modify(tournament_obj, [&](tournament_object& tournament) { - tournament.on_final_game_completed(); + tournament.on_match_completed(event.db, match); }); } diff --git a/libraries/chain/tournament_object.cpp b/libraries/chain/tournament_object.cpp index 0ee5be92..afd965a8 100644 --- a/libraries/chain/tournament_object.cpp +++ b/libraries/chain/tournament_object.cpp @@ -31,8 +31,6 @@ #include #include -#include - namespace graphene { namespace chain { namespace msm = boost::msm; @@ -60,7 +58,13 @@ namespace graphene { namespace chain { database& db; start_time_arrived(database& db) : db(db) {}; }; - struct final_game_completed {}; + + struct match_completed + { + database& db; + const match_object& match; + match_completed(database& db, const match_object& match) : db(db), match(match) {} + }; struct tournament_state_machine_ : public msm::front::state_machine_def { @@ -120,9 +124,6 @@ namespace graphene { namespace chain { ("id", fsm.tournament_obj->id)); const tournament_details_object& tournament_details_obj = fsm.tournament_obj->tournament_details_id(event.db); - // TODO hoist the rng to reset once per block? - fc::hash_ctr_rng rng(event.db.get_dynamic_global_properties().random.data()); - // Create the "seeding" order for the tournament as a random shuffle of the players. // // If this were a game of skill where players were ranked, this algorithm expects the @@ -131,7 +132,7 @@ namespace graphene { namespace chain { tournament_details_obj.registered_players.end()); for (unsigned i = seeded_players.size() - 1; i >= 1; --i) { - unsigned j = (unsigned)rng(i + 1); + unsigned j = (unsigned)event.db.get_random_bits(i + 1); std::swap(seeded_players[i], seeded_players[j]); } @@ -178,6 +179,12 @@ namespace graphene { namespace chain { tournament_details_obj.matches = matches; }); } + void on_entry(const match_completed& event, tournament_state_machine_& fsm) + { + fc_ilog(fc::logger::get("tournament"), + "Tournament ${id} is still in progress, maybe should start a new match here", + ("id", fsm.tournament_obj->id)); + } }; struct registration_period_expired : public msm::front::state<> { @@ -199,7 +206,17 @@ namespace graphene { namespace chain { } } }; - struct concluded : public msm::front::state<>{}; + + struct concluded : public msm::front::state<> + { + void on_entry(const match_completed& event, tournament_state_machine_& fsm) + { + fc_ilog(fc::logger::get("tournament"), + "Tournament ${id} is complete", + ("id", fsm.tournament_obj->id)); + } + }; + typedef accepting_registrations initial_state; @@ -214,6 +231,15 @@ namespace graphene { namespace chain { return tournament_obj->registered_players == tournament_obj->options.number_of_players - 1; } + bool was_final_match(const match_completed& event) + { + const tournament_details_object& tournament_details_obj = tournament_obj->tournament_details_id(event.db); + fc_ilog(fc::logger::get("tournament"), + "In was_final_match guard, returning ${value}", + ("value", event.match.id == tournament_details_obj.matches[tournament_details_obj.matches.size()])); + return event.match.id == tournament_details_obj.matches[tournament_details_obj.matches.size() - 1]; + } + void register_player(const player_registered& event) { fc_ilog(fc::logger::get("tournament"), @@ -235,12 +261,13 @@ namespace graphene { namespace chain { // Start Event Next Action Guard // +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+ a_row < accepting_registrations, player_registered, accepting_registrations, &x::register_player >, - row < accepting_registrations, player_registered, awaiting_start, &x::register_player, &x::will_be_fully_registered >, + row < accepting_registrations, player_registered, awaiting_start, &x::register_player, &x::will_be_fully_registered >, _row < accepting_registrations, registration_deadline_passed, registration_period_expired >, // +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+ _row < awaiting_start, start_time_arrived, in_progress >, // +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+ - _row < in_progress, final_game_completed, concluded > + _row < in_progress, match_completed, in_progress >, + g_row < in_progress, match_completed, concluded, &x::was_final_match > // +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+ > {}; @@ -366,9 +393,9 @@ namespace graphene { namespace chain { my->state_machine.process_event(start_time_arrived(db)); } - void tournament_object::on_final_game_completed() + void tournament_object::on_match_completed(database& db, const match_object& match) { - my->state_machine.process_event(final_game_completed()); + my->state_machine.process_event(match_completed(db, match)); } void tournament_object::check_for_new_matches_to_start(database& db) const diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 604a99f7..0afe527b 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -406,7 +406,8 @@ private: if (match_cache_iter != match_cache.end()) { const match_object& cached_match_obj = *match_cache_iter; - if (cached_match_obj.get_state() != current_match_obj.get_state()) + if (cached_match_obj.get_state() != current_match_obj.get_state() || + cached_match_obj.games.size() != current_match_obj.games.size()) { ilog("match ${id} changed state from ${old} to ${new}", ("id", id) diff --git a/programs/build_helpers/member_enumerator.cpp b/programs/build_helpers/member_enumerator.cpp index 001b47bd..8ad26633 100644 --- a/programs/build_helpers/member_enumerator.cpp +++ b/programs/build_helpers/member_enumerator.cpp @@ -110,6 +110,37 @@ void class_processor::process_class( const static_variant< T... >* dummy ) } } +template +struct if_enum +{ + template< typename T > + static void process_class( class_processor* proc, const T* dummy ) + { + std::string tname = fc::get_typename::name(); + if( proc->result.find( tname ) != proc->result.end() ) + return; + ilog( "processing class ${c}", ("c", tname) ); + // need this to keep from recursing on same class + proc->result.emplace( tname, std::vector< std::string >() ); + + member_visitor vtor( proc ); + fc::reflector::visit( vtor ); + ilog( "members of class ${c} are ${m}", ("c", tname)("m", vtor.members) ); + proc->result[tname] = vtor.members; + } +}; + +template<> +struct if_enum +{ + template< typename T > + static void process_class( class_processor* proc, const T* dummy ) + { + std::string tname = fc::get_typename::name(); + std::cerr << "skipping reflected enum " << tname << std::endl; + } +}; + template struct if_reflected { @@ -127,17 +158,7 @@ struct if_reflected template< typename T > static void process_class( class_processor* proc, const T* dummy ) { - std::string tname = fc::get_typename::name(); - if( proc->result.find( tname ) != proc->result.end() ) - return; - ilog( "processing class ${c}", ("c", tname) ); - // need this to keep from recursing on same class - proc->result.emplace( tname, std::vector< std::string >() ); - - member_visitor vtor( proc ); - fc::reflector::visit( vtor ); - ilog( "members of class ${c} are ${m}", ("c", tname)("m", vtor.members) ); - proc->result[tname] = vtor.members; + if_enum< typename fc::reflector::is_enum >::process_class(proc, dummy); } }; diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index 24c18c93..6c60d943 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include #include @@ -344,32 +346,49 @@ class register_member_visitor } }; +template +struct serializer_init_helper { + static void init() + { + auto name = js_name::name(); + if( st.find(name) == st.end() ) + { + fc::reflector::visit( register_member_visitor() ); + register_serializer( name, [=](){ generate(); } ); + } + } + static void generate() + { + auto name = remove_namespace( js_name::name() ); + if( name == "int64" ) return; + std::cout << "" << name + << " = new Serializer( \n" + << " \"" + name + "\"\n"; + + fc::reflector::visit( serialize_member_visitor() ); + + std::cout <<")\n\n"; + } +}; + +template +struct serializer_init_helper { + static void init() + { + } +}; + + template struct serializer { static_assert( fc::reflector::is_defined::value == reflected, "invalid template arguments" ); + static void init() { - auto name = js_name::name(); - if( st.find(name) == st.end() ) - { - fc::reflector::visit( register_member_visitor() ); - register_serializer( name, [=](){ generate(); } ); - } + serializer_init_helper< T, typename fc::reflector::is_enum >::init(); } - static void generate() - { - auto name = remove_namespace( js_name::name() ); - if( name == "int64" ) return; - std::cout << "" << name - << " = new Serializer( \n" - << " \"" + name + "\"\n"; - - fc::reflector::visit( serialize_member_visitor() ); - - std::cout <<")\n\n"; - } }; } // namespace detail_ns From 72d48cf3d25eaeb6192070268d7f481d6b2c8505 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Wed, 19 Oct 2016 18:19:13 -0400 Subject: [PATCH 4/8] Remove redundant type_of_game field --- .../graphene/chain/protocol/tournament.hpp | 16 ++-------------- libraries/chain/tournament_evaluator.cpp | 2 -- libraries/wallet/wallet.cpp | 1 - 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/libraries/chain/include/graphene/chain/protocol/tournament.hpp b/libraries/chain/include/graphene/chain/protocol/tournament.hpp index 2c390fac..6c5299d4 100644 --- a/libraries/chain/include/graphene/chain/protocol/tournament.hpp +++ b/libraries/chain/include/graphene/chain/protocol/tournament.hpp @@ -36,15 +36,6 @@ namespace graphene { namespace chain { - /** - * @brief List of games currently supported on the blockchain - */ - enum game_type - { - rock_paper_scissors, - GAME_TYPE_COUNT - }; - typedef fc::static_variant game_specific_options; /** @@ -52,9 +43,6 @@ namespace graphene { namespace chain { */ struct tournament_options { - /// The type of game in this tournament - uint16_t type_of_game; /* actually a game_type, but that doesn't reflect properly */ - /// If there aren't enough players registered for the tournament before this time, /// the tournament is canceled fc::time_point_sec registration_deadline; @@ -94,6 +82,8 @@ namespace graphene { namespace chain { fc::variant_object meta; /// Parameters that are specific to the type_of_game in this tournament + /// The type stored in this static_variant field determines what type of game is being + /// played, so each different supported game must have a unique game_options data type game_specific_options game_options; void validate() const; @@ -175,11 +165,9 @@ namespace graphene { namespace chain { } } -FC_REFLECT_ENUM( graphene::chain::game_type, (rock_paper_scissors)(GAME_TYPE_COUNT) ) FC_REFLECT_TYPENAME( graphene::chain::game_specific_options ) FC_REFLECT_TYPENAME( graphene::chain::game_specific_moves ) FC_REFLECT( graphene::chain::tournament_options, - (type_of_game) (registration_deadline) (number_of_players) (buy_in) diff --git a/libraries/chain/tournament_evaluator.cpp b/libraries/chain/tournament_evaluator.cpp index 6bb62866..1c26c092 100644 --- a/libraries/chain/tournament_evaluator.cpp +++ b/libraries/chain/tournament_evaluator.cpp @@ -11,8 +11,6 @@ namespace graphene { namespace chain { void_result tournament_create_evaluator::do_evaluate( const tournament_create_operation& op ) { try { database& d = db(); - FC_ASSERT(op.options.type_of_game == rock_paper_scissors, "Unsupported game type ${type}", ("type", op.options.type_of_game)); - FC_ASSERT(op.options.registration_deadline >= d.head_block_time(), "Registration deadline has already passed"); // TODO: make this committee-set diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 0afe527b..2ecc86e7 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -2451,7 +2451,6 @@ public: { asset_object buy_in_asset = get_asset(tournament_obj.options.buy_in.asset_id); ss << fc::variant(tournament_obj.id).as() << " " - << fc::variant(game_type(tournament_obj.options.type_of_game)).as() << " " << buy_in_asset.amount_to_pretty_string(tournament_obj.options.buy_in.amount) << " " << tournament_obj.options.number_of_players << " players\n"; switch (tournament_obj.get_state()) From 30874697cc9be587300c09199c90ba7ce486da7d Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Fri, 21 Oct 2016 12:14:37 -0400 Subject: [PATCH 5/8] Rework the API calls for tournaments, add an index for getting tournaments registered by a given account --- libraries/app/database_api.cpp | 60 +++++------------ .../app/include/graphene/app/database_api.hpp | 15 ++--- libraries/chain/db_init.cpp | 3 +- .../graphene/chain/tournament_object.hpp | 29 ++++++++ libraries/chain/tournament_evaluator.cpp | 8 ++- libraries/chain/tournament_object.cpp | 67 +++++++++++++++++++ .../wallet/include/graphene/wallet/wallet.hpp | 3 +- libraries/wallet/wallet.cpp | 17 ++--- 8 files changed, 137 insertions(+), 65 deletions(-) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index b8c0c280..be234b20 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -139,8 +139,8 @@ class database_api_impl : public std::enable_shared_from_this vector get_blinded_balances( const flat_set& commitments )const; // Tournaments - vector get_upcoming_tournaments(fc::optional account_filter, uint32_t limit)const; - vector get_active_tournaments(fc::optional account_filter, uint32_t limit)const; + vector get_tournaments_in_state(tournament_state state, uint32_t limit) const; + vector get_registered_tournaments(account_id_type account_filter, uint32_t limit) const; //private: @@ -1769,26 +1769,20 @@ vector database_api_impl::get_blinded_balances( const fl // Tournament methods // // // ////////////////////////////////////////////////////////////////////// - -vector database_api::get_upcoming_tournaments(fc::optional account_filter, uint32_t limit)const +vector database_api::get_tournaments_in_state(tournament_state state, uint32_t limit) const { - return my->get_upcoming_tournaments(account_filter, limit); + return my->get_tournaments_in_state(state, limit); } -vector database_api_impl::get_upcoming_tournaments(fc::optional account_filter, uint32_t limit)const +vector database_api_impl::get_tournaments_in_state(tournament_state state, uint32_t limit) const { vector result; const auto& registration_deadline_index = _db.get_index_type().indices().get(); - const auto range = registration_deadline_index.equal_range(boost::make_tuple(tournament_state::accepting_registrations)); + const auto range = registration_deadline_index.equal_range(boost::make_tuple(state)); for (const tournament_object& tournament_obj : boost::make_iterator_range(range.first, range.second)) { - if (tournament_obj.options.whitelist.empty() || - !account_filter || - tournament_obj.options.whitelist.find(*account_filter) != tournament_obj.options.whitelist.end()) - { - result.emplace_back(tournament_obj); - subscribe_to_item( tournament_obj.id ); - } + result.emplace_back(tournament_obj); + subscribe_to_item( tournament_obj.id ); if (result.size() >= limit) break; @@ -1796,39 +1790,21 @@ vector database_api_impl::get_upcoming_tournaments(fc::option return result; } -vector database_api::get_active_tournaments(fc::optional account_filter, uint32_t limit)const +vector database_api::get_registered_tournaments(account_id_type account_filter, uint32_t limit) const { - return my->get_active_tournaments(account_filter, limit); + return my->get_registered_tournaments(account_filter, limit); } -vector database_api_impl::get_active_tournaments(fc::optional account_filter, uint32_t limit)const +vector database_api_impl::get_registered_tournaments(account_id_type account_filter, uint32_t limit) const { - vector result; - const auto& start_time_index = _db.get_index_type().indices().get(); + const auto& tournament_details_idx = _db.get_index_type(); + const auto& tournament_details_primary_idx = dynamic_cast&>(tournament_details_idx); + const auto& players_idx = tournament_details_primary_idx.get_secondary_index(); - const auto begin = start_time_index.lower_bound(boost::make_tuple(tournament_state::awaiting_start)); - const auto end = start_time_index.upper_bound(boost::make_tuple(tournament_state::in_progress)); - for (const tournament_object& tournament_obj : boost::make_iterator_range(begin, end)) - { - if (account_filter) - { - const tournament_details_object& tournament_details_obj = tournament_obj.tournament_details_id(_db); - if (tournament_details_obj.registered_players.find(*account_filter) != tournament_details_obj.registered_players.end()) - { - result.emplace_back(tournament_obj); - subscribe_to_item( tournament_obj.id ); - } - } - else - { - result.emplace_back(tournament_obj); - subscribe_to_item( tournament_obj.id ); - } - - if (result.size() >= limit) - break; - } - return result; + vector tournament_ids = players_idx.get_registered_tournaments_for_account(account_filter); + if (tournament_ids.size() >= limit) + tournament_ids.resize(limit); + return tournament_ids; } ////////////////////////////////////////////////////////////////////// diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 22dc535e..60d1c44f 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -553,17 +553,14 @@ class database_api // Tournaments // ///////////////// /** - * @param account_filter if provided, this will only return tournaments the given account is - * allowed to join (public tournaments or tournaments the account is whitelisted for) - * @return the list of tournaments that are still accepting new registrations + * @return the list of tournaments in the given state */ - vector get_upcoming_tournaments(fc::optional account_filter, uint32_t limit)const; + vector get_tournaments_in_state(tournament_state state, uint32_t limit) const; /** - * @return the list of tournaments that are either in-progress or fully-registered and just waiting on their start - * time to arrive + * @return the list of tournaments that a given account is registered to play in */ - vector get_active_tournaments(fc::optional account_filter, uint32_t limit)const; + vector get_registered_tournaments(account_id_type account_filter, uint32_t limit) const; private: std::shared_ptr< database_api_impl > my; @@ -667,6 +664,6 @@ FC_API(graphene::app::database_api, (get_blinded_balances) // Tournaments - (get_upcoming_tournaments) - (get_active_tournaments) + (get_tournaments_in_state) + (get_registered_tournaments) ) diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 2f9c3b8f..12da3670 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -208,7 +208,8 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); - add_index< primary_index >(); + auto tournament_details_idx = add_index< primary_index >(); + tournament_details_idx->add_secondary_index(); add_index< primary_index >(); add_index< primary_index >(); diff --git a/libraries/chain/include/graphene/chain/tournament_object.hpp b/libraries/chain/include/graphene/chain/tournament_object.hpp index df144f7d..355e454b 100644 --- a/libraries/chain/include/graphene/chain/tournament_object.hpp +++ b/libraries/chain/include/graphene/chain/tournament_object.hpp @@ -29,6 +29,9 @@ namespace graphene { namespace chain { static const uint8_t space_id = protocol_ids; static const uint8_t type_id = tournament_details_object_type; + /// the tournament object for which this is the details + tournament_id_type tournament_id; + /// List of players registered for this tournament flat_set registered_players; @@ -197,9 +200,35 @@ namespace graphene { namespace chain { return s; } + /** + * @brief This secondary index will allow a reverse lookup of all tournaments + * a particular account has registered for. This will be attached + * to the tournament details index because the registrations are contained + * in the tournament details object, but it will index the tournament ids + * since that is most useful to the GUI. + */ + class tournament_players_index : public secondary_index + { + public: + virtual void object_inserted( const object& obj ) override; + virtual void object_removed( const object& obj ) override; + virtual void about_to_modify( const object& before ) override; + virtual void object_modified( const object& after ) override; + + /** given an account, map it to the set of tournaments in which that account is registered as a player */ + map< account_id_type, flat_set > account_to_joined_tournaments; + + vector get_registered_tournaments_for_account( const account_id_type& a )const; + protected: + + flat_set before_account_ids; + }; + + } } FC_REFLECT_DERIVED(graphene::chain::tournament_details_object, (graphene::db::object), + (tournament_id) (registered_players) (payers) (matches)) diff --git a/libraries/chain/tournament_evaluator.cpp b/libraries/chain/tournament_evaluator.cpp index 1c26c092..12e89661 100644 --- a/libraries/chain/tournament_evaluator.cpp +++ b/libraries/chain/tournament_evaluator.cpp @@ -84,6 +84,12 @@ namespace graphene { namespace chain { t.tournament_details_id = tournament_details.id; }); + // TODO: look up how to do this in the initial create + db().modify(tournament_details, [&]( tournament_details_object& a ) { + a.tournament_id = new_tournament.id; + }); + + fc_ilog(fc::logger::get("tournament"), "Created tournament ${id} with details id ${details_id}", ("id", new_tournament.id)("details_id", tournament_details.id)); @@ -107,7 +113,7 @@ namespace graphene { namespace chain { "Registration deadline has already passed"); FC_ASSERT(_tournament_obj->options.whitelist.empty() || - _tournament_obj->options.whitelist.find(op.player_account_id) == _tournament_obj->options.whitelist.end(), + _tournament_obj->options.whitelist.find(op.player_account_id) != _tournament_obj->options.whitelist.end(), "Player is not on the whitelist for this tournament"); FC_ASSERT(_tournament_details_obj->registered_players.find(op.player_account_id) == _tournament_details_obj->registered_players.end(), diff --git a/libraries/chain/tournament_object.cpp b/libraries/chain/tournament_object.cpp index afd965a8..cad37c5c 100644 --- a/libraries/chain/tournament_object.cpp +++ b/libraries/chain/tournament_object.cpp @@ -479,6 +479,73 @@ namespace graphene { namespace chain { return fc::sha256::hash(full_throw_packed.data(), full_throw_packed.size()); } + + vector tournament_players_index::get_registered_tournaments_for_account( const account_id_type& a )const + { + auto iter = account_to_joined_tournaments.find(a); + if (iter != account_to_joined_tournaments.end()) + return vector(iter->second.begin(), iter->second.end()); + return vector(); + } + + void tournament_players_index::object_inserted(const object& obj) + { + assert( dynamic_cast(&obj) ); // for debug only + const tournament_details_object& details = static_cast(obj); + + for (const account_id_type& account_id : details.registered_players) + account_to_joined_tournaments[account_id].insert(details.tournament_id); + } + + void tournament_players_index::object_removed(const object& obj) + { + assert( dynamic_cast(&obj) ); // for debug only + const tournament_details_object& details = static_cast(obj); + + for (const account_id_type& account_id : details.registered_players) + { + auto iter = account_to_joined_tournaments.find(account_id); + if (iter != account_to_joined_tournaments.end()) + iter->second.erase(details.tournament_id); + } + } + + void tournament_players_index::about_to_modify(const object& before) + { + assert( dynamic_cast(&before) ); // for debug only + const tournament_details_object& details = static_cast(before); + before_account_ids = details.registered_players; + } + + void tournament_players_index::object_modified(const object& after) + { + assert( dynamic_cast(&after) ); // for debug only + const tournament_details_object& details = static_cast(after); + + { + vector newly_registered_players(details.registered_players.size()); + auto end_iter = std::set_difference(details.registered_players.begin(), details.registered_players.end(), + before_account_ids.begin(), before_account_ids.end(), + newly_registered_players.begin()); + newly_registered_players.resize(end_iter - newly_registered_players.begin()); + for (const account_id_type& account_id : newly_registered_players) + account_to_joined_tournaments[account_id].insert(details.tournament_id); + } + + { + vector newly_unregistered_players(before_account_ids.size()); + auto end_iter = std::set_difference(before_account_ids.begin(), before_account_ids.end(), + details.registered_players.begin(), details.registered_players.end(), + newly_unregistered_players.begin()); + newly_unregistered_players.resize(end_iter - newly_unregistered_players.begin()); + for (const account_id_type& account_id : newly_unregistered_players) + { + auto iter = account_to_joined_tournaments.find(account_id); + if (iter != account_to_joined_tournaments.end()) + iter->second.erase(details.tournament_id); + } + } + } } } // graphene::chain namespace fc { diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index e2b04909..9bcc6900 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1422,10 +1422,9 @@ class wallet_api signed_transaction tournament_join( string payer_account, string player_account, tournament_id_type tournament_id, string buy_in_amount, string buy_in_asset_symbol, bool broadcast = false ); /** Get a list of upcoming tournaments - * @param player_accounts if non-empty, only return tournaments for which at least one of the named players is eligible. If empty, return all tournaments * @param limit the number of tournaments to return */ - vector get_upcoming_tournaments(optional player_accounts, uint32_t limit); + vector get_upcoming_tournaments(uint32_t limit); /** Get specific information about a tournament * @param tournament_id the ID of the tournament diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 2ecc86e7..b4a19c6f 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -951,12 +951,12 @@ public: game_cache.clear(); for (const account_object& my_account : _wallet.my_accounts) { - std::vector tournaments = _remote_db->get_active_tournaments(my_account.id, 100); - std::vector tournament_ids; - for (const tournament_object& tournament : tournaments) + std::vector tournament_ids = _remote_db->get_registered_tournaments(my_account.id, 100); + for (const tournament_id_type& tournament_id : tournament_ids) { try { + tournament_object tournament = get_object(tournament_id); auto insert_result = tournament_cache.insert(tournament); if (insert_result.second) { @@ -967,10 +967,10 @@ public: } catch (const fc::exception& e) { - edump((e)(tournament)); + edump((e)(tournament_id)); } } - if (!tournaments.empty()) + if (!tournament_ids.empty()) ilog("Account ${my_account} is registered for tournaments: ${tournaments}", ("my_account", my_account.name)("tournaments", tournament_ids)); else ilog("Account ${my_account} is not registered for any tournaments", ("my_account", my_account.name)); @@ -4552,12 +4552,9 @@ signed_transaction wallet_api::tournament_join( string payer_account, return my->sign_transaction( tx, broadcast ); } -vector wallet_api::get_upcoming_tournaments(fc::optional player_account, uint32_t limit) +vector wallet_api::get_upcoming_tournaments(uint32_t limit) { - fc::optional player_account_id; - if (player_account) - player_account_id = get_account(*player_account).id; - return my->_remote_db->get_upcoming_tournaments(player_account_id, limit); + return my->_remote_db->get_tournaments_in_state(tournament_state::accepting_registrations, limit); } tournament_object wallet_api::get_tournament(tournament_id_type id) From f078fed4fd5bc96fdf0bbb432ef6b38e1eef39f6 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Sun, 23 Oct 2016 13:12:06 -0400 Subject: [PATCH 6/8] Properly sequence matches when there are more than two players in the tournament --- .../include/graphene/chain/match_object.hpp | 2 +- libraries/chain/match_object.cpp | 36 +++++++++++---- libraries/chain/tournament_object.cpp | 46 +++++++++++++++++-- libraries/wallet/wallet.cpp | 27 ++++++++--- 4 files changed, 91 insertions(+), 20 deletions(-) diff --git a/libraries/chain/include/graphene/chain/match_object.hpp b/libraries/chain/include/graphene/chain/match_object.hpp index 6f8423f4..3fe5f697 100644 --- a/libraries/chain/include/graphene/chain/match_object.hpp +++ b/libraries/chain/include/graphene/chain/match_object.hpp @@ -89,7 +89,7 @@ namespace graphene { namespace chain { void pack_impl(std::ostream& stream) const; void unpack_impl(std::istream& stream); - void on_initiate_match(database& db, const vector& players); + void on_initiate_match(database& db); void on_game_complete(database& db, const game_object& game); game_id_type start_next_game(database& db, match_id_type match_id); diff --git a/libraries/chain/match_object.cpp b/libraries/chain/match_object.cpp index 8c39c1b3..3ea3d250 100644 --- a/libraries/chain/match_object.cpp +++ b/libraries/chain/match_object.cpp @@ -44,9 +44,8 @@ namespace graphene { namespace chain { struct initiate_match { database& db; - vector players; - initiate_match(database& db, const vector& players) : - db(db), players(players) + initiate_match(database& db) : + db(db) {} }; @@ -80,7 +79,6 @@ namespace graphene { namespace chain { fc_ilog(fc::logger::get("tournament"), "Match ${id} is now in progress", ("id", match.id)); - match.players = event.players; match.number_of_wins.resize(match.players.size()); match.start_time = event.db.head_block_time(); @@ -96,6 +94,24 @@ namespace graphene { namespace chain { "Match ${id} is complete", ("id", match.id)); + std::map scores_by_player; + for (const flat_set& game_winners : match.game_winners) + for (const account_id_type& account_id : game_winners) + ++scores_by_player[account_id]; + + optional high_scoring_account; + unsigned high_score = 0; + for (const auto& value : scores_by_player) + if (value.second > high_score) + { + high_score = value.second; + high_scoring_account = value.first; + } + + if (high_scoring_account) + match.match_winners.insert(*high_scoring_account); + + match.end_time = event.db.head_block_time(); const tournament_object& tournament_obj = match.tournament_id(event.db); event.db.modify(tournament_obj, [&](tournament_object& tournament) { tournament.on_match_completed(event.db, match); @@ -108,11 +124,13 @@ namespace graphene { namespace chain { fc_ilog(fc::logger::get("tournament"), "Match ${id} is complete, it was a buy", ("id", match)); - match.players = event.players; match.number_of_wins.resize(match.players.size()); - boost::copy(event.players, std::inserter(match.match_winners, match.match_winners.end())); + boost::copy(match.players, std::inserter(match.match_winners, match.match_winners.end())); match.start_time = event.db.head_block_time(); match.end_time = event.db.head_block_time(); + // NOTE: when the match is a buy, we don't send a match completed event to + // the tournament_obj, because it is already in the middle of handling + // an event; it will figure out that the match has completed on its own. } }; typedef waiting_on_previous_matches initial_state; @@ -137,7 +155,7 @@ namespace graphene { namespace chain { bool match_is_a_buy(const initiate_match& event) { - return event.players.size() < 2; + return match_obj->players.size() < 2; } void record_completed_game(const game_complete& event) @@ -294,9 +312,9 @@ namespace graphene { namespace chain { ia >> my->state_machine; } - void match_object::on_initiate_match(database& db, const vector& players) + void match_object::on_initiate_match(database& db) { - my->state_machine.process_event(initiate_match(db, players)); + my->state_machine.process_event(initiate_match(db)); } void match_object::on_game_complete(database& db, const game_object& game) diff --git a/libraries/chain/tournament_object.cpp b/libraries/chain/tournament_object.cpp index cad37c5c..52fb7ba3 100644 --- a/libraries/chain/tournament_object.cpp +++ b/libraries/chain/tournament_object.cpp @@ -172,7 +172,8 @@ namespace graphene { namespace chain { if (paired_players[2 * i + 1] != account_id_type()) players.emplace_back(paired_players[2 * i + 1]); event.db.modify(matches[i](event.db), [&](match_object& match) { - match.on_initiate_match(event.db, players); + match.players = players; + match.on_initiate_match(event.db); }); } event.db.modify(tournament_details_obj, [&](tournament_details_object& tournament_details_obj){ @@ -181,11 +182,50 @@ namespace graphene { namespace chain { } void on_entry(const match_completed& event, tournament_state_machine_& fsm) { + tournament_object& tournament = *fsm.tournament_obj; fc_ilog(fc::logger::get("tournament"), - "Tournament ${id} is still in progress, maybe should start a new match here", - ("id", fsm.tournament_obj->id)); + "Match ${match_id} in tournament tournament ${tournament_id} is still in progress", + ("match_id", event.match.id)("tournament_id", tournament.id)); + + // this wasn't the final match that just finished, so figure out if we can start the next match. + // The next match can start if both this match and the previous match have completed + const tournament_details_object& tournament_details_obj = fsm.tournament_obj->tournament_details_id(event.db); + unsigned num_matches = tournament_details_obj.matches.size(); + auto this_match_iter = std::find(tournament_details_obj.matches.begin(), tournament_details_obj.matches.end(), event.match.id); + assert(this_match_iter != tournament_details_obj.matches.end()); + unsigned this_match_index = std::distance(tournament_details_obj.matches.begin(), this_match_iter); + // TODO: we currently create all matches at startup, so they are numbered sequentially. We could get the index + // by subtracting match.id as long as this behavior doesn't change + + unsigned next_round_match_index = (this_match_index + num_matches + 1) / 2; + assert(next_round_match_index < num_matches); + const match_object& next_round_match = tournament_details_obj.matches[next_round_match_index](event.db); + + // each match will have two players, match.players[0] and match.players[1]. + // for consistency, we want to feed the winner of this match into the correct + // slot in the next match + unsigned winner_index_in_next_match = (this_match_index + num_matches + 1) % 2; + unsigned other_match_index = num_matches - ((num_matches - next_round_match_index) * 2 + winner_index_in_next_match); + const match_object& other_match = tournament_details_obj.matches[other_match_index](event.db); + + // the winners of the matches event.match and other_match will play in next_round_match + + assert(event.match.match_winners.size() <= 1); + + event.db.modify(next_round_match, [&](match_object& next_match_obj) { + if (!event.match.match_winners.empty()) // if there is a winner + { + if (winner_index_in_next_match == 0) + next_match_obj.players.insert(next_match_obj.players.begin(), *event.match.match_winners.begin()); + else + next_match_obj.players.push_back(*event.match.match_winners.begin()); + } + if (other_match.get_state() == match_state::match_complete) + next_match_obj.on_initiate_match(event.db); + }); } }; + struct registration_period_expired : public msm::front::state<> { void on_entry(const registration_deadline_passed& event, tournament_state_machine_& fsm) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index b4a19c6f..ed7f2ea6 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -2525,16 +2525,29 @@ public: unsigned match_number = player_number / 2; unsigned player_in_match = player_number % 2; - match_object match = _remote_db->get_objects({tournament_details.matches[match_number]})[0].as(); std::string player_name; - if (round != num_rounds && - !match.players.empty()) + if (round == num_rounds) { - if (player_in_match < match.players.size()) - player_name = get_account(match.players[player_in_match]).name; - else - player_name = "[bye]"; + match_object match = get_object(tournament_details.matches[num_matches - 1]); + if (match.get_state() == match_state::match_complete && + !match.match_winners.empty()) + { + assert(match.match_winners.size() == 1); + player_name = get_account(*match.match_winners.begin()).name; + } } + else + { + match_object match = get_object(tournament_details.matches[match_number]); + if (!match.players.empty()) + { + if (player_in_match < match.players.size()) + player_name = get_account(match.players[player_in_match]).name; + else + player_name = "[bye]"; + } + } + ss << "__"; ss << std::setfill('_') << std::setw(10) << player_name.substr(0,10); ss << "__"; From 8b0cdacb1123ce8d97f4b89fc0f696f795f31a64 Mon Sep 17 00:00:00 2001 From: Roman Olearski Date: Fri, 28 Oct 2016 19:35:49 +0200 Subject: [PATCH 7/8] Create new blockchain parameters for peerplays that can be voted on by committee members part 1 --- .../chain/protocol/chain_parameters.hpp | 13 +++++++ libraries/chain/tournament_evaluator.cpp | 37 ++++++++++++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index 4dbd6c15..d2ced925 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -69,6 +69,13 @@ namespace graphene { namespace chain { uint16_t accounts_per_fee_scale = GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE; ///< number of accounts between fee scalings uint8_t account_fee_scale_bitshifts = GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS; ///< number of times to left bitshift account registration fee at each scaling uint8_t max_authority_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH; + /* rps tournament parameters constraints */ + uint32_t min_round_delay = 0; ///< miniaml delay between games + uint32_t max_round_delay = 600; ///< maxiaml delay between games + uint32_t min_time_per_commit_move = 0; ///< minimal time to commit the next move + uint32_t max_time_per_commit_move = 600; ///< maximal time to commit the next move + uint32_t min_time_per_reveal_move = 0; ///< minimal time to reveal move + uint32_t max_time_per_reveal_move = 600; ///< maximal time to reveal move extensions_type extensions; /** defined in fee_schedule.cpp */ @@ -106,5 +113,11 @@ FC_REFLECT( graphene::chain::chain_parameters, (accounts_per_fee_scale) (account_fee_scale_bitshifts) (max_authority_depth) + (min_round_delay) + (max_round_delay) + (min_time_per_commit_move) + (max_time_per_commit_move) + (min_time_per_reveal_move) + (max_time_per_reveal_move) (extensions) ) diff --git a/libraries/chain/tournament_evaluator.cpp b/libraries/chain/tournament_evaluator.cpp index 12e89661..0d777aba 100644 --- a/libraries/chain/tournament_evaluator.cpp +++ b/libraries/chain/tournament_evaluator.cpp @@ -56,11 +56,6 @@ namespace graphene { namespace chain { else FC_THROW("Must specify either a fixed start time or a delay"); - // TODO: make this committee-set - const uint32_t maximum_round_delay = 60 * 60; // one hour - FC_ASSERT(op.options.round_delay < maximum_round_delay, - "Round delay is too long"); - // TODO: make this committee-set const uint32_t maximum_tournament_number_of_wins = 100; FC_ASSERT(op.options.number_of_wins > 0); @@ -68,6 +63,38 @@ namespace graphene { namespace chain { "Matches may not require more than ${number_of_wins} wins", ("number_of_wins", maximum_tournament_number_of_wins)); + // round_delay constraints + const uint32_t minimum_round_delay = d.get_global_properties().parameters.min_round_delay; + FC_ASSERT(op.options.round_delay >= minimum_round_delay, + "Delay between games must not be less then ${min}", + ("min", minimum_round_delay)); + const uint32_t maximum_round_delay = d.get_global_properties().parameters.max_round_delay; + FC_ASSERT(op.options.round_delay <= maximum_round_delay, + "Delay between games must not be greater then ${max}", + ("max", maximum_round_delay)); + + const rock_paper_scissors_game_options& game_options = op.options.game_options.get(); + + // time_per_commit_move constraints + const uint32_t minimum_time_per_commit_move = d.get_global_properties().parameters.min_time_per_commit_move; + FC_ASSERT(game_options.time_per_commit_move >= minimum_time_per_commit_move, + "Time to commit the next move must not be less then ${min}", + ("min", minimum_time_per_commit_move)); + const uint32_t maximum_time_per_commit_move = d.get_global_properties().parameters.max_time_per_commit_move; + FC_ASSERT(game_options.time_per_commit_move <= maximum_time_per_commit_move, + "Time to commit the next move must not be greater then ${max}", + ("max", maximum_time_per_commit_move)); + + // time_per_commit_reveal constraints + const uint32_t minimum_time_per_reveal_move = d.get_global_properties().parameters.min_time_per_reveal_move; + FC_ASSERT(game_options.time_per_reveal_move >= minimum_time_per_reveal_move, + "Time to reveal the move must not be less then ${min}", + ("min", minimum_time_per_reveal_move)); + const uint32_t maximum_time_per_reveal_move = d.get_global_properties().parameters.max_time_per_reveal_move; + FC_ASSERT(game_options.time_per_reveal_move <= maximum_time_per_reveal_move, + "Time to reveal the move must not be greater then ${max}", + ("max", maximum_time_per_reveal_move)); + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } From a17f2e48edca4609eefe5a1d97c1c82f9c937170 Mon Sep 17 00:00:00 2001 From: Roman Olearski Date: Fri, 28 Oct 2016 20:27:24 +0200 Subject: [PATCH 8/8] correcting "then" -> "than" --- libraries/chain/tournament_evaluator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/chain/tournament_evaluator.cpp b/libraries/chain/tournament_evaluator.cpp index 0d777aba..5bdab62b 100644 --- a/libraries/chain/tournament_evaluator.cpp +++ b/libraries/chain/tournament_evaluator.cpp @@ -78,21 +78,21 @@ namespace graphene { namespace chain { // time_per_commit_move constraints const uint32_t minimum_time_per_commit_move = d.get_global_properties().parameters.min_time_per_commit_move; FC_ASSERT(game_options.time_per_commit_move >= minimum_time_per_commit_move, - "Time to commit the next move must not be less then ${min}", + "Time to commit the next move must not be less than ${min}", ("min", minimum_time_per_commit_move)); const uint32_t maximum_time_per_commit_move = d.get_global_properties().parameters.max_time_per_commit_move; FC_ASSERT(game_options.time_per_commit_move <= maximum_time_per_commit_move, - "Time to commit the next move must not be greater then ${max}", + "Time to commit the next move must not be greater than ${max}", ("max", maximum_time_per_commit_move)); // time_per_commit_reveal constraints const uint32_t minimum_time_per_reveal_move = d.get_global_properties().parameters.min_time_per_reveal_move; FC_ASSERT(game_options.time_per_reveal_move >= minimum_time_per_reveal_move, - "Time to reveal the move must not be less then ${min}", + "Time to reveal the move must not be less than ${min}", ("min", minimum_time_per_reveal_move)); const uint32_t maximum_time_per_reveal_move = d.get_global_properties().parameters.max_time_per_reveal_move; FC_ASSERT(game_options.time_per_reveal_move <= maximum_time_per_reveal_move, - "Time to reveal the move must not be greater then ${max}", + "Time to reveal the move must not be greater than ${max}", ("max", maximum_time_per_reveal_move)); return void_result();