Meeting the Brief Investigation and Plan Design Implementation Testing Evaluation References Word Count

Implementation

Examination number: 142150

Code Explanation

Classes

Class State


        class State {
            static Idle = new State("Idle");
            static SinglePlayer = new State("SinglePlayer");
            static MultiPlayer = new State("MultiPlayer");
            static Simulation = new State("Simulation");
            static GameOver = new State("GameOver");
            constructor(name) {
                this.name = name;
            }
        }
      

This class is used to represent different states in a game, such as "Idle", "SinglePlayer", "MultiPlayer", "Simulation", and "GameOver".


Class Input


        class Input {
            static BtnSinglePlayer = new Input("singleplayer");
            static BtnMultiPlayer = new Input("multiplayer");
            static BtnSimulation = new Input("simulation");
            static BtnReset = new Input("reset");
            constructor(name) {
                this.name = name;
            }
        
            static from(value) {
                const input = Object.keys(Input)
                    .map((key) => Input[key])
                    .find((i) => i.name === value);
                if (input != null) return input;
                return new Input(value);
            }
        }
      

This class is used to represent different inputs in the game, such as "BtnSinglePlayer", "BtnMultiPlayer", "BtnSimulation", and "BtnReset".
The constructor method is used to initialize a new instance of the Input class with a given name.
The "from" method is a static method that takes a string value as input and returns an instance of the Input class that matches that value.



Functions

shuffleDeck()


        function shuffleDeck(cards) {
            for (let i = cards.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [cards[i], cards[j]] = [cards[j], cards[i]];
            }
        }
      

The purpose of this function is to shuffle the order of the cards in the array randomly, so that they are in a different order than they were originally.


pickRandom()


        function pickRandom(items) {
            let index = Math.floor(Math.random() * items.length);
            return items[index];
        }
      

Return a random index value depending on the length of the parameter.


