Resolve WES-132-Common-Abstract-Minigame

This commit is contained in:
Jerome Coudron
2023-04-23 21:20:12 +00:00
committed by Jelle De Geest
parent 4fdb8f95cb
commit 966475455a
30 changed files with 1046 additions and 1078 deletions

View File

@@ -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")]
/// <summary>
/// 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;
/// <summary>
/// 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
/// </summary>
public Theme fingerSpelling;
@@ -96,11 +95,6 @@ public class HangmanController : AbstractFeedback
/// </summary>
public TMP_Text scoreBonus;
///// <summary>
///// This panel holds the panels for input and playing the game, sharing webcam and feedback
///// </summary>
//public GameObject inputGamePanel;
/// <summary>
/// This int shows what mode we are in, used in update: <br></br>
/// 0 : single or multiplayer?<br></br>
@@ -111,27 +105,6 @@ public class HangmanController : AbstractFeedback
/// </summary>
private int mode;
/// <summary>
/// The game over panel
/// </summary>
public GameObject gameEndedPanel;
/// <summary>
/// Reference to the minigame ScriptableObject
/// </summary>
public Minigame minigame;
/// <summary>
/// We keep the minigamelist as well so that the minigame-index doesn't get reset
/// DO NOT REMOVE
/// </summary>
public MinigameList minigamelist;
/// <summary>
/// Reference to the current user
/// </summary>
private User user;
/// <summary>
/// The button to go into the game
/// </summary>
@@ -152,21 +125,11 @@ public class HangmanController : AbstractFeedback
/// </summary>
public TMP_Text feedbackText;
/// <summary>
/// Reference to the progress bar
/// </summary>
public Slider feedbackProgress;
/// <summary>
/// Reference to the progress bar image, so we can add fancy colors
/// </summary>
public Image feedbackProgressImage;
/// <summary>
/// reference to the webcam background
/// </summary>
public RawImage webcamScreen;
/// <summary>
/// Previous incorrect sign, so we can keep track whether the user is wrong or the user is still changing signs
/// </summary>
@@ -239,48 +202,11 @@ public class HangmanController : AbstractFeedback
private int winScore = 25;
/// <summary>
/// Start is called before the first frame update
/// Set the AbstractMinigameController variable to inform it of the theme for the signPredictor
/// </summary>
void Start()
protected override Theme signPredictorTheme
{
signPredictor.SwapScreen(webcamScreen);
signPredictor.SetModel(ModelIndex.FINGERSPELLING);
AddSelfAsListener();
StartController();
}
/// <summary>
/// Called at the start of the scene AND when the scene is replayed
/// </summary>
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<string>();
get { return fingerSpelling; }
}
/// <summary>
@@ -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<PanelMultiplayerInput>();
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;
}
}
/// <summary>
@@ -599,7 +538,7 @@ public class HangmanController : AbstractFeedback
/// This function returns the score that the user currently has
/// </summary>
/// <returns>The current score of the user</returns>
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
}
/// <summary>
/// Update and save the scores
/// The logic to process the signs sent by the signPredictor
/// </summary>
private void SaveScores()
/// <param name="accuracy">The accuracy of the passed sign</param>
/// <param name="predictedSign">The name of the passed sign</param>
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<Score> latestScores = progress.latestScores;
List<Score> 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();
}
/// <summary>
/// Display win screen
/// </summary>
private void ActivateWin()
{
// Deactivate the model
mode = 0;
// Save the scores and show the scoreboard
SaveScores();
gameEndedPanel.GetComponent<HangmanGameEndedPanel>().GenerateContent(
guessWord: currentWord.ToLower(),
correctLetters: corrects,
incorrectLetters: wrongs,
sprite: hangmanImage.sprite,
result: "GEWONNEN",
score: CalculateScore()
);
gameEndedPanel.SetActive(true);
// @lukas stuff
DeleteWord();
}
/// <summary>
/// Displays the game over panel and score values
/// </summary>
private void ActivateGameOver()
{
// Deactivate the model
mode = 0;
// Save the scores and show the scoreboard
SaveScores();
gameEndedPanel.GetComponent<HangmanGameEndedPanel>().GenerateContent(
guessWord: currentWord.ToLower(),
correctLetters: corrects,
incorrectLetters: wrongs,
sprite: hangmanImage.sprite,
result: "VERLOREN",
score: CalculateScore()
);
gameEndedPanel.SetActive(true);
DeleteWord();
}
/// <summary>
/// The updateFunction that is called when new probabilities become available
/// </summary>
/// <returns></returns>
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<string, float> 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;
}
/// <summary>
/// The logic to set the scoreboard of hangman
/// </summary>
/// <param name="victory">SHows whether or not the player won</param>
protected override void SetScoreBoard(bool victory)
{
string resultTxt;
if (victory)
{
resultTxt = "GEWONNEN";
}
else
{
resultTxt = "VERLOREN";
}
gameEndedPanel.GetComponent<HangmanGameEndedPanel>().GenerateContent(
guessWord: currentWord.ToLower(),
correctLetters: corrects,
incorrectLetters: wrongs,
sprite: hangmanImage.sprite,
result: resultTxt,
score: CalculateScore()
);
}
/// <summary>
/// The hangman-specific logic that needs to be called at the start of the game
/// </summary>
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);
}
/// <summary>
/// The Hangman-specific logic that needs to be called at the end of a game
/// </summary>
/// <param name="victory"></param>
protected override void EndGameLogic(bool victory)
{
// Deactivate the model
SwitchMode(0);
DeleteWord();
}
}

