Use majority agreement for voting

This commit is contained in:
Benau 2018-12-21 18:12:58 +08:00
parent 0e17839136
commit 9029621b59
5 changed files with 159 additions and 23 deletions

View File

@ -229,6 +229,7 @@ void ClientLobby::addAllPlayers(Event* event)
} }
NetworkString& data = event->data(); NetworkString& data = event->data();
uint32_t winner_peer_id = data.getUInt32();
PeerVote winner_vote(data); PeerVote winner_vote(data);
m_game_setup->setRace(winner_vote); m_game_setup->setRace(winner_vote);

View File

@ -161,10 +161,11 @@ void LobbyProtocol::startVotingPeriod(float max_time)
/** Returns the remaining voting time in seconds. */ /** Returns the remaining voting time in seconds. */
float LobbyProtocol::getRemainingVotingTime() float LobbyProtocol::getRemainingVotingTime()
{ {
if (m_end_voting_period.load() == 0) if (m_end_voting_period.load() == 0 ||
StkTime::getRealTimeMs() >= m_end_voting_period.load())
return 0.0f; return 0.0f;
uint64_t t = m_end_voting_period.load()- StkTime::getRealTimeMs(); uint64_t t = m_end_voting_period.load() - StkTime::getRealTimeMs();
return t/1000.0f; return t / 1000.0f;
} // getRemainingVotingTime } // getRemainingVotingTime
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

View File

@ -541,8 +541,8 @@ void ServerLobby::asynchronousUpdate()
{ {
PeerVote winner_vote; PeerVote winner_vote;
uint32_t winner_peer_id = std::numeric_limits<uint32_t>::max(); uint32_t winner_peer_id = std::numeric_limits<uint32_t>::max();
bool all_votes_in = handleAllVotes(&winner_vote, &winner_peer_id); bool go_on_race = handleAllVotes(&winner_vote, &winner_peer_id);
if (isVotingOver() || all_votes_in) if (go_on_race)
{ {
m_game_setup->setRace(winner_vote); m_game_setup->setRace(winner_vote);
// Remove disconnected player (if any) one last time // Remove disconnected player (if any) one last time
@ -560,6 +560,7 @@ void ServerLobby::asynchronousUpdate()
NetworkString* load_world_message = getNetworkString(); NetworkString* load_world_message = getNetworkString();
load_world_message->setSynchronous(true); load_world_message->setSynchronous(true);
load_world_message->addUInt8(LE_LOAD_WORLD); load_world_message->addUInt8(LE_LOAD_WORLD);
load_world_message->addUInt32(winner_peer_id);
winner_vote.encode(load_world_message); winner_vote.encode(load_world_message);
load_world_message->addUInt8((uint8_t)players.size()); load_world_message->addUInt8((uint8_t)players.size());
for (unsigned i = 0; i < players.size(); i++) for (unsigned i = 0; i < players.size(); i++)
@ -2086,10 +2087,18 @@ void ServerLobby::handlePlayerVote(Event* event)
/** Select the track to be used based on all votes being received. /** Select the track to be used based on all votes being received.
* \param winner_vote The PeerVote that was picked. * \param winner_vote The PeerVote that was picked.
* \param winner_peer_id The host id of winner (unchanged if no vote). * \param winner_peer_id The host id of winner (unchanged if no vote).
* \return True if race can go on, otherwise wait.
*/ */
bool ServerLobby::handleAllVotes(PeerVote* winner_vote, bool ServerLobby::handleAllVotes(PeerVote* winner_vote,
uint32_t* winner_peer_id) uint32_t* winner_peer_id)
{ {
// Determine majority agreement when 35% of voting time remains,
// reserve some time for kart selection so it's not 50%
if (getRemainingVotingTime() / getMaxVotingTime() > 0.35f)
{
return false;
}
// First remove all votes from disconnected hosts // First remove all votes from disconnected hosts
auto it = m_peers_votes.begin(); auto it = m_peers_votes.begin();
while (it != m_peers_votes.end()) while (it != m_peers_votes.end())
@ -2103,36 +2112,151 @@ bool ServerLobby::handleAllVotes(PeerVote* winner_vote,
it++; it++;
} }
if (m_peers_votes.empty())
{
if (isVotingOver())
{
*winner_vote = *m_default_vote;
return true;
}
return false;
}
// Count number of players // Count number of players
unsigned int cur_players = 0; float cur_players = 0.0f;
auto peers = STKHost::get()->getPeers(); auto peers = STKHost::get()->getPeers();
for (auto peer : peers) for (auto peer : peers)
{ {
if (peer->hasPlayerProfiles() && !peer->isWaitingForGame()) if (peer->hasPlayerProfiles() && !peer->isWaitingForGame())
cur_players++; cur_players += 1.0f;
} }
if (cur_players == 0 || m_peers_votes.size() < cur_players)
std::string top_track = m_default_vote->m_track_name;
int top_laps = m_default_vote->m_num_laps;
std::map<std::string, unsigned> tracks;
std::map<unsigned, unsigned> laps;
std::map<bool, unsigned> reverses;
// Ratio to determine majority agreement
float tracks_rate = 0.0f;
float laps_rate = 0.0f;
float reverses_rate = 0.0f;
RandomGenerator rg;
for (auto& p : m_peers_votes)
{ {
// Default settings if no votes at all auto track_vote = tracks.find(p.second.m_track_name);
*winner_vote = *m_default_vote; if (track_vote == tracks.end())
return false; tracks[p.second.m_track_name] = 1;
else
track_vote->second++;
auto lap_vote = laps.find(p.second.m_num_laps);
if (lap_vote == laps.end())
laps[p.second.m_num_laps] = 1;
else
lap_vote->second++;
auto reverse_vote = reverses.find(p.second.m_reverse);
if (reverse_vote == reverses.end())
reverses[p.second.m_reverse] = 1;
else
reverse_vote->second++;
} }
// Otherwise if we have all votes, randomly select the vote unsigned vote = 0;
// to use in the next race. auto track_vote = tracks.begin();
RandomGenerator r; // rg.get(2) == 0 will allow not always the "less" in map get picked
auto vote = m_peers_votes.begin(); for (auto c_vote = tracks.begin(); c_vote != tracks.end(); c_vote++)
std::advance(vote, r.get(m_peers_votes.size()) ); {
if (c_vote->second > vote ||
(c_vote->second >= vote && rg.get(2) == 0))
{
vote = c_vote->second;
track_vote = c_vote;
}
}
if (track_vote != tracks.end())
{
top_track = track_vote->first;
tracks_rate = float(track_vote->second) / cur_players;
}
*winner_peer_id = vote->first; vote = 0;
*winner_vote = vote->second; auto lap_vote = laps.begin();
for (auto c_vote = laps.begin(); c_vote != laps.end(); c_vote++)
{
if (c_vote->second > vote ||
(c_vote->second >= vote && rg.get(2) == 0))
{
vote = c_vote->second;
lap_vote = c_vote;
}
}
if (lap_vote != laps.end())
{
top_laps = lap_vote->first;
laps_rate = float(lap_vote->second) / cur_players;
}
// For now: don't trigger start of race if all votes vote = 0;
// have been received. Use: auto reverse_vote = reverses.begin();
// return m_peers_votes.size() == cur_players; for (auto c_vote = reverses.begin(); c_vote != reverses.end(); c_vote++)
// to start the race when all votes have been received. {
if (c_vote->second > vote ||
(c_vote->second >= vote && rg.get(2) == 0))
{
vote = c_vote->second;
reverse_vote = c_vote;
}
}
if (reverse_vote != reverses.end())
{
reverses_rate = float(reverse_vote->second) / cur_players;
}
// End early if there is majority agreement which is all entries rate > 0.5
it = m_peers_votes.begin();
if (tracks_rate > 0.5f && laps_rate > 0.5f && reverses_rate > 0.5f)
{
while (it != m_peers_votes.end())
{
if (it->second.m_track_name == top_track)
break;
else
it++;
}
if (it == m_peers_votes.end())
{
Log::warn("ServerLobby",
"Missing track %s from majority.", top_track.c_str());
it = m_peers_votes.begin();
}
*winner_peer_id = it->first;
*winner_vote = it->second;
return true;
}
else if (isVotingOver())
{
// Pick the best lap (or soccer goal / time) from only the top track
// if no majority agreement from all
int diff = std::numeric_limits<int>::max();
auto closest_lap = m_peers_votes.begin();
while (it != m_peers_votes.end())
{
if (it->second.m_track_name == top_track &&
std::abs((int)it->second.m_num_laps - top_laps) < diff)
{
closest_lap = it;
diff = std::abs((int)it->second.m_num_laps - top_laps);
}
else
it++;
}
*winner_peer_id = closest_lap->first;
*winner_vote = closest_lap->second;
return true;
}
return false; return false;
} // handleAllVotes } // handleAllVotes
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -290,6 +290,15 @@ void TracksScreen::beforeAddingWidget()
"restart.png")), 1); "restart.png")), 1);
} }
calculateLayout(); calculateLayout();
static bool shown_msg = false;
if (!shown_msg)
{
shown_msg = true;
//I18N: In track screen for networking, clarify voting phase
core::stringw msg = _("If a majority of players all select the"
" same track and race settings, voting will end early.");
MessageQueue::add(MessageQueue::MT_GENERIC, msg);
}
} }
else else
{ {

View File

@ -90,6 +90,7 @@ private:
m_quit_server = false; m_quit_server = false;
m_bottom_box_height = -1; m_bottom_box_height = -1;
m_track_icons = NULL; m_track_icons = NULL;
m_timer = NULL;
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void updateProgressBarText(); void updateProgressBarText();