game: gameplay + database interim

This commit is contained in:
Mark Joshwel 2024-11-18 09:16:03 +08:00
parent b88f6499cc
commit cc79a5b1a0
10 changed files with 789 additions and 115 deletions

View file

@ -143,7 +143,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!114 &133964671
MonoBehaviour:
m_ObjectHideFlags: 0
@ -258,7 +258,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!4 &447905427
Transform:
m_ObjectHideFlags: 0
@ -476,7 +476,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!114 &1204483825
MonoBehaviour:
m_ObjectHideFlags: 0
@ -505,6 +505,37 @@ 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
@ -514,3 +545,4 @@ SceneRoots:
- {fileID: 133964672}
- {fileID: 447905427}
- {fileID: 1204483826}
- {fileID: 1680304395}

View file

@ -18,12 +18,12 @@ public class AccountUI : MonoBehaviour
/// <summary>
/// default text colour
/// </summary>
private readonly Color _defaultInputFieldValueTextColour = new(5.88f, 5.1f, 10.59f);
private readonly StyleColor _defaultInputFieldValueTextColour = new(new Color(0.0588f, 0.051f, 0.1059f));
/// <summary>
/// error text colour
/// </summary>
private readonly Color _errorInputFieldValueTextColour = new(1f, 50.59f, 50.2f);
private readonly StyleColor _errorInputFieldValueTextColour = new Color(1f, 0.5059f, 0.502f);
/// <summary>
/// accompanying text for the account input fields, used when an error/notice is needed

View file

