This commit is contained in:
Jelle De Geest
2023-04-26 19:04:34 +02:00
parent 172938cec8
commit 47f8b96122
1004 changed files with 60756 additions and 117444 deletions

View File

@@ -1,62 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HangManWebcam : WebCam
{
/// <summary>
/// The display for player 1
/// </summary>
public RawImage display1;
/// <summary>
/// The display for player 2
/// </summary>
public RawImage display2;
/// <summary>
/// We use a different awake, since we dont want the camera to start immediatelly
/// </summary>
void Awake()
{
WebCamDevice device = WebCamTexture.devices[camdex];
tex = new WebCamTexture(device.name);
display.texture = tex;
}
/// <summary>
/// Hangman uses two different webcam_textures, we need to be able to toggle between them
/// </summary>
public void Switch_texture()
{
if(display == display1)
{
display = display2;
}
else
{
display = display1;
}
// Give the webcamTexture to the new webcam
display.texture = tex;
}
/// <summary>
/// Scene changing is implemented here to avoid problems with webcam
/// </summary>
public new void GotoThemeSelection()
{
//minigameList.GetIndexInMinigameList(MinigameIndex.HANGMAN);
if (tex != null)
{
if (tex.isPlaying)
{
display.texture = null;
tex.Stop();
tex = null;
}
}
SystemController.GetInstance().BackToPreviousScene();
}
}

View File

