Files
unity-application/Assets/Hangman/Tests/PlayMode/HangmanPlaymodeTests.cs
2023-05-16 11:09:41 +00:00

389 lines
16 KiB
C#

using NUnit.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.TestTools;
/// <summary>
/// Test the HangmanController class
/// </summary>
public class HangmanPlaymodeTests
{
/// <summary>
/// Reference to the HangmanController so we can access it quickly
/// </summary>
private HangmanController hangmanController;
/// <summary>
/// The mode of the multiple choice panel that when the confirm button needs to be clicked
/// </summary>
private int multiplayerConfirmInput = 4;
/// <summary>
/// SetupFunction to reach the hangman-game
/// </summary>
[UnitySetUp]
public IEnumerator SetupFunction()
{
string path = $"{Application.persistentDataPath}/wesign_unit_test.json";
string oneUser = $"{{\"version\":1537,\"users\":[{{\"entries\":[],\"username\":\"TEST\",\"avatarIndex\":0,\"playtime\":0.0,\"minigames\":[],\"courses\":[]}}],\"currentUser\":0,\"currentMinigame\":0,\"currentCourse\":0,\"currentTheme\":0}}";
File.WriteAllText(path, oneUser);
PersistentDataController.PATH = path;
PersistentDataController.GetInstance().Load();
AssetDatabase.LoadAssetAtPath<UserAvatarList>("Assets/Accounts/ScriptableObjects/UserAvatarList.asset").Awake();
// Go to the minigame-selection scene to make sure that the minigameIndex is set correctly
SystemController.GetInstance().LoadNextScene("Common/Scenes/ListMinigamesScreen");
yield return new WaitForSeconds(0.2f);
var minigameScreen = GameObject.FindObjectOfType<ListMinigamesScreen>();
minigameScreen.minigameList.SetCurrentMinigame(MinigameIndex.HANGMAN);
// Go to the Hangman-game
SystemController.GetInstance().LoadNextScene("Hangman/Scenes/HangmanGame");
yield return new WaitForSeconds(0.2f);
// Fetch the hangmanController
hangmanController = GameObject.FindObjectOfType<HangmanController>();
yield return new WaitForSeconds(0.2f);
// Fetch the SignPredictor and deactivate it, this stops the basic game-loop from happening
var signPredictor = GameObject.FindObjectOfType<SignPredictor>();
GameObject signPredictorController = signPredictor.gameObject;
signPredictorController.SetActive(false);
yield return new WaitForSeconds(0.2f);
// Put some old scores in the scoreboard to test its functionality
// One from one day ago
Score score1 = new Score();
score1.scoreValue = 50;
score1.time = DateTime.Now.AddDays(-1).ToString();
// One from one hour ago
Score score2 = new Score();
score2.scoreValue = 70;
score2.time = DateTime.Now.AddHours(-1).ToString();
// One from 5 minutes ago
Score score3 = new Score();
score3.scoreValue = 90;
score3.time = DateTime.Now.AddMinutes(-5).ToString();
// Save the new score
var progress = UserList.GetCurrentUser().GetMinigameProgress(MinigameIndex.HANGMAN);
// Get the current list of scores
List<Score> latestScores = progress.latestScores;
List<Score> highestScores = progress.highestScores;
// Add the new score
latestScores.Add(score1);
highestScores.Add(score1);
latestScores.Add(score2);
highestScores.Add(score2);
latestScores.Add(score3);
highestScores.Add(score3);
// 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();
}
/// <summary>
/// Tests the singleplayer functionality, only the winning side as losing is easier when the word is chosen beforehand.
/// </summary>
[UnityTest]
public IEnumerator SinglePlayerTests()
{
// Start singlePlayer
hangmanController.SinglePlayer();
// Assert that a random word was generated and that the game has started
string currentWord = hangmanController.getCurrentWord();
Assert.IsTrue(currentWord.Length > 0);
Assert.IsTrue(hangmanController.getCurrentMode() == 2);
Assert.IsFalse(hangmanController.inputPanel.activeSelf);
Assert.IsFalse(hangmanController.playerPanel.activeSelf);
Assert.IsTrue(hangmanController.gamePanel.activeSelf);
Assert.IsFalse(hangmanController.gameEndedPanel.activeSelf);
// Fetch the panel with the info for the fields and check if the controller has implemented them
var script = GameObject.FindObjectOfType<PanelHangmanGame>();
Assert.IsTrue(hangmanController.inputTextField == script.guessesTextField);
Assert.IsTrue(hangmanController.feedbackText == script.feedbackText);
Assert.IsTrue(hangmanController.feedbackProgress == script.feedbackProgressBar);
Assert.IsTrue(hangmanController.feedbackProgressImage == script.feedbackProgressImage);
Assert.IsTrue(hangmanController.webcamScreen == script.webcamScreen);
Assert.IsTrue(hangmanController.timerCircle == script.timerCircle);
Assert.IsTrue(hangmanController.confirmPanel == script.confirmPanel);
Assert.IsTrue(hangmanController.confirmText == script.confirmText);
Assert.IsTrue(hangmanController.getCorrects() == 0);
Assert.IsTrue(hangmanController.getWrongs() == 0);
Assert.IsTrue(hangmanController.getUsedLetters().Length == 0);
// Start guessing the word, start by trying a wrong letter to check if the image changes
string letter = "";
int localWrongs = 0;
// Wait to run over the update-functionality
yield return new WaitForSeconds(0.2f);
// Fetch a letter that isn't in the word
foreach (char let in new List<char>() { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' })
{
if (!currentWord.Contains(let.ToString().ToUpper()))
{
letter = let.ToString().ToUpper();
break;
}
}
// Input this knowingly-wrong letter
yield return ChooseLetter(letter);
localWrongs++;
// We know the letter is wrong, so check that the wrongs have increased
Assert.IsTrue(hangmanController.getWrongs() == localWrongs);
// Check all the correct letters
Dictionary<string, int> correctLetters = new Dictionary<string, int>();
foreach (char let in currentWord.ToUpper())
{
letter = let.ToString();
if (correctLetters.ContainsKey(letter))
{
correctLetters[letter] += 1;
}
else
{
correctLetters.Add(letter, 1);
}
}
int localCorrects = 0;
// Now add each of the letters and check if the correct are correctly updated
foreach (string s in correctLetters.Keys)
{
yield return ChooseLetter(s);
localCorrects += correctLetters[s];
Assert.IsTrue(hangmanController.getCorrects() == localCorrects);
}
// The game should be over now, check if the gameEndedPanel is active
Assert.IsTrue(hangmanController.gameEndedPanel.activeSelf);
// Fetch the gameEndedPanel to check it's values
var gameEndedScript = GameObject.FindObjectOfType<HangmanGameEndedPanel>();
Assert.IsTrue(gameEndedScript.endText.text == "GEWONNEN");
Assert.IsTrue(gameEndedScript.lettersRightText.text == hangmanController.getCorrects().ToString());
Assert.IsTrue(gameEndedScript.lettersWrongText.text == hangmanController.getWrongs().ToString());
Assert.IsTrue(gameEndedScript.lettersTotalText.text == (hangmanController.getCorrects() + hangmanController.getWrongs()).ToString());
yield return null;
}
/// <summary>
/// Tests the multiplayer-code
/// </summary>
[UnityTest]
public IEnumerator MultiPlayerTests()
{
// Go to the input-screen
hangmanController.GoToInput();
// We will enter the word "testing" but will type a wrong letter after the 'i' that will be backspaced away
// Afterwards, we can go to the game and assert that the currectWord is indeed "testing"
// Correct letters
yield return InputLetter("T");
yield return InputLetter("E");
yield return InputLetter("S");
yield return InputLetter("T");
yield return InputLetter("I");
// Wrong letter, will be backspaced away
yield return InputLetter("V");
yield return new WaitForSeconds(0.2f);
// We can't simulate a key-press so we will need to settle for just calling its logic
hangmanController.BackSpacePressed();
// Wrong letter, but this one will be rejected
while (hangmanController.getCurrentMode() != multiplayerConfirmInput)
{
hangmanController.ProcessMostProbableSign(0.0001f, "Q");
yield return new WaitForSeconds(0.2f);
}
hangmanController.ConfirmDeny();
// Final correct letters
yield return InputLetter("N");
yield return InputLetter("G");
// We are satisfied with the word, pass it along
hangmanController.TwoPlayer();
Assert.IsTrue(hangmanController.getCurrentWord() == "TESTING");
// Input 9 wrong letters to lose the game
int localWrongs = 0;
Assert.IsTrue(hangmanController.getWrongs() == localWrongs);
foreach (string s in new List<string>() { "A", "B", "C", "D", "F", "H", "J", "K", "L", "M", "N", "O", "P" })
{
if (HangmanController.NUMBER_OF_FAILS_BEFORE_GAMEOVER >= localWrongs)
{
yield return ChooseLetter(s);
localWrongs++;
Assert.IsTrue(hangmanController.getWrongs() == localWrongs);
}
}
// The game should be over now, resulting in a loss
Assert.IsTrue(hangmanController.gameEndedPanel.activeSelf);
// Fetch the gameEndedPanel to check it's values
var gameEndedScript = GameObject.FindObjectOfType<HangmanGameEndedPanel>();
Assert.IsTrue(gameEndedScript.endText.text == "VERLOREN");
Assert.IsTrue(gameEndedScript.lettersRightText.text == hangmanController.getCorrects().ToString());
Assert.IsTrue(gameEndedScript.lettersWrongText.text == hangmanController.getWrongs().ToString());
Assert.IsTrue(gameEndedScript.lettersTotalText.text == (hangmanController.getCorrects() + hangmanController.getWrongs()).ToString());
yield return null;
}
/// <summary>
/// Tests some remaning functionality regarding the feedback that isn't contained in the previous tests
/// </summary>
[UnityTest]
public IEnumerator SignChangingTest()
{
hangmanController.SinglePlayer();
hangmanController.ProcessMostProbableSign(0.001f, "A");
yield return new WaitForSeconds(0.2f);
// The sign for A has been held for 0.2f seconds...
Assert.IsTrue(hangmanController.getCurrentTime() >= 0.2f && hangmanController.getCurrentTime() <= 0.3f);
hangmanController.ProcessMostProbableSign(0.001f, "B");
yield return new WaitForSeconds(0.2f);
// The sign changed so the time needs to be at 0.2f again
Assert.IsTrue(hangmanController.getCurrentTime() >= 0.2f && hangmanController.getCurrentTime() <= 0.3f);
hangmanController.ProcessMostProbableSign(1000, "B");
yield return new WaitForSeconds(0.2f);
// The sign changed is way above the threshold, time is no longer tracked and is reset
Assert.IsTrue(hangmanController.getCurrentTime() == 0.0);
// Check that the time stays zero so long as the distance stays above the threshold
Learnable C = hangmanController.fingerSpelling.learnables.Find((l) => l.name == "C");
float threshold = C.thresholdDistance;
for (float i = 2 * threshold; i > threshold; i -= threshold / 6)
{
hangmanController.ProcessMostProbableSign(i, "C");
yield return new WaitForSeconds(0.01f);
Assert.IsTrue(hangmanController.getCurrentTime() == 0.0);
}
// Check that the time rises above zero when you dip just below the threshold
hangmanController.ProcessMostProbableSign(threshold - 0.01f, "C");
yield return new WaitForSeconds(0.01f);
Assert.IsTrue(hangmanController.getCurrentTime() > 0.0);
yield return null;
}
/// <summary>
/// Test the functionality of the scoreboard to display multiple games
/// </summary>
[UnityTest]
public IEnumerator ScoreBoardTest()
{
// Quickly play 2 games to test if scoreboard is correctly reset and if entries are correctly added
// Game 1
hangmanController.GoToInput();
yield return InputLetter("A");
yield return InputLetter("A");
yield return InputLetter("A");
hangmanController.TwoPlayer();
Assert.IsTrue(hangmanController.getCurrentWord() == "AAA");
yield return ChooseLetter("A");
yield return new WaitForSeconds(1f);
Assert.IsTrue(hangmanController.gameEndedPanel.activeSelf);
var gameEndedScript = GameObject.FindObjectOfType<HangmanGameEndedPanel>();
int firstEntries = gameEndedScript.scoreboardEntriesContainer.childCount;
hangmanController.StartController();
// Game 2
hangmanController.GoToInput();
yield return InputLetter("A");
yield return InputLetter("A");
yield return InputLetter("A");
hangmanController.TwoPlayer();
Assert.IsTrue(hangmanController.getCurrentWord() == "AAA");
yield return ChooseLetter("A");
yield return new WaitForSeconds(1f);
Assert.IsTrue(hangmanController.gameEndedPanel.activeSelf);
int secondEntries = gameEndedScript.scoreboardEntriesContainer.childCount;
// Check that one entry has been added between the two games or that the scoreboard was already full
Assert.IsTrue(secondEntries == firstEntries + 1 || firstEntries == 10);
yield return null;
}
/// <summary>
/// Cleanup after testing
/// </summary>
[TearDown]
public void TearDown_HangmanTests()
{
PersistentDataController.PATH = null;
}
/// <summary>
/// Private function to input a letter in multiplayer input-mode
/// </summary>
/// <param name="letter"></param>
/// <returns></returns>
private IEnumerator InputLetter(string letter)
{
while (hangmanController.getCurrentMode() != multiplayerConfirmInput)
{
// Choose the letter by giving it a very small distance
hangmanController.ProcessMostProbableSign(0.001f, letter);
yield return new WaitForSeconds(0.2f);
}
hangmanController.ConfirmAccept();
}
/// <summary>
/// Private function to input a letter in game-mode
/// </summary>
/// <param name="letter"></param>
/// <returns></returns>
private IEnumerator ChooseLetter(string letter)
{
while (!hangmanController.getUsedLetters().Contains(letter))
{
// Choose the letter by giving it a very small distance
hangmanController.ProcessMostProbableSign(0.001f, letter);
yield return new WaitForSeconds(0.2f);
}
}
}