@ -519,6 +519,19 @@ public void GetRecentScores(Action<DatabaseTransactionResult, List<LocalPlayerDa
callback(DatabaseTransactionResult.Error, new List<LocalPlayerData.Score>(0));
}
/// <summary>
/// abstraction function to get the user's best scores from the database
/// </summary>
/// <param name="callback">
/// callback function that takes in a <c>DatabaseTransactionResult</c> enum and a
/// <c>List&lt;LocalPlayerData.Score&gt;</c>
/// </param>
public void GetBestScores(Action<DatabaseTransactionResult, List<LocalPlayerData.Score>> callback)
{
// TODO: implement this
callback(DatabaseTransactionResult.Error, new List<LocalPlayerData.Score>(0));
}
/// <summary>
/// abstraction function to submit a score to the database
/// </summary>
@ -528,7 +541,29 @@ public void GetRecentScores(Action<DatabaseTransactionResult, List<LocalPlayerDa
LocalPlayerData.Score score,
Action<DatabaseTransactionResult> callback)
{
throw new NotImplementedException();
if (!Status.Equals(FirebaseConnectionStatus.Connected)) return;
if (_user == null)
{
callback(DatabaseTransactionResult.Unauthenticated);
return;
}
_db.Child("scores")
.Push()
.SetValueAsync(score.ToDictionary())
.ContinueWithOnMainThread(task =>
{
if (task.IsCompletedSuccessfully)
{
callback(DatabaseTransactionResult.Ok);
}
else
{
Debug.LogError(task.Exception);
callback(DatabaseTransactionResult.Error);
}
});
}
/// <summary>
@ -542,16 +577,42 @@ public void GetRecentScores(Action<DatabaseTransactionResult, List<LocalPlayerDa
public void CalculateUserRating(
Action<DatabaseTransactionResult, float> callback)
{
throw new NotImplementedException();
GetRecentScores((recentRes, recentScores) =>
{
if (recentRes == DatabaseTransactionResult.Error)
{
Debug.Log("failed to get recent scores");
callback(DatabaseTransactionResult.Error, 0f);
return;
}
var recentScoreQueue = GameManager.Instance.Data.RecentOnlineScores;
foreach (var score in recentScores) recentScoreQueue.Enqueue(score);
while (recentScoreQueue.Count > LocalPlayerData.MaxBestOnlineScores) recentScoreQueue.Dequeue();
GetBestScores((bestRes, bestScores) =>
{
if (bestRes == DatabaseTransactionResult.Error)
{
Debug.Log("failed to get recent scores");
callback(DatabaseTransactionResult.Error, 0f);
return;
}
var bestScoreQueue = GameManager.Instance.Data.BestOnlineScores;
foreach (var score in recentScores) bestScoreQueue.Enqueue(score);
while (bestScoreQueue.Count > LocalPlayerData.MaxBestOnlineScores) bestScoreQueue.Dequeue();
callback(DatabaseTransactionResult.Ok, GameManager.Instance.Data.CalculateUserRating());
});
});
}
/// <summary>
/// abstraction function to update the user's rating in the database
/// </summary>
/// <param name="newRating">new user rating value as a float</param>
/// <param name="callback">callback function that takes in a <c>DatabaseTransactionResult</c> enum </param>
public void UpdateUserRating(
float newRating,
Action<DatabaseTransactionResult> callback)
{
throw new NotImplementedException();

View file

@ -3,10 +3,58 @@
public static class Colorimetry
{
/// <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)
{
// rgb to oklab
var templateOklab = linear_srgb_to_oklab(new RGB(
(float)srgb_nonlinear_transform_f_inv(template.r),
(float)srgb_nonlinear_transform_f_inv(template.g),
(float)srgb_nonlinear_transform_f_inv(template.b)));
var responseOklab = linear_srgb_to_oklab(new RGB(
(float)srgb_nonlinear_transform_f_inv(response.r),
(float)srgb_nonlinear_transform_f_inv(response.g),
(float)srgb_nonlinear_transform_f_inv(response.b)));
// https://en.wikipedia.org/wiki/Oklab_color_space#Color_differences
// ... "The perceptual color difference in Oklab is calculated as the Euclidean
// ... distance between the (L, a, b) coordinates."
// https://github.com/svgeesus/svgeesus.github.io/blob/master/Color/OKLab-notes.md#color-difference-metric
// ... ΔL = L1 - L2
// ... C1 = √(a1² + b1²) -> chroma values
// ... C2 = √(a2² + b2²) -> chroma values
// ... ΔC = C1 - C2 -> chroma difference
// ... Δa = a1 - a2
// ... Δb = b1 - b2
// ... ΔH = √(Δa² + Δb² - ΔC²) -> hue difference
// ... ΔE = √(ΔL² + ΔC² + ΔH²) -> final difference
float l1, a1, b1, l2, a2, b2;
(l1, a1, b1) = (templateOklab.L, templateOklab.a, templateOklab.b);
(l2, a2, b2) = (responseOklab.L, responseOklab.a, responseOklab.b);
var deltaL = l1 - l2;
var c1 = Math.Sqrt(a1 * a1 + b1 * b1);
var c2 = Math.Sqrt(a2 * a2 + b2 * b2);
var deltaC = c1 - c2;
var deltaA = a1 - a2;
var deltaB = b1 - b2;
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);
}
/// <summary>
/// convert the oklch colour to a unity rgba colour object
/// </summary>
/// <returns>a unity rgba color object</returns>
/// <returns>a unity rgba <c>Color</c> object</returns>
public static Color RawLchToColor(double lightness, double chroma, double hue)
{
// clamp values
@ -55,7 +103,7 @@ public static double srgb_nonlinear_transform_f_inv(double x)
}
/// <summary>
/// clips a color to the sRGB gamut while preserving chroma
/// clips a colour to the sRGB gamut while preserving chroma
/// </summary>
// https://bottosson.github.io/posts/gamutclipping/ (MIT)
public static RGB gamut_clip_preserve_chroma(RGB rgb)
@ -331,6 +379,48 @@ public static float compute_max_saturation(float a, float b)
return maxSaturation;
}
// ReSharper disable once InconsistentNaming
public struct DeltaLabCHE
{
// ReSharper disable once InconsistentNaming
public double dL;
// ReSharper disable once InconsistentNaming
public double da;
// ReSharper disable once InconsistentNaming
public double db;
// ReSharper disable once InconsistentNaming
public double dC;
// ReSharper disable once InconsistentNaming
public double dH;
// ReSharper disable once InconsistentNaming
public double dE;
public DeltaLabCHE(
// ReSharper disable once InconsistentNaming
double L,
double a,
double b,
// ReSharper disable once InconsistentNaming
double C,
// ReSharper disable once InconsistentNaming
double H,
// ReSharper disable once InconsistentNaming
double E)
{
dL = L;
da = a;
db = b;
dC = C;
dH = H;
dE = E;
}
}
public readonly struct Lab
{
public readonly float L;

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;
@ -34,7 +35,7 @@ public class GameManager : MonoBehaviour
public Backend Backend;
/// <summary>
/// read-only property for accessing the local player data outside of this class
/// read-only property for accessing the local player data outside this class
/// </summary>
public LocalPlayerData Data => _data;
@ -112,11 +113,11 @@ private void OnEnable()
{
Debug.Log($"post-fcStatus change, deciding to show/hide buttons based on new status: {status}");
ui.UI.Q<Button>("LeaderboardButton").style.display =
(status == Backend.FirebaseConnectionStatus.Connected)
status == Backend.FirebaseConnectionStatus.Connected
? DisplayStyle.Flex
: DisplayStyle.None;
ui.UI.Q<Button>("AccountButton").style.display =
(status == Backend.FirebaseConnectionStatus.Connected)
status == Backend.FirebaseConnectionStatus.Connected
? DisplayStyle.Flex
: DisplayStyle.None;
});
@ -158,4 +159,120 @@ private void FireLocalPlayerDataChangeCallbacks(LocalPlayerData data)
Debug.LogError($"error invoking LocalPlayerDataChangeCallback: {e.Message}");
}
}
public void SignalGameEnd(List<Gameplay.RoundInfo> playedRounds)
{
Debug.Log("signalling game end");
// calculate historical averages
var historicalLightnessAcc = 0f;
var historicalChromaAcc = 0f;
var historicalHueAcc = 0f;
foreach (var localScore in _data.RecentLocalScores)
{
historicalLightnessAcc += localScore.AvgLightnessAccuracy;
historicalChromaAcc += localScore.AvgChromaAccuracy;
historicalHueAcc += localScore.AvgHueAccuracy;
}
foreach (var onlineScore in _data.RecentOnlineScores)
{
historicalLightnessAcc += onlineScore.AvgLightnessAccuracy;
historicalChromaAcc += onlineScore.AvgChromaAccuracy;
historicalHueAcc += onlineScore.AvgHueAccuracy;
}
foreach (var onlineScore in _data.BestOnlineScores)
{
historicalLightnessAcc += onlineScore.AvgLightnessAccuracy;
historicalChromaAcc += onlineScore.AvgChromaAccuracy;
historicalHueAcc += onlineScore.AvgHueAccuracy;
}
// calculate round averages
var roundLightnessAcc = 0d;
var roundChromaAcc = 0d;
var roundHueAcc = 0d;
var maxDistance = Colorimetry.CalculateDistance(Color.black, Color.white);
foreach (var distance in playedRounds.Select(round =>
Colorimetry.CalculateDistance(round.TemplateColour, round.ResponseColour)))
{
roundLightnessAcc += distance.dL / maxDistance.dL;
roundChromaAcc += distance.dC / maxDistance.dC;
roundHueAcc += distance.dH / maxDistance.dH;
}
roundLightnessAcc /= playedRounds.Count;
roundChromaAcc /= playedRounds.Count;
roundHueAcc /= playedRounds.Count;
var roundAcc = (roundLightnessAcc + roundChromaAcc + roundHueAcc) / 3;
// make comparison texts
var lAccDeltaText = (roundLightnessAcc > historicalLightnessAcc ? "+" : "-") +
Math.Abs(roundLightnessAcc - historicalLightnessAcc).ToString("P");
var cAccDeltaText = (roundChromaAcc > historicalChromaAcc ? "+" : "-") +
Math.Abs(roundChromaAcc - historicalChromaAcc).ToString("P");
var hAccDeltaText = (roundHueAcc > historicalHueAcc ? "+" : "-") +
Math.Abs(roundHueAcc - historicalHueAcc).ToString("P");
var score = new LocalPlayerData.Score(DateTime.Now,
playedRounds.Count,
(float)roundLightnessAcc,
(float)roundChromaAcc,
(float)roundHueAcc);
_data.RegisterLocalScore(score);
Backend.SubmitScore(score,
submitRes =>
{
if (submitRes != Backend.DatabaseTransactionResult.Ok)
{
Debug.Log("couldn't submit score");
TransitionToResultsView(_data.CalculateUserRating());
return;
}
Backend.CalculateUserRating((urcRes, userRating) =>
{
if (urcRes != Backend.DatabaseTransactionResult.Ok)
{
Debug.Log("couldn't calculate user rating");
TransitionToResultsView(_data.CalculateUserRating());
return;
}
Backend.UpdateUserRating(updateRes =>
{
if (updateRes != Backend.DatabaseTransactionResult.Ok)
{
Debug.Log("calculated user rating but couldn't update it");
TransitionToResultsView(userRating);
return;
}
TransitionToResultsView(userRating);
});
});
});
return;
void TransitionToResultsView(float rating)
{
var ratingText = rating >= 0 ? $"\nYour rating is {rating}" : "\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;
ui.SetDisplayState(UIManager.DisplayState.ResultsView);
}
}
}

