diff --git a/python/static/controller.html b/python/static/controller.html index 1df0c7a..2f43671 100644 --- a/python/static/controller.html +++ b/python/static/controller.html @@ -1,23 +1,33 @@ - - + - - - OBUS controller - - + + + - -

Game state:

+ OBUS controller + - - - + + +
+

Game state:

-
- Timer should be here -
- -
- + + +
+ + +
+ + Timer should be here + +
+ + +
+ + + + + diff --git a/python/static/controller.js b/python/static/controller.js deleted file mode 100644 index 6c8a6f3..0000000 --- a/python/static/controller.js +++ /dev/null @@ -1,108 +0,0 @@ -let estimated_timeout_date; -let display; -let gamestate; -let last_strike_amount = 0; -let strike_audio = new Audio('/static/strike.mp3'); -strike_audio.preload = 'auto'; -let last_puzzle_state = [] - -function startbutton() { - fetch('/start'); -} - -function restartbutton() { - fetch('/restart'); -} - -function updateDisplay() { - if (gamestate != 'GAME' || !estimated_timeout_date) { - return; - } - setTimeleft((estimated_timeout_date - (new Date()))/1000); -} - -function updateModuleState(data) { - if (data.gamestate == 'INACTIVE' || data.gamestate == 'INFO') { - document.getElementById("modules").innerHTML = ''; - } - else { - let newmodulestate = []; - for (const puzzlestate of data.puzzles) { - let modulediv = document.createElement('div'); - modulediv.textContent = puzzlestate.address; - if (puzzlestate.solved === true) { - modulediv.className = "solved"; - } else if (puzzlestate.solved === false) { - modulediv.className = "unsolved"; - } else if (puzzlestate.solved === null) { - modulediv.className = "needy"; - } - newmodulestate.push(modulediv); - } - document.getElementById("modules").replaceChildren(...newmodulestate); - } -} - -function setTimeleft(timeleft) { - if (timeleft <= 0) { - display.setValue('00:00.0'); - return - } - let integral = Math.floor(timeleft); - let fractional = timeleft - integral; - let minutes = Math.floor(integral / 60); - let seconds = integral % 60; - display.setValue(String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0') + '.' + String(Math.floor(fractional * 10))); -} - -function state_update() { - // TODO automatically timeout this request after the update interval - fetch('/status.json') - .then( - function(response) { - if (response.status !== 200) { - console.log('FAIL: ' + response.status); - return; - } - response.json().then(function(data) { - gamestate = data.gamestate; - document.getElementById('gamestate').innerHTML = gamestate; - if (gamestate != 'GAME') { - last_strike_amount = 0; - } - else if (gamestate == 'GAME') { - // TODO maybe smooth this with the previous value of estimated_timeout_date? - let new_estimate = new Date(); - new_estimate.setTime(new_estimate.getTime() + data.timeleft*1000); - estimated_timeout_date = new_estimate; - let total_strikes = data.puzzles.map(x => x.strikes).reduce((a, b) => a + b, 0); - if (last_strike_amount < total_strikes) { - strike_audio.load(); - strike_audio.play(); - } - last_strike_amount = total_strikes; - } - updateModuleState(data); - }); - } - ) -} - -window.onload = function() { - setInterval(state_update, 500); - 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; - display.colorOn = "#24dd22"; - display.colorOff = "#1b4105"; - display.draw(); - display.draw(); - setInterval(updateDisplay, 100); -}; diff --git a/python/static/controller.css b/python/static/css/controller.css similarity index 100% rename from python/static/controller.css rename to python/static/css/controller.css diff --git a/python/static/debugger.html b/python/static/debugger.html index 0f7f3bd..beffa4c 100644 --- a/python/static/debugger.html +++ b/python/static/debugger.html @@ -1,118 +1,140 @@ - + - - - CAN debugger - - + .colorblock { + height: 20px; + width: 20px; + margin: 3.4px; + margin-right: 8.4px; + display: inline-block; + background-color: lime; + vertical-align: middle; + } + + - - - + + + - - + + - - + + - - - - - - - - - -
Human-readable typeSender IDParsed payloadTimeRaw MessageRaw ID
- - + + + + + + + + + +
Human-readable typeSender IDParsed payloadTimeRaw MessageRaw ID
+ + diff --git a/python/static/js/controller.js b/python/static/js/controller.js new file mode 100644 index 0000000..7451a5a --- /dev/null +++ b/python/static/js/controller.js @@ -0,0 +1,197 @@ +/** + * Soundeffects + */ +const sounds = { + strike: new Audio("/static/sounds/strike.mp3"), +}; + +/** + * 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, +}; + +/** + * 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; + } + + if (state.gamestate === "GAME") { + // Update the estimated timeout date + state.estimatedTimeout = new Date( + new Date().getTime() + data.timeleft * 1000 + ); + + // Calculate the ew total amount of strikes for all puzzles. + 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); + } + + // Update the total amount of strikes + state.strikes = newStrikes; + } + + // 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; + display.colorOn = "#24dd22"; + display.colorOff = "#1b4105"; + 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", () => { + // Initiliaz the game state. + initializeGameState(); + + // Initialize the segment display. + initializeSegmentDisplay(); +}); diff --git a/python/static/debugscript.js b/python/static/js/debugscript.js similarity index 100% rename from python/static/debugscript.js rename to python/static/js/debugscript.js diff --git a/python/static/segment-display.js b/python/static/js/segment-display.js similarity index 100% rename from python/static/segment-display.js rename to python/static/js/segment-display.js diff --git a/python/static/strike.mp3 b/python/static/sounds/strike.mp3 similarity index 100% rename from python/static/strike.mp3 rename to python/static/sounds/strike.mp3