diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index 3974da10..2ed2169d 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -218,6 +218,11 @@ struct get_impacted_account_visitor _impacted.insert( op.payer_account_id ); _impacted.insert( op.player_account_id ); } + void operator()( const tournament_leave_operation& op ) + { + _impacted.erase( op.payer_account_id ); + _impacted.erase( 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 c1863e67..69bee98b 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -179,6 +179,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); } void database::initialize_indexes() diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index e7c5689d..c19fa584 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -186,3 +186,4 @@ #define TOURNAMENT_MINIMAL_RAKE_FEE_PERCENTAGE (1*GRAPHENE_1_PERCENT) #define TOURNAMENT_MAXIMAL_RAKE_FEE_PERCENTAGE (20*GRAPHENE_1_PERCENT) #define TOURNAMENT_MAXIMAL_REGISTRATION_DEADLINE (60*60*24*30) // seconds, 30 days +#define TOURNAMENT_MAX_NUMBER_OF_WINS 100 diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index f96f8658..1bc1904d 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -98,7 +98,8 @@ namespace graphene { namespace chain { game_move_operation, asset_update_dividend_operation, asset_dividend_distribution_operation, // VIRTUAL - tournament_payout_operation // VIRTUAL + tournament_payout_operation, // VIRTUAL + tournament_leave_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/tournament.hpp b/libraries/chain/include/graphene/chain/protocol/tournament.hpp index bfc825e9..82bb49dd 100644 --- a/libraries/chain/include/graphene/chain/protocol/tournament.hpp +++ b/libraries/chain/include/graphene/chain/protocol/tournament.hpp @@ -145,6 +145,30 @@ namespace graphene { namespace chain { void validate()const; }; + struct tournament_leave_operation : public base_operation + { + struct fee_parameters_type { + share_type fee = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + + /// The account that is paying the fee + account_id_type payer_account_id; + + /// The account that would play in the tournament, would receive any winnings. + account_id_type player_account_id; + + /// The tournament `player_account_id` is leaving + tournament_id_type tournament_id; + + extensions_type extensions; + account_id_type fee_payer()const { return payer_account_id; } + share_type calculate_fee(const fee_parameters_type& k)const; + void validate()const; + }; + + typedef fc::static_variant game_specific_moves; struct game_move_operation : public base_operation @@ -227,6 +251,12 @@ FC_REFLECT( graphene::chain::tournament_join_operation, (tournament_id) (buy_in) (extensions)) +FC_REFLECT( graphene::chain::tournament_leave_operation, + (fee) + (payer_account_id) + (player_account_id) + (tournament_id) + (extensions)) FC_REFLECT( graphene::chain::game_move_operation, (fee) (game_id) @@ -243,6 +273,7 @@ FC_REFLECT( graphene::chain::tournament_payout_operation, 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::tournament_leave_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::game_move_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::tournament_payout_operation::fee_parameters_type, ) diff --git a/libraries/chain/include/graphene/chain/tournament_evaluator.hpp b/libraries/chain/include/graphene/chain/tournament_evaluator.hpp index ff70a52e..18a0140d 100644 --- a/libraries/chain/include/graphene/chain/tournament_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/tournament_evaluator.hpp @@ -28,6 +28,18 @@ namespace graphene { namespace chain { void_result do_apply( const tournament_join_operation& o ); }; + class tournament_leave_evaluator : public evaluator + { + private: + const tournament_object* _tournament_obj = nullptr; + const tournament_details_object* _tournament_details_obj = nullptr; + public: + typedef tournament_leave_operation operation_type; + + void_result do_evaluate( const tournament_leave_operation& o ); + void_result do_apply( const tournament_leave_operation& o ); + }; + class game_move_evaluator : public evaluator { private: diff --git a/libraries/chain/include/graphene/chain/tournament_object.hpp b/libraries/chain/include/graphene/chain/tournament_object.hpp index 12bfaffc..81edabdc 100644 --- a/libraries/chain/include/graphene/chain/tournament_object.hpp +++ b/libraries/chain/include/graphene/chain/tournament_object.hpp @@ -38,6 +38,9 @@ namespace graphene { namespace chain { /// List of payers who have contributed to the prize pool flat_map payers; + /// List of player payer pairs needed by torunament leave operation + flat_map players_payers; + /// List of all matches in this tournament. When the tournament starts, all matches /// are created. Matches in the first round will have players, matches in later /// rounds will not be populated. @@ -114,6 +117,7 @@ namespace graphene { namespace chain { /// called by database maintenance code when registration for this contest has expired 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_player_unregistered(database& db, account_id_type payer_id, account_id_type player_id); void on_start_time_arrived(database& db); void on_match_completed(database& db, const match_object& match); @@ -232,6 +236,7 @@ FC_REFLECT_DERIVED(graphene::chain::tournament_details_object, (graphene::db::ob (tournament_id) (registered_players) (payers) + (players_payers) (matches)) //FC_REFLECT_TYPENAME(graphene::chain::tournament_object) // manually serialized FC_REFLECT(graphene::chain::tournament_object, (creator)) diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 27c31ae4..4f219565 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -52,6 +52,11 @@ void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_o if( _sell_asset->options.blacklist_markets.size() ) FC_ASSERT( _sell_asset->options.blacklist_markets.find(_receive_asset->id) == _sell_asset->options.blacklist_markets.end() ); + // $$$ I. DEX Task The Peerplays DEX should only allow UIA and sidechain assets to be paired (traded) with the core token (PPY) + FC_ASSERT(_receive_asset->id == asset_id_type() || _sell_asset->id == asset_id_type(), + "No asset in the trade is CORE."); + + FC_ASSERT( is_authorized_asset( d, *_seller, *_sell_asset ) ); FC_ASSERT( is_authorized_asset( d, *_seller, *_receive_asset ) ); diff --git a/libraries/chain/match_object.cpp b/libraries/chain/match_object.cpp index 4f762deb..7819d21e 100644 --- a/libraries/chain/match_object.cpp +++ b/libraries/chain/match_object.cpp @@ -75,7 +75,6 @@ namespace graphene { namespace chain { void on_entry(const initiate_match& event, match_state_machine_& fsm) { match_object& match = *fsm.match_obj; - fc_ilog(fc::logger::get("tournament"), "Match ${id} is now in progress", ("id", match.id)); @@ -89,7 +88,9 @@ namespace graphene { namespace chain { { void on_entry(const game_complete& event, match_state_machine_& fsm) { + match_object& match = *fsm.match_obj; + //wdump((match)); fc_ilog(fc::logger::get("tournament"), "Match ${id} is complete", ("id", match.id)); @@ -124,16 +125,19 @@ namespace graphene { namespace chain { } else { - match.match_winners.insert(match.players[event.db.get_random_bits(match.players.size())]); + // III. Rock Paper Scissors Game Need to review how Ties are dealt with. + short i = match.number_of_ties == match.games.size() ? 0 : event.db.get_random_bits(match.players.size()) ; + match.match_winners.insert(match.players[i]); + ++match.number_of_wins[i]; + if (match.number_of_ties == match.games.size()) + match.game_winners[match.game_winners.size()-1].insert(match.players[i]); } - 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); }); - } void on_entry(const initiate_match& event, match_state_machine_& fsm) { @@ -238,7 +242,8 @@ namespace graphene { namespace chain { }; match_object::match_object() : - my(new impl(this)) + number_of_ties(0), + my(new impl(this)) { } diff --git a/libraries/chain/protocol/tournament.cpp b/libraries/chain/protocol/tournament.cpp index f7faa93b..57e80bf3 100644 --- a/libraries/chain/protocol/tournament.cpp +++ b/libraries/chain/protocol/tournament.cpp @@ -52,6 +52,17 @@ void tournament_join_operation::validate()const FC_ASSERT( fee.amount >= 0 ); } +share_type tournament_leave_operation::calculate_fee(const fee_parameters_type& k)const +{ + return k.fee; +} + +void tournament_leave_operation::validate()const +{ + // todo FC_ASSERT( fee.amount >= 0 ); +} + + share_type game_move_operation::calculate_fee(const fee_parameters_type& k)const { return k.fee; @@ -61,5 +72,4 @@ void game_move_operation::validate()const { } - } } // namespace graphene::chain diff --git a/libraries/chain/tournament_evaluator.cpp b/libraries/chain/tournament_evaluator.cpp index 5b76253b..57d35088 100644 --- a/libraries/chain/tournament_evaluator.cpp +++ b/libraries/chain/tournament_evaluator.cpp @@ -58,7 +58,7 @@ namespace graphene { namespace chain { FC_THROW("Must specify either a fixed start time or a delay"); // TODO: make this committee-set - const uint32_t maximum_tournament_number_of_wins = 100; + const uint32_t maximum_tournament_number_of_wins = TOURNAMENT_MAX_NUMBER_OF_WINS; FC_ASSERT(op.options.number_of_wins > 0); FC_ASSERT(op.options.number_of_wins <= maximum_tournament_number_of_wins, "Matches may not require more than ${number_of_wins} wins", @@ -180,6 +180,36 @@ namespace graphene { namespace chain { return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } + void_result tournament_leave_evaluator::do_evaluate( const tournament_leave_operation& op ) + { + try { + const database& d = db(); + _tournament_obj = &op.tournament_id(d); + fc_ilog(fc::logger::get("tournament"), "details_id = ${id}",("id", _tournament_obj->tournament_details_id)); + + _tournament_details_obj = &_tournament_obj->tournament_details_id(d); + FC_ASSERT(_tournament_details_obj->registered_players.find(op.player_account_id) != _tournament_details_obj->registered_players.end(), + "Player is not registered for this tournament"); + //FC_ASSERT(_tournament_details_obj->payers.find(op.payer_account_id) != _tournament_details_obj->payers.end(), + // "Payer is not registered for this tournament"); + + FC_ASSERT(_tournament_obj->get_state() == tournament_state::accepting_registrations || + _tournament_obj->get_state() == tournament_state::awaiting_start); + FC_ASSERT(d.head_block_time() <= _tournament_obj->options.registration_deadline, + "Registration deadline has already passed"); + return void_result(); + } FC_CAPTURE_AND_RETHROW( (op) ) } + + void_result tournament_leave_evaluator::do_apply( const tournament_leave_operation& op ) + { try { +#if 1 + db().modify(*_tournament_obj, [&](tournament_object& tournament_obj){ + tournament_obj.on_player_unregistered(db(), op.payer_account_id, op.player_account_id); + }); +#endif + 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(); @@ -188,6 +218,7 @@ namespace graphene { namespace chain { 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){ diff --git a/libraries/chain/tournament_object.cpp b/libraries/chain/tournament_object.cpp index fa8830ee..4adb5bb7 100644 --- a/libraries/chain/tournament_object.cpp +++ b/libraries/chain/tournament_object.cpp @@ -48,7 +48,16 @@ namespace graphene { namespace chain { db(db), payer_id(payer_id), player_id(player_id) {} }; - struct registration_deadline_passed + struct player_unregistered + { + database& db; + account_id_type payer_id; + account_id_type player_id; + player_unregistered(database& db, account_id_type payer_id, account_id_type player_id) : + db(db), payer_id(payer_id), player_id(player_id) + {} + }; + struct registration_deadline_passed { database& db; registration_deadline_passed(database& db) : db(db) {}; @@ -234,7 +243,7 @@ namespace graphene { namespace chain { event.db.modify(next_round_match, [&](match_object& next_match_obj) { - if (!event.match.match_winners.empty()) // if there is a winner + 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()); @@ -382,19 +391,42 @@ namespace graphene { namespace chain { event.db.modify(tournament_details_obj, [&](tournament_details_object& tournament_details_obj){ tournament_details_obj.payers[event.payer_id] += tournament_obj->options.buy_in.amount; tournament_details_obj.registered_players.insert(event.player_id); + tournament_details_obj.players_payers[event.player_id] = event.payer_id; }); ++tournament_obj->registered_players; tournament_obj->prize_pool += tournament_obj->options.buy_in.amount; } + void unregister_player(const player_unregistered& event) + { + fc_ilog(fc::logger::get("tournament"), + "In unregister_player action, player_id is ${player_id}", + ("player_id", event.player_id)); + + event.db.adjust_balance(event.payer_id, tournament_obj->options.buy_in); + const tournament_details_object& tournament_details_obj = tournament_obj->tournament_details_id(event.db); + event.db.modify(tournament_details_obj, [&](tournament_details_object& tournament_details_obj){ + account_id_type payer_id = tournament_details_obj.players_payers[event.player_id]; + tournament_details_obj.payers[payer_id] -= tournament_obj->options.buy_in.amount; + if (tournament_details_obj.payers[payer_id] <= 0) + tournament_details_obj.payers.erase(payer_id); + tournament_details_obj.registered_players.erase(event.player_id); + tournament_details_obj.players_payers.erase(event.player_id); + }); + --tournament_obj->registered_players; + tournament_obj->prize_pool -= tournament_obj->options.buy_in.amount; + } + // Transition table for tournament struct transition_table : mpl::vector< // Start Event Next Action Guard // +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+ a_row < accepting_registrations, player_registered, accepting_registrations, &x::register_player >, + a_row < accepting_registrations, player_unregistered, accepting_registrations, &x::unregister_player >, row < accepting_registrations, player_registered, awaiting_start, &x::register_player, &x::will_be_fully_registered >, _row < accepting_registrations, registration_deadline_passed, registration_period_expired >, // +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+ + a_row < awaiting_start, player_unregistered, accepting_registrations, &x::unregister_player >, _row < awaiting_start, start_time_arrived, in_progress >, // +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+ _row < in_progress, match_completed, in_progress >, @@ -519,6 +551,11 @@ namespace graphene { namespace chain { my->state_machine.process_event(player_registered(db, payer_id, player_id)); } + void tournament_object::on_player_unregistered(database& db, account_id_type payer_id, account_id_type player_id) + { + my->state_machine.process_event(player_unregistered(db, payer_id, player_id)); + } + void tournament_object::on_start_time_arrived(database& db) { my->state_machine.process_event(start_time_arrived(db)); diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 525a593b..a40f38d4 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1490,13 +1490,25 @@ class wallet_api signed_transaction tournament_create( string creator, tournament_options options, bool broadcast = false ); /** Join an existing tournament - * @param paying_account the account that is paying the buy-in and the fee to join the tournament - * @param playing_account the account that will be playing in the tournament + * @param payer_account the account that is paying the buy-in and the fee to join the tournament + * @param player_account the account that will be playing in the tournament + * @param buy_in_amount buy_in to pay + * @param buy_in_asset_symbol buy_in asset * @param tournament_id the tournament the user wishes to join + * @param broadcast true if you wish to broadcast the transaction * @return the signed version of the transaction */ 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 ); + /** Leave an existing tournament + * @param payer_account the account that is paying the fee + * @param player_account the account that would be playing in the tournament + * @param tournament_id the tournament the user wishes to leave + * @param broadcast true if you wish to broadcast the transaction + * @return the signed version of the transaction + */ + signed_transaction tournament_leave(string payer_account, string player_account, tournament_id_type tournament_id, bool broadcast = false); + /** Get a list of upcoming tournaments * @param limit the number of tournaments to return */ @@ -1730,6 +1742,7 @@ FC_API( graphene::wallet::wallet_api, (receive_blind_transfer) (tournament_create) (tournament_join) + (tournament_leave) (rps_throw) (get_upcoming_tournaments) (get_tournaments) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 7c681649..3e9db0d7 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -2521,14 +2521,28 @@ public: tournament_object tournament = result.as(); tournament_details_object tournament_details = _remote_db->get_objects({result["tournament_details_id"].as()})[0].as(); tournament_state state = tournament.get_state(); - if (state == tournament_state::awaiting_start) + if (state == tournament_state::accepting_registrations) + { + ss << "Tournament is accepting registrations\n"; + ss << "Players " << tournament.registered_players << "/" << tournament.options.number_of_players << ":\n"; + for (const account_id_type& player : tournament_details.registered_players) + ss << "\t" << get_account(player).name << "\n"; + } + else if (state == tournament_state::registration_period_expired) + { + ss << "Tournament registration period expired\n"; + ss << "Players " << tournament.registered_players << "/" << tournament.options.number_of_players << ":\n"; + for (const account_id_type& player : tournament_details.registered_players) + ss << "\t" << get_account(player).name << "\n"; + } + else if (state == tournament_state::awaiting_start) { ss << "Tournament starts at " << tournament.start_time->to_iso_string() << "\n"; ss << "Players:\n"; for (const account_id_type& player : tournament_details.registered_players) ss << "\t" << get_account(player).name << "\n"; } - else if (state == tournament_state::in_progress || + else if (state == tournament_state::in_progress || state == tournament_state::concluded) { unsigned num_matches = tournament_details.matches.size(); @@ -4834,7 +4848,7 @@ signed_transaction wallet_api::tournament_join( string payer_account, FC_ASSERT( !is_locked() ); account_object payer_account_obj = get_account(payer_account); account_object player_account_obj = get_account(player_account); - graphene::chain::tournament_object tournament_obj = my->get_object(tournament_id); + //graphene::chain::tournament_object tournament_obj = my->get_object(tournament_id); fc::optional buy_in_asset_obj = get_asset(buy_in_asset_symbol); FC_ASSERT(buy_in_asset_obj, "Could not find asset matching ${asset}", ("asset", buy_in_asset_symbol)); @@ -4853,6 +4867,29 @@ signed_transaction wallet_api::tournament_join( string payer_account, return my->sign_transaction( tx, broadcast ); } +signed_transaction wallet_api::tournament_leave( string payer_account, + string player_account, + tournament_id_type tournament_id, + bool broadcast) +{ + FC_ASSERT( !is_locked() ); + account_object player_account_obj = get_account(player_account); + account_object payer_account_obj = get_account(payer_account); + //graphene::chain::tournament_object tournament_obj = my->get_object(tournament_id); + + signed_transaction tx; + tournament_leave_operation op; + op.payer_account_id = payer_account_obj.get_id(); + op.player_account_id = player_account_obj.get_id(); + op.tournament_id = tournament_id; + + tx.operations = {op}; + my->set_operation_fees( tx, my->_remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return my->sign_transaction( tx, broadcast ); +} + vector wallet_api::get_upcoming_tournaments(uint32_t limit) { return my->_remote_db->get_tournaments_in_state(tournament_state::accepting_registrations, limit); diff --git a/programs/witness_node/main.cpp b/programs/witness_node/main.cpp index f1f971b7..0995899f 100644 --- a/programs/witness_node/main.cpp +++ b/programs/witness_node/main.cpp @@ -75,7 +75,7 @@ int main(int argc, char** argv) { auto witness_plug = node->register_plugin(); auto history_plug = node->register_plugin(); auto market_history_plug = node->register_plugin(); - auto generate_genesis_plug = node->register_plugin(); + //auto generate_genesis_plug = node->register_plugin(); try { diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index f4ae577b..125dc5e5 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -82,7 +82,7 @@ database_fixture::database_fixture() genesis_state.initial_timestamp = time_point_sec( GRAPHENE_TESTING_GENESIS_TIMESTAMP ); genesis_state.initial_active_witnesses = 10; - for( int i = 0; i < genesis_state.initial_active_witnesses; ++i ) + for( unsigned i = 0; i < genesis_state.initial_active_witnesses; ++i ) { auto name = "init"+fc::to_string(i); genesis_state.initial_accounts.emplace_back(name, diff --git a/tests/random.sh b/tests/random.sh index fa864eae..925bd9a6 100755 --- a/tests/random.sh +++ b/tests/random.sh @@ -1,10 +1,12 @@ -#!/bin/bash +#!/bin/bash -e i=1 while [ 0 ]; do echo "*** $i `date`" - mv random-2 random-2-last + if [ -f random-2 ]; then + mv random-2 random-2-last + fi ./random_test --log_level=message 2> random-2 echo "*** $i `date`" echo diff --git a/tests/tournament/tournament_tests.cpp b/tests/tournament/tournament_tests.cpp index 22da9a70..8889f205 100644 --- a/tests/tournament/tournament_tests.cpp +++ b/tests/tournament/tournament_tests.cpp @@ -234,7 +234,34 @@ public: players.insert(player_id); players_keys[player_id] = sig_priv_key; - } + } + + void leave_tournament(const tournament_id_type & tournament_id, + const account_id_type& player_id, + const account_id_type& payer_id, + const fc::ecc::private_key& sig_priv_key + ) + { + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + signed_transaction tx; + tournament_leave_operation op; + + op.payer_account_id = payer_id; + op.player_account_id = player_id; + op.tournament_id = tournament_id; + tx.operations = {op}; + for( auto& op : tx.operations ) + db.current_fee_schedule().set_fee(op); + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, sig_priv_key); + PUSH_TX(db, tx); + + //players.erase(player_id); + } + + // stolen from cli_wallet void rps_throw(const game_id_type& game_id, @@ -288,14 +315,15 @@ public: tx.validate(); tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); df.sign(tx, sig_priv_key); - if (game_obj.get_state() == game_state::expecting_commit_moves) // checking again + if (/*match_obj.match_winners.empty() &&*/ game_obj.get_state() == game_state::expecting_commit_moves) // checking again PUSH_TX(db, tx); } // spaghetti programming // walking through all tournaments, matches and games and throwing random moves // optionaly skip generting randomly selected moves - void play_games(unsigned skip_some_commits = 0, unsigned skip_some_reveals = 0) + // every_move_is >= 0 : every game is tie + void play_games(unsigned skip_some_commits = 0, unsigned skip_some_reveals = 0, int every_move_is = -1) { //try //{ @@ -325,7 +353,8 @@ public: auto iter = std::find(game.players.begin(), game.players.end(), player_id); unsigned player_index = std::distance(game.players.begin(), iter); if (!rps_details.commit_moves.at(player_index)) - rps_throw(game_id, player_id, (rock_paper_scissors_gesture) (std::rand() % game_options.number_of_gestures), players_keys[player_id]); + rps_throw(game_id, player_id, + (rock_paper_scissors_gesture) (every_move_is >= 0 ? every_move_is : (std::rand() % game_options.number_of_gestures)), players_keys[player_id]); } } } @@ -414,6 +443,7 @@ private: } }; +#if 1 // Test of basic functionality creating two tournamenst, joinig players, // playing tournaments to completion, distributing prize. // Testing of "bye" matches handling can be performed if "bye" matches fix is available. @@ -431,13 +461,14 @@ BOOST_FIXTURE_TEST_CASE( simple, database_fixture ) #define TEST2_NR_OF_PLAYERS_NUMBER 4 #endif BOOST_TEST_MESSAGE("Hello simple tournament test"); - ACTORS((nathan)(alice)(bob)(carol)(dave)(ed)(frank)(george)(harry)(ike)); + ACTORS((nathan)(alice)(bob)(carol)(dave)(ed)(frank)(george)(harry)(ike)(romek)); tournaments_helper tournament_helper(*this); fc::ecc::private_key nathan_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); BOOST_TEST_MESSAGE( "Giving folks some money" ); transfer(committee_account, nathan_id, asset(1000000000)); + transfer(committee_account, romek_id, asset(2000000)); transfer(committee_account, alice_id, asset(2000000)); transfer(committee_account, bob_id, asset(3000000)); transfer(committee_account, carol_id, asset(4000000)); @@ -483,8 +514,13 @@ BOOST_FIXTURE_TEST_CASE( simple, database_fixture ) 3, 1, 3, 3600, 3, 3, true); BOOST_REQUIRE(tournament_id == tournament_id_type(1)); tournament_helper.join_tournament(tournament_id, alice_id, alice_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("alice"))), buy_in); + // romek joins but will leave + //tournament_helper.leave_tournament(tournament_id, romek_id, romek_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("romek")))); + tournament_helper.join_tournament(tournament_id, romek_id, romek_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("romek"))), buy_in); tournament_helper.join_tournament(tournament_id, bob_id, bob_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("bob"))), buy_in); tournament_helper.join_tournament(tournament_id, carol_id, carol_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("carol"))), buy_in); + // romek leaves + tournament_helper.leave_tournament(tournament_id, romek_id, romek_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("romek")))); tournament_helper.join_tournament(tournament_id, dave_id, dave_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("dave"))), buy_in); #if TEST2_NR_OF_PLAYERS_NUMBER > 4 tournament_helper.join_tournament(tournament_id, ed_id, ed_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("ed"))), buy_in); @@ -532,12 +568,7 @@ BOOST_FIXTURE_TEST_CASE( simple, database_fixture ) #endif }; -#if 0 - // trying to randomize automatic moves ? - auto n = std::rand() % 100; - for(int i = 0; i < n ; ++i) - db.get_random_bits(3); -#endif + abc("@ tournament awaiting start"); BOOST_TEST_MESSAGE( "Generating blocks, waiting for tournaments' completion"); generate_block(); @@ -594,6 +625,122 @@ BOOST_FIXTURE_TEST_CASE( simple, database_fixture ) throw; } } +#endif + +#if 1 +// Test of handling ties, creating two tournamenst, joinig players, +// All generated moves are identical. +BOOST_FIXTURE_TEST_CASE( ties, database_fixture ) +{ + try + { +#ifdef BYE_MATCHES_FIXED + #define TEST1_NR_OF_PLAYERS_NUMBER 3 + #define TEST2_NR_OF_PLAYERS_NUMBER 5 +#else + #define TEST1_NR_OF_PLAYERS_NUMBER 2 + #define TEST2_NR_OF_PLAYERS_NUMBER 4 +#endif + BOOST_TEST_MESSAGE("Hello ties tournament test"); + ACTORS((nathan)(alice)(bob)(carol)(dave)(ed)(frank)(george)(harry)(ike)); + + tournaments_helper tournament_helper(*this); + fc::ecc::private_key nathan_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + + BOOST_TEST_MESSAGE( "Giving folks some money" ); + transfer(committee_account, nathan_id, asset(1000000000)); + transfer(committee_account, alice_id, asset(2000000)); + transfer(committee_account, bob_id, asset(3000000)); + transfer(committee_account, carol_id, asset(4000000)); + transfer(committee_account, dave_id, asset(5000000)); + transfer(committee_account, ed_id, asset(6000000)); + + BOOST_TEST_MESSAGE( "Preparing nathan" ); + upgrade_to_lifetime_member(nathan); + BOOST_CHECK(nathan.is_lifetime_member()); + + uint16_t tournaments_to_complete = 0; + asset buy_in = asset(12000); + tournament_id_type tournament_id; + + BOOST_TEST_MESSAGE( "Preparing a tournament, insurance disabled" ); + tournament_id = tournament_helper.create_tournament (nathan_id, nathan_priv_key, buy_in, TEST1_NR_OF_PLAYERS_NUMBER, 30, 30); + BOOST_REQUIRE(tournament_id == tournament_id_type()); + + tournament_helper.join_tournament(tournament_id, alice_id, alice_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("alice"))), buy_in); + tournament_helper.join_tournament(tournament_id, bob_id, bob_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("bob"))), buy_in); +#if TEST1_NR_OF_PLAYERS_NUMBER > 2 + tournament_helper.join_tournament(tournament_id, carol_id, carol_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("carol"))), buy_in); +#endif + ++tournaments_to_complete; + + BOOST_TEST_MESSAGE( "Preparing another one, insurance enabled" ); + buy_in = asset(13000); + tournament_id = tournament_helper.create_tournament (nathan_id, nathan_priv_key, buy_in, TEST2_NR_OF_PLAYERS_NUMBER, + 30, 30, 3, 3600, 3, 3, true); + BOOST_REQUIRE(tournament_id == tournament_id_type(1)); + tournament_helper.join_tournament(tournament_id, alice_id, alice_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("alice"))), buy_in); + tournament_helper.join_tournament(tournament_id, bob_id, bob_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("bob"))), buy_in); + tournament_helper.join_tournament(tournament_id, carol_id, carol_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("carol"))), buy_in); + tournament_helper.join_tournament(tournament_id, dave_id, dave_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("dave"))), buy_in); +#if TEST2_NR_OF_PLAYERS_NUMBER > 4 + tournament_helper.join_tournament(tournament_id, ed_id, ed_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("ed"))), buy_in); +#endif + ++tournaments_to_complete; + + + std::set tournaments = tournament_helper.list_tournaments(); + std::map> players_balances = tournament_helper.list_players_balances(); + uint16_t rake_fee_percentage = db.get_global_properties().parameters.rake_fee_percentage; + + + BOOST_TEST_MESSAGE( "Generating blocks, waiting for tournaments' completion"); + while(tournaments_to_complete > 0) + { + generate_block(); + //tournament_helper.play_games(3, 4, 1); + tournament_helper.play_games(0, 0, 1); + for(const auto& tournament_id: tournaments) + { + const tournament_object& tournament = tournament_id(db); + if (tournament.get_state() == tournament_state::concluded) { + const tournament_details_object& tournament_details = tournament.tournament_details_id(db); + const match_object& final_match = (tournament_details.matches[tournament_details.matches.size() - 1])(db); + + assert(final_match.match_winners.size() == 1); + const account_id_type& winner_id = *final_match.match_winners.begin(); + BOOST_TEST_MESSAGE( "The winner of " + std::string(object_id_type(tournament_id)) + " is " + winner_id(db).name + " " + std::string(object_id_type(winner_id))); + share_type rake_amount = (fc::uint128_t(tournament.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64(); + optional dividend_account = tournament_helper.get_asset_dividend_account(tournament.options.buy_in.asset_id); + if (dividend_account.valid()) + players_balances[*dividend_account][tournament.options.buy_in.asset_id] += rake_amount; players_balances[winner_id][tournament.options.buy_in.asset_id] += tournament.prize_pool - rake_amount; + + tournaments.erase(tournament_id); + --tournaments_to_complete; + break; + } + } + sleep(1); + } + + // checking if prizes were distributed correctly + BOOST_CHECK(tournaments.size() == 0); + std::map> last_players_balances = tournament_helper.list_players_balances(); + for (auto a: last_players_balances) + { + BOOST_TEST_MESSAGE( "Checking " + a.first(db).name + "'s balance " + std::to_string((uint64_t)(a.second[asset_id_type()].value)) ); + BOOST_CHECK(a.second[asset_id_type()] == players_balances[a.first][asset_id_type()]); + } + BOOST_TEST_MESSAGE("Bye ties tournament test\n"); + + } + catch (fc::exception& e) + { + edump((e.to_detail_string())); + throw; + } +} +#endif // Test of canceled tournament // Checking buyin refund. @@ -957,8 +1104,8 @@ BOOST_FIXTURE_TEST_CASE( massive, database_fixture ) { try { - #define MIN_TOURNAMENTS_NUMBER 1 - #define MAX_TOURNAMENTS_NUMBER 10 + #define MIN_TOURNAMENTS_NUMBER 7 + #define MAX_TOURNAMENTS_NUMBER 13 #define MIN_PLAYERS_NUMBER 2 #define MAX_PLAYERS_NUMBER 64 @@ -976,7 +1123,7 @@ BOOST_FIXTURE_TEST_CASE( massive, database_fixture ) // creating a pool of actors std::vector> actors; - for(unsigned i = 0; i < 2 * MAX_PLAYERS_NUMBER; ++i) + for(unsigned i = 0; i < 3 * MAX_PLAYERS_NUMBER; ++i) { std::string name = "account" + std::to_string(i); auto priv_key = generate_private_key(name); @@ -997,14 +1144,34 @@ BOOST_FIXTURE_TEST_CASE( massive, database_fixture ) asset buy_in = asset(1000 * number_of_players + 100 * i); tournament_id_type tournament_id; tournament_id = tournament_helper.create_tournament (nathan_id, nathan_priv_key, buy_in, number_of_players, 30, 30, number_of_wins); + const tournament_object& tournament = tournament_id(db); - for (unsigned j = 0; j < actors.size() && number_of_players > 0; ++j) + for (unsigned j = 0; j < actors.size()-1 && number_of_players > 0; ++j) { if (number_of_players < actors.size() - j && std::rand() % 2 == 0) continue; auto a = actors[j]; - --number_of_players; tournament_helper.join_tournament(tournament_id, std::get<1>(a), std::get<1>(a), std::get<2>(a), buy_in); + if (j == i) + { + BOOST_TEST_MESSAGE("Player " + std::get<0>(a) + " is leaving tournament " + std::to_string(i) + + ", when tournament state is " + std::to_string((int)tournament.get_state())); + tournament_helper.leave_tournament(tournament_id, std::get<1>(a), std::get<1>(a), std::get<2>(a)); + continue; + } + --number_of_players; + if (!number_of_players) + { + BOOST_TEST_MESSAGE("Player " + std::get<0>(a) + " is leaving tournament " + std::to_string(i) + + ", when tournament state is " + std::to_string((int)tournament.get_state())); + tournament_helper.leave_tournament(tournament_id, std::get<1>(a), std::get<1>(a), std::get<2>(a)); + ++j; + a = actors[j]; + BOOST_TEST_MESSAGE("Player " + std::get<0>(a) + " is joinig tournament " + std::to_string(i) + + ", when tournament state is " + std::to_string((int)tournament.get_state())); + tournament_helper.join_tournament(tournament_id, std::get<1>(a), std::get<1>(a), std::get<2>(a), buy_in); + } } + BOOST_TEST_MESSAGE("Tournament " + std::to_string(i) + " is in state " + std::to_string((int)tournament.get_state())); } uint16_t tournaments_to_complete = number_of_tournaments; diff --git a/tests/tournaments.sh b/tests/tournaments.sh index b9e7bb16..d0e925f8 100755 --- a/tests/tournaments.sh +++ b/tests/tournaments.sh @@ -1,11 +1,18 @@ #!/bin/bash -e i=1 +file='tournament-2' +file2=$file while [ 0 ]; do echo "*** $i `date`" - mv tournament-2 tournament-2-last - ./tournament_test --log_level=message 2> tournament-2 + if [ "$1" = "-c" ]; then + file2=$file-`date +%Y-%m-%d:%H:%M:%S` + + elif [ -f $file2 ]; then + mv $file2 $file2-last + fi + ./tournament_test --log_level=message 2> $file2 echo if [ "$1" = "-c" ]; then sleep 2