diff --git a/Assets/Common/Scenes/Boot.unity b/Assets/Common/Scenes/Boot.unity index 3b18f14..f0a8103 100644 --- a/Assets/Common/Scenes/Boot.unity +++ b/Assets/Common/Scenes/Boot.unity @@ -513,10 +513,10 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} m_Name: m_EditorClassIdentifier: - m_UiScaleMode: 0 + m_UiScaleMode: 1 m_ReferencePixelsPerUnit: 100 m_ScaleFactor: 1 - m_ReferenceResolution: {x: 800, y: 600} + m_ReferenceResolution: {x: 1920, y: 1080} m_ScreenMatchMode: 0 m_MatchWidthOrHeight: 0 m_PhysicalUnit: 3 diff --git a/Assets/Common/Scripts/CourseActivityScreen.cs b/Assets/Common/Scripts/CourseActivityScreen.cs index c5fbd81..c589316 100644 --- a/Assets/Common/Scripts/CourseActivityScreen.cs +++ b/Assets/Common/Scripts/CourseActivityScreen.cs @@ -39,6 +39,7 @@ public class CourseActivityScreen : MonoBehaviour /// public Image courseImage; + /// /// Progress bar Display /// diff --git a/Assets/Courses/Scripts/CoursesController.cs b/Assets/Courses/Scripts/CoursesController.cs index 7661a04..f5af8fe 100644 --- a/Assets/Courses/Scripts/CoursesController.cs +++ b/Assets/Courses/Scripts/CoursesController.cs @@ -1,6 +1,7 @@ using DigitalRuby.Tween; using System; using System.Collections; +using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine; @@ -170,10 +171,26 @@ public class CoursesController : AbstractFeedback void Start() { StartCourseController(); + signPredictor.SetSignsList(GetSignsList()); signPredictor.SetModel(course.theme.modelIndex); AddSelfAsListener(); } + /// + /// Fetches all the strings of the signs of the course + /// + /// The signsList that needs to be passed to the signPredictor + private List GetSignsList() + { + List signsList = new List(); + foreach (Learnable learnable in course.theme.learnables) + { + signsList.Add(learnable.name); + } + + return signsList; + } + /// /// Holds the course-specific logic to start the controller, it is seperated to allow the course to be reset (if that would become needed) /// @@ -410,6 +427,7 @@ public class CoursesController : AbstractFeedback float accPredictSign = signPredictor.learnableProbabilities[predictedSign]; Learnable predSign = course.theme.learnables.Find(l => l.name.ToUpper().Replace(" ", "-") == predictedSign); + // If there is a feedback-object, we wil change its appearance if (feedbackText != null && feedbackProgressImage != null) { Color col; @@ -439,6 +457,7 @@ public class CoursesController : AbstractFeedback feedbackText.color = col; feedbackProgressImage.color = col; + // Tween the feedback-bar float oldValue = feedbackProgress.value; // use an exponential scale float newValue = Mathf.Exp(4 * (Mathf.Clamp(accCurrentSign / sign.thresholdPercentage, 0.0f, 1.0f) - 1.0f)); @@ -451,6 +470,7 @@ public class CoursesController : AbstractFeedback }); } + // The internal logic for the courses if (accPredictSign > sign.thresholdPercentage) { // Correct sign diff --git a/Assets/Hangman/Scenes/HangmanGame.unity b/Assets/Hangman/Scenes/HangmanGame.unity index 1acc316..98c396d 100644 --- a/Assets/Hangman/Scenes/HangmanGame.unity +++ b/Assets/Hangman/Scenes/HangmanGame.unity @@ -38,7 +38,7 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0.37311953, g: 0.38074014, b: 0.3587274, a: 1} + m_IndirectSpecularColor: {r: 0.37311918, g: 0.3807398, b: 0.35872716, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: @@ -2241,7 +2241,9 @@ MonoBehaviour: m_text: 'Speler 1 geeft een woord in aan de hand van vingerspelling. Het woord - moet tussen 3 en 17 letters zijn.' + moet tussen 3 en 17 letters zijn. + + Je kan letters verwijderen met backspace.' m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 1baf2eae62f542f4585aaf3c9c3e229a, type: 2} m_sharedMaterial: {fileID: -2577534979213189211, guid: 1baf2eae62f542f4585aaf3c9c3e229a, type: 2} @@ -3276,7 +3278,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: + m_text: AZERTY m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 1baf2eae62f542f4585aaf3c9c3e229a, type: 2} m_sharedMaterial: {fileID: -2577534979213189211, guid: 1baf2eae62f542f4585aaf3c9c3e229a, type: 2} @@ -4540,6 +4542,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 3576f66af2a0eea42ae06ac73d9779e6, type: 3} m_Name: m_EditorClassIdentifier: + scoreboardEntriesContainer: {fileID: 2082181368} + scoreboardEntry: {fileID: 9154151134820372555, guid: d4a3a228b08d61847acc6da35b44e52c, type: 3} endText: {fileID: 1205429732} lettersRightText: {fileID: 815411823} lettersWrongText: {fileID: 1606437998} @@ -4547,19 +4551,7 @@ MonoBehaviour: accuracyText: {fileID: 1341392955} wordText: {fileID: 328407984} scoreText: {fileID: 941310846} - scoreboardEntriesContainer: {fileID: 2082181368} - scoreboardEntry: {fileID: 9154151134820372555, guid: d4a3a228b08d61847acc6da35b44e52c, type: 3} image: {fileID: 1503788513} - sprites: - - {fileID: 21300000, guid: 46b9ec93c5782294f93be12a7c7bc3f0, type: 3} - - {fileID: 21300000, guid: 0a2c6671fa08a1249ba97ddd3432ac60, type: 3} - - {fileID: 21300000, guid: e5b621bb42e52654d88ad633777004a8, type: 3} - - {fileID: 21300000, guid: 42b25d40421cfce4684bad733747c2e3, type: 3} - - {fileID: 21300000, guid: 76120f6b7a0251949a56f73d49985d3a, type: 3} - - {fileID: 21300000, guid: c9b599bbbe306584a926c415d7e96983, type: 3} - - {fileID: 21300000, guid: 3fa072f7f8a26cf45b84781ce2dfdc51, type: 3} - - {fileID: 21300000, guid: 3f6826496e2a1334ab74ec68262b2c11, type: 3} - - {fileID: 21300000, guid: 98ec939c075818c4e869916da0b33aad, type: 3} --- !u!1 &987091747 GameObject: m_ObjectHideFlags: 0 @@ -7675,6 +7667,10 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: signPredictor: {fileID: 1991376311} + minigamelist: {fileID: 11400000, guid: 51453f9b41bc72f468ba3e67ab622f8f, type: 2} + feedbackProgress: {fileID: 211555568} + webcamScreen: {fileID: 1966441454} + gameEndedPanel: {fileID: 974474713} themeList: {fileID: 11400000, guid: a247e2ce790f0f746a3bc521e6ab7d58, type: 2} fingerSpelling: {fileID: 11400000, guid: e02921b294fdad940b6e4d57e716d3bf, type: 2} letterPrefab: {fileID: 4639383499500021565, guid: c3e66e8957864914cb022af914df6a28, type: 3} @@ -7696,15 +7692,10 @@ MonoBehaviour: inputPanel: {fileID: 640467091} scoreDisplay: {fileID: 2026578772} scoreBonus: {fileID: 1537143412} - gameEndedPanel: {fileID: 974474713} - minigame: {fileID: 11400000, guid: 165c1d9867275924d9720d409e935f95, type: 2} - minigamelist: {fileID: 11400000, guid: 51453f9b41bc72f468ba3e67ab622f8f, type: 2} gotoGameButton: {fileID: 482979078} inputTextField: {fileID: 752351940} feedbackText: {fileID: 887237819} - feedbackProgress: {fileID: 211555568} feedbackProgressImage: {fileID: 1107240765} - webcamScreen: {fileID: 1966441454} timerCircle: {fileID: 683997248} confirmPanel: {fileID: 1002919202} confirmText: {fileID: 1672600188} diff --git a/Assets/Hangman/Scripts/HangmanController.cs b/Assets/Hangman/Scripts/HangmanController.cs index 3862eec..f65b0ca 100644 --- a/Assets/Hangman/Scripts/HangmanController.cs +++ b/Assets/Hangman/Scripts/HangmanController.cs @@ -1,15 +1,14 @@ using DigitalRuby.Tween; using System; -using System.Collections; using System.Collections.Generic; -using System.Linq; using TMPro; using UnityEngine; using UnityEngine.UI; using Random = UnityEngine.Random; -public class HangmanController : AbstractFeedback +public class HangmanController : AbstractMinigameController { + [Header("ConcreteVariables")] /// /// The scriptable with all the themes, will be used to select a random word for hangman. /// The spellingthemeList will be used for the words. @@ -17,7 +16,7 @@ public class HangmanController : AbstractFeedback public ThemeList themeList; /// - /// reference to the fingerspelling-theme to reach the letter-thresholds + /// reference to the fingerspelling-theme to reach the letter-thresholds and to pass to the signPredictor /// public Theme fingerSpelling; @@ -96,11 +95,6 @@ public class HangmanController : AbstractFeedback /// public TMP_Text scoreBonus; - ///// - ///// This panel holds the panels for input and playing the game, sharing webcam and feedback - ///// - //public GameObject inputGamePanel; - /// /// This int shows what mode we are in, used in update:

/// 0 : single or multiplayer?