@@ -0,0 +1,726 @@
using DigitalRuby.Tween;
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Random = UnityEngine.Random;
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.
/// </summary>
public ThemeList themeList;
/// <summary>
/// reference to the fingerspelling-theme to reach the letter-thresholds and to pass to the signPredictor
/// </summary>
public Theme fingerSpelling;
/// <summary>
/// The word that is currently being spelled
/// </summary>
private string currentWord;
/// <summary>
/// This integer holds the total amount of wrong guesses the player has made
/// </summary>
private int wrongs;
/// <summary>
/// This integer holds the amount of correct letters of the word that the user has guessed
/// </summary>
private int corrects;
/// <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 hangmanImage;
/// <summary>
/// The GameObjects representing the letters
/// </summary>
private List<GameObject> letters = new List<GameObject>();
/// <summary>
/// This scriptable holds all the images for the different stages of hangman
/// </summary>
public List<Sprite> images = new List<Sprite>();
/// <summary>
/// This initially empty list holds all the previous guesses that the user has made, as to not allow repeated msitakes
/// </summary>
private List<string> guesses = new List<string>();
/// <summary>
/// Holds a string of all the used letters, used to display them to the player
/// </summary>
public TMP_Text usedLettersText;
/// <summary>
/// The panel holding the actual game
/// </summary>
public GameObject gamePanel;
/// <summary>
/// The panel holding the player-selection screen
/// </summary>
public GameObject playerPanel;
/// <summary>
/// The panel holding the screen where player 1 can input their word
/// </summary>
public GameObject inputPanel;
/// <summary>
/// Reference to display the score
/// </summary>
public TMP_Text scoreDisplay;
/// <summary>
/// Reference to display the points lost/won
/// </summary>
public TMP_Text scoreBonus;
/// <summary>
/// This int shows what mode we are in, used in update: <br></br>
/// 0 : single or multiplayer?<br></br>
/// 1 : multiplayer word input<br></br>
/// 2 : signing letter<br></br>
/// 3 : confirming choice gameplay
/// 4 : confirming choice multiplayer input
/// </summary>
private int mode;
/// <summary>
/// The button to go into the game
/// </summary>
public GameObject gotoGameButton;
/// <summary>
/// This textfield holds the word that player 1 is typing
/// </summary>
public TMP_Text inputTextField;
/// <summary>
/// Current sign out of the predictor
/// </summary>
private string currentSign = "";
/// <summary>
/// Reference to the feedback field
/// </summary>
public TMP_Text feedbackText;
/// <summary>
/// Reference to the progress bar image, so we can add fancy colors
/// </summary>
public Image feedbackProgressImage;
/// <summary>
/// Previous incorrect sign, so we can keep track whether the user is wrong or the user is still changing signs
/// </summary>
protected string previousSign = null;
/// <summary>
/// variable to remember whether or not the timer needs to be updates.
/// Only update the timer if the model is confident for a certain letter
/// </summary>
private bool runTime = false;
/// <summary>
/// Holds the time which the user needs to hold the sign for, for it to be accepted.
/// </summary>
private float maxTime = 0.3f;
/// <summary>
/// Holds the current amount of time the user has held their sign for
/// </summary>
private float currentTime = 0f;
/// <summary>
/// Holds a reference to the TimerCircle to update its fill
/// </summary>
public Image timerCircle;
/// <summary>
/// Hold a reference to the confirmPanel to toggle its activity
/// </summary>
public GameObject confirmPanel;
/// <summary>
/// Hold a reference to the confirmPanel to toggle its activity
/// </summary>
public TMP_Text confirmText;
///// <summary>
///// Temporary reference to timer to turn it off
///// </summary>
//public GameObject timer;
/// <summary>
/// Maximum length of the words
/// </summary>
public const int MIN_INC_WORD_LENGHT = 3;
/// <summary>
/// Maximum length of the words
/// </summary>
public const int MAX_EXC_WORD_LENGHT = 17;
/// <summary>
/// Number of fails before the game ends
/// </summary>
public const int NUMBER_OF_FAILS_BEFORE_GAMEOVER = 7;
/// <summary>
/// Score obtained when guessing a correct letter
/// </summary>
private int correctLetterScore = 10;
/// <summary>
/// Score obtained when guessing an incorrect letter
/// </summary>
private int incorrectLetterScore = -5;
/// <summary>
/// Score obtained when guessing the entire word
/// </summary>
private int winScore = 25;
/// <summary>
/// Set the AbstractMinigameController variable to inform it of the theme for the signPredictor
/// </summary>
protected override Theme signPredictorTheme
{
get { return fingerSpelling; }
}
/// <summary>
/// Hangman starts by asking the amount of players, so this function holds all the info needed to start the actual game
/// </summary>
public void StartGame()
{
// Change the mode
SwitchMode(2);
// Activate the right panel
gamePanel.SetActive(true);
//inputGamePanel.SetActive(true);
inputPanel.SetActive(false);
playerPanel.SetActive(false);
PanelHangmanGame script = gamePanel.GetComponent<PanelHangmanGame>();
inputTextField = script.guessesTextField;
feedbackText = script.feedbackText;
feedbackProgress = script.feedbackProgressBar;
feedbackProgressImage = script.feedbackProgressImage;
webcamScreen = script.webcamScreen;
timerCircle = script.timerCircle;
confirmPanel = script.confirmPanel;
confirmText = script.confirmText;
confirmPanel.SetActive(false);
signPredictor.SwapScreen(webcamScreen);
// Reset values of parameters, so that the replaybutton works properly
corrects = 0;
wrongs = 0;
guesses.Clear();
usedLettersText.text = "";
// Delete first, to make sure that the letters are empty
DeleteWord();
DisplayWord(currentWord);
ChangeSprite();
scoreDisplay.text = $"Score: {CalculateScore()}";
scoreBonus.text = "";
// Temporary
//timer.SetActive(true);
}
/// <summary>
/// This function is called when the "two player"-button is clicked, it goed to the input-screen
/// </summary>
public void GoToInput()
{
// Change the mode
SwitchMode(1);
// Activate the right panel
gamePanel.SetActive(false);
//inputGamePanel.SetActive(true);
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;
feedbackText = script.feedbackText;
feedbackProgress = script.feedbackProgressBar;
feedbackProgressImage = script.feedbackProgressImage;
webcamScreen = script.webcamScreen;
timerCircle = script.timerCircle;
confirmPanel = script.confirmPanel;
confirmText = script.confirmText;
confirmPanel.SetActive(false);
signPredictor.SwapScreen(script.webcamScreen);
//temporarily turn off timer in input-mode
//timer.SetActive(false);
}
/// <summary>
/// This function is called if singleplayer is selected, we generate a random word for the player and start the game.
/// </summary>
public void SinglePlayer()
{
// This word is used for testing before dynamic word-fetching is added
PickRandomWord();
StartGame();
}
/// <summary>
/// Randomly select a word from a randomly selected theme, use this word for the hangman game for singleplayer.
/// </summary>
private void PickRandomWord()
{
// Get a random index for the themes
// Then get a random index for a word to pull
// First get random index for the themes
int amountThemes = themeList.themes.Count;
int themeIndex = Random.Range(0, amountThemes);
// Check how many words are in this theme
int amountWords = themeList.themes[themeIndex].learnables.Count;
int wordIndex = Random.Range(0, amountWords);
// Take the word, but lowercase it.
currentWord = themeList.themes[themeIndex].learnables[wordIndex].name.ToUpper();
}
/// <summary>
/// This function starts the game after player 1 has entered their word, but only if its length >= 2.
/// </summary>
public void TwoPlayer()
{
if (MIN_INC_WORD_LENGHT <= currentWord.Length)
{
// Reset the model-parameters
previousSign = null;
currentTime = 0;
// Start the game
StartGame();
}
}
/// <summary>
/// Update is called once per frame
/// </summary>
public void Update()
{
if (mode == 1)
{
if (Input.GetKey(KeyCode.Backspace))
{
// Remove the last letter from the currentword
if (0 < currentWord.Length)
{
currentWord = currentWord[0..^1];
inputTextField.text = currentWord;
}
Input.ResetInputAxes();
}
gotoGameButton.SetActive(MIN_INC_WORD_LENGHT <= currentWord.Length);
}
// The following logic is used to fill the timer
if ((mode == 1 || mode == 2) &&
runTime)
{
currentTime += Time.deltaTime; // subtract the time since last frame
if (currentTime > maxTime)
{
currentTime = maxTime;
}
float oldValue = timerCircle.fillAmount;
float newValue = currentTime / maxTime;
timerCircle.gameObject.Tween("TimerUpdate", oldValue, newValue, 1f / 60f, TweenScaleFunctions.CubicEaseInOut, (t) =>
{
if (timerCircle != null)
{
timerCircle.fillAmount = t.CurrentValue;
}
});
}
}
/// <summary>
/// Handles sign logic, so that it does not have to run every frame
/// This function is called when the UpdateFeedback has accepted a letter
/// </summary>
public void UpdateSign()
{
switch (mode)
{
case 0: // Singleplayer or multiplayer?
break;
case 1: // Multiplayer word
{
if (currentSign != null && currentSign != "" && currentWord.Length < MAX_EXC_WORD_LENGHT)
{
confirmPanel.SetActive(true);
confirmText.text = $"Letter '{currentSign.ToUpper()}' ?";
SwitchMode(4);
}
}
break;
case 2: // Sign your letter
if (!guesses.Contains(currentSign))
{
SwitchMode(3);
ConfirmAccept();
}
break;
case 3: // Confirm signed letter
break;
}
}
public void ConfirmAccept()
{
string letter = currentSign;
currentSign = "";
confirmPanel.SetActive(false);
if (mode == 3)
{
if (currentWord.Contains(letter))
{
// The guess was correct, we can display all the letters that correspond to the guess
UpdateWord(letter);
}
else
{
// The guess was wrong, the wrongs integer needs to be incremented
wrongs++;
// Afterwards, the next stage needs to be displayed
ChangeSprite();
}
guesses.Add(letter);
if (usedLettersText.text != "")
usedLettersText.text += ", ";
usedLettersText.text += letter.ToString().ToUpper();
// The current sign was accepted, return to the game
SwitchMode(2);
if (corrects == currentWord.Length)
{
// Victory, deactivate the model and show the scoreboard
ActivateEnd(true);
}
else if (NUMBER_OF_FAILS_BEFORE_GAMEOVER < wrongs)
{
// You lost, deactivate the model and show the scoreboard
ActivateEnd(false);
}
}
else if (mode == 4)
{
currentWord += letter;
inputTextField.text = currentWord.ToUpper();
SwitchMode(1);
}
}
public void ConfirmDeny()
{
confirmPanel.SetActive(false);
// The current sign was rejected, return to the game-mode
if (mode == 3)
SwitchMode(2);
else if (mode == 4)
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>
/// Change the image that is being displayed
/// </summary>
private void ChangeSprite()
{
// Load the new sprite from the HangmanImages scriptable
Sprite sprite = images[wrongs];
// Set the new sprite as the Image component's source image
hangmanImage.sprite = sprite;
scoreDisplay.text = $"Score: {CalculateScore()}";
scoreBonus.text = $"{incorrectLetterScore}";
scoreBonus.color = new Color(0xf5 / 255.0f, 0x49 / 255.0f, 0x3d / 255.0f);
}
/// <summary>
/// In this function, the letters of the word selected in DisplayWord are updated after a correct guess.
/// </summary>
/// <param name="c">The letter that needs to be updated</param>
private void UpdateWord(string c)
{
int hits = 0;
for (int i = 0; i < currentWord.Length; i++)
{
if (currentWord[i] == c[0])
{
// Display the letter and change its background to green
Image background = letters[i].GetComponent<Image>();
background.color = new Color(139f / 255f, 212f / 255f, 94f / 255f);
TMP_Text txt = letters[i].GetComponentInChildren<TMP_Text>();
txt.text = c;
// You correctly guessed a letter
corrects++;
hits++;
}
}
scoreDisplay.text = $"Score: {CalculateScore()}";
scoreBonus.text = $"+{hits * correctLetterScore}";
scoreBonus.color = new Color(0x8b / 255.0f, 0xd4 / 255.0f, 0x5e / 255.0f);
}
/// <summary>
/// This function returns the score that the user currently has
/// </summary>
/// <returns>The current score of the user</returns>
public override int CalculateScore()
{
int won = corrects == currentWord.Length ? 1 : 0;
return corrects * correctLetterScore + wrongs * incorrectLetterScore + winScore * won;
}
// The following functions originate from Spellingbee
/// <summary>
/// Delete all letter objects
/// </summary>
private void DeleteWord()
{
for (int i = 0; i < letters.Count; i++)
{
Destroy(letters[i]);
}
letters.Clear();
}
/// <summary>
/// Displays the word that needs to be spelled
/// </summary>
/// <param name="word">The word to display</param>
private void DisplayWord(string word)
{
foreach (Char c in word)
{
// Create instance of prefab
GameObject instance = GameObject.Instantiate(letterPrefab, letterContainer);
letters.Add(instance);
// Dynamically load appearance
Image background = instance.GetComponent<Image>();
background.color = Color.clear;
TMP_Text txt = instance.GetComponentInChildren<TMP_Text>();
txt.text = c == ' ' ? "" : Char.ToString('_');
}
}
/// <summary>
/// The logic to process the signs sent by the signPredictor
/// </summary>
/// <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)
{
// Grab the threshold for the most probable letter
Learnable letter = fingerSpelling.learnables.Find((l) => l.name == predictedSign);
float threshold = letter.thresholdPercentage;
// If there is a feedback-object, we wil change its appearance
if (feedbackText != null && feedbackProgressImage != null)
{
float oldValue = feedbackProgress.value;
// use an exponential scale
float newValue = Mathf.Exp(4 * (Mathf.Clamp(accuracy / threshold, 0.0f, 1.0f) - 1.0f));
feedbackProgress.gameObject.Tween("FeedbackUpdate", oldValue, newValue, 0.2f, TweenScaleFunctions.CubicEaseInOut, (t) =>
{
if (feedbackProgress != null)
{
feedbackProgress.value = t.CurrentValue;
}
});
if (accuracy > threshold)
{
feedbackText.text = $"Herkent '{predictedSign}'";
Color green = new Color(139.0f / 255.0f, 212.0f / 255.0f, 94.0f / 255.0f);
feedbackText.color = green;
feedbackProgressImage.color = green;
}
else if (accuracy > threshold * 0.9)
{
feedbackText.text = $"Lijkt op '{predictedSign}'";
Color orange = new Color(242.0f / 255.0f, 127.0f / 255.0f, 12.0f / 255.0f);
feedbackText.color = orange;
feedbackProgressImage.color = orange;
}
else
{
feedbackText.text = "Detecteren...";
Color red = new Color(245.0f / 255.0f, 73.0f / 255.0f, 61.0f / 255.0f);
feedbackText.color = red;
feedbackProgressImage.color = red;
}
}
// 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)
{
// 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())))
{
runTime = true;
}
timerCircle.fillAmount = currentTime;
}
// The same sign was predicted as last time and said sign has been held for a sufficiently long time
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;
}
}
else
{
// The sign was dropped, reset the timer
previousSign = null;
currentTime = 0;
runTime = false;
timerCircle.fillAmount = currentTime;
}
}
/// <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

