Merge remote-tracking branch 'STK-helper/RankingFormula' into aes

This commit is contained in:
Benau 2018-06-03 15:39:01 +08:00
commit a9696792c2
4 changed files with 214 additions and 0 deletions

View File

@ -49,6 +49,7 @@ NetworkConfig::NetworkConfig()
m_auto_connect = false;
m_is_server = false;
m_is_public_server = false;
m_is_ranked_server = false;
m_done_adding_network_players = false;
m_max_players = 4;
m_cur_user_id = 0;

View File

@ -67,6 +67,9 @@ private:
/** True if this host is a server, false otherwise. */
bool m_is_server;
/** True if this is a ranked server */
bool m_is_ranked_server;
/** The password for a server (or to authenticate to a server). */
std::string m_password;
@ -260,6 +263,11 @@ public:
/** Returns if an immediate connection to the first server was
* requested. */
bool isAutoConnect() const { return m_auto_connect; }
// ------------------------------------------------------------------------
/** Returns if the server use multi-session rankings. */
bool isRankedServer() const { return m_is_ranked_server; }
// ------------------------------------------------------------------------
/** Returns the minor and majar game mode from server database id. */
std::pair<RaceManager::MinorRaceModeType, RaceManager::MajorRaceModeType>

View File

@ -759,6 +759,9 @@ void ServerLobby::checkRaceFinished()
total->addUInt32(last_score).addUInt32(cur_score)
.addFloat(overall_time);
}
if (NetworkConfig::get()->isRankedServer())
computeNewRankings();
}
else if (race_manager->modeHasLaps())
{
@ -778,6 +781,192 @@ void ServerLobby::checkRaceFinished()
} // checkRaceFinished
//-----------------------------------------------------------------------------
/** Compute the new player's rankings in ranked servers
* //FIXME : this function assumes that m_rankings,
* m_num_ranked_races and m_max_ranking
* are correctly filled before
* It also assumes that the data stored by them
* is written back to the main list after the GP
*/
void ServerLobby::computeNewRankings()
{
auto players = m_game_setup->getConnectedPlayers(true/*same_index_for_disconnected*/);
assert (m_rankings.size() == players.size() &&
m_num_ranked_races.size() == players.size() &&
m_max_ranking.size() == players.size() );
// No ranking yet for battle mode
// TODO : separate rankings for time-trial and normal ??
if (!race_manager->modeHasLaps())
return;
// Using a vector of vector, it would be possible to fill
// all j < i v[i][j] with -v[j][i]
// Would this be worth it ?
std::vector<double> ranking_change;
for (unsigned i = 0; i < players.size(); i++)
{
m_rankings[i] += distributeBasePoints(i);
}
for (unsigned i = 0; i < players.size(); i++)
{
ranking_change.push_back(0);
int player1_ranking = m_rankings[i];
// If the player has quitted before the race end,
// the value will be incorrect, but it will not be used
float player1_time = race_manager->getKartRaceTime(i);
float player1_factor = computeRankingFactor(i);
for (unsigned j = 0; j < players.size(); i++)
{
// Don't compare a player with itself
if (i == j)
continue;
double result = 0.0f;
double expected_result = 0.0f;
double ranking_importance = 0.0f;
// No change between two quitting players
if (!players[i] && !players[j])
continue;
int player2_ranking = m_rankings[j];
float player2_time = race_manager->getKartRaceTime(j);
// Compute the expected result using an ELO-like function
double diff = (double) player2_ranking - player1_ranking;
expected_result = 1.0f/(1.0f+std::pow(10.0f, diff/(BASE_RANKING_POINTS*getModeSpread()/(2.0f))));
// Compute the result and race ranking importance
float player_factors = std::max(player1_factor,
computeRankingFactor(j) );
float mode_factor = getModeFactor();
if (!players[i])
{
result = 0.0f;
ranking_importance =
mode_factor*MAX_SCALING_TIME*MAX_POINTS_PER_SECOND*player_factors;
}
else if (!players[j])
{
result = 1.0f;
ranking_importance =
mode_factor*MAX_SCALING_TIME*MAX_POINTS_PER_SECOND*player_factors;
}
else
{
// If time difference > 2,5% ; the result is 1 or 0
// Otherwise, it is averaged between 0 and 1.
if (player1_time <= player2_time)
{
result = (player2_time - player1_time)/(player1_time/20);
result = std::min( (double) 1.0f, 0.5f + result);
}
else
{
result = (player1_time - player2_time)/(player2_time/20);
result = std::max( (double) 0.0f, 0.5f - result);
}
ranking_importance = mode_factor * std::min ( std::max (player1_time, player2_time),
MAX_SCALING_TIME ) * MAX_POINTS_PER_SECOND * player_factors;
}
// Compute the ranking change
ranking_change[i] += ranking_importance * (result - expected_result);
}
}
// Don't merge it in the main loop as m_rankings value are used there
for (unsigned i = 0; i < players.size(); i++)
{
m_rankings[i] += ranking_change[i];
if (m_rankings[i] > m_max_ranking[i])
m_max_ranking[i] = m_rankings[i];
m_num_ranked_races[i]++;
}
} //computeNewRankings
//-----------------------------------------------------------------------------
/** Compute the ranking factor, used to make top rankings more stable
* and to allow new players to faster get to an appropriate ranking
*/
float ServerLobby::computeRankingFactor(unsigned int player_id)
{
double max_points = m_max_ranking[player_id];
int num_races = m_num_ranked_races[player_id];
if ( max_points >= (BASE_RANKING_POINTS * 2.0f))
return 0.4f;
else if (max_points >= (BASE_RANKING_POINTS * 1.75f) || num_races > 500)
return 0.5f;
else if (max_points >= (BASE_RANKING_POINTS * 1.5f) || num_races > 250)
return 0.6f;
else if (max_points >= (BASE_RANKING_POINTS * 1.25f) || num_races > 100)
return 0.7f;
// The base ranking points are not distributed all at once
// So it's not guaranteed a player reach them
else if (max_points >= (BASE_RANKING_POINTS ) || num_races > 50)
return 0.8f;
else
return 1.0f;
} //computeRankingFactor
//-----------------------------------------------------------------------------
/** Returns the mode race importance factor,
* used to make ranking move slower in more random modes.
*/
float ServerLobby::getModeFactor()
{
if (race_manager->isTimeTrialMode())
return 1.0f;
//else
return 0.4f;
}
//-----------------------------------------------------------------------------
/** Returns the mode spread factor, used so that a similar difference in
* skill will result in a similar ranking difference in more random modes.
*/
float ServerLobby::getModeSpread()
{
if (race_manager->isTimeTrialMode())
return 1.0f;
//else
//TODO : the value used here for normal races is a wild guess.
// When hard data to the spread tendencies of time-trial
// and normal mode becomes available, update this to make
// the spreads more comparable
return 1.4f;
}
//-----------------------------------------------------------------------------
/** Manages the distribution of the base points.
* Gives half of the points progressively
* by smaller and smaller chuncks from race 1 to 45.
* The first half is distributed when the player enters
* for the first time in the ranked server.
*/
float ServerLobby::distributeBasePoints(unsigned int player_id)
{
int num_races = m_num_ranked_races[player_id];
if (num_races < 45)
return (BASE_RANKING_POINTS/2000.0f * std::max((45-num_races),4)*2.0f);
else
return 0.0f;
}
//-----------------------------------------------------------------------------
/** Stop any race currently in server, should only be called in main thread.
*/

View File

@ -90,6 +90,17 @@ private:
std::pair<uint32_t, BareNetworkString>,
std::owner_less<std::weak_ptr<STKPeer> > > m_pending_connection;
/* Ranking related variables */
// If updating the base points, update the base points distribution in DB
const float BASE_RANKING_POINTS = 4000.0f;
const float MAX_SCALING_TIME = 600.0f;
const float MAX_POINTS_PER_SECOND = 0.125f;
std::vector<double> m_rankings; // TODO : convert from and to int when communicating with the server. Think to round it correctly
std::vector<unsigned int> m_num_ranked_races;
std::vector<double> m_max_ranking;
// connection management
void clientDisconnected(Event* event);
void connectionRequested(Event* event);
@ -149,6 +160,11 @@ private:
const irr::core::stringw& online_name);
std::tuple<std::string, uint8_t, bool, bool> handleVote();
void stopCurrentRace();
void computeNewRankings();
float computeRankingFactor(unsigned int player_id);
float distributeBasePoints(unsigned int player_id);
float getModeFactor();
float getModeSpread();
public:
ServerLobby();