2022-02-10 18:46:25 +01:00
|
|
|
/**
|
|
|
|
* Soundeffects
|
|
|
|
*/
|
|
|
|
const sounds = {
|
|
|
|
strike: new Audio("/static/sounds/strike.mp3"),
|
2022-02-10 20:45:57 +01:00
|
|
|
alarm: new Audio("/static/sounds/alarm.mp3"),
|
2022-02-10 18:46:25 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* State
|
|
|
|
*/
|
|
|
|
const state = {
|
|
|
|
// Segment display used for telling the remaining time.
|
|
|
|
display: null,
|
|
|
|
|
|
|
|
// Current game state.
|
|
|
|
gamestate: null,
|
|
|
|
|
|
|
|
// Estimated timeout date when the timer should be stopped.
|
|
|
|
estimatedTimeout: new Date(),
|
|
|
|
|
|
|
|
// Amount of strikes (wrong answers) the player has made.
|
|
|
|
strikes: 0,
|
2022-02-10 20:45:57 +01:00
|
|
|
|
|
|
|
// If the alarm has been played already
|
|
|
|
alarmPlayed: false,
|
2022-02-10 18:46:25 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Play a given audio file
|
|
|
|
* @param {Audio} sound Audio object containing a sound.
|
|
|
|
*/
|
|
|
|
function playSound(sound) {
|
|
|
|
sound.load();
|
|
|
|
sound.play();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the modules.
|
|
|
|
* @param {Array} List of puzzles
|
|
|
|
*/
|
|
|
|
function updateModules(puzzles) {
|
|
|
|
// Delete the modules when the game is inactive or in info mode.
|
|
|
|
if (state.gamestate == "INACTIVE" || state.gamestate == "INFO") {
|
|
|
|
document.getElementById("modules").innerHTML = "";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const modulesElement = document.getElementById("modules");
|
|
|
|
|
|
|
|
// Update the modules.
|
|
|
|
const modules = [];
|
|
|
|
for (const puzzle of puzzles) {
|
|
|
|
const moduleElement = document.createElement("div");
|
|
|
|
moduleElement.textContent = puzzle.address;
|
|
|
|
|
|
|
|
// State: Solved
|
|
|
|
if (puzzle.solved === true) {
|
|
|
|
moduleElement.className = "solved";
|
|
|
|
}
|
|
|
|
|
|
|
|
// State: Unsolved
|
|
|
|
if (puzzle.solved === false) {
|
|
|
|
moduleElement.className = "unsolved";
|
|
|
|
}
|
|
|
|
|
|
|
|
// State: Needy
|
|
|
|
if (puzzle.solved === null) {
|
|
|
|
moduleElement.className = "needy";
|
|
|
|
}
|
|
|
|
|
|
|
|
modules.push(moduleElement);
|
|
|
|
}
|
|
|
|
|
|
|
|
modulesElement.replaceChildren(...modules);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the game state.
|
|
|
|
*/
|
|
|
|
function initializeGameState() {
|
|
|
|
updateGameState();
|
|
|
|
setInterval(updateGameState, 500);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the game state.
|
|
|
|
*/
|
|
|
|
function updateGameState() {
|
|
|
|
fetch("/status.json")
|
|
|
|
.then((res) => res.json())
|
|
|
|
.then((data) => {
|
|
|
|
// Update the game state
|
|
|
|
state.gamestate = data.gamestate;
|
|
|
|
document.getElementById("gamestate").innerHTML = state.gamestate;
|
|
|
|
|
|
|
|
// Reset the strike amount if the game is not running anymore.
|
|
|
|
if (state.gamestate != "GAME") {
|
|
|
|
state.strikeAmount = 0;
|
2022-02-10 20:45:57 +01:00
|
|
|
state.alarmPlayed = false;
|
2022-02-10 18:46:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (state.gamestate === "GAME") {
|
|
|
|
// Update the estimated timeout date
|
|
|
|
state.estimatedTimeout = new Date(
|
|
|
|
new Date().getTime() + data.timeleft * 1000
|
|
|
|
);
|
|
|
|
|
2022-02-11 21:38:27 +01:00
|
|
|
// Calculate the total amount of strikes for all puzzles.
|
2022-02-10 18:46:25 +01:00
|
|
|
const newStrikes = data.puzzles
|
|
|
|
.map((p) => p.strikes)
|
|
|
|
.reduce((a, b) => a + b, 0);
|
|
|
|
|
|
|
|
// Play a "buzzer" sound when a strike is made.
|
|
|
|
if (state.strikes < newStrikes) {
|
|
|
|
playSound(sounds.strike);
|
|
|
|
}
|
|
|
|
|
2022-02-10 20:45:57 +01:00
|
|
|
// Play a "alarm" sound when the time is at 10 seconds.
|
|
|
|
if (data.timeleft <= 20 && data.timeleft > 19 && !state.alarmPlayed) {
|
|
|
|
playSound(sounds.alarm);
|
|
|
|
state.alarmPlayed = true;
|
|
|
|
}
|
|
|
|
|
2022-02-10 18:46:25 +01:00
|
|
|
// Update the total amount of strikes
|
|
|
|
state.strikes = newStrikes;
|
|
|
|
}
|
|
|
|
|
2022-02-10 20:45:57 +01:00
|
|
|
// Update the start/restart button visibility.
|
|
|
|
const startButton = document.querySelector("#buttonStart");
|
|
|
|
const restartButton = document.querySelector("#buttonRestart");
|
|
|
|
startButton.disabled = state.gamestate !== "DISCOVER";
|
|
|
|
restartButton.disabled = state.gamestate !== "GAMEOVER";
|
|
|
|
|
2022-02-10 18:46:25 +01:00
|
|
|
// Update the modules
|
|
|
|
updateModules(data.puzzles);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When the start button is clicked.
|
|
|
|
*/
|
|
|
|
function onStartButtonClick() {
|
|
|
|
fetch("/start");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When the restart button is clicked.
|
|
|
|
*/
|
|
|
|
function onRestartButtonClick() {
|
|
|
|
fetch("/restart");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the segment display.
|
|
|
|
*/
|
|
|
|
function initializeSegmentDisplay() {
|
|
|
|
display = new SegmentDisplay("display");
|
|
|
|
display.pattern = "##:##.#";
|
|
|
|
display.displayAngle = 0;
|
|
|
|
display.digitHeight = 100;
|
|
|
|
display.digitWidth = display.digitHeight / 2;
|
|
|
|
display.digitDistance = display.digitHeight / 10;
|
|
|
|
display.segmentWidth = display.digitHeight / 10;
|
|
|
|
display.segmentDistance = display.digitHeight / 100;
|
|
|
|
display.segmentCount = 7;
|
|
|
|
display.cornerType = 1;
|
2022-02-10 20:45:57 +01:00
|
|
|
display.colorOn = "#ff0000";
|
|
|
|
display.colorOff = "#ff000022";
|
2022-02-10 18:46:25 +01:00
|
|
|
display.draw();
|
|
|
|
|
|
|
|
// Update the segment
|
|
|
|
setInterval(updateSegmentDisplay, 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the segment display with the latest game data.
|
|
|
|
*/
|
|
|
|
function updateSegmentDisplay() {
|
|
|
|
// Do not update the timer when the game is not running.
|
|
|
|
if (state.gamestate != "GAME" || !state.estimatedTimeout) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const timeLeft = (state.estimatedTimeout - new Date()) / 1000;
|
|
|
|
|
|
|
|
// Set the display to 0 when there is no time left.
|
|
|
|
if (timeLeft <= 0) {
|
|
|
|
display.setValue("00:00.0");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const integral = Math.floor(timeLeft);
|
|
|
|
const fractional = timeLeft - integral;
|
|
|
|
const minutes = Math.floor(integral / 60);
|
|
|
|
const seconds = integral % 60;
|
|
|
|
|
|
|
|
const minutesStr = String(minutes).padStart(2, "0");
|
|
|
|
const secondsStr = String(seconds).padStart(2, "0");
|
|
|
|
const fractionalStr = String(Math.floor(fractional * 10)).padStart(1, "0");
|
|
|
|
|
|
|
|
display.setValue(`${minutesStr}:${secondsStr}.${fractionalStr}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When the window is loaded.
|
|
|
|
*/
|
|
|
|
window.addEventListener("load", () => {
|
|
|
|
initializeGameState();
|
|
|
|
initializeSegmentDisplay();
|
|
|
|
});
|