@@ -1,731 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class HangmanGameController : MonoBehaviour
{
/// <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.
/// </summary>
public ThemeList themelist;
/// <summary>
/// The word that is currently being spelled
/// </summary>
private string currentWord;
/// <summary>
/// All of the words that can be used in this session
/// </summary>
private string[] words;
/// <summary>
/// This integer holds the total amount of wrong guesses the player has made
/// </summary>
private int wrongs;
/// <summary>
/// This integer holds the total amount of times a user can be wrong to lose.
/// </summary>
private int maxWrong = 9;
/// <summary>
/// This integer holds the amount of correct letters of the word that the user has guessed
/// </summary>
private int corrects;
/// <summary>
/// This integer holds the length of the word that needs to be guessed, game ends in victory if corrects == word_length
/// </summary>
private int wordLength;
/// <summary>
/// Indicates if the game is still going
/// </summary>
private bool gameEnded;
/// <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 hangmanImage;
/// <summary>
/// The GameObjects representing the letters
/// </summary>
private List<GameObject> letters = new List<GameObject>();
/// <summary>
/// This scriptable holds all the images for the different stages of hangman
/// </summary>
public HangmanImages images;
/// <summary>
/// This initially empty list holds all the previous guesses that the user has made, as to not allow repeated msitakes
/// </summary>
private List<char> guesses;
/// <summary>
/// This lists holds all valid guesses that the user can make
/// </summary>
private List<char> guessables = new List<char> { 'a', 'z', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'q', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'w', 'x', 'c', 'v', 'b', 'n' };
// The following attributes are necessary for multiplayer interactions
/// <summary>
/// The panel holding the actual game
/// </summary>
public GameObject gamePanel;
/// <summary>
/// The panel holding the player-selection screen
/// </summary>
public GameObject playerPanel;
/// <summary>
/// The panel holding the screen where player 1 can input their word
/// </summary>
public GameObject inputPanel;
/// <summary>
/// This textfield holds the word that player 1 is typing
/// </summary>
public TMP_Text inputTextField;
/// <summary>
/// This int shows what mode we are in, used in update: 0 is playerselect, 1 is wordInput, 2 is the actual game
/// </summary>
private int mode;
// The following attributes are necessary to finish and gameover
/// <summary>
/// "Game over" or "You win!"
/// </summary>
public TMP_Text endText;
/// <summary>
/// The game over panel
/// </summary>
public GameObject gameEndedPanel;
/// <summary>
/// Button for restarting the game
/// </summary>
public Button replayButton;
/// <summary>
/// Letters
/// </summary>
public TMP_Text lettersText;
/// <summary>
/// Letters ( right | wrong )
/// </summary>
public TMP_Text lettersRightText;
public TMP_Text lettersWrongText;
/// <summary>
/// Accuracy
/// </summary>
public TMP_Text accuracyText;
/// <summary>
/// Words
/// </summary>
public TMP_Text wordsText;
/// <summary>
/// Score
/// </summary>
public TMP_Text scoreText;
// The following attributes are necessary for user interaction
/// <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 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 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>
/// Accuracy feeback object
/// </summary>
public Feedback feedback;
/// <summary>
/// The button to go into the game
/// </summary>
public GameObject gottogamebutton;
/// <summary>
/// Current sign out of the predictor
/// </summary>
private String currentsign = "";
// Start is called before the first frame update
void Start()
{
// 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);
// Create entry in current user for keeping track of progress
userList.Load();
user = userList.GetCurrentUser();
Progress progress = user.GetMinigameProgress(minigame.index);
if (progress == null)
{
progress = new Progress();
progress.AddOrUpdate<MinigameIndex>("minigameIndex", minigame.index);
progress.AddOrUpdate<List<Score>>("highestScores", new List<Score>());
progress.AddOrUpdate<List<Score>>("latestScores", new List<Score>());
user.minigames.Add(progress);
}
userList.Save();
// Set calllbacks
feedback.getSignCallback = () =>
{
return "A";
};
feedback.predictSignCallback = (sign) =>
{
currentsign = sign;
};
}
/// <summary>
/// Hangman starts by asking the amount of players, so this function holds all the info needed to start the actual game
/// </summary>
public void StartGame()
{
// Change the mode
mode = 2;
// Activate the right panel
gamePanel.SetActive(true);
inputPanel.SetActive(false);
playerPanel.SetActive(false);
// Reset values of parameters, so that the replaybutton works properly
corrects = 0;
wrongs = 0;
guesses = new List<char>();
// Display a test-word to test the functionality
wordLength = currentWord.Length;
gameEnded = false;
gameEndedPanel.SetActive(false);
// Delete first, to make sure that the letters are empty
DeleteWord();
DisplayWord(currentWord);
replayButton.onClick.AddListener(Start);
// Call to display the first image, corresponding to a clean image.
ChangeSprite();
}
/// <summary>
/// This function is called when the "two player"-button is clicked, it goed to the input-screen
/// </summary>
public void GoToInput()
{
// Change the mode
mode = 1;
// Initialise the word to an empty String
currentWord = "";
inputTextField.text = currentWord.ToString();
// Activate the right panel
gamePanel.SetActive(false);
inputPanel.SetActive(true);
playerPanel.SetActive(false);
}
/// <summary>
/// This function is called if singleplayer is selected, we generate a random word for the player and start the game.
/// </summary>
public void SinglePlayer()
{
// This word is used for testing before dynamic word-fetching is added
PickRandomWord();
StartGame();
}
/// <summary>
/// Randomly select a word from a randomly selected theme, use this word for the hangman game for singleplayer.
/// </summary>
private void PickRandomWord()
{
// Get a random index for the themes
// Then get a random index for a word to pull
// First get random index for the themes
int amountThemes = themelist.themes.Count;
System.Random r = new System.Random();
int themeIndex = r.Next(amountThemes);
// Check how many words are in this theme
int amountWords = themelist.themes[themeIndex].learnables.Count;
int wordIndex = r.Next(amountWords);
// Take the word, but lowercase it.
currentWord = themelist.themes[themeIndex].learnables[wordIndex].name.ToLower();
}
/// <summary>
/// This function starts the game after player 1 has entered their word, but only if its length >= 2.
/// </summary>
public void TwoPlayer()
{
if (currentWord.Length >= 2)
{
StartGame();
}
}
/// <summary>
/// Update is called once per frame
/// </summary>
public void Update()
{
if (mode == 1)
{
if (currentsign != "")
{
char letter = currentsign.ToLower()[0];
currentsign = "";
currentWord = currentWord + letter.ToString();
inputTextField.text = currentWord.ToString();
}
// We are in the word-input mode
// We want to show the user what they are typing
// Check to make sure the inputfield is not empty
if (Input.inputString != "")
{
char firstLetter = Input.inputString.ToLower()[0];
if (Input.GetKey(KeyCode.Backspace))
{
// Remove the last letter from the currentword
if (currentWord.Length > 0)
{
currentWord = currentWord.Substring(0, currentWord.Length - 1);
inputTextField.text = currentWord.ToString();
}
}
else if (guessables.Contains(firstLetter))
{
// Append the letter to the currentWord and display it to the user
currentWord = currentWord + firstLetter.ToString();
inputTextField.text = currentWord.ToString();
}
}
gottogamebutton.SetActive(currentWord.Length > 2);
}
if (mode == 2)
{
// We are in the actual game
if (!gameEnded)
{
// Get keyboard input
// For the first input char given by the user, check if the letter is in the word that needs to be spelled.
// Check to make sure the inputfield is not empty
if (currentsign != "")
{
char firstLetter = currentsign.ToLower()[0];
currentsign = "";
// Check if the letter is a valid guess and that it has not been guessed before
if (!guesses.Contains(firstLetter) && guessables.Contains(firstLetter))
{
if (currentWord.Contains(firstLetter))
{
// The guess was correct, we can display all the letters that correspond to the guess
UpdateWord(firstLetter);
}
else
{
// The guess was wrong, the wrongs integer needs to be incremented
wrongs++;
// For now, we will loop back to stage zero after we reach the losing stage 10
wrongs = wrongs % 11;
// Afterwards, the next stage needs to be displayed
ChangeSprite();
}
guesses.Add(firstLetter);
}
}
if (corrects == wordLength)
{
// Victory
ActivateWin();
}
else if (wrongs == maxWrong)
{
// You lost
ActivateGameOver();
}
}
}
}
/// <summary>
/// Change the image that is being displayed
/// </summary>
private void ChangeSprite()
{
// Load the new sprite from the HangmanImages scriptable
Sprite sprite = images.hangmanStages[wrongs];
// Set the new sprite as the Image component's source image
hangmanImage.sprite = sprite;
}
/// <summary>
/// In this function, the letters of the word selected in DisplayWord are updated after a correct guess.
/// </summary>
/// <param name="c">The letter that needs to be updated</param>
private void UpdateWord(char c)
{
for (int i = 0; i < currentWord.Length; i++)
{
if (currentWord[i] == c)
{
// Display the letter and change its background to green
Image background = letters[i].GetComponent<Image>();
background.color = Color.green;
TMP_Text txt = letters[i].GetComponentInChildren<TMP_Text>();
txt.text = Char.ToString(c);
// You correctly guessed a letter
corrects++;
}
}
}
/// <summary>
/// This function returns the score that the user currently has
/// </summary>
/// <returns>The current score of the user</returns>
private int getScore()
{
// Scoring works as follows:
// You get 3 points for each letter in the word that is correctly guessed (corrects * 3)
// You get 9 points for each part of the hangman figure which is NOT displayed ((10 - wrongs) * 9)
return 3 * corrects + (10 - wrongs) * 9;
}
/// <summary>
/// Set score metrics
/// </summary>
private void SetScoreMetrics()
{
// Letters ( right | wrong ) total
lettersRightText.text = corrects.ToString();
lettersWrongText.text = wrongs.ToString();
lettersText.text = (corrects + wrongs).ToString();
// Accuracy
if (corrects + wrongs > 0)
{
accuracyText.text = ((corrects) * 100f / (corrects + wrongs)).ToString("#.##") + "%";
}
else
{
accuracyText.text = "-";
}
// The word that needed to be guessed
wordsText.text = currentWord.ToString();
// Score
scoreText.text = "Score: " + getScore().ToString();
}
// The following functions originate from Spellingbee
/// <summary>
/// Delete all letter objects
/// </summary>
private void DeleteWord()
{
for (int i = 0; i < letters.Count; i++)
{
Destroy(letters[i]);
}
letters.Clear();
}
/// <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(' ');
}
}
/// <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>
/// Update and save the scores
/// </summary>
private void SaveScores()
{
// Calculate new score
int newScore = getScore();
// 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 progress = user.GetMinigameProgress(minigame.index);
// Get the current list of scores
List<Score> latestScores = progress.Get<List<Score>>("latestScores");
List<Score> highestScores = progress.Get<List<Score>>("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.AddOrUpdate<List<Score>>("latestScores", latestScores.Take(10).ToList());
progress.AddOrUpdate<List<Score>>("highestScores", highestScores.Take(10).ToList());
userList.Save();
}
/// <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>
/// 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>
/// 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>>("highestScores");
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++;
}
}
}