stateMachine()


        function stateMachine(input) {
            console.log("Before---------------------------------");
            console.log(input);
            console.log(`currentState: ${currentState.name}`);
            console.log(`currentPlayer: ${currentPlayer}`);
        
            if (currentState == State.Idle && input == Input.BtnSinglePlayer) {
                currentState = State.SinglePlayer;
                elapsedTime = performance.now();
                BtnSinglePlayer.disabled = true;
                BtnMultiPlayer.disabled = true;
                BtnSimulation.disabled = true;
            }
        
            if (currentState == State.Idle && input == Input.BtnMultiPlayer) {
                currentState = State.MultiPlayer;
                elapsedTime = performance.now();
                BtnSinglePlayer.disabled = true;
                BtnMultiPlayer.disabled = true;
                BtnSimulation.disabled = true;
            }
        
            if (currentState == State.Idle && input == Input.BtnSimulation) {
                currentState = State.Simulation;
                elapsedTime = performance.now();
                BtnSinglePlayer.disabled = true;
                BtnMultiPlayer.disabled = true;
                BtnSimulation.disabled = true;
            }
        
            if (input == Input.BtnReset) {
                currentState = State.Idle;
                minResponseTime = 1000; // Adjust response time for singleplayer and simulation
                maxResponseTime = 3000;
                speedFactor = 1.08;
                currentPlayer = 1;
                p1won.style.display = "none";
                p2won.style.display = "none";
                statsContainer.classList.add("hide");
                resetDataValues(data);
                shuffleDeck(airplanes);
                const middleIndex = Math.ceil(airplanes.length / 2);
                p1Cards = airplanes.slice(0, middleIndex);
                p2Cards = airplanes.slice(middleIndex, airplanes.length);
                p1Card = pickRandom(p1Cards);
                p2Card = pickRandom(p2Cards);
                BtnSinglePlayer.disabled = false;
                BtnMultiPlayer.disabled = false;
                BtnSimulation.disabled = false;
            }
        
            if (currentState == State.GameOver && input == Input.BtnReset) {
                currentState = State.Idle;
            }
        
            if (
                (input.name.startsWith("p1") || input.name.startsWith("p2")) &&
                (currentState == State.SinglePlayer ||
                    currentState == State.MultiPlayer ||
                    currentState == State.Simulation)
            ) {
                const stat = input.name.substring(2);
                console.log(`stat: ${stat}`);
        
                const winner = whoWins(p1Card, p2Card, stat);
                console.log("Winner: " + winner);
        
                if (winner === "p1") {
                    p1Cards.push(p2Card); // Add card to winner
                    p2Cards = p2Cards.filter((card) => card !== p2Card); // Remove card from loser
                    currentPlayer = 1;
                } else if (winner === "p2") {
                    p2Cards.push(p1Card); // Add card to winner
                    p1Cards = p1Cards.filter((card) => card !== p1Card); // Remove card from loser
                    currentPlayer = 2;
                }
        
                if (p1Cards.length === 0 || p2Cards.length === 0) {
                    currentState = State.GameOver;
                    setTimeout(() => {
                        statsContainer.classList.remove("hide");
                        statsContainer.classList.add("show");
        
                        const averageclicks = (data) => {
                            let sum = 0;
                            for (let key in data) {
                                sum += data[key];
                            }
                            return (sum / 6).toFixed(2);
                        };
                        mean.textContent = `Average clicks per stat: ${averageclicks(
                            data
                        )}`;
        
                        const getMode = (data) => {
                            let maxKey = null;
                            let maxValue = -Infinity;
        
                            for (const key in data) {
                                if (data[key] > maxValue) {
                                    maxValue = data[key];
                                    maxKey = key;
                                }
                            }
        
                            return maxKey;
                        };
        
                        mode.textContent = `Most clicked stat (mode): ${getMode(data)}`;
        
                        const getMedian = (data) => {
                            const values = Object.values(data).sort((a, b) => a - b);
                            const medianIndex = Math.floor(values.length / 2);
                            const medianValue =
                                values.length % 2 === 0
                                    ? (values[medianIndex - 1] + values[medianIndex]) /
                                      2
                                    : values[medianIndex];
        
                            for (const key in data) {
                                if (data[key] === medianValue) {
                                    return key;
                                }
                            }
                        };
        
                        median.textContent = `Median: ${getMedian(data)}`;
        
                        const gamestats = JSON.parse(localStorage.getItem("GameStats"));
                        gamestats.unshift({
                            player: currentPlayer,
                            time: ((performance.now() - elapsedTime) / 1000).toFixed(2),
                        });
                        gamestats.pop();
                        renderStatsTable(gamestats);
                        let c = 0;
                        gamestats.forEach((x) => console.log(x.time));
                        gamestats.forEach((x) => (c = c + parseFloat(x.time)));
                        let avtime = c / 3;
                        console.log(avtime);
                        const TaverageTime = document.getElementById("TaverageTime");
                        TaverageTime.textContent = `Average game duration: ${avtime.toFixed(
                            2
                        )} seconds`;
        
                        localStorage.setItem("GameStats", JSON.stringify(gamestats));
                    }, 1500);
                } else {
                    p1Card = pickRandom(p1Cards);
                    p2Card = pickRandom(p2Cards);
                }
        
                if (p1Cards.length === 0) {
                    p2won.style.display = "block";
                } else if (p2Cards.length === 0) {
                    p1won.style.display = "block";
                }
            }
        
            processState(currentState, currentPlayer, p1Card, p2Card);
        
            console.log("After---------------------------------");
            console.log(`currentState: ${currentState.name}`);
            console.log(`currentPlayer: ${currentPlayer}`);
        
            const responseTime = Math.round(
                Math.random() * (maxResponseTime - minResponseTime) + minResponseTime
            );
        
            minResponseTime = minResponseTime / speedFactor;
            maxResponseTime = maxResponseTime / speedFactor;
        
            if (
                (currentState == State.SinglePlayer && currentPlayer === 2) ||
                (currentState == State.Simulation && currentPlayer === 2)
            ) {
                const button = pickRandom(p2buttons);
                setTimeout(() => {
                    button.style.background = "green";
                }, responseTime / 2);
                setTimeout(() => {
                    button.click();
                    button.style.background = "";
                }, responseTime);
            }
        
            // USE FOR NORMAL FUNCTIONING
            if (currentState == State.Simulation && currentPlayer === 1) {
                const button = pickRandom(p1buttons);
                setTimeout(() => {
                    button.style.background = "green";
                }, responseTime / 2);
                setTimeout(() => {
                    button.click();
                    button.style.background = "";
                }, responseTime);
            }
        
            // USE TO TEST HYPOTHESIS (PLAYER 1 CHOOSES PAYLOAD IF CARD IS AIRBUS OR BOEING)
            // if (currentState == State.Simulation && currentPlayer === 1) {
            //     const Tcard = p1Card.name.split(" ").shift();
            //     console.log(Tcard);
            //     let button;
            //     if (Tcard == "Airbus" || Tcard == "Boeing") {
            //         button = p1buttons[5];
            //         console.log(button);
            //     } else {
            //         button = pickRandom(p1buttons);
            //     }
        
            //     setTimeout(() => {
            //         button.style.background = "green";
            //     }, responseTime / 2);
            //     setTimeout(() => {
            //         button.click();
            //         button.style.background = "";
            //     }, responseTime);
            // }
        }
      