View File

@@ -7,8 +7,16 @@ using UnityEngine.UI;
/// <summary>
/// The hangman-variant of the ScoreBoard
/// </summary>
public class HangmanGameEndedPanel : MonoBehaviour
public class HangmanGameEndedPanel : AbstractGameEndedPanel
{
/// <summary>
/// Tell the scoreboard that the scoreboard is for HangMan
/// </summary>
protected override MinigameIndex minigameIndex
{
get { return MinigameIndex.HANGMAN; }
}
/// <summary>
/// "VERLOREN" or "GEWONNEN"
/// </summary>
@@ -40,28 +48,11 @@ public class HangmanGameEndedPanel : MonoBehaviour
/// </summary>
public TMP_Text scoreText;
/// <summary>
/// Reference to the scoreboard entries container
/// </summary>
public Transform scoreboardEntriesContainer;
/// <summary>
/// The GameObjects representing the letters
/// </summary>
private List<GameObject> scoreboardEntries = new List<GameObject>();
/// <summary>
/// Reference to the ScoreboardEntry prefab
/// </summary>
public GameObject scoreboardEntry;
/// <summary>
/// Reference to the end result image
/// </summary>
public Image image;
public List<Sprite> sprites;
/// <summary>
/// Generate the content of the GameEnded panel
/// </summary>
@@ -99,105 +90,4 @@ public class HangmanGameEndedPanel : MonoBehaviour
scoreText.text = $"Score: {score}";
SetScoreBoard();
}
/// <summary>
/// Sets the scoreboard
/// </summary>
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<Tuple<string, Score>> allScores = new List<Tuple<string, Score>>();
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<Score> scores = progress.highestScores;
foreach (Score score in scores)
{
allScores.Add(new Tuple<string, Score>(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<string, Score> 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<Image>().sprite = UserList.GetUserByUsername(username).GetAvatar();
// 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++;
}
}
}

View File

@@ -5,9 +5,10 @@
"GUID:e83ddf9a537a96b4a804a16bb7872ec1",
"GUID:6055be8ebefd69e48b49212b09b47b2f",
"GUID:1631ed2680c61245b8211d943c1639a8",
"GUID:d0b6b39a21908f94fbbd9f2c196a9725",
"GUID:58e104b97fb3752438ada2902a36dcbf",
"GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25"
"GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25",
"GUID:403dd94a93598934eb522dc36df43d7b",
"GUID:d0b6b39a21908f94fbbd9f2c196a9725"
],
"includePlatforms": [],
"excludePlatforms": [],