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 distance = signPredictor.learnableProbabilities[predictedSign]; ProcessMostProbableSign(distance, 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.ToUpper().Replace(" ", "-")); ; } return signsList; } }