View file

@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using Random = System.Random;
/// <summary>
/// gameplay behaviour class
/// </summary>
public class Gameplay
{
/// <summary>
/// countdown text label for showing the countdown
/// </summary>
private readonly Label _countdownText;
/// <summary>
/// list of played rounds
/// </summary>
private readonly List<RoundInfo> _playedRounds = new(5);
/// <summary>
/// response colour for the player to match
/// </summary>
private readonly VisualElement _responseColour;
/// <summary>
/// round text label for showing the current round
/// </summary>
private readonly Label _roundText;
/// <summary>
/// template colour for the player to match
/// </summary>
private readonly VisualElement _templateColour;
/// <summary>
/// game countdown timer
/// </summary>
private DateTime _countdownDatetime;
/// <summary>
/// game round counter
/// </summary>
private int _round = -1;
public int RoundsPerGame = 5;
public double SecondsPerRound = 15d;
/// <summary>
/// constructor for the gameplay class
/// </summary>
/// <param name="ui">the visual element object for the ui document with the GameView</param>
private Gameplay(VisualElement ui)
{
_roundText = ui.Q<Label>("RoundText");
_countdownText = ui.Q<Label>("TimeText");
_templateColour = ui.Q<VisualElement>("TemplateColour");
_responseColour = ui.Q<VisualElement>("ResponseColour");
}
/// <summary>
/// function for starting the game
/// </summary>
private void StartGame()
{
_round = 0;
AdvanceToNextRound();
}
/// <summary>
/// called once per frame
/// </summary>
private void Update()
{
if (_round < 1) return;
if (_countdownDatetime < DateTime.Now) AdvanceToNextRound();
Render();
}
/// <summary>
/// function for advancing to the next round
/// </summary>
private void AdvanceToNextRound()
{
if (_round > 0) StoreRoundInfo();
GenerateNewTemplateColour();
if (_round < RoundsPerGame)
{
_round++;
_countdownDatetime = DateTime.Now.AddSeconds(SecondsPerRound);
}
else
{
// end game
_round = -1;
GameManager.Instance.SignalGameEnd(_playedRounds);
}
}
/// <summary>
/// function for storing the round information
/// </summary>
private void StoreRoundInfo()
{
_playedRounds.Add(new RoundInfo
{
TemplateColour = _templateColour.style.backgroundColor.value,
ResponseColour = _responseColour.style.backgroundColor.value
});
}
/// <summary>
/// function for generating a new template colour
/// (a random colour that isn't hard to visually match)
/// </summary>
private void GenerateNewTemplateColour()
{
var r = new Random();
// - lightness: 0.4-0.8
// - chroma: 0.0-0.2
// - 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);
}
/// <summary>
/// function for rendering the game state
/// </summary>
private void Render()
{
var remaining = (_countdownDatetime - DateTime.Now).TotalSeconds;
_roundText.text = $"{_round}/{RoundsPerGame}";
_countdownText.text = $"{remaining:F}";
}
/// <summary>
/// struct for storing round information
/// </summary>
public struct RoundInfo
{
public Color TemplateColour;
public Color ResponseColour;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ac045ae5da8a0794496e2eb879e3b175
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -5,11 +5,21 @@
public class LocalPlayerData
{
/// <summary>
/// maximum number of the best online scores to keep track of
/// </summary>
public const int MaxBestOnlineScores = 10;
/// <summary>
/// maximum number of recent local scores to keep track of
/// </summary>
public const int MaxRecentLocalScores = 10;
/// <summary>
/// queue of the best online scores,
/// used in user rating calculation and accuracy display stats
/// </summary>
public Queue<Score> BestOnlineScores = new(30);
public Queue<Score> BestOnlineScores = new(20);
/// <summary>
/// last known email used
@ -77,7 +87,8 @@ public void LoadFromTheWorld(Action<LocalPlayerData> callback)
// if any of the values are invalid, don't add the score
if (noOfRounds < 0 || l < 0 || c < 0 || h < 0) continue;
RegisterLocalScore(new Score(timestamp, noOfRounds, l, c, h));
RegisterLocalScore(new Score(timestamp, Math.Max(1, noOfRounds), Math.Clamp(l, 0f, 100f),
Math.Clamp(c, 0f, 100f), Math.Clamp(h, 0f, 100f)));
}
// load online scores
@ -92,6 +103,7 @@ public void LoadFromTheWorld(Action<LocalPlayerData> callback)
Debug.Log(
$"loaded lpdata from the world ({LastKnownUsername} <{LastKnownEmail}> with RLS.Count={RecentLocalScores.Count}, ROS.Count={RecentOnlineScores.Count}");
callback(this);
});
}
@ -125,10 +137,59 @@ public void SaveToTheWorld()
/// <param name="score">the score to register</param>
public void RegisterLocalScore(Score score)
{
if (RecentLocalScores.Count > 10) RecentLocalScores.Dequeue();
while (RecentLocalScores.Count >= MaxRecentLocalScores) RecentLocalScores.Dequeue();
RecentLocalScores.Enqueue(score);
}
/// <summary>
/// calculates the user rating based on whatever local data is available
/// </summary>
/// <returns>the user rating (0-100f)</returns>
public float CalculateUserRating()
{
// user rating is like CHUNITHM's rating system
// where best + recent scores are averaged out
// 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();
// if online scores are available, use them
var recentScores = RecentOnlineScores.Count > 0 ? RecentOnlineScores : RecentLocalScores;
var bestScores = BestOnlineScores;
var scores = 0;
var totalRating = 0d;
foreach (var score in recentScores)
{
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) / 4d;
}
foreach (var score in bestScores)
{
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) / 4d;
}
return Math.Clamp((float)(totalRating / scores), 0f, 100f);
}
public struct Score
{
/// <summary>
@ -139,22 +200,22 @@ public struct Score
/// <summary>
/// number of rounds played (0-100)
/// </summary>
public int NoOfRounds;
public readonly int NoOfRounds;
/// <summary>
/// average lightness accuracy across all rounds (0-100)
/// </summary>
public float AvgLightnessAccuracy;
public readonly float AvgLightnessAccuracy;
/// <summary>
/// average chroma accuracy across all rounds (0-100)
/// </summary>
public float AvgChromaAccuracy;
public readonly float AvgChromaAccuracy;
/// <summary>
/// average hue accuracy across all rounds (0-100)
/// </summary>
public float AvgHueAccuracy;
public readonly float AvgHueAccuracy;
/// <summary>
/// constructor for the score struct
@ -173,5 +234,42 @@ public struct Score
AvgChromaAccuracy = c;
AvgHueAccuracy = h;
}
/// <summary>
/// dict-based constructor for the score struct
/// </summary>
/// <param name="data">dictionary of the score data</param>
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, set it to a default value
Timestamp = data.ContainsKey("timestamp") && data["timestamp"] is long t
? DateTimeOffset.FromUnixTimeSeconds(t).DateTime
: DateTime.MinValue;
NoOfRounds = data.ContainsKey("noOfRounds") && data["noOfRounds"] is int n ? n : 1;
AvgLightnessAccuracy = data.ContainsKey("avgLightnessAccuracy") && data["avgLightnessAccuracy"] is float l
? l
: 100.0f;
AvgChromaAccuracy = data.ContainsKey("avgChromaAccuracy") && data["avgChromaAccuracy"] is float c
? c
: 100.0f;
AvgHueAccuracy = data.ContainsKey("avgHueAccuracy") && data["avgHueAccuracy"] is float h ? h : 100.0f;
}
/// <summary>
/// converts the score struct to a dictionary safe for the backend
/// </summary>
/// <returns></returns>
public Dictionary<string, object> ToDictionary()
{
return new Dictionary<string, object>
{
{ "timestamp", new DateTimeOffset(Timestamp).ToUnixTimeSeconds() },
{ "noOfRounds", NoOfRounds },
{ "avgLightnessAccuracy", AvgLightnessAccuracy },
{ "avgChromaAccuracy", AvgChromaAccuracy },
{ "avgHueAccuracy", AvgHueAccuracy }
};
}
}
}

