630 lines
17 KiB
C#
630 lines
17 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
public partial class GameController : MonoBehaviour
|
|
{
|
|
/// <summary>
|
|
/// All of the words that can be used in this session
|
|
/// </summary>
|
|
private string[] words;
|
|
|
|
/// <summary>
|
|
/// Where we currently are in the word
|
|
/// </summary>
|
|
private int letterIndex;
|
|
|
|
/// <summary>
|
|
/// Where we currently are in the word list
|
|
/// </summary>
|
|
private int wordIndex;
|
|
|
|
/// <summary>
|
|
/// The word that is currently being spelled
|
|
/// </summary>
|
|
private string currentWord;
|
|
|
|
/// <summary>
|
|
/// All of the available themes
|
|
/// </summary>
|
|
private ThemeList themeList;
|
|
|
|
/// <summary>
|
|
/// The theme we are currently using
|
|
/// </summary>
|
|
private Theme currentTheme;
|
|
|
|
/// <summary>
|
|
/// Current value of timer in seconds
|
|
/// </summary>
|
|
private float timerValue;
|
|
|
|
/// <summary>
|
|
/// "Game over" or "You win!"
|
|
/// </summary>
|
|
public TMP_Text endText;
|
|
|
|
/// <summary>
|
|
/// LPM
|
|
/// </summary>
|
|
public TMP_Text lpmText;
|
|
|
|
/// <summary>
|
|
/// Letters ( right | wrong )
|
|
/// </summary>
|
|
public TMP_Text lettersRightText;
|
|
public TMP_Text lettersWrongText;
|
|
|
|
/// <summary>
|
|
/// Letters
|
|
/// </summary>
|
|
public TMP_Text lettersText;
|
|
|
|
/// <summary>
|
|
/// Accuracy
|
|
/// </summary>
|
|
public TMP_Text accuracyText;
|
|
|
|
/// <summary>
|
|
/// Words
|
|
/// </summary>
|
|
public TMP_Text wordsText;
|
|
|
|
/// <summary>
|
|
/// Time
|
|
/// </summary>
|
|
public TMP_Text timeText;
|
|
|
|
/// <summary>
|
|
/// Score
|
|
/// </summary>
|
|
public TMP_Text scoreText;
|
|
|
|
/// <summary>
|
|
/// The game over panel
|
|
/// </summary>
|
|
public GameObject gameEndedPanel;
|
|
|
|
/// <summary>
|
|
/// Button for restarting the game
|
|
/// </summary>
|
|
public Button replayButton;
|
|
|
|
/// <summary>
|
|
/// Indicates if the game is still going
|
|
/// </summary>
|
|
private bool gameEnded;
|
|
|
|
/// <summary>
|
|
/// Amount of seconds user gets per letter of the current word
|
|
/// Set to 1 for testing; should be increased later
|
|
/// </summary>
|
|
private int secondsPerLetter = 1;
|
|
|
|
/// <summary>
|
|
/// Counter that keeps track of how many letters have been spelled correctly
|
|
/// </summary>
|
|
private int correctLetters;
|
|
|
|
/// <summary>
|
|
/// Counter that keeps track of how many letters have been spelled incorrectly
|
|
/// </summary>
|
|
private int incorrectLetters;
|
|
|
|
/// <summary>
|
|
/// Counter that keeps track of how many words have been spelled correctly
|
|
/// </summary>
|
|
private int spelledWords;
|
|
|
|
/// <summary>
|
|
/// Timer that keeps track of when the game was started
|
|
/// </summary>
|
|
private DateTime startTime;
|
|
|
|
/// <summary>
|
|
/// Reference to the user list to access the current user
|
|
/// </summary>
|
|
public UserList userList;
|
|
|
|
/// <summary>
|
|
/// Reference to the current user
|
|
/// </summary>
|
|
private User user;
|
|
|
|
/// <summary>
|
|
/// Reference to the minigame progress of the current user
|
|
/// </summary>
|
|
private Progress progress = null;
|
|
|
|
/// <summary>
|
|
/// Reference to the minigame ScriptableObject
|
|
/// </summary>
|
|
public Minigame minigame;
|
|
|
|
|
|
/// <summary>
|
|
/// Letter prefab
|
|
/// </summary>
|
|
public GameObject letterPrefab;
|
|
|
|
/// <summary>
|
|
/// Reference to letter prefab
|
|
/// </summary>
|
|
public Transform letterContainer;
|
|
|
|
/// <summary>
|
|
/// The Image component for displaying the appropriate sprite
|
|
/// </summary>
|
|
public Image wordImage;
|
|
|
|
/// <summary>
|
|
/// Timer display
|
|
/// </summary>
|
|
public TMP_Text timerText;
|
|
|
|
/// <summary>
|
|
/// The GameObjects representing the letters
|
|
/// </summary>
|
|
private List<GameObject> letters = new List<GameObject>();
|
|
|
|
/// <summary>
|
|
/// Reference to the scoreboard
|
|
/// </summary>
|
|
public Transform Scoreboard;
|
|
|
|
/// <summary>
|
|
/// Reference to the entries grid
|
|
/// </summary>
|
|
public Transform EntriesGrid;
|
|
|
|
/// <summary>
|
|
/// The GameObjects representing the letters
|
|
/// </summary>
|
|
private List<GameObject> entries = new List<GameObject>();
|
|
|
|
/// <summary>
|
|
/// Reference to the ScoreboardEntry prefab
|
|
/// </summary>
|
|
public GameObject scoreboardEntry;
|
|
|
|
|
|
/// <summary>
|
|
/// Start is called before the first frame update
|
|
/// </summary>
|
|
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>("minigameIndex") == minigame.index);
|
|
if (progress == null)
|
|
{
|
|
progress = new Progress();
|
|
progress.AddOrUpdate<MinigameIndex>("minigameIndex", MinigameIndex.SPELLING_BEE);
|
|
progress.AddOrUpdate<int>("highscore", 0);
|
|
progress.AddOrUpdate<List<Score>>("scores", new List<Score>());
|
|
user.minigames.Add(progress);
|
|
}
|
|
userList.Save();
|
|
|
|
DeleteWord();
|
|
|
|
// TODO: change to ScriptableObject
|
|
themeList = ThemeLoader.LoadJson();
|
|
currentTheme = FindThemeByName(PlayerPrefs.GetString("themeName"));
|
|
words = currentTheme.words;
|
|
ShuffleWords();
|
|
SetNextWord();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update is called once per frame
|
|
/// </summary>
|
|
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<Image>().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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Randomly shuffle the list of words
|
|
/// </summary>
|
|
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]);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the score
|
|
/// </summary>
|
|
/// <returns>The calculated score</returns>
|
|
private int CalculateScore()
|
|
{
|
|
return spelledWords * 5 + correctLetters;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set score metrics
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Displays the game over panel and score values
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display win screen
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update and save the scores
|
|
/// </summary>
|
|
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>("minigameIndex") == minigame.index);
|
|
if (progress != null)
|
|
{
|
|
// Get the current list of scores
|
|
List<Score> scores = progress.Get<List<Score>>("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<List<Score>>("scores", scores.Take(10).ToList());
|
|
}
|
|
|
|
// Update the highscore
|
|
int highscore = progress.Get<int>("highscore");
|
|
if (score.scoreValue < highscore)
|
|
{
|
|
progress.AddOrUpdate<int>("highscore", score.scoreValue);
|
|
}
|
|
|
|
userList.Save();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the scoreboard
|
|
/// </summary>
|
|
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<Tuple<string, Score>> allScores = new List<Tuple<string, Score>>();
|
|
foreach (User user in userList.GetUsers())
|
|
{
|
|
// Get user's progress for this minigame
|
|
progress = user.minigames.Find((p) => p != null && p.Get<MinigameIndex>("minigameIndex") == minigame.index);
|
|
if (progress != null)
|
|
{
|
|
// Add scores to dictionary
|
|
List<Score> scores = progress.Get<List<Score>>("scores");
|
|
foreach (Score score in scores)
|
|
{
|
|
allScores.Add(new Tuple<string, Score>(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<string, Score> 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<Image>().sprite = userList.GetUserByUsername(username).avatar;
|
|
|
|
// Set the player name
|
|
entry.transform.Find("PlayerName").GetComponent<TMP_Text>().text = username;
|
|
|
|
// Set the score
|
|
entry.transform.Find("Score").GetComponent<TMP_Text>().text = score.scoreValue.ToString();
|
|
|
|
// Set the rank
|
|
entry.transform.Find("Rank").GetComponent<TMP_Text>().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<TMP_Text>().text = formatted;
|
|
|
|
|
|
// Alternating colors looks nice
|
|
if (rank % 2 == 0)
|
|
{
|
|
Image image = entry.transform.GetComponent<Image>();
|
|
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>();
|
|
image.color = new Color(0, 229, 255, 233);
|
|
}
|
|
|
|
rank++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete all letter objects
|
|
/// </summary>
|
|
private void DeleteWord()
|
|
{
|
|
for (int i = 0; i < letters.Count; i++)
|
|
{
|
|
Destroy(letters[i]);
|
|
}
|
|
letters.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds seconds to timer
|
|
/// </summary>
|
|
/// <param name="seconds"></param>
|
|
private void AddSeconds(int seconds)
|
|
{
|
|
timerValue += (float)seconds;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find the chosen theme by its name
|
|
/// </summary>
|
|
/// <param name="themeName">The name of the theme to find</param>
|
|
/// <returns>The requested theme</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display next word in the series
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Displays the word that needs to be spelled
|
|
/// </summary>
|
|
/// <param name="word">The word to display</param>
|
|
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<Image>();
|
|
background.color = Color.red;
|
|
TMP_Text txt = instance.GetComponentInChildren<TMP_Text>();
|
|
txt.text = Char.ToString(Char.ToUpper(word[i]));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Change the image that is being displayed
|
|
/// </summary>
|
|
/// <param name="spriteName">Name of the new sprite</param>
|
|
private void ChangeSprite(string spriteName)
|
|
{
|
|
// Load the new sprite from the Resources folder
|
|
Sprite sprite = Resources.Load<Sprite>("SpellingBee/images/" + spriteName);
|
|
|
|
// Set the new sprite as the Image component's source image
|
|
wordImage.sprite = sprite;
|
|
}
|
|
|
|
/// <summary>
|
|
/// wait for 2 seconds
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private IEnumerator Wait()
|
|
{
|
|
yield return new WaitForSecondsRealtime(2);
|
|
}
|
|
}
|