Source: GameData.js

/**
 * This class manages the game session and rounds on the server
 */
//  GAME SETTINGS
const roundTimerLength = 60; //set length of round timer
let imagesPerPrompt = 3; // represents the amount of images to choose from per prompt
let usedPromptIds = []; // Store prompts already used

// Drawing prompt objects categorized by type
const promptList = [
    {id: 1, word: 'Eat', type: 'Actions'},
    {id: 2, word: 'Jump', type: 'Actions'},
    {id: 3, word: 'Run', type: 'Actions'},
    {id: 4, word: 'Sleep', type: 'Actions'},

    {id: 5, word: 'Bird', type: 'Animals'},
    {id: 6, word: 'Cat', type: 'Animals'},
    {id: 7, word: 'Dog', type: 'Animals'},
    {id: 8, word: 'Elephant', type: 'Animals'},
    {id: 9, word: 'Horse', type: 'Animals'},
    {id: 10, word: 'Mouse', type: 'Animals'},

    {id: 11, word: 'Glasses', type: 'Clothing'},
    {id: 12, word: 'Glove', type: 'Clothing'},
    {id: 13, word: 'Hat', type: 'Clothing'},
    {id: 14, word: 'Pants', type: 'Clothing'},
    {id: 15, word: 'Shirt', type: 'Clothing'},
    {id: 16, word: 'Shoe', type: 'Clothing'},

    {id: 17, word: 'Apple', type: 'Food'},
    {id: 18, word: 'Banana', type: 'Food'},
    {id: 19, word: 'Carrot', type: 'Food'},
    {id: 20, word: 'Grapes', type: 'Food'},
    {id: 21, word: 'Pizza', type: 'Food'},
    {id: 22, word: 'Spaghetti', type: 'Food'},

    {id: 23, word: 'Circle', type: 'Shapes'},
    {id: 24, word: 'Oval', type: 'Shapes'},
    {id: 25, word: 'Square', type: 'Shapes'},
    {id: 26, word: 'Triangle', type: 'Shapes'},
]

class GameData{
    /**
     * Creates an instance of an GameSession to track data about the current game session
     * 
     * @param {number} numberRounds The number of rounds selected for the game
     * @param {number} currentRound The current round for the game
     * @param {number} maxPlayers The maximum number of players allowed to join the game
     * @param {string} players An array of avatars currently in game
     * @param {Map}    playerData Game data associate to individual players such as score and current guess
     * @param {object} prompt The prompt for drawers to draw and guessers to guess
     * @param {string} drawer The currently selected drawer
     * @param {number} timerID The ID related to the timer interval for the room
     * @param {number} timerValue The current value of the round timer
     */

    constructor( numberRounds, currentRound, maxPlayers, players, prompt, drawer, timerID, timerValue, playerData ){
        this.numberRounds = numberRounds;
        this.currentRound = currentRound;
        this.maxPlayers = maxPlayers;
        this.players = players;
        this.prompt = prompt;
        this.drawer = drawer;
        this.timerID = timerID;
        this.timerValue = timerValue;
        this.playerData = playerData;
    }

    /**
     * Start new round in specified room
     * @param {SocketServer} server Socket.io server instance
     * @param {number} room Socket.io room 
     */
    startNewRound(server, room, gameDataMap) {

        console.log(`Starting new round in room ${room}...`)
        this.clearGuesses(server, room);

        console.log(this.playerData);
        if (this.currentRound != 0)
            server.to(room).emit("cast-draw-clear");

        //  Increment round count and verify it is not end of round
        this.currentRound++
        server.to(room).emit('update-round', this.currentRound);
        if (this.currentRound > this.numberRounds) {
            this.currentRound = 0
            server.to(room).emit('end-game');
            return;
        }

        //  Select new drawer for room and update current drawer for all users in room
        let newDrawerSelected = false;
        while (!newDrawerSelected) {
            //  Randomly select player from players array
            let randomIndex = Math.floor(Math.random() * this.players.length);
            
            //  Verify new drawer is not the current drawer
            if (this.drawer == this.players[randomIndex])
                console.log(`${this.drawer} is already drawing, choosing new drawer`)
            else {
                //  Set as new drawer and update all users in room
                this.drawer = this.players[randomIndex];
                console.log(`${this.drawer} chosen as drawer`)
                server.to(room).emit('update-drawer', this.drawer);
                newDrawerSelected = true;
            }
        }
        
        //  Generate new prompt and update prompt for all users in room
        this.prompt = this.getPromptObject();
        server.to(room).emit('update-prompt', this.prompt, this.getPath(this.prompt));

        //  Start Round Timer
        gameDataMap.get(room).timerValue = roundTimerLength;
        this.timerID = setInterval(this.updateTimer, 1000, server, room, gameDataMap);
        console.log(this)
    }

