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);
}
}