This function manages the game's states based on user inputs and game logic. It uses several variables and functions to store and manipulate game data, including the game state, the current player, the player's cards. It is also used to calculate the mean, mode and median (second advanced requirement).
The function then processes the current state and player to update the UI accordingly.
The function also includes code to simulate player responses in single player and simulation modes.


whoWins()


        function whoWins(p1Card, p2Card, stat) {
            // // Used for debugging purposes
            console.log(`p1 ${stat}: ${p1Card[stat]}`);
            console.log(`p2 ${stat}: ${p2Card[stat]}`);
        
            if (stat == "firstflight") {
                if (p1Card[stat] < p2Card[stat]) {
                    data[stat] += 1;
                    chart.data.datasets[0].data = Object.values(data);
                    chart.update();
                    return "p1";
                } else if (p1Card[stat] > p2Card[stat]) {
                    data[stat] += 1;
                    chart.data.datasets[0].data = Object.values(data);
                    chart.update();
                    return "p2";
                } else {
                    return "tie";
                }
            } else {
                if (p1Card[stat] > p2Card[stat]) {
                    data[stat] += 1;
                    chart.data.datasets[0].data = Object.values(data);
                    chart.update();
                    return "p1";
                } else if (p1Card[stat] < p2Card[stat]) {
                    data[stat] += 1;
                    chart.data.datasets[0].data = Object.values(data);
                    chart.update();
                    return "p2";
                } else {
                    return "tie";
                }
            }
        }
      

This function takes in two player cards and a stat to compare, and then determines which player wins based on the value of the given stat. If the stat is "firstflight", the player with the lower value wins. For any other stat, the player with the higher value wins. The function also updates the chart with the number of wins for each stat, and returns the winner or "tie" if there is no clear winner.


processState()


        function processState(currentState, currentPlayer, p1Card, p2Card) {
            if (currentState === State.Idle) {
                currentPlayer = 1;
                p1buttons.forEach((button) => (button.style.visibility = "hidden"));
                p2buttons.forEach((button) => (button.style.visibility = "hidden"));
            } else if (currentPlayer === 1) {
                p1buttons.forEach((button) => (button.style.visibility = "visible"));
                p2buttons.forEach((button) => (button.style.visibility = "hidden"));
            } else if (currentPlayer === 2) {
                p1buttons.forEach((button) => (button.style.visibility = "hidden"));
                p2buttons.forEach((button) => (button.style.visibility = "visible"));
            } else if (currentState === State.SinglePlayer) {
                p1buttons.forEach((button) => (button.style.visibility = "visible"));
                p2buttons.forEach((button) => (button.style.visibility = "visible"));
            } else if (currentState === State.MultiPlayer) {
                p1buttons.forEach((button) => (button.style.visibility = "visible"));
                p2buttons.forEach((button) => (button.style.visibility = "visible"));
            }
        
            if (currentState != State.GameOver) {
                // Display card pic
                document.getElementById("p1-card-pic").src = p1Card.img;
                document.getElementById("p2-card-pic").src = p2Card.img;
        
                // Display card name
                document.getElementById("p1-card-name").textContent = p1Card.name;
                document.getElementById("p2-card-name").textContent = p2Card.name;
            }
        
            // Display cards left in deck
            const p1CardsLeftDisplay = document.getElementById("p1-cards-left");
            const p2CardsLeftDisplay = document.getElementById("p2-cards-left");
            p1CardsLeftDisplay.textContent = `Cards: ${p1Cards.length}`;
            p2CardsLeftDisplay.textContent = `Cards: ${p2Cards.length}`;
        }
      

