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();
uint32_t winner_peer_id = data.getUInt32();
PeerVote winner_vote(data);
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. */
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;
uint64_t t = m_end_voting_period.load()- StkTime::getRealTimeMs();
return t/1000.0f;
uint64_t t = m_end_voting_period.load() - StkTime::getRealTimeMs();
return t / 1000.0f;
} // getRemainingVotingTime
//-----------------------------------------------------------------------------

View File

@ -541,8 +541,8 @@ void ServerLobby::asynchronousUpdate()
{
PeerVote winner_vote;
uint32_t winner_peer_id = std::numeric_limits<uint32_t>::max();
bool all_votes_in = handleAllVotes(&winner_vote, &winner_peer_id);
if (isVotingOver() || all_votes_in)
bool go_on_race = handleAllVotes(&winner_vote, &winner_peer_id);
if (go_on_race)
{
m_game_setup->setRace(winner_vote);
// Remove disconnected player (if any) one last time
@ -560,6 +560,7 @@ void ServerLobby::asynchronousUpdate()
NetworkString* load_world_message = getNetworkString();
load_world_message->setSynchronous(true);
load_world_message->addUInt8(LE_LOAD_WORLD);
load_world_message->addUInt32(winner_peer_id);
winner_vote.encode(load_world_message);
load_world_message->addUInt8((uint8_t)players.size());
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.
* \param winner_vote The PeerVote that was picked.
* \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,
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
auto it = m_peers_votes.begin();
while (it != m_peers_votes.end())
@ -2103,36 +2112,151 @@ bool ServerLobby::handleAllVotes(PeerVote* winner_vote,
it++;
}
if (m_peers_votes.empty())
{
if (isVotingOver())
{
*winner_vote = *m_default_vote;
return true;
}
return false;
}
// Count number of players
unsigned int cur_players = 0;
float cur_players = 0.0f;
auto peers = STKHost::get()->getPeers();
for (auto peer : peers)
{
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
*winner_vote = *m_default_vote;
return false;
auto track_vote = tracks.find(p.second.m_track_name);
if (track_vote == tracks.end())
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
// to use in the next race.
RandomGenerator r;
auto vote = m_peers_votes.begin();
std::advance(vote, r.get(m_peers_votes.size()) );
unsigned vote = 0;
auto track_vote = tracks.begin();
// rg.get(2) == 0 will allow not always the "less" in map get picked
for (auto c_vote = tracks.begin(); c_vote != tracks.end(); c_vote++)
{
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;
*winner_vote = vote->second;
vote = 0;
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
// have been received. Use:
// return m_peers_votes.size() == cur_players;
// to start the race when all votes have been received.
vote = 0;
auto reverse_vote = reverses.begin();
for (auto c_vote = reverses.begin(); c_vote != reverses.end(); c_vote++)
{
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;
} // handleAllVotes
// ----------------------------------------------------------------------------

View File

@ -290,6 +290,15 @@ void TracksScreen::beforeAddingWidget()
"restart.png")), 1);
}
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
{

View File

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