game: working score + stats calcs

This commit is contained in:
Mark Joshwel 2024-11-19 03:59:29 +08:00
parent e1aee5d946
commit a5679b4270
6 changed files with 98 additions and 88 deletions

View file

@ -661,7 +661,7 @@ private void OnLocalPlayerDataChangeCallback(LocalPlayerData data)
$"updating AccountView ui with lkUsername={data.LastKnownUsername} and lkEmail={data.LastKnownEmail}");
_usernameField.value = data.LastKnownUsername;
_emailField.value = data.LastKnownEmail;
_header.text = $"Signed in as {data.LastKnownUsername}";
if (state == State.SignedIn) _header.text = $"Signed in as {data.LastKnownUsername}";
}
/// <summary>

View file

@ -571,7 +571,7 @@ public void GetRecentScores(Action<DatabaseTransactionResult, List<LocalPlayerDa
_db.Child("scores")
.OrderByChild("timestamp")
.LimitToLast(LocalPlayerData.MaxBestOnlineScores)
.LimitToLast(LocalPlayerData.MaxBestScores)
.GetValueAsync()
.ContinueWithOnMainThread(task =>
{
@ -618,7 +618,7 @@ private void GetBestScores(Action<DatabaseTransactionResult, List<LocalPlayerDat
_db.Child("scores")
.OrderByChild("avgPerceivedAccuracy")
.LimitToLast(LocalPlayerData.MaxBestOnlineScores)
.LimitToLast(LocalPlayerData.MaxBestScores)
.GetValueAsync()
.ContinueWithOnMainThread(task =>
{
@ -702,21 +702,23 @@ private void GetBestScores(Action<DatabaseTransactionResult, List<LocalPlayerDat
var recentScoreQueue = GameManager.Instance.Data.RecentOnlineScores;
foreach (var score in recentScores) recentScoreQueue.Enqueue(score);
while (recentScoreQueue.Count > LocalPlayerData.MaxRecentLocalScores) recentScoreQueue.Dequeue();
while (recentScoreQueue.Count > LocalPlayerData.MaxRecentScores) recentScoreQueue.Dequeue();
GetBestScores((bestRes, bestScores) =>
{
if (bestRes != DatabaseTransactionResult.Ok)
{
Debug.Log("failed to get recent scores");
GameManager.Instance.FireLocalPlayerDataChangeCallbacks(GameManager.Instance.Data);
callback(recentRes, 0f);
return;
}
var bestScoreQueue = GameManager.Instance.Data.BestOnlineScores;
foreach (var score in bestScores) bestScoreQueue.Enqueue(score);
while (bestScoreQueue.Count > LocalPlayerData.MaxBestOnlineScores) bestScoreQueue.Dequeue();
while (bestScoreQueue.Count > LocalPlayerData.MaxBestScores) bestScoreQueue.Dequeue();
GameManager.Instance.FireLocalPlayerDataChangeCallbacks(GameManager.Instance.Data);
callback(DatabaseTransactionResult.Ok, GameManager.Instance.Data.CalculateUserRating());
});
});
@ -737,14 +739,17 @@ private void GetBestScores(Action<DatabaseTransactionResult, List<LocalPlayerDat
return;
}
var userRating = GameManager.Instance.Data.CalculateUserRating();
_db.Child("users")
.Child(_user.UserId)
.Child("rating")
.SetValueAsync(GameManager.Instance.Data.CalculateUserRating())
.SetValueAsync(userRating)
.ContinueWithOnMainThread(task =>
{
if (task.IsCompletedSuccessfully)
{
Debug.Log($"updated online user rating to {userRating}");
callback(DatabaseTransactionResult.Ok);
}
else

View file

@ -175,42 +175,47 @@ public void SignalGameEnd(List<Gameplay.RoundInfo> playedRounds)
var historicalLightnessAcc = 0f;
var historicalChromaAcc = 0f;
var historicalHueAcc = 0f;
var historicalRounds = 0;
var historicalPerceivedAcc = 0f;
var historicalGames = 0;
foreach (var localScore in _data.RecentLocalScores)
foreach (var localScore in _data.RecentLocalScores.Take(LocalPlayerData.MaxRecentScores))
{
historicalLightnessAcc += localScore.AvgLightnessAccuracy;
historicalChromaAcc += localScore.AvgChromaAccuracy;
historicalHueAcc += localScore.AvgHueAccuracy;
historicalRounds += localScore.NoOfRounds;
historicalPerceivedAcc += localScore.AvgPerceivedAccuracy;
historicalGames++;
}
foreach (var onlineScore in _data.RecentOnlineScores)
foreach (var onlineScore in _data.RecentOnlineScores.Take(LocalPlayerData.MaxRecentScores))
{
historicalLightnessAcc += onlineScore.AvgLightnessAccuracy;
historicalChromaAcc += onlineScore.AvgChromaAccuracy;
historicalHueAcc += onlineScore.AvgHueAccuracy;
historicalRounds += onlineScore.NoOfRounds;
historicalPerceivedAcc += onlineScore.AvgPerceivedAccuracy;
historicalGames++;
}
foreach (var onlineScore in _data.BestOnlineScores)
foreach (var onlineScore in _data.BestOnlineScores.Take(LocalPlayerData.MaxBestScores))
{
historicalLightnessAcc += onlineScore.AvgLightnessAccuracy;
historicalChromaAcc += onlineScore.AvgChromaAccuracy;
historicalHueAcc += onlineScore.AvgHueAccuracy;
historicalRounds += onlineScore.NoOfRounds;
historicalPerceivedAcc += onlineScore.AvgPerceivedAccuracy;
historicalGames++;
}
historicalLightnessAcc /= historicalRounds;
historicalChromaAcc /= historicalRounds;
historicalHueAcc /= historicalRounds;
historicalGames = Math.Max(1, historicalGames);
historicalLightnessAcc /= historicalGames;
historicalChromaAcc /= historicalGames;
historicalHueAcc /= historicalGames;
historicalPerceivedAcc /= historicalGames;
// calculate round averages
var gameLightnessAcc = 0d;
var gameChromaAcc = 0d;
var gameHueAcc = 0d;
var gamePerceivedAcc = 0d;
var templateColour = Color.clear;
var responseColour = Color.clear;
var roundNumber = 1;
@ -226,7 +231,7 @@ public void SignalGameEnd(List<Gameplay.RoundInfo> playedRounds)
var roundLightnessAcc = Math.Clamp(dLCh.L * 100d, 0d, 100d);
var roundChromaAcc = Math.Clamp(dLCh.C * 100d, 0d, 100d);
var roundHueAcc = Math.Clamp(dLCh.h * 100d, 0d, 100d);
var roundPerceivedAcc = Math.Clamp((100d - distance.dE) * 100d, 0d, 100d);
var roundPerceivedAcc = Math.Clamp((1d - distance.dE) * 100d, 0d, 100d);
gameLightnessAcc += roundLightnessAcc;
gameChromaAcc += roundChromaAcc;
@ -242,7 +247,7 @@ public void SignalGameEnd(List<Gameplay.RoundInfo> playedRounds)
showcaseTemplate.style.backgroundColor = templateColour;
showcaseResponse.style.backgroundColor = responseColour;
showcaseInfo.text =
$"{roundLightnessAcc:N0}% {roundChromaAcc:N0}% {roundHueAcc:N0}% ({roundPerceivedAcc:N0}%)";
$"{roundLightnessAcc * (roundPerceivedAcc / 100d):N0}% {roundChromaAcc * (roundPerceivedAcc / 100d):N0}% {roundHueAcc * (roundPerceivedAcc / 100d):N0}% ({roundPerceivedAcc:N0}%)";
}
else
{
@ -257,25 +262,30 @@ public void SignalGameEnd(List<Gameplay.RoundInfo> playedRounds)
gameHueAcc /= Gameplay.RoundsPerGame;
gamePerceivedAcc /= Gameplay.RoundsPerGame;
// NOTE: this is NOT equiv to user rating, this is just a per-game accuracy score
// all that math is done in LocalPlayerData.CalculateUserRating
var gameAccuracy = (gameLightnessAcc + gameChromaAcc + gameHueAcc + gamePerceivedAcc) / 4;
var adjustedHistoricalLightnessAcc = historicalLightnessAcc * (historicalPerceivedAcc / 100d);
var adjustedHistoricalChromaAcc = historicalChromaAcc * (historicalPerceivedAcc / 100d);
var adjustedHistoricalHueAcc = historicalHueAcc * (historicalPerceivedAcc / 100d);
var adjustedGameLightnessAcc = gameLightnessAcc * (gamePerceivedAcc / 100d);
var adjustedGameChromaAcc = gameChromaAcc * (gamePerceivedAcc / 100d);
var adjustedGameHueAcc = gameHueAcc * (gamePerceivedAcc / 100d);
var gameAccuracy = (gameLightnessAcc + gameChromaAcc + gameHueAcc + gamePerceivedAcc) / 4d;
// make comparison texts
var lAccDeltaText = (gameLightnessAcc > historicalLightnessAcc ? "+" : "-") +
Math.Abs(gameLightnessAcc - historicalLightnessAcc).ToString("F2");
var cAccDeltaText = (gameChromaAcc > historicalChromaAcc ? "+" : "-") +
Math.Abs(gameChromaAcc - historicalChromaAcc).ToString("F2");
var hAccDeltaText = (gameHueAcc > historicalHueAcc ? "+" : "-") +
Math.Abs(gameHueAcc - historicalHueAcc).ToString("F2");
var lAccDeltaText = (adjustedGameLightnessAcc > adjustedHistoricalLightnessAcc ? "+" : "-") +
$"{Math.Abs(adjustedGameLightnessAcc - adjustedHistoricalLightnessAcc):F1}%";
var cAccDeltaText = (adjustedGameChromaAcc > adjustedHistoricalChromaAcc ? "+" : "-") +
$"{Math.Abs(adjustedGameChromaAcc - adjustedHistoricalChromaAcc):F1}%";
var hAccDeltaText = (adjustedGameHueAcc > adjustedHistoricalHueAcc ? "+" : "-") +
$"{Math.Abs(adjustedGameHueAcc - adjustedHistoricalHueAcc):F1}%";
var score = new LocalPlayerData.Score(DateTime.Now,
playedRounds.Count,
(float)gameLightnessAcc,
(float)gameChromaAcc,
(float)gameHueAcc,
(float)adjustedGameLightnessAcc,
(float)adjustedGameChromaAcc,
(float)adjustedGameHueAcc,
(float)gamePerceivedAcc);
// breakpoint here: why is gamePerceivedAcc always "100"?
var oldRating = _data.CalculateUserRating();
_data.RegisterLocalScore(score);
FireLocalPlayerDataChangeCallbacks(Instance.Data);
@ -298,7 +308,6 @@ public void SignalGameEnd(List<Gameplay.RoundInfo> playedRounds)
{
Debug.Log("couldn't calculate user rating");
TransitionToResultsView(_data.CalculateUserRating());
FireLocalPlayerDataChangeCallbacks(Instance.Data);
return;
}
@ -322,17 +331,17 @@ void TransitionToResultsView(float rating)
{
Debug.Log("signal GameManager-UIManager transition to results view");
var ratingDifferenceDescriptor = oldRating > rating ? "decreased" : "increased";
var ratingDifferenceDescriptor = rating > oldRating ? "increased" : "decreased";
var ratingText = rating >= 0
? $"\nYour rating has {ratingDifferenceDescriptor} by {Math.Abs(rating - oldRating):F2}."
: "\nYour rating could not be calculated.";
// build the result text and show the results view
ui.UI.Q<Label>("ResultsText").text = string.Join(Environment.NewLine, $"Over {playedRounds.Count} rounds,",
$"you were {gameAccuracy} accurate.", "",
$"Lightness was {gameLightnessAcc:F2}% accurate. ({lAccDeltaText} from your average)",
$"Chroma was {gameChromaAcc:F2}% accurate. ({cAccDeltaText} from your average)",
$"Hue was {gameHueAcc:F2}% accurate. ({hAccDeltaText} from your average)") + ratingText;
ui.UI.Q<Label>("ResultsText").text = string.Join(Environment.NewLine, $"Over {playedRounds.Count:N0} rounds,",
$"you were {LocalPlayerData.UserRatingScalingF(gameAccuracy):F2}% accurate. (raw ΔEok={gameAccuracy:F2}%)", "",
$"Lightness was {adjustedGameLightnessAcc:F2}% accurate. ({lAccDeltaText} from your average)",
$"Chroma was {adjustedGameChromaAcc:F2}% accurate. ({cAccDeltaText} from your average)",
$"Hue was {adjustedGameHueAcc:F2}% accurate. ({hAccDeltaText} from your average)") + ratingText;
ui.SetDisplayState(UIManager.DisplayState.ResultsView);
}

View file

@ -18,7 +18,7 @@ public class Gameplay
/// seconds per round
/// </summary>
// ReSharper disable once MemberCanBePrivate.Global
public const double SecondsPerRound = 15d;
public const double SecondsPerRound = 12d;
/// <summary>
/// countdown text label for showing the countdown
@ -118,7 +118,7 @@ private void StoreRoundInfo()
TemplateColour = _templateColour.style.backgroundColor.value,
ResponseColour = _responseColour.style.backgroundColor.value
});
Debug.Log("stored round info");
Debug.Log($"stored round info (Template is {_templateColour.style.backgroundColor.value}, Response is {_responseColour.style.backgroundColor.value})");
}
/// <summary>
@ -127,13 +127,13 @@ private void StoreRoundInfo()
/// </summary>
private void GenerateNewTemplateColour()
{
// - lightness: 40-80
// - chroma: 0.05-0.20
// - lightness: 30-90
// - chroma: 0.03-0.20
// - hue: all (0-360)
var r = new Random();
var l = Math.Clamp(r.NextDouble() * 40d + 40d, 40d, 100d);
var c = Math.Clamp(r.NextDouble() * 0.15d + 0.05d, 0.05d, 0.20d);
var l = Math.Clamp(r.NextDouble() * 60d + 30d, 30d, 90d);
var c = Math.Clamp(r.NextDouble() * 0.17d + 0.03d, 0.03d, 0.20d);
var h = Math.Clamp(r.NextDouble() * 360d, 0d, 360d);
var colour = Colorimetry.RawLchToColor(l, c, h);
Debug.Log($"generated new template colour LCh({l:F}, {c:F}, {h:F}) -> {colour}");
@ -147,7 +147,7 @@ private void Render()
{
var remaining = (_countdownDatetime - DateTime.Now).TotalSeconds;
_roundText.text = $"{Round}/{RoundsPerGame}";
_countdownText.text = $"{remaining:F}";
_countdownText.text = $"{remaining:F2}";
}
/// <summary>

View file

@ -9,17 +9,17 @@ public class LocalPlayerData
/// <summary>
/// maximum number of the best online scores to keep track of
/// </summary>
public const int MaxBestOnlineScores = 10;
public const int MaxBestScores = 10;
/// <summary>
/// maximum number of recent local scores to keep track of
/// </summary>
public const int MaxRecentLocalScores = 10;
public const int MaxRecentScores = 10;
/// <summary>
/// the gamma value used in the exponential user rating calculation
/// </summary>
private const float ExponentialUserRatingGamma = 1.75f;
private const float ExponentialUserRatingGamma = 2f;
/// <summary>
/// last known email used
@ -149,10 +149,23 @@ public void SaveToTheWorld()
/// <param name="score">the score to register</param>
public void RegisterLocalScore(Score score)
{
while (RecentLocalScores.Count >= MaxRecentLocalScores) RecentLocalScores.Dequeue();
while (RecentLocalScores.Count >= MaxRecentScores) RecentLocalScores.Dequeue();
RecentLocalScores.Enqueue(score);
}
/// <summary>
/// registers a score to the player's online data
/// </summary>
/// <returns>
/// the
/// </returns>
public static double UserRatingScalingF(double rating)
{
var rawUserRating = Math.Clamp(rating, 0d, 100d);
var exponentialRating = 100d * Math.Pow(rawUserRating / 100d, ExponentialUserRatingGamma);
return Math.Clamp(exponentialRating, 0d, 100d);
}
/// <summary>
/// calculates the user rating based on whatever local data is available
/// </summary>
@ -164,9 +177,9 @@ public float CalculateUserRating()
// in this case 20 best scores, and 10 recent scores are used
// ensure the scores don't exceed their arbitrary limits
while (RecentOnlineScores.Count > MaxRecentLocalScores) RecentOnlineScores.Dequeue();
while (RecentLocalScores.Count > MaxRecentLocalScores) RecentLocalScores.Dequeue();
while (BestOnlineScores.Count > MaxBestOnlineScores) BestOnlineScores.Dequeue();
while (RecentOnlineScores.Count > MaxRecentScores) RecentOnlineScores.Dequeue();
while (RecentLocalScores.Count > MaxRecentScores) RecentLocalScores.Dequeue();
while (BestOnlineScores.Count > MaxBestScores) BestOnlineScores.Dequeue();
// if online scores are available, use them
var recentScores = RecentOnlineScores.Count > 0 ? RecentOnlineScores : RecentLocalScores;
@ -177,37 +190,20 @@ public float CalculateUserRating()
foreach (var score in recentScores.Take(10))
{
totalRating += (score.AvgLightnessAccuracy + score.AvgChromaAccuracy + score.AvgHueAccuracy + score.AvgPerceivedAccuracy) / 4d;
scores++;
var dL = score.AvgLightnessAccuracy;
var dC = score.AvgChromaAccuracy;
var dH = score.AvgHueAccuracy;
var dE = Math.Sqrt(score.AvgLightnessAccuracy * score.AvgLightnessAccuracy
+ score.AvgChromaAccuracy * score.AvgChromaAccuracy
+ score.AvgHueAccuracy * score.AvgHueAccuracy);
totalRating += (dL + dC + dH + dE + dE) / 5d;
}
foreach (var score in bestScores.Take(20))
{
totalRating += (score.AvgLightnessAccuracy + score.AvgChromaAccuracy + score.AvgHueAccuracy + score.AvgPerceivedAccuracy) / 4d;
scores++;
var dL = score.AvgLightnessAccuracy;
var dC = score.AvgChromaAccuracy;
var dH = score.AvgHueAccuracy;
var dE = Math.Sqrt(score.AvgLightnessAccuracy * score.AvgLightnessAccuracy
+ score.AvgChromaAccuracy * score.AvgChromaAccuracy
+ score.AvgHueAccuracy * score.AvgHueAccuracy);
totalRating += (dL + dC + dH + dE + dE) / 5d;
}
scores = Math.Max(1, scores);
totalRating /= scores;
var rating = UserRatingScalingF(totalRating /= Math.Max(1, scores));
Debug.Log($"locally calculated user rating: lin: {totalRating} -> exp: {rating}");
var rawUserRating = Math.Clamp(totalRating, 0d, 100d);
var exponentialRating = 100d * Math.Pow(rawUserRating / 100d, ExponentialUserRatingGamma);
Debug.Log($"locally calculated user rating: lin: {rawUserRating} -> exp: {exponentialRating}");
return (float)exponentialRating;
return (float)rating;
}
public struct Score

View file

@ -111,7 +111,7 @@ private void RenderFromPlayerData(LocalPlayerData data)
var totalLightnessAcc = 0f;
var totalChromaAcc = 0f;
var totalHueAcc = 0f;
var totalRounds = 0;
var totalGames = 0;
// average out all the scores we have to get a stable-ish average
@ -120,7 +120,7 @@ private void RenderFromPlayerData(LocalPlayerData data)
totalLightnessAcc += localScore.AvgLightnessAccuracy;
totalChromaAcc += localScore.AvgChromaAccuracy;
totalHueAcc += localScore.AvgHueAccuracy;
totalRounds += localScore.NoOfRounds;
totalGames++;
}
foreach (var onlineScore in data.RecentOnlineScores)
@ -128,7 +128,7 @@ private void RenderFromPlayerData(LocalPlayerData data)
totalLightnessAcc += onlineScore.AvgLightnessAccuracy;
totalChromaAcc += onlineScore.AvgChromaAccuracy;
totalHueAcc += onlineScore.AvgHueAccuracy;
totalRounds += onlineScore.NoOfRounds;
totalGames++;
}
foreach (var onlineScore in data.BestOnlineScores)
@ -136,14 +136,14 @@ private void RenderFromPlayerData(LocalPlayerData data)
totalLightnessAcc += onlineScore.AvgLightnessAccuracy;
totalChromaAcc += onlineScore.AvgChromaAccuracy;
totalHueAcc += onlineScore.AvgHueAccuracy;
totalRounds += onlineScore.NoOfRounds;
totalGames++;
}
Debug.Log($"tL={totalLightnessAcc} tC={totalChromaAcc} tH={totalHueAcc} tR={totalRounds}");
if (totalRounds == 0) totalRounds = 1;
var lightnessAcc = totalLightnessAcc / totalRounds;
var chromaAcc = totalChromaAcc / totalRounds;
var hueAcc = totalHueAcc / totalRounds;
Debug.Log($"tL={totalLightnessAcc} tC={totalChromaAcc} tH={totalHueAcc} tG={totalGames}");
if (totalGames == 0) totalGames = 1;
var lightnessAcc = totalLightnessAcc / totalGames;
var chromaAcc = totalChromaAcc / totalGames;
var hueAcc = totalHueAcc / totalGames;
var playerText = GameManager.Instance.Backend.IsSignedIn
? data.LastKnownUsername
: $"{data.LastKnownUsername} (Not Signed In)";
@ -151,9 +151,9 @@ private void RenderFromPlayerData(LocalPlayerData data)
// finally, set the labels
_playerText.text = playerText;
_ratingText.text = $"{rating:F}";
_lightnessAccuracyText.text = $"{lightnessAcc:F}";
_chromaAccuracyText.text = $"{chromaAcc:F}";
_hueAccuracyText.text = $"{hueAcc:F}";
_ratingText.text = $"{rating:F3}";
_lightnessAccuracyText.text = $"{lightnessAcc:F2}%";
_chromaAccuracyText.text = $"{chromaAcc:F2}%";
_hueAccuracyText.text = $"{hueAcc:F2}%";
}
}