game: working game, half-working database
This commit is contained in:
parent
a338a62f27
commit
5e1defa793
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="MarkdownNoTableBorders" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
|
@ -143,7 +143,7 @@ GameObject:
|
|||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &133964671
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
@ -258,7 +258,7 @@ GameObject:
|
|||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &447905427
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
|
@ -476,7 +476,7 @@ GameObject:
|
|||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1204483825
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
@ -505,37 +505,6 @@ Transform:
|
|||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1680304394
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1680304395}
|
||||
m_Layer: 0
|
||||
m_Name: GameObject
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1680304395
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1680304394}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
|
@ -545,4 +514,3 @@ SceneRoots:
|
|||
- {fileID: 133964672}
|
||||
- {fileID: 447905427}
|
||||
- {fileID: 1204483826}
|
||||
- {fileID: 1680304395}
|
||||
|
|
|
@ -546,7 +546,7 @@ public void GetRecentScores(Action<DatabaseTransactionResult, List<LocalPlayerDa
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError(e);
|
||||
Debug.LogError($"{e}\n{child.GetRawJsonValue()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,48 @@
|
|||
|
||||
public static class Colorimetry
|
||||
{
|
||||
/// <summary>
|
||||
/// calculate a similarity percentage from a colour distance
|
||||
/// </summary>
|
||||
/// <param name="delta">
|
||||
/// the <c>DeltaLabChE</c> object returned by <c>CalculateDistance</c>,
|
||||
/// </param>
|
||||
/// <param name="chromaMax">
|
||||
/// the maximum chroma value to use for the similarity percentage calculation,
|
||||
/// defaults to 1.0f
|
||||
/// </param>
|
||||
/// <param name="hueMax">
|
||||
/// the maximum hue value to use for the similarity percentage calculation,
|
||||
/// defaults to 1.0f
|
||||
/// </param>
|
||||
/// <param name="lightnessMax">
|
||||
/// the maximum lightness value to use for the similarity percentage calculation,
|
||||
/// defaults to 1.0f
|
||||
/// </param>
|
||||
/// <returns>a <c>LCh</c> struct with 0-1f values</returns>
|
||||
public static LCh CalculateLChSimilarityPercentage(
|
||||
DeltaLabChE delta,
|
||||
double chromaMax = 1.0d,
|
||||
double hueMax = 1.0d,
|
||||
double lightnessMax = 1.0d)
|
||||
{
|
||||
// dL = [-1, 1] lightness difference (negative = template is darker)
|
||||
// dC = [-inf, +inf] chroma difference (negative = template is more chromatic)
|
||||
// dH = [0, +inf] hue difference (zero for grayscale or similar hues)
|
||||
// dE = [0, 1] overall perceptual difference in the oklab colour space
|
||||
// (but since we're using sRGB, we just use 1.0f as the max bounds)
|
||||
return new LCh((float)Math.Clamp(1 - Math.Abs(delta.dL) / lightnessMax, 0, 1),
|
||||
(float)Math.Clamp(1 - Math.Abs(delta.dC) / chromaMax, 0, 1),
|
||||
(float)Math.Clamp(1 - delta.dh / hueMax, 0, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// calculate a 0-100% distance/accuracy between two unity rgba colour objects
|
||||
/// </summary>
|
||||
/// <param name="template">the template colour to compare against</param>
|
||||
/// <param name="response">the response colour to compare</param>
|
||||
/// <returns>a <c>DeltaLabCHE</c> struct</returns>
|
||||
public static DeltaLabCHE CalculateDistance(Color template, Color response)
|
||||
/// <returns>a <c>DeltaLabChE</c> struct</returns>
|
||||
public static DeltaLabChE CalculateDistance(Color template, Color response)
|
||||
{
|
||||
// rgb to oklab
|
||||
var templateOklab = linear_srgb_to_oklab(new RGB(
|
||||
|
@ -48,7 +83,7 @@ public static DeltaLabCHE CalculateDistance(Color template, Color response)
|
|||
var deltaH = Math.Max(0d, Math.Sqrt(deltaA * deltaA + deltaB * deltaB - deltaC * deltaC));
|
||||
var deltaE = Math.Sqrt(deltaL * deltaL + deltaC * deltaC + deltaH * deltaH);
|
||||
|
||||
return new DeltaLabCHE(deltaL, deltaA, deltaB, deltaC, deltaH, deltaE);
|
||||
return new DeltaLabChE(deltaL, deltaA, deltaB, deltaC, deltaH, deltaE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -136,7 +171,6 @@ public static RGB gamut_clip_preserve_chroma(RGB rgb)
|
|||
/// a and b must be normalized so a^2 + b^2 == 1
|
||||
/// </summary>
|
||||
// https://bottosson.github.io/posts/gamutclipping/ (MIT)
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public static float find_gamut_intersection(
|
||||
float a,
|
||||
float b,
|
||||
|
@ -380,27 +414,27 @@ public static float compute_max_saturation(float a, float b)
|
|||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public struct DeltaLabCHE
|
||||
public struct DeltaLabChE
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public double dL;
|
||||
public readonly double dL;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public double da;
|
||||
public readonly double da;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public double db;
|
||||
public readonly double db;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public double dC;
|
||||
public readonly double dC;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public double dH;
|
||||
public readonly double dh;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public double dE;
|
||||
public readonly double dE;
|
||||
|
||||
public DeltaLabCHE(
|
||||
public DeltaLabChE(
|
||||
// ReSharper disable once InconsistentNaming
|
||||
double L,
|
||||
double a,
|
||||
|
@ -416,7 +450,7 @@ public struct DeltaLabCHE
|
|||
da = a;
|
||||
db = b;
|
||||
dC = C;
|
||||
dH = H;
|
||||
dh = H;
|
||||
dE = E;
|
||||
}
|
||||
}
|
||||
|
@ -440,6 +474,27 @@ public Lab(float L, float a, float b)
|
|||
}
|
||||
}
|
||||
|
||||
public readonly struct LCh
|
||||
{
|
||||
public readonly float L;
|
||||
public readonly float C;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public readonly float h;
|
||||
|
||||
public LCh(
|
||||
// ReSharper disable once InconsistentNaming
|
||||
float L,
|
||||
// ReSharper disable once InconsistentNaming
|
||||
float C,
|
||||
float h)
|
||||
{
|
||||
this.L = L;
|
||||
this.C = C;
|
||||
this.h = h;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct RGB
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
|
|
|
@ -203,23 +203,47 @@ public void SignalGameEnd(List<Gameplay.RoundInfo> playedRounds)
|
|||
var roundHueAcc = 0d;
|
||||
var roundPerceivedAcc = 0d;
|
||||
|
||||
var maxDistance = Colorimetry.CalculateDistance(Color.black, Color.white);
|
||||
|
||||
foreach (var distance in playedRounds.Select(round =>
|
||||
Colorimetry.CalculateDistance(round.TemplateColour, round.ResponseColour)))
|
||||
var templateColour = Color.clear;
|
||||
var responseColour = Color.clear;
|
||||
var roundNumber = 1;
|
||||
foreach (var distance in playedRounds.Take(Gameplay.RoundsPerGame).Select(round =>
|
||||
Colorimetry.CalculateDistance(templateColour = round.TemplateColour,
|
||||
responseColour = round.ResponseColour)))
|
||||
{
|
||||
roundLightnessAcc += distance.dL / maxDistance.dL;
|
||||
roundChromaAcc += distance.dC / maxDistance.dC;
|
||||
roundHueAcc += distance.dH / maxDistance.dH;
|
||||
roundPerceivedAcc += distance.dE / maxDistance.dE;
|
||||
var dLCh = Colorimetry.CalculateLChSimilarityPercentage(distance);
|
||||
|
||||
Debug.Log(
|
||||
$"processing round: template={templateColour}, response={responseColour} (dL%={dLCh.L}, dC%={dLCh.C}, dh%={dLCh.h}, dEok={distance.dE:F})");
|
||||
|
||||
roundLightnessAcc += Math.Clamp(dLCh.L * 100d, 0d, 100d);
|
||||
roundChromaAcc += Math.Clamp(dLCh.C * 100d, 0d, 100d);
|
||||
roundHueAcc += Math.Clamp(dLCh.h * 100d, 0d, 100d);
|
||||
roundPerceivedAcc += Math.Clamp((100d - distance.dE) * 100d, 0d, 100d);
|
||||
|
||||
var showcaseTemplate = ui.UI.Q<VisualElement>($"ShowcasePair{roundNumber}TemplateColour");
|
||||
var showcaseResponse = ui.UI.Q<VisualElement>($"ShowcasePair{roundNumber}ResponseColour");
|
||||
var showcaseInfo = ui.UI.Q<Label>($"ShowcasePair{roundNumber}Info");
|
||||
|
||||
if (showcaseTemplate == null || showcaseResponse == null || showcaseInfo == null)
|
||||
{
|
||||
Debug.LogError($"showcase pair {roundNumber} not found");
|
||||
roundNumber++;
|
||||
continue;
|
||||
}
|
||||
|
||||
showcaseTemplate.style.backgroundColor = templateColour;
|
||||
showcaseResponse.style.backgroundColor = responseColour;
|
||||
showcaseInfo.text = $"{roundLightnessAcc:F}% {roundChromaAcc:F}% {roundHueAcc:F}% ({roundPerceivedAcc:F}%)";
|
||||
|
||||
roundNumber++;
|
||||
}
|
||||
|
||||
roundLightnessAcc /= playedRounds.Count;
|
||||
roundChromaAcc /= playedRounds.Count;
|
||||
roundHueAcc /= playedRounds.Count;
|
||||
roundPerceivedAcc /= playedRounds.Count;
|
||||
roundLightnessAcc /= Gameplay.RoundsPerGame;
|
||||
roundChromaAcc /= Gameplay.RoundsPerGame;
|
||||
roundHueAcc /= Gameplay.RoundsPerGame;
|
||||
roundPerceivedAcc /= Gameplay.RoundsPerGame;
|
||||
|
||||
var roundAcc = (roundLightnessAcc + roundChromaAcc + roundHueAcc) / 3;
|
||||
var roundAcc = (roundLightnessAcc + roundChromaAcc + roundHueAcc + roundPerceivedAcc + roundPerceivedAcc) / 5;
|
||||
|
||||
// make comparison texts
|
||||
var lAccDeltaText = (roundLightnessAcc > historicalLightnessAcc ? "+" : "-") +
|
||||
|
@ -237,7 +261,7 @@ public void SignalGameEnd(List<Gameplay.RoundInfo> playedRounds)
|
|||
(float)roundPerceivedAcc);
|
||||
|
||||
_data.RegisterLocalScore(score);
|
||||
FireLocalPlayerDataChangeCallbacks(GameManager.Instance.Data);
|
||||
FireLocalPlayerDataChangeCallbacks(Instance.Data);
|
||||
|
||||
Backend.SubmitScore(score,
|
||||
submitRes =>
|
||||
|
@ -255,7 +279,7 @@ public void SignalGameEnd(List<Gameplay.RoundInfo> playedRounds)
|
|||
{
|
||||
Debug.Log("couldn't calculate user rating");
|
||||
TransitionToResultsView(_data.CalculateUserRating());
|
||||
FireLocalPlayerDataChangeCallbacks(GameManager.Instance.Data);
|
||||
FireLocalPlayerDataChangeCallbacks(Instance.Data);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -277,14 +301,14 @@ public void SignalGameEnd(List<Gameplay.RoundInfo> playedRounds)
|
|||
|
||||
void TransitionToResultsView(float rating)
|
||||
{
|
||||
var ratingText = rating >= 0 ? $"\nYour rating is {rating}" : "\nYour rating could not be calculated.";
|
||||
var ratingText = rating >= 0 ? $"\nYour rating is {rating:F}" : "\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 {roundAcc} accurate.", "",
|
||||
$"Lightness was {roundLightnessAcc}% accurate. ({lAccDeltaText} from your average)",
|
||||
$"Chroma was {roundChromaAcc}% accurate. ({cAccDeltaText} from your average)",
|
||||
$"Hue was {roundHueAcc}% accurate. ({hAccDeltaText} from your average)") + ratingText;
|
||||
$"Lightness was {roundLightnessAcc:P}% accurate. ({lAccDeltaText} from your average)",
|
||||
$"Chroma was {roundChromaAcc:P}% accurate. ({cAccDeltaText} from your average)",
|
||||
$"Hue was {roundHueAcc:P}% accurate. ({hAccDeltaText} from your average)") + ratingText;
|
||||
|
||||
ui.SetDisplayState(UIManager.DisplayState.ResultsView);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,17 @@
|
|||
/// </summary>
|
||||
public class Gameplay
|
||||
{
|
||||
/// <summary>
|
||||
/// singleton instance of the gameplay class
|
||||
/// </summary>
|
||||
public const int RoundsPerGame = 5;
|
||||
|
||||
/// <summary>
|
||||
/// seconds per round
|
||||
/// </summary>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public const double SecondsPerRound = 15d;
|
||||
|
||||
/// <summary>
|
||||
/// countdown text label for showing the countdown
|
||||
/// </summary>
|
||||
|
@ -44,16 +55,6 @@ public class Gameplay
|
|||
/// </summary>
|
||||
public int Round = -1;
|
||||
|
||||
/// <summary>
|
||||
/// singleton instance of the gameplay class
|
||||
/// </summary>
|
||||
private const int RoundsPerGame = 5;
|
||||
|
||||
/// <summary>
|
||||
/// seconds per round
|
||||
/// </summary>
|
||||
private const double SecondsPerRound = 15d;
|
||||
|
||||
/// <summary>
|
||||
/// constructor for the gameplay class
|
||||
/// </summary>
|
||||
|
@ -126,20 +127,17 @@ private void StoreRoundInfo()
|
|||
/// </summary>
|
||||
private void GenerateNewTemplateColour()
|
||||
{
|
||||
var r = new Random();
|
||||
|
||||
// - lightness: 0.4-0.8
|
||||
// - chroma: 0.0-0.2
|
||||
// - lightness: 40-80
|
||||
// - chroma: 0.05-0.20
|
||||
// - hue: all (0-360)
|
||||
|
||||
var colour = Colorimetry.RawLchToColor(
|
||||
Math.Clamp(r.NextDouble() * 0.4d + 0.4d, 0.4d, 0.8d),
|
||||
Math.Clamp(r.NextDouble() * 0.2d, 0d, 0.2d),
|
||||
Math.Clamp(r.NextDouble() * 360d, 0d, 360d)
|
||||
);
|
||||
|
||||
_templateColour.style.backgroundColor = new StyleColor(colour);
|
||||
Debug.Log($"generated new template colour {colour}");
|
||||
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 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}");
|
||||
_templateColour.style.backgroundColor = colour;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -254,17 +254,17 @@ public Score(Dictionary<string, object> data)
|
|||
// try to safely construct the score from a backend-provided dictionary
|
||||
// for each value, if it's not found, or not a valid value, throw an exception
|
||||
|
||||
if (!data.ContainsKey("timestamp") || !(data["timestamp"] is long timestamp))
|
||||
if (!data.ContainsKey("timestamp") || data["timestamp"] is not long timestamp)
|
||||
throw new ArgumentException("timestamp not found or invalid");
|
||||
if (!data.ContainsKey("noOfRounds") || !(data["noOfRounds"] is int noOfRounds))
|
||||
if (!data.ContainsKey("noOfRounds") || data["noOfRounds"] is not int noOfRounds)
|
||||
throw new ArgumentException("noOfRounds not found or invalid");
|
||||
if (!data.ContainsKey("avgLightnessAccuracy") || !(data["avgLightnessAccuracy"] is float avgLightnessAccuracy))
|
||||
if (!data.ContainsKey("avgLightnessAccuracy") || data["avgLightnessAccuracy"] is not float avgLightnessAccuracy)
|
||||
throw new ArgumentException("avgLightnessAccuracy not found or invalid");
|
||||
if (!data.ContainsKey("avgChromaAccuracy") || !(data["avgChromaAccuracy"] is float avgChromaAccuracy))
|
||||
if (!data.ContainsKey("avgChromaAccuracy") || data["avgChromaAccuracy"] is not float avgChromaAccuracy)
|
||||
throw new ArgumentException("avgChromaAccuracy not found or invalid");
|
||||
if (!data.ContainsKey("avgHueAccuracy") || !(data["avgHueAccuracy"] is float avgHueAccuracy))
|
||||
if (!data.ContainsKey("avgHueAccuracy") || data["avgHueAccuracy"] is not float avgHueAccuracy)
|
||||
throw new ArgumentException("avgHueAccuracy not found or invalid");
|
||||
if (!data.ContainsKey("avgPerceivedAccuracy") || !(data["avgPerceivedAccuracy"] is float avgPerceivedAccuracy))
|
||||
if (!data.ContainsKey("avgPerceivedAccuracy") || data["avgPerceivedAccuracy"] is not float avgPerceivedAccuracy)
|
||||
throw new ArgumentException("avgPerceivedAccuracy not found or invalid");
|
||||
|
||||
Timestamp = DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime;
|
||||
|
|
|
@ -145,19 +145,13 @@ private void RenderFromPlayerData(LocalPlayerData data)
|
|||
var playerText = GameManager.Instance.Backend.IsSignedIn
|
||||
? data.LastKnownUsername
|
||||
: $"{data.LastKnownUsername} (Not Signed In)";
|
||||
var rating = data.CalculateUserRating();
|
||||
|
||||
// finally, set the labels
|
||||
_playerText.text = playerText;
|
||||
_lightnessAccuracyText.text = $"{lightnessAcc:F}";
|
||||
_chromaAccuracyText.text = $"{chromaAcc:F}";
|
||||
_hueAccuracyText.text = $"{hueAcc:F}";
|
||||
|
||||
// and set the player rating, but after we get it from the backend
|
||||
// (god I LOVE async (I am LYING out of my teeth))
|
||||
GameManager.Instance.Backend.CalculateUserRating((dtr, rating) =>
|
||||
{
|
||||
if (dtr != Backend.DatabaseTransactionResult.Ok) return;
|
||||
_ratingText.text = $"{rating:F}";
|
||||
});
|
||||
_ratingText.text = $"{rating:F}";
|
||||
}
|
||||
}
|
Reference in a new issue