View file

@ -58,14 +58,6 @@ private void Awake()
}
}
/// <summary>
/// initialise variables and register callbacks
/// </summary>
private void OnEnable()
{
UI = GetComponent<UIDocument>().rootVisualElement;
}
/// <summary>
/// modify state of initial variables
/// </summary>
@ -74,6 +66,14 @@ private void Start()
SetDisplayState(DisplayState.DefaultView);
}
/// <summary>
/// initialise variables and register callbacks
/// </summary>
private void OnEnable()
{
UI = GetComponent<UIDocument>().rootVisualElement;
}
/// <summary>
/// function to show a menu based on the enum passed,
/// and any other necessary actions
@ -140,11 +140,11 @@ public void SetDisplayState(DisplayState newDisplayState)
// if we're not in the game view, show everything!
UI.Q<Button>("PlayButton").style.display = DisplayStyle.Flex;
UI.Q<Button>("LeaderboardButton").style.display =
(GameManager.Instance.Backend.Status == Backend.FirebaseConnectionStatus.Connected)
GameManager.Instance.Backend.Status == Backend.FirebaseConnectionStatus.Connected
? DisplayStyle.Flex
: DisplayStyle.None;
UI.Q<Button>("AccountButton").style.display =
(GameManager.Instance.Backend.Status == Backend.FirebaseConnectionStatus.Connected)
GameManager.Instance.Backend.Status == Backend.FirebaseConnectionStatus.Connected
? DisplayStyle.Flex
: DisplayStyle.None;
UI.Q<VisualElement>("AccountSection").style.display = DisplayStyle.Flex;

View file