View File

@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// The hangman-variant of the ScoreBoard
/// </summary>
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>
public TMP_Text endText;
/// <summary>
/// Letters ( right | wrong )
/// </summary>
public TMP_Text lettersRightText;
public TMP_Text lettersWrongText;
/// <summary>
/// Letters
/// </summary>
public TMP_Text lettersTotalText;
/// <summary>
/// Accuracy
/// </summary>
public TMP_Text accuracyText;
/// <summary>
/// Word that needed to be guessed
/// </summary>
public TMP_Text wordText;
/// <summary>
/// Score
/// </summary>
public TMP_Text scoreText;
/// <summary>
/// Reference to the end result image
/// </summary>
public Image image;
/// <summary>
/// Generate the content of the GameEnded panel
/// </summary>
/// <param name="guessWord">Total number of words</param>
/// <param name="correctLetters">Total number of correctly spelled letters</param>
/// <param name="incorrectLetters">Total number of incorrectly spelled letters</param>
/// <param name="sprite">Sprite to be displayed alongside the final score</param>
/// <param name="result">"VERLOREN" or "GEWONNEN"</param>
/// <param name="score">Final score</param>
public void GenerateContent(string guessWord, int correctLetters, int incorrectLetters, Sprite sprite, string result, int score)
{
// Final result
endText.text = result;
image.sprite = sprite;
// Letters ( right | wrong ) total
lettersRightText.text = correctLetters.ToString();
lettersWrongText.text = incorrectLetters.ToString();
lettersTotalText.text = (correctLetters + incorrectLetters).ToString();
// Accuracy
if (correctLetters + incorrectLetters > 0)
{
accuracyText.text = ((correctLetters) * 100f / (correctLetters + incorrectLetters)).ToString("#.##") + "%";
}
else
{
accuracyText.text = "-";
}
// Words
wordText.text = guessWord;
// Score
scoreText.text = $"Score: {score}";
SetScoreBoard();
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d9c124cfcaadaf040b00ede50b1b022c
guid: 3576f66af2a0eea42ae06ac73d9779e6
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;
/// <summary>
/// Class for holding all stage images for the hangman minigame
/// </summary>
[CreateAssetMenu(menuName = "Create new Scriptable/HangmanImages")]
public class HangmanImages : ScriptableObject
{
/// <summary>
/// This list will hold all the images for the stages of hangman.
/// </summary>
public List<Sprite> hangmanStages = new List<Sprite>();
}

View File

@@ -0,0 +1,22 @@
{
"name": "HangmanScripts",
"rootNamespace": "",
"references": [
"GUID:e83ddf9a537a96b4a804a16bb7872ec1",
"GUID:6055be8ebefd69e48b49212b09b47b2f",
"GUID:1631ed2680c61245b8211d943c1639a8",
"GUID:58e104b97fb3752438ada2902a36dcbf",
"GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25",
"GUID:403dd94a93598934eb522dc36df43d7b",
"GUID:d0b6b39a21908f94fbbd9f2c196a9725"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 99b7a088eba870d4486dbdc5ad359f27
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,46 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class PanelHangmanGame : MonoBehaviour
{
/// <summary>
/// This textfield holds the letters previously guessed
/// </summary>
public TMP_Text guessesTextField;
/// <summary>
/// Reference to the feedback field
/// </summary>
public TMP_Text feedbackText;
/// <summary>
/// Reference to the progress bar
/// </summary>
public Slider feedbackProgressBar;
/// <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>
/// Holds a reference to the TimerCircle to update its fill
/// </summary>
public Image timerCircle;
/// <summary>
/// Hold a reference to the confirmPanel to toggle its activity
/// </summary>
public GameObject confirmPanel;
/// <summary>
/// Hold a reference to the confirmPanel to toggle its activity
/// </summary>
public TMP_Text confirmText;
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: a62d2b8bded916443835dc19010b83c1
guid: 6cef5e38c4eef7f4dbefa1dbffa7961e
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,51 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class PanelMultiplayerInput : MonoBehaviour
{
/// <summary>
/// The button to go into the game
/// </summary>
public GameObject gotoGameButton;
/// <summary>
/// This textfield holds the word that player 1 is typing
/// </summary>
public TMP_Text inputTextField;
/// <summary>
/// Reference to the feedback field
/// </summary>
public TMP_Text feedbackText;
/// <summary>
/// Reference to the progress bar
/// </summary>
public Slider feedbackProgressBar;
/// <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>
/// Holds a reference to the TimerCircle to update its fill
/// </summary>
public Image timerCircle;
/// <summary>
/// Hold a reference to the confirmPanel to toggle its activity
/// </summary>
public GameObject confirmPanel;
/// <summary>
/// Hold a reference to the confirmPanel to toggle its activity
/// </summary>
public TMP_Text confirmText;
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5db51e2552e03de4b9e7e91b5746adbc
guid: 34e8302b098dcf24ba94b0396f0ce9ef
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -1,95 +0,0 @@
using UnityEngine.UI;
using UnityEngine;
public class WebCam : MonoBehaviour
{
/// <summary>
/// Index of the current camera
/// </summary>
protected int camdex = 0;
/// <summary>
/// Texture to paste on the display
/// </summary>
protected WebCamTexture tex;
/// <summary>
/// Display for the video feed
/// </summary>
public RawImage display;
/// <summary>
/// Setup the webcam correctly
/// </summary>
void Awake()
{
WebCamDevice device = WebCamTexture.devices[camdex];
tex = new WebCamTexture(device.name);
display.texture = tex;
tex.Play();
}
/// <summary>
/// Function to toggle between stopping and starting
/// </summary>
/*
public void toggle()
{
if (tex.isPlaying)
{
tex.Stop();
}
else
{
tex.Play();
}
}
*/
public void PlayCam()
{
if (!tex.isPlaying) tex.Play();
}
public void StopCam()
{
if (tex.isPlaying) tex.Stop();
}
/// <summary>
/// Swap webcam by cycling through the `WebCamTexture.devices` list
/// </summary>
public void SwapCam()
{
if (WebCamTexture.devices.Length > 0)
{
// Stop the old camera
display.texture = null;
tex.Stop();
tex = null;
// Find the new camera
camdex += 1;
camdex %= WebCamTexture.devices.Length;
// Start the new camera
WebCamDevice device = WebCamTexture.devices[camdex];
tex = new WebCamTexture(device.name);
display.texture = tex;
tex.Play();
}
}
/// <summary>
/// Scene changing is implemented here to avoid problems with webcam
/// </summary>
public void GotoThemeSelection()
{
display.texture = null;
tex.Stop();
tex = null;
SystemController.GetInstance().BackToPreviousScene();
}
}