From 122dbfc698f353a3d2f0d53d75ed54a9ff0df919 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Sat, 3 Sep 2016 17:51:27 -0400 Subject: [PATCH] More work on tournament registration, tournaments are now canceled and buy-in is refunded --- libraries/chain/db_update.cpp | 52 +++++++++++++++++++ .../chain/include/graphene/chain/database.hpp | 1 + .../graphene/chain/tournament_object.hpp | 31 ++++++++++- libraries/chain/tournament_evaluator.cpp | 16 ++++-- 4 files changed, 95 insertions(+), 5 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index da96eef9..fb96c537 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include @@ -468,4 +469,55 @@ void database::update_withdraw_permissions() remove(*permit_index.begin()); } +void database::update_tournaments() +{ + // First, cancel any tournaments that didn't get enough players + auto& registration_deadline_index = get_index_type().indices().get(); + // this index is sorted on state and deadline, so the tournaments awaiting registrations with the earliest + // deadlines will be at the beginning + while (registration_deadline_index.empty() && + registration_deadline_index.begin()->state == tournament_state::accepting_registrations && + registration_deadline_index.begin()->options.registration_deadline <= head_block_time()) + { + const tournament_object& tournament_obj = *registration_deadline_index.begin(); + fc_ilog(fc::logger::get("tournament"), + "Canceling tournament ${id} because its deadline expired", + ("id", tournament_obj.id)); + // cancel this tournament + // repay everyone who paid into the prize pool + const tournament_details_object& details = tournament_obj.tournament_details_id(*this); + for (const auto& payer_pair : details.payers) + { + // TODO: create a virtual operation to record the refund + // we'll think of this as just releasing an asset that the user had locked up + // for a period of time, not as a transfer back to the user; it doesn't matter + // if they are currently authorized to transfer this asset, they never really + // transferred it in the first place + adjust_balance(payer_pair.first, asset(payer_pair.second, tournament_obj.options.buy_in.asset_id)); + } + + modify(tournament_obj, [&](tournament_object& t) { + t.state = tournament_state::registration_period_expired; + }); + } + + // Next, start any tournaments that have enough players and whose start time just arrived + auto& start_time_index = get_index_type().indices().get(); + while (1) + { + // find the first tournament waiting to start; if its start time has arrived, start it + auto start_iter = start_time_index.lower_bound(boost::make_tuple(tournament_state::awaiting_start)); + if (start_iter->state == tournament_state::awaiting_start && + *start_iter->start_time <= head_block_time()) + { + modify(*start_iter, [&](tournament_object& t) { + t.state = tournament_state::in_progress; + }); + } + else + break; + } + +} + } } diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index b73b8931..36f7f622 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -432,6 +432,7 @@ namespace graphene { namespace chain { void update_expired_feeds(); void update_maintenance_flag( bool new_maintenance_flag ); void update_withdraw_permissions(); + void update_tournaments(); bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true ); ///Steps performed only at maintenance intervals diff --git a/libraries/chain/include/graphene/chain/tournament_object.hpp b/libraries/chain/include/graphene/chain/tournament_object.hpp index e77b99c7..9f132161 100644 --- a/libraries/chain/include/graphene/chain/tournament_object.hpp +++ b/libraries/chain/include/graphene/chain/tournament_object.hpp @@ -23,6 +23,14 @@ namespace graphene { namespace chain { flat_map payers; }; + enum class tournament_state + { + accepting_registrations, + awaiting_start, + in_progress, + registration_period_expired + }; + class tournament_object : public graphene::db::abstract_object { public: @@ -50,15 +58,30 @@ namespace graphene { namespace chain { /// the GUI having to get the details object) uint32_t registered_players = 0; + /// The current high-level status of the tournament (whether it is currently running or has been canceled, etc) + tournament_state state; + /// Detailed information on this tournament tournament_details_id_type tournament_details_id; + + time_point_sec get_registration_deadline() const { return options.registration_deadline; } + }; struct by_registration_deadline {}; + struct by_start_time {}; typedef multi_index_container< tournament_object, indexed_by< - ordered_unique< tag, member< object, object_id_type, &object::id > > + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_non_unique< tag, + composite_key, + const_mem_fun > >, + ordered_non_unique< tag, + composite_key, + member, &tournament_object::start_time> > > > > tournament_object_multi_index_type; typedef generic_index tournament_index; @@ -74,4 +97,10 @@ FC_REFLECT_DERIVED(graphene::chain::tournament_object, (graphene::db::object), (start_time) (end_time) (prize_pool) + (state) (tournament_details_id)) +FC_REFLECT_ENUM(graphene::chain::tournament_state, + (accepting_registrations) + (awaiting_start) + (in_progress) + (registration_period_expired)) diff --git a/libraries/chain/tournament_evaluator.cpp b/libraries/chain/tournament_evaluator.cpp index 0b8d96fe..00e70050 100644 --- a/libraries/chain/tournament_evaluator.cpp +++ b/libraries/chain/tournament_evaluator.cpp @@ -29,7 +29,7 @@ namespace graphene { namespace chain { // TODO: make this committee-set const uint32_t maximum_tournament_whitelist_length = 1000; - FC_ASSERT(op.options.whitelist.size() != 1, "Can't create a tournament for one player"); + FC_ASSERT(op.options.whitelist.size() >= op.options.number_of_players, "Whitelist must allow enough players to fill the tournament"); FC_ASSERT(op.options.whitelist.size() < maximum_tournament_whitelist_length, "Whitelist must not be longer than ${maximum_tournament_whitelist_length}", ("maximum_tournament_whitelist_length", maximum_tournament_whitelist_length)); @@ -76,6 +76,7 @@ namespace graphene { namespace chain { db().create( [&]( tournament_object& t ) { t.options = op.options; t.creator = op.creator; + t.state = tournament_state::accepting_registrations; // t.dynamic_tournament_data_id = dyn_tournament.id; }); return new_tournament.id; @@ -90,6 +91,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); + FC_ASSERT(_tournament_obj->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, @@ -125,7 +127,11 @@ namespace graphene { namespace chain { void_result tournament_join_evaluator::do_apply( const tournament_join_operation& op ) { try { - bool start_tournament = _tournament_details_obj->registered_players.size() + 1 == _tournament_obj->options.number_of_players; + bool registration_complete = _tournament_details_obj->registered_players.size() + 1 == _tournament_obj->options.number_of_players; + if (registration_complete) + fc_ilog(fc::logger::get("tournament"), + "Tournament ${id} now has enough players registered to begin", + ("id", _tournament_obj->id)); db().adjust_balance(op.payer_account_id, -op.buy_in); db().modify(*_tournament_details_obj, [&](tournament_details_object& tournament_details_obj){ @@ -135,13 +141,15 @@ namespace graphene { namespace chain { db().modify(*_tournament_obj, [&](tournament_object& tournament_obj){ ++tournament_obj.registered_players; tournament_obj.prize_pool += op.buy_in.amount; - if (start_tournament) + if (registration_complete) { if (tournament_obj.options.start_time) tournament_obj.start_time = tournament_obj.options.start_time; else tournament_obj.start_time = db().head_block_time() + fc::seconds(*tournament_obj.options.start_delay); - + // even if the start time is now, mark it as awaiting; we will promote it to in_progress + // in update_tournaments() called at the end of the block + tournament_obj.state = tournament_state::awaiting_start; } }); return void_result();