@ -1,130 +1,242 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<Style src="project://database/Assets/UI/GameUI.uss?fileID=7433441132597879392&amp;guid=2c7ff79f21a3e8e408e76d75944d575b&amp;type=3#GameUI" />
<ui:VisualElement name="Root" style="flex-grow: 1; background-color: rgb(208, 152, 194); justify-content: space-around; align-items: stretch; align-self: stretch; flex-direction: row;">
<ui:VisualElement name="SideView" style="flex-grow: 0; background-color: rgb(15, 13, 27); flex-shrink: 0; width: 25%; justify-content: space-between;">
<ui:VisualElement name="Header" style="flex-grow: 0; margin-top: 10%; margin-right: 10%; margin-bottom: 0; margin-left: 10%; justify-content: space-between; align-items: flex-start; flex-direction: column;">
<ui:Label tabindex="-1" text="Colour Me OK" parse-escape-sequences="true" display-tooltip-when-elided="true" name="Name" style="color: rgb(208, 152, 194); -unity-font-style: bold; font-size: 58px; white-space: normal;" />
<ui:Label tabindex="-1" text="Color Me OK is a colour-matching game using the coordinates of the OKLCh colour model on the OKLab perceptually uniform colour space." parse-escape-sequences="true" display-tooltip-when-elided="true" name="Description" style="font-size: 16px; white-space: normal; text-overflow: clip; color: rgb(208, 152, 194);" />
<ui:UXML xmlns:ui="UnityEngine.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance"
engine="UnityEngine.UIElements" editor="UnityEditor.UIElements"
noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<Style src="project://database/Assets/UI/GameUI.uss?fileID=7433441132597879392&amp;guid=2c7ff79f21a3e8e408e76d75944d575b&amp;type=3#GameUI"/>
<ui:VisualElement name="Root"
style="flex-grow: 1; background-color: rgb(208, 152, 194); justify-content: space-around; align-items: stretch; align-self: stretch; flex-direction: row;">
<ui:VisualElement name="SideView"
style="flex-grow: 0; background-color: rgb(15, 13, 27); flex-shrink: 0; width: 25%; justify-content: space-between;">
<ui:VisualElement name="Header"
style="flex-grow: 0; margin-top: 10%; margin-right: 10%; margin-bottom: 0; margin-left: 10%; justify-content: space-between; align-items: flex-start; flex-direction: column;">
<ui:Label tabindex="-1" text="Colour Me OK" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="Name"
style="color: rgb(208, 152, 194); -unity-font-style: bold; font-size: 58px; white-space: normal;"/>
<ui:Label tabindex="-1"
text="Color Me OK is a colour-matching game using the coordinates of the OKLCh colour model on the OKLab perceptually uniform colour space."
parse-escape-sequences="true" display-tooltip-when-elided="true" name="Description"
style="font-size: 16px; white-space: normal; text-overflow: clip; color: rgb(208, 152, 194);"/>
</ui:VisualElement>
<ui:VisualElement name="Content" style="flex-grow: 0; padding-right: 10%; padding-bottom: 10%; padding-left: 10%;">
<ui:Button text="Play ↗" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PlayButton" style="display: none;" />
<ui:Button text="Leaderboard ↗" parse-escape-sequences="true" display-tooltip-when-elided="true" name="LeaderboardButton" style="display: none;" />
<ui:Button text="Account ↗" parse-escape-sequences="true" display-tooltip-when-elided="true" name="AccountButton" style="display: none;" />
<ui:VisualElement name="AccountSection" style="flex-grow: 0; border-top-color: rgb(208, 152, 194); margin-top: 0; border-top-width: 1px; margin-right: 0; margin-bottom: 0; margin-left: 0; border-bottom-color: rgb(208, 152, 194); padding-bottom: 12px; border-bottom-width: 1px; display: flex;">
<ui:VisualElement name="PlayerDetails" style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch;">
<ui:VisualElement name="Content"
style="flex-grow: 0; padding-right: 10%; padding-bottom: 10%; padding-left: 10%;">
<ui:Button text="Play ↗" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="PlayButton" style="display: none;"/>
<ui:Button text="Leaderboard ↗" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="LeaderboardButton" style="display: none;"/>
<ui:Button text="Account ↗" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="AccountButton" style="display: none;"/>
<ui:VisualElement name="AccountSection"
style="flex-grow: 0; border-top-color: rgb(208, 152, 194); margin-top: 0; border-top-width: 1px; margin-right: 0; margin-bottom: 0; margin-left: 0; border-bottom-color: rgb(208, 152, 194); padding-bottom: 12px; border-bottom-width: 1px; display: flex;">
<ui:VisualElement name="PlayerDetails"
style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch;">
<ui:VisualElement name="PlayerNameDetail" style="flex-grow: 1;">
<ui:Label tabindex="-1" text="Player" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PlayerHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left;" />
<ui:Label tabindex="-1" text="Not Signed In" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PlayerText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px;" />
<ui:Label tabindex="-1" text="Player" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="PlayerHeader"
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left;"/>
<ui:Label tabindex="-1" text="Not Signed In" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="PlayerText"
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px;"/>
</ui:VisualElement>
<ui:VisualElement name="PlayerRatingDetail" style="flex-grow: 0;">
<ui:Label tabindex="-1" text="Rating" parse-escape-sequences="true" display-tooltip-when-elided="true" name="RatingHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-right;" />
<ui:Label tabindex="-1" text="00.00%" parse-escape-sequences="true" display-tooltip-when-elided="true" name="RatingText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-right;" />
<ui:Label tabindex="-1" text="Rating" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="RatingHeader"
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-right;"/>
<ui:Label tabindex="-1" text="00.00%" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="RatingText"
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-right;"/>
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="AccuracyDetails" style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch; padding-top: 4px;">
<ui:VisualElement name="AccuracyDetails"
style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch; padding-top: 4px;">
<ui:VisualElement name="LightnessAccuracyDetail" style="flex-grow: 0;">
<ui:Label tabindex="-1" text="Lightness&#10;Accuracy" parse-escape-sequences="true" display-tooltip-when-elided="true" name="LightnessAccuracyHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left; padding-top: 0;" />
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true" display-tooltip-when-elided="true" name="LightnessAccuracyText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; padding-bottom: 0;" />
<ui:Label tabindex="-1" text="Lightness&#10;Accuracy" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="LightnessAccuracyHeader"
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left; padding-top: 0;"/>
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="LightnessAccuracyText"
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; padding-bottom: 0;"/>
</ui:VisualElement>
<ui:VisualElement name="ChromaAccuracyDetail" style="flex-grow: 0;">
<ui:Label tabindex="-1" text="Chroma&#10;Accuracy" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ChromaAccuracyHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-center; padding-top: 0;" />
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ChromaAccuracyText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-center; padding-bottom: 0;" />
<ui:Label tabindex="-1" text="Chroma&#10;Accuracy" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ChromaAccuracyHeader"
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-center; padding-top: 0;"/>
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ChromaAccuracyText"
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-center; padding-bottom: 0;"/>
</ui:VisualElement>
<ui:VisualElement name="HueAccuracyDetail" style="flex-grow: 0;">
<ui:Label tabindex="-1" text="Hue&#10;Accuracy" parse-escape-sequences="true" display-tooltip-when-elided="true" name="HueAccuracyHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-right; padding-top: 0;" />
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true" display-tooltip-when-elided="true" name="HueAccuracyText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-right; padding-bottom: 0;" />
<ui:Label tabindex="-1" text="Hue&#10;Accuracy" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="HueAccuracyHeader"
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-right; padding-top: 0;"/>
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="HueAccuracyText"
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-right; padding-bottom: 0;"/>
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="ConnectionStatus" style="flex-grow: 0; border-top-color: rgb(208, 152, 194); margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; border-bottom-color: rgb(208, 152, 194); padding-bottom: 12px; display: flex; border-bottom-width: 1px;">
<ui:Label tabindex="-1" text="Status: Unknown" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ConnectionStatusText" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left; display: flex;" />
<ui:VisualElement name="ConnectionStatus"
style="flex-grow: 0; border-top-color: rgb(208, 152, 194); margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; border-bottom-color: rgb(208, 152, 194); padding-bottom: 12px; display: flex; border-bottom-width: 1px;">
<ui:Label tabindex="-1" text="Status: Unknown" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ConnectionStatusText"
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left; display: flex;"/>
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="MainView" style="flex-grow: 0; flex-shrink: 0; width: 75%; justify-content: space-between;">
<ui:VisualElement name="DefaultView" style="flex-grow: 0; justify-content: space-around; height: 100%; align-self: stretch; display: none;">
<ui:VisualElement name="DemoResponseColour" style="flex-grow: 1; background-color: rgb(0, 0, 0); align-self: stretch; justify-content: center;">
<ui:VisualElement name="DemoResponseSliderContainer" style="flex-grow: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; padding-top: 2%; padding-right: 2%; padding-bottom: 2%; padding-left: 2%; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(208, 152, 194);">
<ui:Slider label="Lightness" high-value="100" name="DemoResponseLightnessSlider" class="lch-slider" />
<ui:Slider label="Chroma" high-value="0.5" name="DemoResponseChromaSlider" class="lch-slider" />
<ui:Slider label="Hue" high-value="360" name="DemoResponseHueSlider" class="lch-slider" />
<ui:VisualElement name="MainView"
style="flex-grow: 0; flex-shrink: 0; width: 75%; justify-content: space-between;">
<ui:VisualElement name="DefaultView"
style="flex-grow: 0; justify-content: space-around; height: 100%; align-self: stretch; display: none;">
<ui:VisualElement name="DemoResponseColour"
style="flex-grow: 1; background-color: rgb(0, 0, 0); align-self: stretch; justify-content: center;">
<ui:VisualElement name="DemoResponseSliderContainer"
style="flex-grow: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; padding-top: 2%; padding-right: 2%; padding-bottom: 2%; padding-left: 2%; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(208, 152, 194);">
<ui:Slider label="Lightness" high-value="100" name="DemoResponseLightnessSlider"
class="lch-slider"/>
<ui:Slider label="Chroma" high-value="0.5" name="DemoResponseChromaSlider" class="lch-slider"/>
<ui:Slider label="Hue" high-value="360" name="DemoResponseHueSlider" class="lch-slider"/>
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="GameView" style="flex-grow: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; justify-content: space-between; height: 100%; align-self: stretch; display: none;">
<ui:VisualElement name="GameHeader" style="flex-grow: 0; flex-direction: row; justify-content: space-between; align-self: stretch;">
<ui:Label tabindex="-1" text="1/5" parse-escape-sequences="true" display-tooltip-when-elided="true" name="RoundText" style="font-size: 58px; -unity-font-style: normal;" />
<ui:Label tabindex="-1" text="0.00s" parse-escape-sequences="true" display-tooltip-when-elided="true" name="TimeText" style="-unity-text-align: lower-right; font-size: 58px;" />
<ui:VisualElement name="GameView"
style="flex-grow: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; justify-content: space-between; height: 100%; align-self: stretch; display: none;">
<ui:VisualElement name="GameHeader"
style="flex-grow: 0; flex-direction: row; justify-content: space-between; align-self: stretch;">
<ui:Label tabindex="-1" text="1/5" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="RoundText" style="font-size: 58px; -unity-font-style: normal;"/>
<ui:Label tabindex="-1" text="0.00s" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="TimeText"
style="-unity-text-align: lower-right; font-size: 58px;"/>
</ui:VisualElement>
<ui:VisualElement name="ColourPreview" style="flex-grow: 0; height: 50%; background-color: rgb(255, 255, 255); border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; flex-direction: row; padding-top: 2%; padding-right: 2%; padding-bottom: 2%; padding-left: 2%; justify-content: space-between;">
<ui:VisualElement name="TemplatePreview" style="flex-grow: 0; flex-shrink: 0; height: 100%; width: 49%;">
<ui:Label tabindex="-1" text="Template" parse-escape-sequences="true" display-tooltip-when-elided="true" name="TemplateText" style="margin-bottom: 12px; margin-top: 0; -unity-font-style: normal;" />
<ui:VisualElement name="TemplateColour" style="flex-grow: 1; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(0, 0, 0);" />
<ui:VisualElement name="ColourPreview"
style="flex-grow: 0; height: 50%; background-color: rgb(255, 255, 255); border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; flex-direction: row; padding-top: 2%; padding-right: 2%; padding-bottom: 2%; padding-left: 2%; justify-content: space-between;">
<ui:VisualElement name="TemplatePreview"
style="flex-grow: 0; flex-shrink: 0; height: 100%; width: 49%;">
<ui:Label tabindex="-1" text="Template" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="TemplateText"
style="margin-bottom: 12px; margin-top: 0; -unity-font-style: normal;"/>
<ui:VisualElement name="TemplateColour"
style="flex-grow: 1; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(0, 0, 0);"/>
</ui:VisualElement>
<ui:VisualElement name="ResponsePreview" style="flex-grow: 0; flex-shrink: 0; height: 100%; width: 49%;">
<ui:Label tabindex="-1" text="Response" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ResponseText" style="margin-bottom: 12px; margin-top: 0; -unity-font-style: normal; -unity-text-align: upper-right;" />
<ui:VisualElement name="ResponseColour" style="flex-grow: 1; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(0, 0, 0);" />
<ui:VisualElement name="ResponsePreview"
style="flex-grow: 0; flex-shrink: 0; height: 100%; width: 49%;">
<ui:Label tabindex="-1" text="Response" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ResponseText"
style="margin-bottom: 12px; margin-top: 0; -unity-font-style: normal; -unity-text-align: upper-right;"/>
<ui:VisualElement name="ResponseColour"
style="flex-grow: 1; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(0, 0, 0);"/>
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="ResponseSliders" style="flex-grow: 0; display: flex;">
<ui:Slider label="Lightness" high-value="100" name="ResponseLightnessSlider" class="lch-slider" />
<ui:Slider label="Chroma" high-value="0.5" name="ResponseChromaSlider" class="lch-slider" />
<ui:Slider label="Hue" high-value="360" name="ResponseHueSlider" class="lch-slider" />
<ui:Slider label="Lightness" high-value="100" name="ResponseLightnessSlider" class="lch-slider"/>
<ui:Slider label="Chroma" high-value="0.5" name="ResponseChromaSlider" class="lch-slider"/>
<ui:Slider label="Hue" high-value="360" name="ResponseHueSlider" class="lch-slider"/>
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="ResultsView" style="flex-grow: 1; display: flex; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; justify-content: space-between;">
<ui:VisualElement name="ColourShowcase" style="flex-grow: 1; background-color: rgb(255, 255, 255); border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; padding-top: 2%; padding-right: 2%; padding-bottom: 2%; padding-left: 2%; margin-bottom: 2%; margin-top: 0; margin-right: 0; margin-left: 0;">
<ui:Label tabindex="-1" text="Templates" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ShowcaseTemplatesText" style="-unity-text-align: upper-center; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 2%; padding-left: 0;" />
<ui:VisualElement name="ResultsView"
style="flex-grow: 1; display: flex; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; justify-content: space-between;">
<ui:VisualElement name="ColourShowcase"
style="flex-grow: 1; background-color: rgb(255, 255, 255); border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; padding-top: 2%; padding-right: 2%; padding-bottom: 2%; padding-left: 2%; margin-bottom: 2%; margin-top: 0; margin-right: 0; margin-left: 0;">
<ui:Label tabindex="-1" text="Templates" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ShowcaseTemplatesText"
style="-unity-text-align: upper-center; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 2%; padding-left: 0;"/>
<ui:VisualElement name="Gallery" style="flex-grow: 1; flex-direction: row;">
<ui:VisualElement name="ShowcasePair1" style="flex-grow: 1;">
<ui:VisualElement name="ShowcasePair1TemplateColour" style="flex-grow: 1; background-color: rgb(15, 13, 27); border-top-left-radius: 8px;" />
<ui:VisualElement name="ShowcasePair1ResponseColour" style="flex-grow: 1; border-bottom-left-radius: 8px; border-left-color: rgb(26, 22, 40); border-right-color: rgb(26, 22, 40); border-top-color: rgb(26, 22, 40); border-bottom-color: rgb(26, 22, 40); background-color: rgb(26, 22, 40);" />
<ui:VisualElement name="ShowcasePair1TemplateColour"
style="flex-grow: 1; background-color: rgb(15, 13, 27); border-top-left-radius: 8px; border-bottom-left-radius: 8px;"/>
<ui:Label tabindex="-1" text="100% 100% 100% (100%)" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ShowcasePair1Info"
style="-unity-text-align: upper-center; font-size: 18px;"/>
<ui:VisualElement name="ShowcasePair1ResponseColour"
style="flex-grow: 1; border-bottom-left-radius: 8px; border-left-color: rgb(26, 22, 40); border-right-color: rgb(26, 22, 40); border-top-color: rgb(26, 22, 40); border-bottom-color: rgb(26, 22, 40); background-color: rgb(26, 22, 40); border-top-left-radius: 8px;"/>
</ui:VisualElement>
<ui:VisualElement name="ShowcasePair2" style="flex-grow: 1;">
<ui:VisualElement name="ShowcasePair2TemplateColour" style="flex-grow: 1; background-color: rgb(42, 33, 56);" />
<ui:VisualElement name="ShowcasePair2ResponseColour" style="flex-grow: 1; background-color: rgb(63, 48, 76);" />
<ui:VisualElement name="ShowcasePair2TemplateColour"
style="flex-grow: 1; background-color: rgb(42, 33, 56);"/>
<ui:Label tabindex="-1" text="100% 100% 100% (100%)" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ShowcasePair2Info"
style="-unity-text-align: upper-center; font-size: 18px;"/>
<ui:VisualElement name="ShowcasePair2ResponseColour"
style="flex-grow: 1; background-color: rgb(63, 48, 76);"/>
</ui:VisualElement>
<ui:VisualElement name="ShowcasePair3" style="flex-grow: 1;">
<ui:VisualElement name="ShowcasePair3TemplateColour" style="flex-grow: 1; background-color: rgb(63, 48, 76);" />
<ui:VisualElement name="ShowcasePair3ResponseColour" style="flex-grow: 1; background-color: rgb(89, 67, 100);" />
<ui:VisualElement name="ShowcasePair3TemplateColour"
style="flex-grow: 1; background-color: rgb(63, 48, 76);"/>
<ui:Label tabindex="-1" text="100% 100% 100% (100%)" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ShowcasePair3Info"
style="-unity-text-align: upper-center; font-size: 18px;"/>
<ui:VisualElement name="ShowcasePair3ResponseColour"
style="flex-grow: 1; background-color: rgb(89, 67, 100);"/>
</ui:VisualElement>
<ui:VisualElement name="ShowcasePair4" style="flex-grow: 1;">
<ui:VisualElement name="ShowcasePair4TemplateColour" style="flex-grow: 1; background-color: rgb(89, 67, 100);" />
<ui:VisualElement name="ShowcasePair4ResponseColour" style="flex-grow: 1; background-color: rgb(122, 90, 126);" />
<ui:VisualElement name="ShowcasePair4TemplateColour"
style="flex-grow: 1; background-color: rgb(89, 67, 100);"/>
<ui:Label tabindex="-1" text="100% 100% 100% (100%)" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ShowcasePair4Info"
style="-unity-text-align: upper-center; font-size: 18px;"/>
<ui:VisualElement name="ShowcasePair4ResponseColour"
style="flex-grow: 1; background-color: rgb(122, 90, 126);"/>
</ui:VisualElement>
<ui:VisualElement name="ShowcasePair5" style="flex-grow: 1;">
<ui:VisualElement name="ShowcasePair5TemplateColour" style="flex-grow: 1; background-color: rgb(162, 118, 158); border-top-right-radius: 8px;" />
<ui:VisualElement name="ShowcasePair5ResponseColour" style="flex-grow: 1; background-color: rgb(208, 152, 194); border-bottom-right-radius: 8px;" />
<ui:VisualElement name="ShowcasePair5TemplateColour"
style="flex-grow: 1; background-color: rgb(162, 118, 158); border-top-right-radius: 8px; border-bottom-right-radius: 8px;"/>
<ui:Label tabindex="-1" text="100% 100% 100% (100%)" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ShowcasePair5Info"
style="-unity-text-align: upper-center; font-size: 18px;"/>
<ui:VisualElement name="ShowcasePair5ResponseColour"
style="flex-grow: 1; background-color: rgb(208, 152, 194); border-top-right-radius: 8px; border-bottom-right-radius: 8px;"/>
</ui:VisualElement>
</ui:VisualElement>
<ui:Label tabindex="-1" text="Responses" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ShowcaseResponsesText" style="-unity-text-align: upper-center; padding-top: 1.5%; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0;" />
<ui:Label tabindex="-1" text="Responses" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ShowcaseResponsesText"
style="-unity-text-align: upper-center; padding-top: 1.5%; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0;"/>
</ui:VisualElement>
<ui:Label tabindex="-1" text="Over n rounds,&#10;you were 100.00% accurate.&#10;&#10;Lightness was 100.00% accurate. (+100.00% from your average)&#10;Chroma was 100.00% accurate. (+100.00% from your average)&#10;Hue was 100.00% accurate. (+100.00% from your average)&#10;&#10;Your RATING has increased by 100.00%." parse-escape-sequences="true" display-tooltip-when-elided="true" name="ResultsText" style="flex-grow: 0; font-size: 32px; margin-top: 2%; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0;" />
<ui:Label tabindex="-1"
text="Over n rounds,&#10;you were 100.00% accurate.&#10;&#10;Lightness was 100.00% accurate. (+100.00% from your average)&#10;Chroma was 100.00% accurate. (+100.00% from your average)&#10;Hue was 100.00% accurate. (+100.00% from your average)&#10;&#10;Your RATING has increased by 100.00%."
parse-escape-sequences="true" display-tooltip-when-elided="true" name="ResultsText"
style="flex-grow: 0; margin-top: 2%; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0;"/>
</ui:VisualElement>
<ui:VisualElement name="LeaderboardView" style="flex-grow: 1; display: none; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;">
<ui:Label tabindex="-1" text="Leaderboard" parse-escape-sequences="true" display-tooltip-when-elided="true" name="LeaderboardHeader" style="font-size: 58px; -unity-font-style: normal;" />
<ui:ListView name="LeaderboardListView" />
<ui:VisualElement name="LeaderboardView"
style="flex-grow: 1; display: none; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;">
<ui:Label tabindex="-1" text="Leaderboard" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="LeaderboardHeader"
style="font-size: 58px; -unity-font-style: normal;"/>
<ui:ListView name="LeaderboardListView"/>
</ui:VisualElement>
<ui:VisualElement name="AccountView" style="flex-grow: 1; display: none; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;">
<ui:Label tabindex="-1" text="You are not signed in." parse-escape-sequences="true" display-tooltip-when-elided="true" name="AccountHeader" style="font-size: 58px; -unity-font-style: normal;" />
<ui:VisualElement name="AccountView"
style="flex-grow: 1; display: none; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;">
<ui:Label tabindex="-1" text="You are not signed in." parse-escape-sequences="true"
display-tooltip-when-elided="true" name="AccountHeader"
style="font-size: 58px; -unity-font-style: normal;"/>
<ui:VisualElement name="AccountFields" style="flex-grow: 0;">
<ui:VisualElement name="UsernameContainer" style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
<ui:TextField picking-mode="Ignore" label="Username" name="UsernameField" />
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true" name="UsernameUpdateButton" />
<ui:VisualElement name="UsernameContainer"
style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
<ui:TextField picking-mode="Ignore" label="Username" name="UsernameField"/>
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="UsernameUpdateButton"/>
</ui:VisualElement>
<ui:VisualElement name="EmailContainer" style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
<ui:TextField picking-mode="Ignore" label="Email" name="EmailField" keyboard-type="EmailAddress" />
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true" name="EmailUpdateButton" />
<ui:VisualElement name="EmailContainer"
style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
<ui:TextField picking-mode="Ignore" label="Email" name="EmailField"
keyboard-type="EmailAddress"/>
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="EmailUpdateButton"/>
</ui:VisualElement>
<ui:VisualElement name="PasswordContainer" style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
<ui:TextField picking-mode="Ignore" label="Password" name="PasswordField" password="true" />
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PasswordUpdateButton" />
<ui:VisualElement name="PasswordContainer"
style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
<ui:TextField picking-mode="Ignore" label="Password" name="PasswordField" password="true"/>
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="PasswordUpdateButton"/>
</ui:VisualElement>
<ui:Label tabindex="-1" text="A verification email has been sent. Check your inbox." parse-escape-sequences="true" display-tooltip-when-elided="true" name="AccompanyingText" style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 1.5%; margin-right: 0; margin-bottom: 0.75%; margin-left: 0; -unity-text-align: upper-left; display: none;" />
<ui:Label tabindex="-1" text="A verification email has been sent. Check your inbox."
parse-escape-sequences="true" display-tooltip-when-elided="true" name="AccompanyingText"
style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 1.5%; margin-right: 0; margin-bottom: 0.75%; margin-left: 0; -unity-text-align: upper-left; display: none;"/>
</ui:VisualElement>
<ui:VisualElement name="AccountButtons" style="flex-grow: 0; align-items: flex-start;">
<ui:Button text="Primary Action Button →" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PrimaryActionButton" style="-unity-text-align: middle-center; margin-bottom: 1%; margin-right: 1%; margin-top: 1%; -unity-font-style: bold;" />
<ui:Button text="Secondary Action Button →" parse-escape-sequences="true" display-tooltip-when-elided="true" name="SecondaryActionButton" style="margin-top: 1%; margin-right: 1%; -unity-font-style: bold;" />
<ui:Button text="Primary Action Button →" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="PrimaryActionButton"
style="-unity-text-align: middle-center; margin-bottom: 1%; margin-right: 1%; margin-top: 1%; -unity-font-style: bold;"/>
<ui:Button text="Secondary Action Button →" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="SecondaryActionButton"
style="margin-top: 1%; margin-right: 1%; -unity-font-style: bold;"/>
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>