using System; using System.Collections; using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine; using UnityEngine.UI; public partial class GameController : MonoBehaviour { /// /// All of the words that can be used in this session /// private string[] words; /// /// Where we currently are in the word /// private int letterIndex; /// /// Where we currently are in the word list /// private int wordIndex; /// /// The word that is currently being spelled /// private string currentWord; /// /// All of the available themes /// private ThemeList themeList; /// /// The theme we are currently using /// private Theme currentTheme; /// /// Current value of timer in seconds /// private float timerValue; /// /// "Game over" or "You win!" /// public TMP_Text endText; /// /// LPM /// public TMP_Text lpmText; /// /// Letters ( right | wrong ) /// public TMP_Text lettersRightText; public TMP_Text lettersWrongText; /// /// Letters /// public TMP_Text lettersText; /// /// Accuracy /// public TMP_Text accuracyText; /// /// Words /// public TMP_Text wordsText; /// /// Time /// public TMP_Text timeText; /// /// Score /// public TMP_Text scoreText; /// /// The game over panel /// public GameObject gameEndedPanel; /// /// Button for restarting the game /// public Button replayButton; /// /// Indicates if the game is still going /// private bool gameEnded; /// /// Amount of seconds user gets per letter of the current word /// Set to 1 for testing; should be increased later /// private int secondsPerLetter = 1; /// /// Counter that keeps track of how many letters have been spelled correctly /// private int correctLetters; /// /// Counter that keeps track of how many letters have been spelled incorrectly /// private int incorrectLetters; /// /// Counter that keeps track of how many words have been spelled correctly /// private int spelledWords; /// /// Timer that keeps track of when the game was started /// private DateTime startTime; /// /// Reference to the user list to access the current user /// public UserList userList; /// /// Reference to the current user /// private User user; /// /// Reference to the minigame progress of the current user /// private Progress progress = null; /// /// Reference to the minigame ScriptableObject /// public Minigame minigame; /// /// Letter prefab /// public GameObject letterPrefab; /// /// Reference to letter prefab /// public Transform letterContainer; /// /// The Image component for displaying the appropriate sprite /// public Image wordImage; /// /// Timer display /// public TMP_Text timerText; /// /// The GameObjects representing the letters /// private List letters = new List(); /// /// Reference to the scoreboard /// public Transform Scoreboard; /// /// Reference to the entries grid /// public Transform EntriesGrid; /// /// The GameObjects representing the letters /// private List entries = new List(); /// /// Reference to the ScoreboardEntry prefab /// public GameObject scoreboardEntry; /// /// Start is called before the first frame update /// public void Start() { correctLetters = 0; incorrectLetters = 0; // We use -1 instead of 0 so SetNextWord can simply increment it each time spelledWords = -1; gameEnded = false; wordIndex = 0; timerValue = 0.0f; startTime = DateTime.Now; gameEndedPanel.SetActive(false); replayButton.onClick.AddListener(Start); // Create entry in current user for keeping track of progress user = userList.GetCurrentUser(); progress = user.minigames.Find((p) => p != null && p.Get("minigameIndex") == minigame.index); if (progress == null) { progress = new Progress(); progress.AddOrUpdate("minigameIndex", MinigameIndex.SPELLING_BEE); progress.AddOrUpdate("highscore", 0); progress.AddOrUpdate>("scores", new List()); user.minigames.Add(progress); } userList.Save(); DeleteWord(); // TODO: change to ScriptableObject themeList = ThemeLoader.LoadJson(); currentTheme = FindThemeByName(PlayerPrefs.GetString("themeName")); words = currentTheme.words; ShuffleWords(); SetNextWord(); } /// /// Update is called once per frame /// public void Update() { if (!gameEnded) { // Get keyboard input // Check if the correct char has been given as input foreach (char c in Input.inputString) { if (Char.ToUpper(c) == Char.ToUpper(currentWord[letterIndex])) { // correct letter letters[letterIndex].GetComponent().color = Color.green; correctLetters++; letterIndex++; if (letterIndex >= currentWord.Length) { DeleteWord(); StartCoroutine(Wait()); SetNextWord(); } } else { // incorrect letter incorrectLetters++; } } timerValue -= Time.deltaTime; if (timerValue <= 0.0f) { timerValue = 0.0f; ActivateGameOver(); } int minutes = Mathf.FloorToInt(timerValue / 60.0f); int seconds = Mathf.FloorToInt(timerValue % 60.0f); timerText.text = string.Format("{0:00}:{1:00}", minutes, seconds); } } /// /// Randomly shuffle the list of words /// private void ShuffleWords() { for (int i = words.Length - 1; i > 0; i--) { // Generate a random index between 0 and i (inclusive) int j = UnityEngine.Random.Range(0, i + 1); // Swap the values at indices i and j (words[j], words[i]) = (words[i], words[j]); } } /// /// Calculate the score /// /// The calculated score private int CalculateScore() { return spelledWords * 5 + correctLetters; } /// /// Set score metrics /// private void SetScoreMetrics() { // 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(); lettersText.text = (correctLetters + incorrectLetters).ToString(); // Accuracy if (correctLetters + incorrectLetters > 0) { accuracyText.text = ((correctLetters) * 100f / (correctLetters + incorrectLetters)).ToString("#.##") + "%"; } else { accuracyText.text = "-"; } // Words wordsText.text = spelledWords.ToString(); // Time timeText.text = duration.ToString(@"mm\:ss"); // Score scoreText.text = "Score: " + CalculateScore().ToString(); } /// /// Displays the game over panel and score values /// private void ActivateGameOver() { DeleteWord(); endText.text = "GAME OVER"; SetScoreMetrics(); gameEndedPanel.SetActive(true); gameEndedPanel.transform.SetAsLastSibling(); gameEnded = true; // Save the scores and show the scoreboard SaveScores(); SetScoreBoard(); } /// /// Display win screen /// private void ActivateWin() { // @lukas stuff DeleteWord(); endText.text = "YOU WIN!"; SetScoreMetrics(); gameEndedPanel.SetActive(true); gameEndedPanel.transform.SetAsLastSibling(); gameEnded = true; // Save the scores and show the scoreboard SaveScores(); SetScoreBoard(); } /// /// Update and save the scores /// private void SaveScores() { // Calculate new score int newScore = spelledWords * 5 + correctLetters; // 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 user = userList.GetCurrentUser(); progress = user.minigames.Find((p) => p != null && p.Get("minigameIndex") == minigame.index); if (progress != null) { // Get the current list of scores List scores = progress.Get>("scores"); // Add the new score scores.Add(score); // Sort the scores scores.Sort((a, b) => b.scoreValue.CompareTo(a.scoreValue)); // Only save the top 10 scores, so this list doesn't keep growing endlessly progress.AddOrUpdate>("scores", scores.Take(10).ToList()); } // Update the highscore int highscore = progress.Get("highscore"); if (score.scoreValue < highscore) { progress.AddOrUpdate("highscore", score.scoreValue); } userList.Save(); } /// /// Sets the scoreboard /// private void SetScoreBoard() { // Clean the previous scoreboard entries for (int i = 0; i < entries.Count; i++) { Destroy(entries[i]); } entries.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 progress = user.minigames.Find((p) => p != null && p.Get("minigameIndex") == minigame.index); if (progress != null) { // Add scores to dictionary List scores = progress.Get>("scores"); foreach (Score score in scores) { allScores.Add(new Tuple(user.username, 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, EntriesGrid); entries.Add(entry); // Set the player icon entry.transform.Find("Image").GetComponent().sprite = userList.GetUserByUsername(username).avatar; // 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++; } } /// /// Delete all letter objects /// private void DeleteWord() { for (int i = 0; i < letters.Count; i++) { Destroy(letters[i]); } letters.Clear(); } /// /// Adds seconds to timer /// /// private void AddSeconds(int seconds) { timerValue += (float)seconds; } /// /// Find the chosen theme by its name /// /// The name of the theme to find /// The requested theme private Theme FindThemeByName(string themeName) { int themeIndex = 0; while (themeIndex < themeList.themes.Length) { Theme theme = themeList.themes[themeIndex]; if (theme.name == themeName) { return theme; } themeIndex++; } Debug.Log("Requested theme not found"); return null; } /// /// Display next word in the series /// private void SetNextWord() { spelledWords++; if (wordIndex < words.Length) { currentWord = words[wordIndex]; ChangeSprite(currentWord); DisplayWord(currentWord); AddSeconds(currentWord.Length * secondsPerLetter + 1); letterIndex = 0; wordIndex++; } else { ActivateWin(); } } /// /// Displays the word that needs to be spelled /// /// The word to display private void DisplayWord(string word) { for (int i = 0; i < word.Length; i++) { // Create instance of prefab GameObject instance = GameObject.Instantiate(letterPrefab, letterContainer); letters.Add(instance); // Dynamically load appearance Image background = instance.GetComponent(); background.color = Color.red; TMP_Text txt = instance.GetComponentInChildren(); txt.text = Char.ToString(Char.ToUpper(word[i])); } } /// /// Change the image that is being displayed /// /// Name of the new sprite private void ChangeSprite(string spriteName) { // Load the new sprite from the Resources folder Sprite sprite = Resources.Load("SpellingBee/images/" + spriteName); // Set the new sprite as the Image component's source image wordImage.sprite = sprite; } /// /// wait for 2 seconds /// /// private IEnumerator Wait() { yield return new WaitForSecondsRealtime(2); } }