@@ -111,27 +105,6 @@ public class HangmanController : AbstractFeedback ///
private int mode; - /// - /// The game over panel - /// - public GameObject gameEndedPanel; - - /// - /// Reference to the minigame ScriptableObject - /// - public Minigame minigame; - - /// - /// We keep the minigamelist as well so that the minigame-index doesn't get reset - /// DO NOT REMOVE - /// - public MinigameList minigamelist; - - /// - /// Reference to the current user - /// - private User user; - /// /// The button to go into the game /// @@ -152,21 +125,11 @@ public class HangmanController : AbstractFeedback /// public TMP_Text feedbackText; - /// - /// Reference to the progress bar - /// - public Slider feedbackProgress; - /// /// Reference to the progress bar image, so we can add fancy colors /// public Image feedbackProgressImage; - /// - /// reference to the webcam background - /// - public RawImage webcamScreen; - /// /// Previous incorrect sign, so we can keep track whether the user is wrong or the user is still changing signs /// @@ -239,48 +202,11 @@ public class HangmanController : AbstractFeedback private int winScore = 25; /// - /// Start is called before the first frame update + /// Set the AbstractMinigameController variable to inform it of the theme for the signPredictor /// - void Start() + protected override Theme signPredictorTheme { - signPredictor.SwapScreen(webcamScreen); - signPredictor.SetModel(ModelIndex.FINGERSPELLING); - AddSelfAsListener(); - - StartController(); - } - - /// - /// Called at the start of the scene AND when the scene is replayed - /// - public void StartController() - { - // Make sure the mode starts at zero - mode = 0; - - - // Make sure that only the player-selection panel is the one shown - gamePanel.SetActive(false); - inputPanel.SetActive(false); - playerPanel.SetActive(true); - - // Make sure that unneeded panels are inactive - gameEndedPanel.SetActive(false); - confirmPanel.SetActive(false); - - // Create entry in current user for keeping track of progress - user = UserList.GetCurrentUser(); - var progress = user.GetMinigameProgress(minigame.index); - if (progress == null) - { - progress = new PersistentDataController.SavedMinigameProgress(); - progress.minigameIndex = minigame.index; - user.AddMinigameProgress(progress); - } - UserList.Save(); - - // Guesses needs to be created instantly because it is used in the FeedbackLoop - //guesses = new List(); + get { return fingerSpelling; } } /// @@ -289,7 +215,7 @@ public class HangmanController : AbstractFeedback public void StartGame() { // Change the mode - mode = 2; + SwitchMode(2); // Activate the right panel gamePanel.SetActive(true); @@ -334,11 +260,7 @@ public class HangmanController : AbstractFeedback public void GoToInput() { // Change the mode - mode = 1; - - // Initialise the word to an empty String - currentWord = ""; - inputTextField.text = currentWord.ToUpper(); + SwitchMode(1); // Activate the right panel gamePanel.SetActive(false); @@ -346,7 +268,11 @@ public class HangmanController : AbstractFeedback inputPanel.SetActive(true); playerPanel.SetActive(false); + // Initialise the word to an empty String + currentWord = ""; + PanelMultiplayerInput script = inputPanel.GetComponent(); + script.inputTextField.text = ""; gotoGameButton = script.gotoGameButton; inputTextField = script.inputTextField; @@ -470,7 +396,7 @@ public class HangmanController : AbstractFeedback { confirmPanel.SetActive(true); confirmText.text = $"Letter '{currentSign.ToUpper()}' ?"; - mode = 4; + SwitchMode(4); } } break; @@ -478,7 +404,7 @@ public class HangmanController : AbstractFeedback case 2: // Sign your letter if (!guesses.Contains(currentSign)) { - mode = 3; + SwitchMode(3); ConfirmAccept(); } break; @@ -517,24 +443,23 @@ public class HangmanController : AbstractFeedback usedLettersText.text += letter.ToString().ToUpper(); // The current sign was accepted, return to the game - mode = 2; - - if (corrects == currentWord.Replace(" ", "").Length) + SwitchMode(2); + if (corrects == currentWord.Length) { // Victory, deactivate the model and show the scoreboard - ActivateWin(); + ActivateEnd(true); } else if (NUMBER_OF_FAILS_BEFORE_GAMEOVER < wrongs) { // You lost, deactivate the model and show the scoreboard - ActivateGameOver(); + ActivateEnd(false); } } else if (mode == 4) { currentWord += letter; inputTextField.text = currentWord.ToUpper(); - mode = 1; + SwitchMode(1); } } @@ -544,9 +469,23 @@ public class HangmanController : AbstractFeedback // The current sign was rejected, return to the game-mode if (mode == 3) - mode = 2; + SwitchMode(2); else if (mode == 4) - mode = 1; + SwitchMode(1); + } + + public void SwitchMode(int mode) + { + this.mode = mode; + // In mode 1 and 2, the signPredictor needs to run, otherwise it does not + if (mode == 1 || mode == 2) + { + gameIsActive = true; + } + else + { + gameIsActive = false; + } } /// @@ -599,7 +538,7 @@ public class HangmanController : AbstractFeedback /// This function returns the score that the user currently has /// /// The current score of the user - private int CalculateScore() + public override int CalculateScore() { int won = corrects == currentWord.Length ? 1 : 0; return corrects * correctLetterScore + wrongs * incorrectLetterScore + winScore * won; @@ -640,115 +579,19 @@ public class HangmanController : AbstractFeedback } /// - /// Update and save the scores + /// The logic to process the signs sent by the signPredictor /// - private void SaveScores() + /// The accuracy of the passed sign + /// The name of the passed sign + protected override void ProcessMostProbableSign(float accuracy, string predictedSign) { - // Calculate new score - int newScore = CalculateScore(); - // Save the score as a tuple: < int score, string time ago> - Score score = new Score(); - score.scoreValue = newScore; - score.time = DateTime.Now.ToString(); + // Grab the threshold for the most probable letter + Learnable letter = fingerSpelling.learnables.Find((l) => l.name == predictedSign); + float threshold = letter.thresholdPercentage; - // Save the new score - var progress = user.GetMinigameProgress(minigame.index); - - // Get the current list of scores - List latestScores = progress.latestScores; - List highestScores = progress.highestScores; - - // Add the new score - latestScores.Add(score); - highestScores.Add(score); - - // Sort the scores - highestScores.Sort((a, b) => b.scoreValue.CompareTo(a.scoreValue)); - - // Only save the top 10 scores, so this list doesn't keep growing endlessly - progress.latestScores = latestScores.Take(10).ToList(); - progress.highestScores = highestScores.Take(10).ToList(); - - UserList.Save(); - } - - /// - /// Display win screen - /// - private void ActivateWin() - { - // Deactivate the model - mode = 0; - - // Save the scores and show the scoreboard - SaveScores(); - gameEndedPanel.GetComponent().GenerateContent( - guessWord: currentWord.ToLower(), - correctLetters: corrects, - incorrectLetters: wrongs, - sprite: hangmanImage.sprite, - result: "GEWONNEN", - score: CalculateScore() - ); - - gameEndedPanel.SetActive(true); - - // @lukas stuff - DeleteWord(); - } - - /// - /// Displays the game over panel and score values - /// - private void ActivateGameOver() - { - // Deactivate the model - mode = 0; - - // Save the scores and show the scoreboard - SaveScores(); - gameEndedPanel.GetComponent().GenerateContent( - guessWord: currentWord.ToLower(), - correctLetters: corrects, - incorrectLetters: wrongs, - sprite: hangmanImage.sprite, - result: "VERLOREN", - score: CalculateScore() - ); - - gameEndedPanel.SetActive(true); - - DeleteWord(); - } - - /// - /// The updateFunction that is called when new probabilities become available - /// - /// - protected override IEnumerator UpdateFeedback() - { - // Get the sign with the highest prediction - if ((mode == 1 || mode == 2) && - signPredictor != null && - signPredictor.learnableProbabilities != null) + // If there is a feedback-object, we wil change its appearance + if (feedbackText != null && feedbackProgressImage != null) { - KeyValuePair highestPrediction = signPredictor.learnableProbabilities.Aggregate((x, y) => x.Value > y.Value ? x : y); - float accuracy = highestPrediction.Value; - string predictedSign = highestPrediction.Key; - - // vvv TEMPORARY STUFF vvv - if (predictedSign == "J" && accuracy <= 0.965f) - { - highestPrediction = signPredictor.learnableProbabilities.Aggregate((x, y) => x.Value > y.Value && x.Key != "J" ? x : y); - } - accuracy = highestPrediction.Value; - predictedSign = highestPrediction.Key; - // ^^^ TEMPORARY STUFF ^^^ - - // Grab the threshold for the most probable letter - Learnable letter = fingerSpelling.learnables.Find((l) => l.name == predictedSign); - float threshold = letter.thresholdPercentage; - float oldValue = feedbackProgress.value; // use an exponential scale float newValue = Mathf.Exp(4 * (Mathf.Clamp(accuracy / threshold, 0.0f, 1.0f) - 1.0f)); @@ -781,37 +624,33 @@ public class HangmanController : AbstractFeedback feedbackText.color = red; feedbackProgressImage.color = red; } + } - if (accuracy > threshold) + // The logic for the internal workings of the game + if (accuracy > threshold) + { + // A different sign was predicted compared to the last call of this function + if (previousSign != predictedSign) { - if (previousSign != predictedSign) + // Reset the timer + previousSign = predictedSign; + currentTime = 0; + // If you are entering a word the timer needs to work + // If you are playing the game and haven't guessed the letter yet, then the timer needs to work + if ((mode == 1) || + (mode == 2 && !guesses.Contains(previousSign.ToUpper()))) { - // Reset the timer - previousSign = predictedSign; - currentTime = 0; - if ((mode == 1) || - (mode == 2 && !guesses.Contains(previousSign.ToUpper()))) - { - runTime = true; - } - timerCircle.fillAmount = currentTime; + runTime = true; } - else if (currentTime == maxTime) - { - // Set the predictedSign as your guess and update the Hangman - currentSign = predictedSign; - UpdateSign(); - // reset the timer and look for a new prediction - previousSign = null; - currentTime = 0; - runTime = false; - timerCircle.fillAmount = currentTime; - } - + timerCircle.fillAmount = currentTime; } - else + // The same sign was predicted as last time and said sign has been held for a sufficiently long time + else if (currentTime == maxTime) { - // The sign was dropped, reset the timer + // Set the predictedSign as your guess and update the Hangman + currentSign = predictedSign; + UpdateSign(); + // reset the timer and look for a new prediction previousSign = null; currentTime = 0; runTime = false; @@ -819,11 +658,69 @@ public class HangmanController : AbstractFeedback } } - else if (feedbackProgress != null) + else { - - feedbackProgress.value = 0.0f; + // The sign was dropped, reset the timer + previousSign = null; + currentTime = 0; + runTime = false; + timerCircle.fillAmount = currentTime; } - yield return null; + } + + /// + /// The logic to set the scoreboard of hangman + /// + /// SHows whether or not the player won + protected override void SetScoreBoard(bool victory) + { + string resultTxt; + if (victory) + { + resultTxt = "GEWONNEN"; + } + else + { + resultTxt = "VERLOREN"; + } + gameEndedPanel.GetComponent().GenerateContent( + guessWord: currentWord.ToLower(), + correctLetters: corrects, + incorrectLetters: wrongs, + sprite: hangmanImage.sprite, + result: resultTxt, + score: CalculateScore() + ); + } + + /// + /// The hangman-specific logic that needs to be called at the start of the game + /// + protected override void StartGameLogic() + { + // Make sure the mode starts at zero + SwitchMode(0); + + + // Make sure that only the player-selection panel is the one shown + gamePanel.SetActive(false); + inputPanel.SetActive(false); + playerPanel.SetActive(true); + + // Make sure that unneeded panels are inactive + gameEndedPanel.SetActive(false); + confirmPanel.SetActive(false); + } + + /// + /// The Hangman-specific logic that needs to be called at the end of a game + /// + /// + protected override void EndGameLogic(bool victory) + { + // Deactivate the model + SwitchMode(0); + + DeleteWord(); } } diff --git a/Assets/Hangman/Scripts/HangmanGameEndedPanel.cs b/Assets/Hangman/Scripts/HangmanGameEndedPanel.cs index aca391c..4e6eb8f 100644 --- a/Assets/Hangman/Scripts/HangmanGameEndedPanel.cs +++ b/Assets/Hangman/Scripts/HangmanGameEndedPanel.cs @@ -7,8 +7,16 @@ using UnityEngine.UI; /// /// The hangman-variant of the ScoreBoard /// -public class HangmanGameEndedPanel : MonoBehaviour +public class HangmanGameEndedPanel : AbstractGameEndedPanel { + /// + /// Tell the scoreboard that the scoreboard is for HangMan + /// + protected override MinigameIndex minigameIndex + { + get { return MinigameIndex.HANGMAN; } + } + /// /// "VERLOREN" or "GEWONNEN" /// @@ -40,28 +48,11 @@ public class HangmanGameEndedPanel : MonoBehaviour /// public TMP_Text scoreText; - /// - /// Reference to the scoreboard entries container - /// - public Transform scoreboardEntriesContainer; - - /// - /// The GameObjects representing the letters - /// - private List scoreboardEntries = new List(); - - /// - /// Reference to the ScoreboardEntry prefab - /// - public GameObject scoreboardEntry; - /// /// Reference to the end result image /// public Image image; - public List sprites; - /// /// Generate the content of the GameEnded panel /// @@ -99,105 +90,4 @@ public class HangmanGameEndedPanel : MonoBehaviour scoreText.text = $"Score: {score}"; SetScoreBoard(); } - - - /// - /// Sets the scoreboard - /// - private void SetScoreBoard() - { - // Clean the previous scoreboard entries - for (int i = 0; i < scoreboardEntries.Count; i++) - { - Destroy(scoreboardEntries[i]); - } - scoreboardEntries.Clear(); - - // Instantiate new entries - // Get all scores from all users - List> allScores = new List>(); - foreach (User user in UserList.GetUsers()) - { - // Get user's progress for this minigame - var progress = user.GetMinigameProgress(MinigameIndex.HANGMAN); - if (progress != null) - { - // Add scores to dictionary - List scores = progress.highestScores; - foreach (Score score in scores) - { - allScores.Add(new Tuple(user.GetUsername(), score)); - } - } - } - - // Sort allScores based on Score.scoreValue - allScores.Sort((a, b) => b.Item2.scoreValue.CompareTo(a.Item2.scoreValue)); - - // Instantiate scoreboard entries - int rank = 1; - foreach (Tuple tup in allScores.Take(10)) - { - string username = tup.Item1; - Score score = tup.Item2; - - GameObject entry = Instantiate(scoreboardEntry, scoreboardEntriesContainer); - scoreboardEntries.Add(entry); - - // Set the player icon - entry.transform.Find("Image").GetComponent().sprite = UserList.GetUserByUsername(username).GetAvatar(); - - // Set the player name - entry.transform.Find("PlayerName").GetComponent().text = username; - - // Set the score - entry.transform.Find("Score").GetComponent().text = score.scoreValue.ToString(); - - // Set the rank - entry.transform.Find("Rank").GetComponent().text = rank.ToString(); - - // Set the ago - // Convert the score.time to Datetime - DateTime time = DateTime.Parse(score.time); - DateTime currentTime = DateTime.Now; - TimeSpan diff = currentTime.Subtract(time); - - string formatted; - if (diff.Days > 0) - { - formatted = $"{diff.Days}d "; - } - else if (diff.Hours > 0) - { - formatted = $"{diff.Hours}h "; - } - else if (diff.Minutes > 0) - { - formatted = $"{diff.Minutes}m "; - } - else - { - formatted = "now"; - } - - entry.transform.Find("Ago").GetComponent().text = formatted; - - - // Alternating colors looks nice - if (rank % 2 == 0) - { - Image image = entry.transform.GetComponent(); - image.color = new Color(image.color.r, image.color.g, image.color.b, 0f); - } - - // Make new score stand out - if (diff.TotalSeconds < 1) - { - Image image = entry.transform.GetComponent(); - image.color = new Color(0, 229, 255, 233); - } - - rank++; - } - } } diff --git a/Assets/Hangman/Scripts/HangmanScripts.asmdef b/Assets/Hangman/Scripts/HangmanScripts.asmdef index ec792c4..161066e 100644 --- a/Assets/Hangman/Scripts/HangmanScripts.asmdef +++ b/Assets/Hangman/Scripts/HangmanScripts.asmdef @@ -5,9 +5,10 @@ "GUID:e83ddf9a537a96b4a804a16bb7872ec1", "GUID:6055be8ebefd69e48b49212b09b47b2f", "GUID:1631ed2680c61245b8211d943c1639a8", - "GUID:d0b6b39a21908f94fbbd9f2c196a9725", "GUID:58e104b97fb3752438ada2902a36dcbf", - "GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25" + "GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25", + "GUID:403dd94a93598934eb522dc36df43d7b", + "GUID:d0b6b39a21908f94fbbd9f2c196a9725" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/JustSign/Scenes/JustSignGame.unity b/Assets/JustSign/Scenes/JustSignGame.unity index a80a109..21b1120 100644 --- a/Assets/JustSign/Scenes/JustSignGame.unity +++ b/Assets/JustSign/Scenes/JustSignGame.unity @@ -1557,20 +1557,19 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: signPredictor: {fileID: 1150323775} + minigamelist: {fileID: 11400000, guid: 51453f9b41bc72f468ba3e67ab622f8f, type: 2} + feedbackProgress: {fileID: 1753834854} + webcamScreen: {fileID: 496523654} + gameEndedPanel: {fileID: 2498222378566216023} answerField: {fileID: 0} timingFeedback: {fileID: 128049076} scoreDisplay: {fileID: 1627575593} - minigame: {fileID: 11400000, guid: e726e0b93ea88465db7ee27605deb83f, type: 2} - minigamelist: {fileID: 11400000, guid: 51453f9b41bc72f468ba3e67ab622f8f, type: 2} songList: {fileID: 11400000, guid: 4f0ce70309bb901feb28199a82a7d195, type: 2} hitZonePerfect: {fileID: 2012531008} hitZoneGood: {fileID: 369868393} hitZoneMeh: {fileID: 141066497} - webcamScreen: {fileID: 496523654} symbolPrefab: {fileID: 4639383499500021565, guid: f3117b0203a1342a48a95904347b03c8, type: 3} symbolContainer: {fileID: 2093721209} - scoreboardEntriesContainer: {fileID: 2498222377153197597} - scoreboardEntry: {fileID: 9154151134820372555, guid: d4a3a228b08d61847acc6da35b44e52c, type: 3} lpmText: {fileID: 2498222377094258231} perfectSignsText: {fileID: 2498222378642934129} goodSignsText: {fileID: 524776817} @@ -1578,9 +1577,7 @@ MonoBehaviour: terribleSignsText: {fileID: 1343527143} notFoundSignsText: {fileID: 850357042} scoreText: {fileID: 2498222378754007924} - gameEndedPanel: {fileID: 2498222378566216023} feedbackText: {fileID: 1753834853} - feedbackProgressBar: {fileID: 1753834854} feedbackProgressImage: {fileID: 1753834852} perfectSprite: {fileID: 21300000, guid: 43b0de2d8fcec1540bb9989e45db4581, type: 3} goodSprite: {fileID: 21300000, guid: 168acd43cf46d1c419991a2620485bf6, type: 3} @@ -1857,7 +1854,7 @@ MonoBehaviour: modelInfoFile: {fileID: 4900000, guid: fb8b51022bdcd654a9f29c054832a1b5, type: 3} modelInfoFileEmbedding: {fileID: 4900000, guid: 4e303164823194bc4be87f4c9550cfd0, type: 3} configAsset: {fileID: 4900000, guid: 6288c43cdca97374782dac1ea87aa029, type: 3} - screen: {fileID: 0} + screen: {fileID: 496523654} --- !u!4 &1150323776 Transform: m_ObjectHideFlags: 0 @@ -3524,7 +3521,7 @@ GameObject: - component: {fileID: 2498222377094258230} - component: {fileID: 2498222377094258231} m_Layer: 5 - m_Name: LPM + m_Name: GPM m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -3578,7 +3575,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: XXX LPM + m_text: XXX GPM m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 1baf2eae62f542f4585aaf3c9c3e229a, type: 2} m_sharedMaterial: {fileID: -2577534979213189211, guid: 1baf2eae62f542f4585aaf3c9c3e229a, type: 2} @@ -4700,29 +4697,6 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2498222378566216023} m_CullTransparentMesh: 1 ---- !u!114 &2498222378566215979 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2498222378566216023} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 5aa929dce1f59b340b4a0cca1bb68edc, type: 3} - m_Name: - m_EditorClassIdentifier: - endText: {fileID: 2498222377166680336} - lpmText: {fileID: 2498222377094258231} - lettersRightText: {fileID: 0} - lettersWrongText: {fileID: 0} - lettersTotalText: {fileID: 0} - accuracyText: {fileID: 2498222378642934129} - wordsText: {fileID: 0} - timeText: {fileID: 0} - scoreText: {fileID: 2498222378754007924} - scoreboardEntriesContainer: {fileID: 2498222377153197597} - scoreboardEntry: {fileID: 9154151134820372555, guid: d4a3a228b08d61847acc6da35b44e52c, type: 3} --- !u!224 &2498222378566216020 RectTransform: m_ObjectHideFlags: 0 @@ -4788,7 +4762,7 @@ GameObject: - component: {fileID: 2498222378566216020} - component: {fileID: 2498222378566215978} - component: {fileID: 2498222378566216021} - - component: {fileID: 2498222378566215979} + - component: {fileID: 2498222378566216024} m_Layer: 5 m_Name: GameEnded Panel m_TagString: Untagged @@ -4796,6 +4770,27 @@ GameObject: m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 0 +--- !u!114 &2498222378566216024 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2498222378566216023} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9a86c239be1aa1543ba8a4ace5f658b1, type: 3} + m_Name: + m_EditorClassIdentifier: + scoreboardEntriesContainer: {fileID: 2498222377153197597} + scoreboardEntry: {fileID: 9154151134820372555, guid: d4a3a228b08d61847acc6da35b44e52c, type: 3} + perfectSignsText: {fileID: 2498222378642934129} + goodSignsText: {fileID: 524776817} + mehSignsText: {fileID: 6218584} + terribleSignsText: {fileID: 1343527143} + notFoundSignsText: {fileID: 850357042} + gpmText: {fileID: 2498222377094258231} + scoreText: {fileID: 2498222378754007924} --- !u!1 &2498222378611643141 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/JustSign/Scripts/JustSignController.cs b/Assets/JustSign/Scripts/JustSignController.cs index 032986d..b0ea321 100644 --- a/Assets/JustSign/Scripts/JustSignController.cs +++ b/Assets/JustSign/Scripts/JustSignController.cs @@ -1,8 +1,6 @@ using DigitalRuby.Tween; -using System; using System.Collections; using System.Collections.Generic; -using System.Linq; using TMPro; using UnityEngine; using UnityEngine.UI; @@ -11,7 +9,7 @@ using Random = UnityEngine.Random; /// /// Contains all game logic for the JustSign game /// -public class JustSignController : AbstractFeedback +public class JustSignController : AbstractMinigameController { /// /// All of the words that can be used in this session @@ -33,17 +31,6 @@ public class JustSignController : AbstractFeedback /// public TMP_Text scoreDisplay; - /// - /// Reference to the minigame ScriptableObject - /// - public Minigame minigame; - - /// - /// We keep the minigamelist as well so that the minigame-index doesn't get reset - /// DO NOT REMOVE - /// - public MinigameList minigamelist; - /// /// Reference to the list of available songs /// @@ -69,11 +56,6 @@ public class JustSignController : AbstractFeedback /// public RectTransform hitZoneMeh; - /// - /// Reference to the webcam - /// - public RawImage webcamScreen; - /// /// Score obtained when getting a perfect hit /// @@ -124,11 +106,6 @@ public class JustSignController : AbstractFeedback /// private List activeSymbols = new List(); - /// - /// Have the symbols started spawning or not - /// - private bool gameIsActive = false; - /// /// Controls movement speed of symbols (higher -> faster) /// @@ -184,26 +161,6 @@ public class JustSignController : AbstractFeedback /// private int incorrectSigns; - /// - /// Reference to the scoreboard entries container - /// - public Transform scoreboardEntriesContainer; - - /// - /// The GameObjects representing the letters - /// - private List scoreboardEntries = new List(); - - /// - /// Reference to the ScoreboardEntry prefab - /// - public GameObject scoreboardEntry; - - /// - /// Reference to the current user - /// - private User user; - /// /// LPM /// @@ -239,21 +196,11 @@ public class JustSignController : AbstractFeedback /// public TMP_Text scoreText; - /// - /// Reference to the gameEnded panel, so we can update its display - /// - public GameObject gameEndedPanel; - /// /// Reference to the feedback field /// public TMP_Text feedbackText; - /// - /// Reference to the progress bar - /// - public Slider feedbackProgressBar; - /// /// Reference to the progress bar image, so we can add fancy colors /// @@ -299,53 +246,9 @@ public class JustSignController : AbstractFeedback /// public GameObject userFeedback; - /// - /// Start is called before the first frame update - /// - public void Start() + protected override Theme signPredictorTheme { - currentTheme = minigame.themeList.themes[minigame.themeList.currentThemeIndex]; - signPredictor.SetModel(currentTheme.modelIndex); - signPredictor.SwapScreen(webcamScreen); - AddSelfAsListener(); - - StartController(); - } - - /// - /// Holds the game-specific logic to start the controller - /// - public void StartController() - { - userFeedback.SetActive(currentTheme.modelIndex != ModelIndex.NONE); - previewMessage.SetActive(currentTheme.modelIndex == ModelIndex.NONE); - perfectSigns = 0; - goodSigns = 0; - mehSigns = 0; - terribleSigns = 0; - incorrectSigns = 0; - timingFeedback.text = ""; - imageFeedback.sprite = minigame.thumbnail; - gameEndedPanel.SetActive(false); - // Create entry in current user for keeping track of progress - user = UserList.GetCurrentUser(); - var progress = user.GetMinigameProgress(minigame.index); - if (progress == null) - { - progress = new PersistentDataController.SavedMinigameProgress(); - progress.minigameIndex = minigame.index; - user.AddMinigameProgress(progress); - } - UserList.Save(); - - scoreDisplay.text = $"Score: {CalculateScore()}"; - words.AddRange(currentTheme.learnables); - currentSong = songList.songs[songList.currentSongIndex]; - AudioSource.PlayClipAtPoint(currentSong.song, Vector3.zero, 1.0f); - beginTime = Time.time; - lastSymbolTime = beginTime + currentSong.duration - 1920.0f / moveSpeed; - - StartCoroutine(WaitThenStart(currentSong.firstSymbolTime)); + get { return currentTheme; } } /// @@ -388,7 +291,8 @@ public class JustSignController : AbstractFeedback // Check if the song has ended and activate scorescreen if it has if (currentTime - beginTime > currentSong.duration) { - ActivateEnd(); + // The boolean that is passed is irrelevant for this game + ActivateEnd(true); } // Move all active symbols to the right @@ -406,28 +310,11 @@ public class JustSignController : AbstractFeedback /// Calculate the score /// /// The calculated score - public int CalculateScore() + public override int CalculateScore() { return goodSigns * goodScore + perfectSigns * perfectScore + mehScore * mehSigns + terribleScore * terribleSigns + incorrectSigns * offscreenScore; } - /// - /// Display Scoreboard + Metrics - /// - public void ActivateEnd() - { - gameIsActive = false; - while (activeSymbols.Count > 0) - { - DestroySymbolAt(0); - } - // TODO: Scoreboard - SaveScores(); - SetScoreMetrics(); - SetScoreBoard(); - gameEndedPanel.SetActive(true); - } - /// /// Destroy the symbol at the given index /// @@ -474,282 +361,160 @@ public class JustSignController : AbstractFeedback } /// - /// Update and save the scores + /// The logic to process the signs sent by the signPredictor /// - public void SaveScores() + /// The accuracy of the passed sign + /// The name of the passed sign + protected override void ProcessMostProbableSign(float accuracy, string predictedSign) { - // Calculate new score - int newScore = CalculateScore(); + Learnable predSign = currentTheme.learnables.Find(l => l.name.ToUpper() == predictedSign); - // Save the score as a tuple: < int score, string time ago> - Score score = new Score(); - score.scoreValue = newScore; - score.time = DateTime.Now.ToString(); - - // Save the new score - var progress = user.GetMinigameProgress(minigame.index); - - // Get the current list of scores - List latestScores = progress.latestScores; - List highestScores = progress.highestScores; - - // Add the new score - latestScores.Add(score); - highestScores.Add(score); - - // Sort the scores - highestScores.Sort((a, b) => b.scoreValue.CompareTo(a.scoreValue)); - - // Only save the top 10 scores, so this list doesn't keep growing endlessly - progress.latestScores = latestScores.Take(10).ToList(); - progress.highestScores = highestScores.Take(10).ToList(); - - PersistentDataController.GetInstance().Save(); - } - - /// - /// Set score metrics - /// - private void SetScoreMetrics() - { - // In de zone - perfectSignsText.text = perfectSigns.ToString(); - - // Aanvaardbaar - goodSignsText.text = goodSigns.ToString(); - - // Nipt - mehSignsText.text = mehSigns.ToString(); - - // Slechte timing - terribleSignsText.text = terribleSigns.ToString(); - - // Niet Geraden - notFoundSignsText.text = incorrectSigns.ToString(); - - // LPM - int duration = songList.songs[songList.currentSongIndex].duration; - int correctSigns = goodSigns + perfectSigns + mehSigns + terribleSigns; - lpmText.text = (60f * correctSigns / duration).ToString("#") + " GPM"; - - // Score - scoreText.text = $"Score: {CalculateScore()}"; - } - - /// - /// Sets the scoreboard - /// - private void SetScoreBoard() - { - // Clean the previous scoreboard entries - for (int i = 0; i < scoreboardEntries.Count; i++) + // If there is a feedback-object, we wil change its appearance + if (feedbackText != null && feedbackProgressImage != null) { - Destroy(scoreboardEntries[i]); - } - scoreboardEntries.Clear(); - - // Instantiate new entries - // Get all scores from all users - List> allScores = new List>(); - foreach (User user in UserList.GetUsers()) - { - // Get user's progress for this minigame - var progress = user.GetMinigameProgress(minigame.index); - if (progress != null) + Color col; + if (accuracy > predSign.thresholdPercentage) { - // Add scores to dictionary - List scores = progress.highestScores; - foreach (Score score in scores) - { - allScores.Add(new Tuple(user.GetUsername(), score)); - } + feedbackText.text = $"Herkent '{predictedSign}'"; + col = new Color(0x8b / 255.0f, 0xd4 / 255.0f, 0x5e / 255.0f); } - } - - // Sort allScores based on Score.scoreValue - allScores.Sort((a, b) => b.Item2.scoreValue.CompareTo(a.Item2.scoreValue)); - - // Instantiate scoreboard entries - int rank = 1; - foreach (Tuple tup in allScores.Take(10)) - { - string username = tup.Item1; - Score score = tup.Item2; - - GameObject entry = Instantiate(scoreboardEntry, scoreboardEntriesContainer); - scoreboardEntries.Add(entry); - - // Set the player icon - entry.transform.Find("Image").GetComponent().sprite = UserList.GetUserByUsername(username).GetAvatar(); - - // Set the player name - entry.transform.Find("PlayerName").GetComponent().text = username; - - // Set the score - entry.transform.Find("Score").GetComponent().text = score.scoreValue.ToString(); - - // Set the rank - entry.transform.Find("Rank").GetComponent().text = rank.ToString(); - - // Set the ago - // Convert the score.time to Datetime - DateTime time = DateTime.Parse(score.time); - DateTime currentTime = DateTime.Now; - TimeSpan diff = currentTime.Subtract(time); - - string formatted; - if (diff.Days > 0) + else if (accuracy > 0.9 * predSign.thresholdPercentage) { - formatted = $"{diff.Days}d "; - } - else if (diff.Hours > 0) - { - formatted = $"{diff.Hours}h "; - } - else if (diff.Minutes > 0) - { - formatted = $"{diff.Minutes}m "; + feedbackText.text = $"Lijkt op '{predictedSign}'"; + col = new Color(0xf2 / 255.0f, 0x7f / 255.0f, 0x0c / 255.0f); } else { - formatted = "now"; + feedbackText.text = "Detecteren..."; + col = new Color(0xf5 / 255.0f, 0x49 / 255.0f, 0x3d / 255.0f); } - entry.transform.Find("Ago").GetComponent().text = formatted; + feedbackText.color = col; + feedbackProgressImage.color = col; - - // Alternating colors looks nice - if (rank % 2 == 0) + float oldValue = feedbackProgress.value; + // use an exponential scale + float newValue = Mathf.Exp(4 * (Mathf.Clamp(accuracy / predSign.thresholdPercentage, 0.0f, 1.0f) - 1.0f)); + feedbackProgress.gameObject.Tween("FeedbackUpdate", oldValue, newValue, 0.2f, TweenScaleFunctions.CubicEaseInOut, (t) => { - Image image = entry.transform.GetComponent(); - image.color = new Color(image.color.r, image.color.g, image.color.b, 0f); - } + if (feedbackProgress != null) + { + feedbackProgress.value = t.CurrentValue; + } + }); + } - // Make new score stand out - if (diff.TotalSeconds < 1) + // The logic for the internal workings of the game + if (accuracy > predSign.thresholdPercentage) + { + int matchedSymbolIndex = activeWords.IndexOf(predictedSign.ToUpper()); + + // Destroy the oldest symbol if the current input matches it + if (0 <= matchedSymbolIndex) { - Image image = entry.transform.GetComponent(); - image.color = new Color(0, 229, 255, 233); - } + float x = activeSymbols[matchedSymbolIndex].transform.localPosition.x; - rank++; + // parameters to define the Perfect hit zone + float perfectRange = hitZonePerfect.sizeDelta.x; + float perfectCenter = hitZonePerfect.localPosition.x; + // parameters to define the Good hit zone + float goodRange = hitZoneGood.sizeDelta.x; + float goodCenter = hitZoneGood.localPosition.x; + // parameters to define the Meh hit zone + float mehRange = hitZoneMeh.sizeDelta.x; + float mehCenter = hitZoneMeh.localPosition.x; + + if (perfectCenter - perfectRange / 2 <= x && x <= perfectCenter + perfectRange / 2) + { + timingFeedback.text = $"Perfect! \n +{perfectScore}"; + imageFeedback.sprite = perfectSprite; + perfectSigns++; + timingFeedback.color = new Color(0x8b / 255.0f, 0xd4 / 255.0f, 0x5e / 255.0f); + } + else if (goodCenter - goodRange / 2 <= x && x <= goodCenter + goodRange / 2) + { + timingFeedback.text = $"Goed \n +{goodScore}"; + imageFeedback.sprite = goodSprite; + goodSigns++; + timingFeedback.color = new Color(0xf7 / 255.0f, 0xad / 255.0f, 0x19 / 255.0f); + } + else if (mehCenter - mehRange / 2 <= x && x <= mehCenter + mehRange / 2) + { + timingFeedback.text = $"Bijna... \n +{mehScore}"; + imageFeedback.sprite = mehSprite; + mehSigns++; + timingFeedback.color = new Color(0xf2 / 255.0f, 0x7f / 255.0f, 0x0c / 255.0f); + } + else + { + timingFeedback.text = $"Te vroeg! \n {terribleScore}"; + imageFeedback.sprite = terribleSprite; + terribleSigns++; + timingFeedback.color = new Color(0xf5 / 255.0f, 0x49 / 255.0f, 0x3d / 255.0f); + } + + DestroySymbolAt(matchedSymbolIndex); + } } } /// - /// The updateFunction that is called when new probabilities become available + /// The logic to set the scoreboard of justsign /// - /// - protected override IEnumerator UpdateFeedback() + /// Shows whether or not the player won, is not relevant for JustSIgn + protected override void SetScoreBoard(bool victory) { - // Get the predicted sign - if (signPredictor != null && signPredictor.learnableProbabilities != null && gameIsActive) + gameEndedPanel.GetComponent().GenerateContent( + perfectSigns: perfectSigns, + goodSigns: goodSigns, + mehSigns: mehSigns, + terribleSigns: terribleSigns, + incorrectSigns: incorrectSigns, + duration: currentSong.duration, + score: CalculateScore() + ); + } + + /// + /// The justsign-specific logic that needs to be called at the start of the game + /// + protected override void StartGameLogic() + { + // Set the current theme so that it can be passed along + + currentTheme = minigame.themeList.themes[minigame.themeList.currentThemeIndex]; + + userFeedback.SetActive(currentTheme.modelIndex != ModelIndex.NONE); + previewMessage.SetActive(currentTheme.modelIndex == ModelIndex.NONE); + perfectSigns = 0; + goodSigns = 0; + mehSigns = 0; + terribleSigns = 0; + incorrectSigns = 0; + timingFeedback.text = ""; + imageFeedback.sprite = minigame.thumbnail; + gameEndedPanel.SetActive(false); + + scoreDisplay.text = $"Score: {CalculateScore()}"; + words.AddRange(currentTheme.learnables); + currentSong = songList.songs[songList.currentSongIndex]; + AudioSource.PlayClipAtPoint(currentSong.song, Vector3.zero, 1.0f); + beginTime = Time.time; + lastSymbolTime = beginTime + currentSong.duration - 1920.0f / moveSpeed; + + StartCoroutine(WaitThenStart(currentSong.firstSymbolTime)); + } + + /// + /// The justsign-specific logic that needs to be called at the end of a game + /// + /// + protected override void EndGameLogic(bool victory) + { + gameIsActive = false; + while (activeSymbols.Count > 0) { - // Get highest predicted sign - string predictedSign = signPredictor.learnableProbabilities.Aggregate((a, b) => a.Value > b.Value ? a : b).Key; - float accuracy = signPredictor.learnableProbabilities[predictedSign]; - - // vvv TEMPORARY STUFF vvv - if (predictedSign == "J" && accuracy <= 0.97f) - { - predictedSign = signPredictor.learnableProbabilities.Aggregate((x, y) => x.Value > y.Value && x.Key != "J" ? x : y).Key; - } - accuracy = signPredictor.learnableProbabilities[predictedSign]; - // ^^^ TEMPORARY STUFF ^^^ - - Learnable predSign = currentTheme.learnables.Find(l => l.name.ToUpper().Replace(" ", "-") == predictedSign); - - if (feedbackText != null && feedbackProgressImage != null) - { - Color col; - if (accuracy > predSign.thresholdPercentage) - { - feedbackText.text = $"Herkent '{predictedSign}'"; - col = new Color(0x8b / 255.0f, 0xd4 / 255.0f, 0x5e / 255.0f); - } - else if (accuracy > 0.9 * predSign.thresholdPercentage) - { - feedbackText.text = $"Lijkt op '{predictedSign}'"; - col = new Color(0xf2 / 255.0f, 0x7f / 255.0f, 0x0c / 255.0f); - } - else - { - feedbackText.text = "Detecteren..."; - col = new Color(0xf5 / 255.0f, 0x49 / 255.0f, 0x3d / 255.0f); - } - - feedbackText.color = col; - feedbackProgressImage.color = col; - - float oldValue = feedbackProgressBar.value; - // use an exponential scale - float newValue = Mathf.Exp(4 * (Mathf.Clamp(accuracy / predSign.thresholdPercentage, 0.0f, 1.0f) - 1.0f)); - feedbackProgressBar.gameObject.Tween("FeedbackUpdate", oldValue, newValue, 0.2f, TweenScaleFunctions.CubicEaseInOut, (t) => - { - if (feedbackProgressBar != null) - { - feedbackProgressBar.value = t.CurrentValue; - } - }); - } - - if (accuracy > predSign.thresholdPercentage) - { - int matchedSymbolIndex = activeWords.IndexOf(predictedSign.ToUpper()); - - // Destroy the oldest symbol if the current input matches it - if (0 <= matchedSymbolIndex) - { - float x = activeSymbols[matchedSymbolIndex].transform.localPosition.x; - - // parameters to define the Perfect hit zone - float perfectRange = hitZonePerfect.sizeDelta.x; - float perfectCenter = hitZonePerfect.localPosition.x; - // parameters to define the Good hit zone - float goodRange = hitZoneGood.sizeDelta.x; - float goodCenter = hitZoneGood.localPosition.x; - // parameters to define the Meh hit zone - float mehRange = hitZoneMeh.sizeDelta.x; - float mehCenter = hitZoneMeh.localPosition.x; - - if (perfectCenter - perfectRange / 2 <= x && x <= perfectCenter + perfectRange / 2) - { - timingFeedback.text = $"Perfect! \n +{perfectScore}"; - imageFeedback.sprite = perfectSprite; - perfectSigns++; - timingFeedback.color = new Color(0x8b / 255.0f, 0xd4 / 255.0f, 0x5e / 255.0f); - } - else if (goodCenter - goodRange / 2 <= x && x <= goodCenter + goodRange / 2) - { - timingFeedback.text = $"Goed \n +{goodScore}"; - imageFeedback.sprite = goodSprite; - goodSigns++; - timingFeedback.color = new Color(0xf7 / 255.0f, 0xad / 255.0f, 0x19 / 255.0f); - } - else if (mehCenter - mehRange / 2 <= x && x <= mehCenter + mehRange / 2) - { - timingFeedback.text = $"Bijna... \n +{mehScore}"; - imageFeedback.sprite = mehSprite; - mehSigns++; - timingFeedback.color = new Color(0xf2 / 255.0f, 0x7f / 255.0f, 0x0c / 255.0f); - } - else - { - timingFeedback.text = $"Te vroeg! \n {terribleScore}"; - imageFeedback.sprite = terribleSprite; - terribleSigns++; - timingFeedback.color = new Color(0xf5 / 255.0f, 0x49 / 255.0f, 0x3d / 255.0f); - } - - DestroySymbolAt(matchedSymbolIndex); - } - } + DestroySymbolAt(0); } - else if (feedbackProgressBar != null) - { - - feedbackProgressBar.value = 0.0f; - } - yield return null; } } diff --git a/Assets/JustSign/Scripts/JustSignGameEndedPanel.cs b/Assets/JustSign/Scripts/JustSignGameEndedPanel.cs new file mode 100644 index 0000000..f54de98 --- /dev/null +++ b/Assets/JustSign/Scripts/JustSignGameEndedPanel.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using TMPro; +using UnityEngine; +using UnityEngine.UI; +/// +/// The JustSign-variant of the ScoreBoard +/// +public class JustSignGameEndedPanel : AbstractGameEndedPanel +{ + /// + /// Tell the scoreboard that the scoreboard is for JustSign + /// + protected override MinigameIndex minigameIndex + { + get { return MinigameIndex.JUST_SIGN; } + } + + /// + /// The field that will display the amount of perfect signs + /// + public TMP_Text perfectSignsText; + + /// + /// The field that will display the amount of good signs + /// + public TMP_Text goodSignsText; + + /// + /// The field that will display the amount of meh signs + /// + public TMP_Text mehSignsText; + + /// + /// The field that will display the amount of terrible signs + /// + public TMP_Text terribleSignsText; + + /// + /// The field that will display the amount of not found signs + /// + public TMP_Text notFoundSignsText; + + /// + /// The field that will display the signs per minute + /// + public TMP_Text gpmText; + + /// + /// Score + /// + public TMP_Text scoreText; + + /// + /// Generate the content of the gameEnded panel + /// + /// The amount of perfect signs + /// The amount of good signs + /// The amount of meh signs + /// The emount of terrible signs + /// The amount of incorrect signs + /// The duration of the song that was played + /// The score obtained by the player + public void GenerateContent(int perfectSigns, int goodSigns, int mehSigns, int terribleSigns, int incorrectSigns, int duration, int score) + { + // In de zone + perfectSignsText.text = perfectSigns.ToString(); + + // Aanvaardbaar + goodSignsText.text = goodSigns.ToString(); + + // Nipt + mehSignsText.text = mehSigns.ToString(); + + // Slechte timing + terribleSignsText.text = terribleSigns.ToString(); + + // Niet Geraden + notFoundSignsText.text = incorrectSigns.ToString(); + + // LPM + int correctSigns = goodSigns + perfectSigns + mehSigns + terribleSigns; + gpmText.text = (60f * correctSigns / duration).ToString("#") + " GPM"; + + // Score + scoreText.text = $"Score: {score}"; + SetScoreBoard(); + } +} diff --git a/Assets/JustSign/Scripts/JustSignGameEndedPanel.cs.meta b/Assets/JustSign/Scripts/JustSignGameEndedPanel.cs.meta new file mode 100644 index 0000000..8ceccbe --- /dev/null +++ b/Assets/JustSign/Scripts/JustSignGameEndedPanel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9a86c239be1aa1543ba8a4ace5f658b1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/JustSign/Scripts/JustSignScripts.asmdef b/Assets/JustSign/Scripts/JustSignScripts.asmdef index dba74d9..8db4bcf 100644 --- a/Assets/JustSign/Scripts/JustSignScripts.asmdef +++ b/Assets/JustSign/Scripts/JustSignScripts.asmdef @@ -7,7 +7,8 @@ "GUID:1631ed2680c61245b8211d943c1639a8", "GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25", "GUID:58e104b97fb3752438ada2902a36dcbf", - "GUID:d0b6b39a21908f94fbbd9f2c196a9725" + "GUID:d0b6b39a21908f94fbbd9f2c196a9725", + "GUID:403dd94a93598934eb522dc36df43d7b" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/MediaPipeUnity/Scripts/SignPredictor.cs b/Assets/MediaPipeUnity/Scripts/SignPredictor.cs index 6e4a5a3..ba4d1ab 100644 --- a/Assets/MediaPipeUnity/Scripts/SignPredictor.cs +++ b/Assets/MediaPipeUnity/Scripts/SignPredictor.cs @@ -297,6 +297,7 @@ public class SignPredictor : MonoBehaviour /// private static bool resourceManagerIsInitialized = false; + private List signs; private EmbeddingDataList embeddingDataList; private ModelIndex modelID; @@ -521,15 +522,6 @@ public class SignPredictor : MonoBehaviour { learnableProbabilities = new Dictionary(); - // Temporary fix - List signs = new List() - { - "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", - "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" - }; - - - for (int j = 0; j < result.Count; j++) { learnableProbabilities.Add(signs[j].ToUpper(), result[j]); @@ -715,4 +707,8 @@ public class SignPredictor : MonoBehaviour } } + public void SetSignsList(List signs) + { + this.signs = signs; + } } diff --git a/Assets/Minigames.meta b/Assets/Minigames.meta new file mode 100644 index 0000000..c8b700b --- /dev/null +++ b/Assets/Minigames.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 353d02d53aeb14341835f87efae01039 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Minigames/Scripts.meta b/Assets/Minigames/Scripts.meta new file mode 100644 index 0000000..9fd5eb6 --- /dev/null +++ b/Assets/Minigames/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d5e1253d871fce14ab568e5c9ad4ced2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/SpellingBee/Scripts/GameEndedPanel.cs b/Assets/Minigames/Scripts/AbstractGameEndedPanel.cs similarity index 60% rename from Assets/SpellingBee/Scripts/GameEndedPanel.cs rename to Assets/Minigames/Scripts/AbstractGameEndedPanel.cs index 096da0c..905533e 100644 --- a/Assets/SpellingBee/Scripts/GameEndedPanel.cs +++ b/Assets/Minigames/Scripts/AbstractGameEndedPanel.cs @@ -4,50 +4,19 @@ using System.Linq; using TMPro; using UnityEngine; using UnityEngine.UI; - -public class GameEndedPanel : MonoBehaviour +/// +/// Abstract class for all minigame-gameEndedPanels +/// +public abstract class AbstractGameEndedPanel : MonoBehaviour { /// - /// "VERLOREN" or "GEWONNEN" + /// The index of minigame that needs a GameEndedPanel /// - public TMP_Text endText; - - /// - /// LPM - /// - public TMP_Text lpmText; - - /// - /// Letters ( right | wrong ) - /// - public TMP_Text lettersRightText; - public TMP_Text lettersWrongText; - - /// - /// Letters - /// - public TMP_Text lettersTotalText; - - /// - /// Accuracy - /// - public TMP_Text accuracyText; - - /// - /// Words - /// - public TMP_Text wordsText; - - /// - /// Time - /// - public TMP_Text timeText; - - /// - /// Score - /// - public TMP_Text scoreText; - + protected abstract MinigameIndex minigameIndex + { + get; + } + /// /// Reference to the scoreboard entries container /// @@ -63,55 +32,10 @@ public class GameEndedPanel : MonoBehaviour /// public GameObject scoreboardEntry; - /// - /// Generate the content of the GameEnded panel - /// - /// Time of starting the minigame - /// Total number of words - /// Total number of correctly spelled letters - /// Total number of incorrectly spelled letters - /// "VERLOREN" or "GEWONNEN" - /// Final score - public void GenerateContent(DateTime startTime, int totalWords, int correctLetters, int incorrectLetters, string result, int score) - { - // Final result - endText.text = result; - - // LPM - TimeSpan duration = DateTime.Now.Subtract(startTime); - lpmText.text = (60f * correctLetters / duration.TotalSeconds).ToString("#") + " LPM"; - - // Letters ( right | wrong ) total - lettersRightText.text = correctLetters.ToString(); - lettersWrongText.text = incorrectLetters.ToString(); - lettersTotalText.text = (correctLetters + incorrectLetters).ToString(); - - // Accuracy - if (correctLetters + incorrectLetters > 0) - { - accuracyText.text = ((correctLetters) * 100f / (correctLetters + incorrectLetters)).ToString("#.##") + "%"; - } - else - { - accuracyText.text = "-"; - } - - // Words - wordsText.text = $"{totalWords}"; - - // Time - timeText.text = duration.ToString(@"mm\:ss"); - - // Score - scoreText.text = $"Score: {score}"; - SetScoreBoard(); - } - - /// /// Sets the scoreboard /// - private void SetScoreBoard() + protected void SetScoreBoard() { // Clean the previous scoreboard entries for (int i = 0; i < scoreboardEntries.Count; i++) @@ -126,7 +50,7 @@ public class GameEndedPanel : MonoBehaviour foreach (User user in UserList.GetUsers()) { // Get user's progress for this minigame - var progress = user.GetMinigameProgress(MinigameIndex.SPELLING_BEE); + var progress = user.GetMinigameProgress(minigameIndex); if (progress != null) { // Add scores to dictionary diff --git a/Assets/Minigames/Scripts/AbstractGameEndedPanel.cs.meta b/Assets/Minigames/Scripts/AbstractGameEndedPanel.cs.meta new file mode 100644 index 0000000..def7668 --- /dev/null +++ b/Assets/Minigames/Scripts/AbstractGameEndedPanel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 73d452eb6e118ec4091d6cdd82f3550c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Minigames/Scripts/AbstractMinigameController.cs b/Assets/Minigames/Scripts/AbstractMinigameController.cs new file mode 100644 index 0000000..c888f8d --- /dev/null +++ b/Assets/Minigames/Scripts/AbstractMinigameController.cs @@ -0,0 +1,221 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.UI; +/// +/// Shared abstract class for the minigameControllers +/// +public abstract class AbstractMinigameController : AbstractFeedback +{ + [Header("AbstractVariables")] + /// + /// We keep the minigamelist so that the minigame-index doesn't get reset + /// DO NOT REMOVE + /// + public MinigameList minigamelist; + + /// + /// A bool to denote whether or not the game is still being played + /// + protected bool gameIsActive; + + /// + /// Reference to the progress bar + /// + public Slider feedbackProgress; + + /// + /// Reference to the current user + /// + private User user; + + /// + /// Reference to the minigame ScriptableObject + /// + protected Minigame minigame; + + /// + /// Each minigame has a webcamTexture, this will be used in children-methods + /// + public RawImage webcamScreen; + + /// + /// Reference to the gameEnded panel, so we can update its display + /// + public GameObject gameEndedPanel; + + /// + /// The theme that will be used by the signpredictor, this needs to be passed from the concrete class. + /// This theme CAN be different from the theme that words are fetched from (Think SpellingBee and Hangman) + /// + protected abstract Theme signPredictorTheme + { + get; + } + + /// + /// Start is called before the first frame update, seal it to prevent minigames from changing it + /// + protected void Start() + { + // Get the scriptable of the current minigame + minigame = minigamelist.minigames[minigamelist.currentMinigameIndex]; + + // Start the game-specific start-logic + StartController(); + + // Prepare the signPredictor + signPredictor.SetModel(signPredictorTheme.modelIndex); + signPredictor.SwapScreen(webcamScreen); + signPredictor.SetSignsList(GetSignsList()); + AddSelfAsListener(); + } + + /// + /// All minigames use the same principle, they grab the most probable sign and use said sign to show feedback to the user + /// Because we don't want minigames to write their own UpdateFeedbacks this function will be sealed + /// + /// + /// + protected override sealed IEnumerator UpdateFeedback() + { + // Get the predicted sign + if (signPredictor != null && signPredictor.learnableProbabilities != null && gameIsActive) + { + // Get highest predicted sign + string predictedSign = signPredictor.learnableProbabilities.Aggregate((a, b) => a.Value > b.Value ? a : b).Key; + float accuracy = signPredictor.learnableProbabilities[predictedSign]; + + // vvv TEMPORARY STUFF vvv + if (predictedSign == "J" && accuracy <= 0.97f) + { + predictedSign = signPredictor.learnableProbabilities.Aggregate((x, y) => x.Value > y.Value && x.Key != "J" ? x : y).Key; + } + accuracy = signPredictor.learnableProbabilities[predictedSign]; + // ^^^ TEMPORARY STUFF ^^^ + + ProcessMostProbableSign(accuracy, predictedSign); + } + + // This part is the only reason that feedbackProgress is needed in the abstract + else if (feedbackProgress != null) + { + + feedbackProgress.value = 0.0f; + } + + yield return null; + } + + /// + /// Each game keeps a score, this score needs to be saved at some point + /// + public void SaveScores() + { + // Calculate new score + int newScore = CalculateScore(); + + // Save the score as a tuple: < int score, string time ago> + Score score = new Score(); + score.scoreValue = newScore; + score.time = DateTime.Now.ToString(); + + // Save the new score + var progress = user.GetMinigameProgress(minigame.index); + + // Get the current list of scores + List latestScores = progress.latestScores; + List highestScores = progress.highestScores; + + // Add the new score + latestScores.Add(score); + highestScores.Add(score); + + // Sort the scores + highestScores.Sort((a, b) => b.scoreValue.CompareTo(a.scoreValue)); + + // Only save the top 10 scores, so this list doesn't keep growing endlessly + progress.latestScores = latestScores.Take(10).ToList(); + progress.highestScores = highestScores.Take(10).ToList(); + + PersistentDataController.GetInstance().Save(); + } + /// + /// The function that activates when the game ends, handles some endgame logic and displays the EndPanel + /// + /// + public void ActivateEnd(bool victory) + { + EndGameLogic(victory); + SaveScores(); + SetScoreBoard(victory); + + gameEndedPanel.SetActive(true); + } + + /// + /// Once the most probable sign has been fetched, they can be processed + /// + /// The accuracy of the passed sign + /// The name of the passed sign + protected abstract void ProcessMostProbableSign(float accuracy, string predictedSign); + + /// + /// Each minigame has their own way of calculating their score + /// + /// The score that the user has at that point + public abstract int CalculateScore(); + + /// + /// Each minigame has an AbstractGameEndedPanel at the end, but they each have their own unique concrete instance + /// + /// 1 if the player won, 0 if they lost. Some games need this + protected abstract void SetScoreBoard(bool victory); + + /// + /// Each minigame puts their GameLogic to be called at (re)start in a seperate function from Start() + /// + public void StartController() + { + StartGameLogic(); + + // Create entry in current user for keeping track of progress + user = UserList.GetCurrentUser(); + var progress = user.GetMinigameProgress(minigame.index); + if (progress == null) + { + progress = new PersistentDataController.SavedMinigameProgress(); + progress.minigameIndex = minigame.index; + user.AddMinigameProgress(progress); + } + UserList.Save(); + } + + /// + /// Logic to be called at the start of the game + /// + protected abstract void StartGameLogic(); + + /// + /// Function that contains all the logic to end the game + /// + /// 1 if the player won, 0 if they lost. Some games need this + protected abstract void EndGameLogic(bool victory); + + /// + /// All non-fingerspelling-minigames have the same logic for the GetSignsList + /// + /// The signsList that needs to be passed to the signPredictor + private List GetSignsList() + { + List signsList = new List(); + foreach (Learnable learnable in signPredictorTheme.learnables) + { + signsList.Add(learnable.name); + } + + return signsList; + } +} diff --git a/Assets/Minigames/Scripts/AbstractMinigameController.cs.meta b/Assets/Minigames/Scripts/AbstractMinigameController.cs.meta new file mode 100644 index 0000000..a91101a --- /dev/null +++ b/Assets/Minigames/Scripts/AbstractMinigameController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a32c0ecc5507e4542a79c1b96a47b0a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Minigames/Scripts/MinigameScripts.asmdef b/Assets/Minigames/Scripts/MinigameScripts.asmdef new file mode 100644 index 0000000..96a7380 --- /dev/null +++ b/Assets/Minigames/Scripts/MinigameScripts.asmdef @@ -0,0 +1,20 @@ +{ + "name": "MinigameScripts", + "rootNamespace": "", + "references": [ + "GUID:d0b6b39a21908f94fbbd9f2c196a9725", + "GUID:e83ddf9a537a96b4a804a16bb7872ec1", + "GUID:1631ed2680c61245b8211d943c1639a8", + "GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25", + "GUID:6055be8ebefd69e48b49212b09b47b2f" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Minigames/Scripts/MinigameScripts.asmdef.meta b/Assets/Minigames/Scripts/MinigameScripts.asmdef.meta new file mode 100644 index 0000000..9b1396f --- /dev/null +++ b/Assets/Minigames/Scripts/MinigameScripts.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 403dd94a93598934eb522dc36df43d7b +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/SpellingBee/PlayModeTests/GameEndedPanelTests.cs b/Assets/SpellingBee/PlayModeTests/GameEndedPanelTests.cs index 307413d..df4428a 100644 --- a/Assets/SpellingBee/PlayModeTests/GameEndedPanelTests.cs +++ b/Assets/SpellingBee/PlayModeTests/GameEndedPanelTests.cs @@ -5,7 +5,7 @@ using UnityEditor; using UnityEngine; using UnityEngine.TestTools; -public class GameEndedPanelTests +public class SpellingBeeGameEndedPanelTests { [UnitySetUp] public IEnumerator SetupFunction() @@ -31,15 +31,15 @@ public class GameEndedPanelTests yield return new WaitForSeconds(1f); - spellingBeeController.ActivateWin(); + spellingBeeController.ActivateEnd(true); - GameEndedPanel gameEndedPanel = (GameEndedPanel)GameObject.FindObjectOfType(typeof(GameEndedPanel)); - Assert.NotNull(gameEndedPanel); - Assert.AreEqual("Score: 0", gameEndedPanel.scoreText.text); - Assert.AreEqual("1", gameEndedPanel.lettersRightText.text); - Assert.AreEqual("2", gameEndedPanel.lettersWrongText.text); - Assert.AreEqual("3", gameEndedPanel.lettersTotalText.text); - Assert.AreEqual("00:01", gameEndedPanel.timeText.text); + SpellingBeeGameEndedPanel SpellingBeeGameEndedPanel = (SpellingBeeGameEndedPanel)GameObject.FindObjectOfType(typeof(SpellingBeeGameEndedPanel)); + Assert.NotNull(SpellingBeeGameEndedPanel); + Assert.AreEqual("Score: 0", SpellingBeeGameEndedPanel.scoreText.text); + Assert.AreEqual("1", SpellingBeeGameEndedPanel.lettersRightText.text); + Assert.AreEqual("2", SpellingBeeGameEndedPanel.lettersWrongText.text); + Assert.AreEqual("3", SpellingBeeGameEndedPanel.lettersTotalText.text); + Assert.AreEqual("00:01", SpellingBeeGameEndedPanel.timeText.text); } } diff --git a/Assets/SpellingBee/PlayModeTests/SpellingBeeControllerTests.cs b/Assets/SpellingBee/PlayModeTests/SpellingBeeControllerTests.cs index b42d5c0..d8db61d 100644 --- a/Assets/SpellingBee/PlayModeTests/SpellingBeeControllerTests.cs +++ b/Assets/SpellingBee/PlayModeTests/SpellingBeeControllerTests.cs @@ -36,12 +36,12 @@ public class SpellingBeeControllerTests public IEnumerator ActivateGameOverTest() { SpellingBeeController spellingBeeController = (SpellingBeeController)GameObject.FindObjectOfType(typeof(SpellingBeeController)); - spellingBeeController.ActivateGameOver(); + spellingBeeController.ActivateEnd(false); yield return new WaitForSeconds(0.2f); - GameEndedPanel gameEndedPanel = (GameEndedPanel)GameObject.FindObjectOfType(typeof(GameEndedPanel)); - Assert.NotNull(gameEndedPanel); - Assert.AreEqual("VERLOREN", gameEndedPanel.endText.text); + SpellingBeeGameEndedPanel SpellingBeeGameEndedPanel = (SpellingBeeGameEndedPanel)GameObject.FindObjectOfType(typeof(SpellingBeeGameEndedPanel)); + Assert.NotNull(SpellingBeeGameEndedPanel); + Assert.AreEqual("VERLOREN", SpellingBeeGameEndedPanel.endText.text); } @@ -49,12 +49,12 @@ public class SpellingBeeControllerTests public IEnumerator ActivateWinTests() { SpellingBeeController spellingBeeController = (SpellingBeeController)GameObject.FindObjectOfType(typeof(SpellingBeeController)); - spellingBeeController.ActivateWin(); + spellingBeeController.ActivateEnd(true); yield return new WaitForSeconds(0.2f); - GameEndedPanel gameEndedPanel = (GameEndedPanel)GameObject.FindObjectOfType(typeof(GameEndedPanel)); - Assert.NotNull(gameEndedPanel); - Assert.AreEqual("GEWONNEN", gameEndedPanel.endText.text); + SpellingBeeGameEndedPanel SpellingBeeGameEndedPanel = (SpellingBeeGameEndedPanel)GameObject.FindObjectOfType(typeof(SpellingBeeGameEndedPanel)); + Assert.NotNull(SpellingBeeGameEndedPanel); + Assert.AreEqual("GEWONNEN", SpellingBeeGameEndedPanel.endText.text); } [UnityTest] @@ -64,8 +64,8 @@ public class SpellingBeeControllerTests spellingBeeController.AddSeconds(-60); yield return new WaitForSeconds(0.1f); - GameEndedPanel gameEndedPanel = (GameEndedPanel)GameObject.FindObjectOfType(typeof(GameEndedPanel)); - Assert.NotNull(gameEndedPanel); - Assert.AreEqual("VERLOREN", gameEndedPanel.endText.text); + SpellingBeeGameEndedPanel SpellingBeeGameEndedPanel = (SpellingBeeGameEndedPanel)GameObject.FindObjectOfType(typeof(SpellingBeeGameEndedPanel)); + Assert.NotNull(SpellingBeeGameEndedPanel); + Assert.AreEqual("VERLOREN", SpellingBeeGameEndedPanel.endText.text); } } diff --git a/Assets/SpellingBee/PlayModeTests/SpellingBeePlayModeTests.asmdef b/Assets/SpellingBee/PlayModeTests/SpellingBeePlayModeTests.asmdef index 4c3d59e..7c82f99 100644 --- a/Assets/SpellingBee/PlayModeTests/SpellingBeePlayModeTests.asmdef +++ b/Assets/SpellingBee/PlayModeTests/SpellingBeePlayModeTests.asmdef @@ -9,7 +9,8 @@ "SpellingBeeScripts", "AccountsScripts", "SystemArchitecture", - "SignPredictor" + "SignPredictor", + "MinigameScripts" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/SpellingBee/Scenes/SpellingBeeGame.unity b/Assets/SpellingBee/Scenes/SpellingBeeGame.unity index ec22d51..4d0fafa 100644 --- a/Assets/SpellingBee/Scenes/SpellingBeeGame.unity +++ b/Assets/SpellingBee/Scenes/SpellingBeeGame.unity @@ -1432,6 +1432,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 5aa929dce1f59b340b4a0cca1bb68edc, type: 3} m_Name: m_EditorClassIdentifier: + scoreboardEntriesContainer: {fileID: 1499197559} + scoreboardEntry: {fileID: 9154151134820372555, guid: d4a3a228b08d61847acc6da35b44e52c, type: 3} endText: {fileID: 1502459770} lpmText: {fileID: 1172084829} lettersRightText: {fileID: 994850063} @@ -1441,8 +1443,6 @@ MonoBehaviour: wordsText: {fileID: 1754130538} timeText: {fileID: 1052827058} scoreText: {fileID: 653157662} - scoreboardEntriesContainer: {fileID: 1499197559} - scoreboardEntry: {fileID: 9154151134820372555, guid: d4a3a228b08d61847acc6da35b44e52c, type: 3} --- !u!1 &778704239 GameObject: m_ObjectHideFlags: 0 @@ -3982,21 +3982,20 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: signPredictor: {fileID: 1592592444} + minigamelist: {fileID: 11400000, guid: 51453f9b41bc72f468ba3e67ab622f8f, type: 2} + feedbackProgress: {fileID: 967164046} + webcamScreen: {fileID: 1743003084} + gameEndedPanel: {fileID: 757133117} themeList: {fileID: 11400000, guid: a247e2ce790f0f746a3bc521e6ab7d58, type: 2} fingerspelling: {fileID: 11400000, guid: e02921b294fdad940b6e4d57e716d3bf, type: 2} - minigame: {fileID: 11400000, guid: 8a087d241d652634eb4f6352267ea7dc, type: 2} - minigamelist: {fileID: 11400000, guid: 51453f9b41bc72f468ba3e67ab622f8f, type: 2} letterPrefab: {fileID: 4639383499500021565, guid: c3e66e8957864914cb022af914df6a28, type: 3} letterContainer: {fileID: 1346005056} wordImage: {fileID: 1338727891} timerText: {fileID: 1843239267} bonusTimeText: {fileID: 1812475780} Scoreboard: {fileID: 862382568} - gameEndedPanel: {fileID: 757133117} feedbackText: {fileID: 967164047} - feedbackProgress: {fileID: 967164046} feedbackProgressImage: {fileID: 967164045} - webcamScreen: {fileID: 1743003084} scoreDisplay: {fileID: 1985911006} scoreBonus: {fileID: 1130901870} --- !u!1 &1499197558 diff --git a/Assets/SpellingBee/Scripts/SpellingBeeController.cs b/Assets/SpellingBee/Scripts/SpellingBeeController.cs index 2485370..37f41b2 100644 --- a/Assets/SpellingBee/Scripts/SpellingBeeController.cs +++ b/Assets/SpellingBee/Scripts/SpellingBeeController.cs @@ -7,7 +7,7 @@ using TMPro; using UnityEngine; using UnityEngine.UI; -public partial class SpellingBeeController : AbstractFeedback +public partial class SpellingBeeController : AbstractMinigameController { /// /// All of the words that can be used in this session @@ -45,11 +45,6 @@ public partial class SpellingBeeController : AbstractFeedback /// private float timerValue; - /// - /// Indicates if the game is still going - /// - private bool gameEnded; - /// /// List of learnables to get the threshold for the letters /// @@ -81,22 +76,6 @@ public partial class SpellingBeeController : AbstractFeedback /// private DateTime startTime; - /// - /// Reference to the current user - /// - private User user; - - /// - /// Reference to the minigame ScriptableObject - /// - public Minigame minigame; - - /// - /// We keep the minigamelist as well so that the minigame-index doesn't get reset - /// DO NOT REMOVE - /// - public MinigameList minigamelist; - /// /// Letter prefab /// @@ -137,21 +116,11 @@ public partial class SpellingBeeController : AbstractFeedback /// public Transform Scoreboard; - /// - /// Reference to the gameEnded panel, so we can update its display - /// - public GameObject gameEndedPanel; - /// /// Reference to the feedback field /// public TMP_Text feedbackText; - /// - /// Reference to the progress bar - /// - public Slider feedbackProgress; - /// /// Reference to the progress bar image, so we can add fancy colors /// @@ -172,11 +141,6 @@ public partial class SpellingBeeController : AbstractFeedback /// protected string previousIncorrectSign = null; - /// - /// Reference used to set the webcam for the SignPredictor - /// - public RawImage webcamScreen; - /// /// Reference to display the score /// @@ -198,57 +162,11 @@ public partial class SpellingBeeController : AbstractFeedback private int incorrectLettersScore = -5; /// - /// Start is called before the first frame update + /// Set the AbstractMinigameController variable to inform it of the theme for the signPredictor /// - public void Start() + protected override Theme signPredictorTheme { - signPredictor.SetModel(ModelIndex.FINGERSPELLING); - signPredictor.SwapScreen(webcamScreen); - AddSelfAsListener(); - - StartController(); - } - - /// - /// Is called at the start of the scene AND when the game is replayed - /// - public void StartController() - { - correctLetters = 0; - incorrectLetters = 0; - - words.Clear(); - // We use -1 instead of 0 so SetNextWord can simply increment it each time - spelledWords = -1; - wordIndex = 0; - - gameEnded = false; - timerValue = 30.0f; - bonusActiveRemaining = 0.0f; - startTime = DateTime.Now; - - gameEndedPanel.SetActive(false); - bonusTimeText.SetActive(false); - - // Create entry in current user for keeping track of progress - user = UserList.GetCurrentUser(); - var progress = user.GetMinigameProgress(minigame.index); - if (progress == null) - { - progress = new PersistentDataController.SavedMinigameProgress(); - progress.minigameIndex = MinigameIndex.SPELLING_BEE; - user.AddMinigameProgress(progress); - } - UserList.Save(); - - currentTheme = minigame.themeList.themes[minigame.themeList.currentThemeIndex]; - //feedback.signPredictor.SetModel(currentTheme.modelIndex); - words.AddRange(currentTheme.learnables); - ShuffleWords(); - NextWord(); - - scoreDisplay.text = $"Score: {CalculateScore()}"; - scoreBonus.text = ""; + get { return fingerspelling; } } /// @@ -256,7 +174,7 @@ public partial class SpellingBeeController : AbstractFeedback /// public void Update() { - if (!gameEnded) + if (gameIsActive) { timerValue -= Time.deltaTime; if (bonusActiveRemaining <= 0.0 && bonusTimeText.activeSelf) @@ -272,7 +190,8 @@ public partial class SpellingBeeController : AbstractFeedback if (timerValue <= 0.0f) { timerValue = 0.0f; - ActivateGameOver(); + //ActivateGameOver(); + ActivateEnd(false); } int minutes = Mathf.FloorToInt(timerValue / 60.0f); @@ -302,89 +221,11 @@ public partial class SpellingBeeController : AbstractFeedback /// Calculate the score /// /// The calculated score - public int CalculateScore() + public override int CalculateScore() { return correctLetters * correctLettersScore + incorrectLetters * incorrectLettersScore; } - /// - /// Displays the game over panel and score values - /// - public void ActivateGameOver() - { - gameEnded = true; - DeleteWord(); - - // Save the scores and show the scoreboard - SaveScores(); - gameEndedPanel.GetComponent().GenerateContent( - startTime: startTime, - totalWords: spelledWords, - correctLetters: correctLetters, - incorrectLetters: incorrectLetters, - result: "VERLOREN", - score: CalculateScore() - ); - - gameEndedPanel.SetActive(true); - } - - /// - /// Display win screen - /// - public void ActivateWin() - { - gameEnded = true; - DeleteWord(); - - // Save the scores and show the scoreboard - SaveScores(); - gameEndedPanel.GetComponent().GenerateContent( - startTime: startTime, - totalWords: spelledWords, - correctLetters: correctLetters, - incorrectLetters: incorrectLetters, - result: "GEWONNEN", - score: CalculateScore() - ); - - gameEndedPanel.SetActive(true); - } - - /// - /// Update and save the scores - /// - public void SaveScores() - { - // Calculate new score - int newScore = CalculateScore(); - - // Save the score as a tuple: < int score, string time ago> - Score score = new Score(); - score.scoreValue = newScore; - score.time = DateTime.Now.ToString(); - - // Save the new score - var progress = user.GetMinigameProgress(minigame.index); - - // Get the current list of scores - List latestScores = progress.latestScores; - List highestScores = progress.highestScores; - - // Add the new score - latestScores.Add(score); - highestScores.Add(score); - - // Sort the scores - highestScores.Sort((a, b) => b.scoreValue.CompareTo(a.scoreValue)); - - // Only save the top 10 scores, so this list doesn't keep growing endlessly - progress.latestScores = latestScores.Take(10).ToList(); - progress.highestScores = highestScores.Take(10).ToList(); - - UserList.Save(); - } - /// /// Delete all letter objects /// @@ -414,7 +255,7 @@ public partial class SpellingBeeController : AbstractFeedback /// true if the letter was correctly signed, false otherwise public void NextLetter(bool successful) { - if (gameEnded) { return; } + if (!gameIsActive) { return; } // Change color of current letter (skip spaces) if (successful) @@ -469,7 +310,8 @@ public partial class SpellingBeeController : AbstractFeedback } else { - ActivateWin(); + //ActivateWin(); + ActivateEnd(true); } } @@ -514,7 +356,7 @@ public partial class SpellingBeeController : AbstractFeedback Learnable letter = fingerspelling.learnables.Find(l => l.name == sign); return letter.thresholdPercentage; } - + /* /// /// The updateFunction that is called when new probabilities become available /// @@ -525,7 +367,7 @@ public partial class SpellingBeeController : AbstractFeedback string currentSign = GetSign(); // Get the predicted sign if (signPredictor != null && signPredictor.learnableProbabilities != null && - currentSign != null && signPredictor.learnableProbabilities.ContainsKey(currentSign)) + currentSign != null && signPredictor.learnableProbabilities.ContainsKey(currentSign) && gameIsActive) { float accCurrentSign = signPredictor.learnableProbabilities[currentSign]; float thresholdCurrentSign = GetTresholdPercentage(currentSign); @@ -613,6 +455,7 @@ public partial class SpellingBeeController : AbstractFeedback } yield return null; } + */ /// /// Function to get the current letter that needs to be signed /// @@ -638,4 +481,157 @@ public partial class SpellingBeeController : AbstractFeedback } NextLetter(successful); } + + /// + /// The logic to process the signs sent by the signPredictor + /// + /// The accuracy of the passed sign + /// The name of the passed sign + protected override void ProcessMostProbableSign(float accuracy, string predictedSign) + { + string currentSign = GetSign(); + float accPredictSign = accuracy; + float accCurrentSign = signPredictor.learnableProbabilities[currentSign]; + float thresholdCurrentSign = GetTresholdPercentage(currentSign); + float thresholdPredictedSign = GetTresholdPercentage(predictedSign); + + // If there is a feedback-object, we wil change its appearance + if (feedbackText != null && feedbackProgressImage != null) + { + Color col; + if (accPredictSign > thresholdCurrentSign) + { + feedbackText.text = "Goed"; + col = new Color(0x8b / 255.0f, 0xd4 / 255.0f, 0x5e / 255.0f); + } + else if (accCurrentSign > 0.9 * thresholdCurrentSign) + { + feedbackText.text = "Bijna..."; + col = new Color(0xf2 / 255.0f, 0x7f / 255.0f, 0x0c / 255.0f); + } + else if (accPredictSign > thresholdPredictedSign) + { + feedbackText.text = $"Verkeerde gebaar: '{predictedSign}'"; + col = new Color(0xf5 / 255.0f, 0x49 / 255.0f, 0x3d / 255.0f); + accCurrentSign = 0.0f; + } + else + { + feedbackText.text = "Detecteren..."; + col = new Color(0xf5 / 255.0f, 0x49 / 255.0f, 0x3d / 255.0f); + } + + feedbackText.color = col; + feedbackProgressImage.color = col; + + float oldValue = feedbackProgress.value; + // use an exponential scale + float newValue = Mathf.Exp(4 * (Mathf.Clamp(accCurrentSign / thresholdCurrentSign, 0.0f, 1.0f) - 1.0f)); + feedbackProgress.gameObject.Tween("FeedbackUpdate", oldValue, newValue, 0.2f, TweenScaleFunctions.CubicEaseInOut, (t) => + { + if (feedbackProgress != null) + { + feedbackProgress.value = t.CurrentValue; + } + }); + } + + // The logic for the internal workings of the game + if (accPredictSign > thresholdPredictedSign) + { + // Correct sign, instantly pass it along + if (predictedSign == currentSign) + { + PredictSign(predictedSign); + timer = DateTime.Now; + predictedSign = null; + previousIncorrectSign = null; + } + + // Incorrect sign, wait a bit before passing it along + else + { + if (previousIncorrectSign != predictedSign) + { + timer = DateTime.Now; + previousIncorrectSign = predictedSign; + } + else if (DateTime.Now - timer > TimeSpan.FromSeconds(2.0f)) + { + PredictSign(predictedSign); + timer = DateTime.Now; + predictedSign = null; + previousIncorrectSign = null; + } + } + + } + } + + /// + /// The logic to set the scoreboard of spellingbee + /// + /// SHows whether or not the player won + protected override void SetScoreBoard(bool victory) + { + string resultTxt; + if (victory) + { + resultTxt = "GEWONNEN"; + } + else + { + resultTxt = "VERLOREN"; + } + // Save the scores and show the scoreboard + gameEndedPanel.GetComponent().GenerateContent( + startTime: startTime, + totalWords: spelledWords, + correctLetters: correctLetters, + incorrectLetters: incorrectLetters, + result: resultTxt, + score: CalculateScore() + ); + } + + /// + /// The spellinbee-specific logic that needs to be called at the start of the game + /// + protected override void StartGameLogic() + { + correctLetters = 0; + incorrectLetters = 0; + + words.Clear(); + // We use -1 instead of 0 so SetNextWord can simply increment it each time + spelledWords = -1; + wordIndex = 0; + + gameIsActive = true; + timerValue = 30.0f; + bonusActiveRemaining = 0.0f; + startTime = DateTime.Now; + + gameEndedPanel.SetActive(false); + bonusTimeText.SetActive(false); + + currentTheme = minigame.themeList.themes[minigame.themeList.currentThemeIndex]; + //feedback.signPredictor.SetModel(currentTheme.modelIndex); + words.AddRange(currentTheme.learnables); + ShuffleWords(); + NextWord(); + + scoreDisplay.text = $"Score: {CalculateScore()}"; + scoreBonus.text = ""; + } + + /// + /// The spellingbee-specific logic that needs to be called at the end of a game + /// + /// + protected override void EndGameLogic(bool victory) + { + gameIsActive = false; + DeleteWord(); + } } diff --git a/Assets/SpellingBee/Scripts/SpellingBeeGameEndedPanel.cs b/Assets/SpellingBee/Scripts/SpellingBeeGameEndedPanel.cs new file mode 100644 index 0000000..de6c3d5 --- /dev/null +++ b/Assets/SpellingBee/Scripts/SpellingBeeGameEndedPanel.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +public class SpellingBeeGameEndedPanel : AbstractGameEndedPanel +{ + /// + /// Tell the scoreboard that the scoreboard is for SpellingBee + /// + protected override MinigameIndex minigameIndex + { + get { return MinigameIndex.SPELLING_BEE; } + } + + /// + /// "VERLOREN" or "GEWONNEN" + /// + public TMP_Text endText; + + /// + /// LPM + /// + public TMP_Text lpmText; + + /// + /// Letters ( right | wrong ) + /// + public TMP_Text lettersRightText; + public TMP_Text lettersWrongText; + + /// + /// Letters + /// + public TMP_Text lettersTotalText; + + /// + /// Accuracy + /// + public TMP_Text accuracyText; + + /// + /// Words + /// + public TMP_Text wordsText; + + /// + /// Time + /// + public TMP_Text timeText; + + /// + /// Score + /// + public TMP_Text scoreText; + + /// + /// Generate the content of the GameEnded panel + /// + /// Time of starting the minigame + /// Total number of words + /// Total number of correctly spelled letters + /// Total number of incorrectly spelled letters + /// "VERLOREN" or "GEWONNEN" + /// Final score + public void GenerateContent(DateTime startTime, int totalWords, int correctLetters, int incorrectLetters, string result, int score) + { + // Final result + endText.text = result; + + // LPM + TimeSpan duration = DateTime.Now.Subtract(startTime); + lpmText.text = (60f * correctLetters / duration.TotalSeconds).ToString("#") + " LPM"; + + // Letters ( right | wrong ) total + lettersRightText.text = correctLetters.ToString(); + lettersWrongText.text = incorrectLetters.ToString(); + lettersTotalText.text = (correctLetters + incorrectLetters).ToString(); + + // Accuracy + if (correctLetters + incorrectLetters > 0) + { + accuracyText.text = ((correctLetters) * 100f / (correctLetters + incorrectLetters)).ToString("#.##") + "%"; + } + else + { + accuracyText.text = "-"; + } + + // Words + wordsText.text = $"{totalWords}"; + + // Time + timeText.text = duration.ToString(@"mm\:ss"); + + // Score + scoreText.text = $"Score: {score}"; + SetScoreBoard(); + } +} diff --git a/Assets/SpellingBee/Scripts/GameEndedPanel.cs.meta b/Assets/SpellingBee/Scripts/SpellingBeeGameEndedPanel.cs.meta similarity index 100% rename from Assets/SpellingBee/Scripts/GameEndedPanel.cs.meta rename to Assets/SpellingBee/Scripts/SpellingBeeGameEndedPanel.cs.meta diff --git a/Assets/SpellingBee/Scripts/SpellingBeeScripts.asmdef b/Assets/SpellingBee/Scripts/SpellingBeeScripts.asmdef index 7c87746..bbf25b5 100644 --- a/Assets/SpellingBee/Scripts/SpellingBeeScripts.asmdef +++ b/Assets/SpellingBee/Scripts/SpellingBeeScripts.asmdef @@ -8,7 +8,8 @@ "GUID:d0b6b39a21908f94fbbd9f2c196a9725", "GUID:58e104b97fb3752438ada2902a36dcbf", "GUID:e83ddf9a537a96b4a804a16bb7872ec1", - "GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25" + "GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25", + "GUID:403dd94a93598934eb522dc36df43d7b" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 9466c19..8502e64 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -168,7 +168,8 @@ PlayerSettings: - {fileID: 0} - {fileID: 0} - {fileID: 0} - - {fileID: 11400000, guid: 57fdfdc7df920454ba35444c783867d8, type: 2} + - {fileID: 0} + - {fileID: 0} metroInputSource: 0 wsaTransparentSwapchain: 0 m_HolographicPauseOnTrackingLoss: 1