    /**
     * Randomly selects a prompt from the prompt list
     * @returns the selected prompt
     */
    getPromptObject() {
        // Get a random number based on the length of the promptList
        const randomIndex = Math.floor(Math.random() * promptList.length);
        // Use the random number as the index for the prompt
        let chosenPromptObj = promptList[randomIndex];
    
        // Look through the used prompt ids
        for (let index in usedPromptIds){
            // While current used is is equal to the id of the chosen prompt object,
            while (usedPromptIds[index] == chosenPromptObj.id){
                // Get a random number based on the length of the promptList
                const randomIndex = Math.floor(Math.random() * promptList.length);
                // Use the random number as the index for the prompt
                chosenPromptObj = promptList[randomIndex];
            }
        }
        // Push the id of the prompt into a used list
        usedPromptIds.push(chosenPromptObj.id)
        // Return the chosen prompt
        return chosenPromptObj;
    }

    /**
     * Form the file path for the prompt image
     * @param {object} promptObject 
     * @returns path to prompt image
     */
    getPath(promptObject){
        // Get random number to append for the image associated for the prompt 
        let randomImgNumber = Math.floor(Math.random() * imagesPerPrompt) + 1;

        // Ensure the image name is in all lowercase and append the number 
        // Images follow this format: "1image.png", "2image.png", ...
        let lowerCaseWord = randomImgNumber + promptObject.word.toLowerCase();

        // Assemble the path
        let path = 'promptImages/' + promptObject.type + '/' + promptObject.word + '/' + lowerCaseWord + '.png';

        return path;
    }

    /**
     * Clears all user guesses and updates each user in the room 
     */
    clearGuesses(server, room) {

        console.log("\n\nCLEARING ALL GUESSES\n")

        this.playerData.forEach((value, key) => {
            value.currentGuess = ''
            server.to(room).emit("update-user-guess", {
                user: key,
                guess: '',
                imagePath: value.imagePath,
                score: value.score
            })
        })
    }

    /**
     * Check all guesses, return true if all are correct, return false if there are no correct guesses
     * @returns true if all guesses are correct, false if any guesses are incorrect
     */
    allGuessesCorrect() {
        let output = true
        this.playerData.forEach((value, key) => {
            if (key != this.drawer && value.currentGuess != "Correct!")       
                output = false
        })
        return output
    }

    /**
     * Update timer value when called. 
     * @param {SocketServer} server Socket.io server instance
     * @param {number} room Socket.io room 
     */
    updateTimer(server, room, gameDataMap) {
        
        try {
            server.in(room).emit("timer-update", gameDataMap.get(room).timerValue);

            //  Clear interval if it is still running and all players have left
            if (!gameDataMap.get(room).playerData) {
                console.log("Clearing empty game timer")
                clearInterval(gameDataMap.get(room).timerID)
                //mappedGameData.delete(room) 
            }
            
            //  Start new round once timer hits 0
            if (gameDataMap.get(room).timerValue == 0) {
                console.log("Timer cleared")

                const message = "Timer Ran Out!"
                server.to(room).emit("timer-ran-out" , {message}); //emit to everyone

                if (gameDataMap.get(room).timerID)
                    clearInterval(gameDataMap.get(room).timerID);
                gameDataMap.get(room).startNewRound(server, room, gameDataMap);
              
            }
            else
            gameDataMap.get(room).timerValue--;
        }
        catch (error) {
            console.error(`Error during timer update in room ${room} ` + error)
        }
    };
}



module.exports = GameData;