This function performs different actions based on the values of these parameters, such as showing or hiding buttons for the respective players, displaying card images and names, and updating the cards left in the deck.


renderStatsTable()

This function first sets up an HTML table with headers for "Who won?" and "Game duration", and then iterates over the data array to generate a row for each game with the winner and duration displayed in the corresponding columns. Finally, the generated HTML table is set as the inner HTML of an element with the ID "statsTable".


showExplanation()


        function showExplanation() {
            alert(
                `
        How to play?
        --------------
        1. Choose a gamemode
        2. Select a stat to compare
        3. Keep playing until some player gets all the cards
        
        • The Computer response time gets faster as it wins cards
        • Game analysis will appear after each game
        • Data from each game is saved in the window local storage
        • Press reset go back to Idle state
        `
            );
        }
      

This function shows an alert box with instructions on how to play the game.


resetDataValues()


         function resetDataValues(data) {
            for (let key in data) {
                data[key] = 0;
            }
            chart.update();
        } 
      

This function is used to reset the data values in the chart used in the game.



Others

Cards


        const airplanesX = [
            {
                name: "Boeing 747",
                firstflight: 1969,
                speed: 933,
                range: 14225,
                wingspan: 64.4,
                seats: 416,
                payload: 149875,
                img: "images/b747.jpg",
            },
        .
        .
        .
      

The cards are stored in an array of objects, each card has six stats + an image.


Window Local Storage


        if (!localStorage.getItem("GameStats")) {
            const gamestats = JSON.stringify([
                {
                    player: "-",
                    time: "-",
                },
                {
                    player: "-",
                    time: "-",
                },
                {
                    player: "-",
                    time: "-",
                },
            ]);
        
            localStorage.setItem("GameStats", gamestats);
        }
      

This code block is used to meet the first advanced requirement, it checks if there is any data stored in the local storage under the key "GameStats". If there is no data, it creates a default set of game statistics with three games and saves it as a JSON string in the local storage under the key "GameStats".
The default set of game statistics has three objects with two properties: "player" and "time". The "-" character is used as a placeholder for both properties indicating that no player has won any game yet and no game duration has been recorded.


Pie Chart


        const data = {
            firstflight: 0,
            speed: 0,
            range: 0,
            wingspan: 0,
            seats: 0,
            payload: 0,
        };
        
const chart = new Chart(canvas, { type: "pie", data: { labels: Object.keys(data), datasets: [ { data: Object.values(data), backgroundColor: [ "rgb(255, 99, 132)", "rgb(255, 205, 86)", "rgb(54, 162, 235)", "rgb(75, 192, 192)", "rgb(153, 102, 255)", "rgb(255, 159, 64)", ], }, ], }, options: { responsive: false, }, });

This code is used to create a pie chart in order to show which how many times each stat won a turn. It is used to meet the second advanced requirement.


Selecting Buttons


        document
            .querySelectorAll("button")
            .forEach((button) =>
                button.addEventListener("click", (e) =>
                    stateMachine(Input.from(e.target.id))
                )
            );
      

This code selects all the buttons on the page using the "querySelectorAll" method and adds a click event listener to each of them. When a button is clicked, it creates an "Input" object with the button's "id" as a parameter and passes it as an argument to the "stateMachine" function.