Created
August 24, 2024 23:08
-
-
Save johnowhitaker/95c5cbf4cd4a40994dd74fe8a885cc18 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let context = null; | |
let canvas = null; | |
let video = null; | |
let lastSendTime = 0; | |
const throttleInterval = 1000; // 1 second in milliseconds | |
let throttleTimeout = null; | |
let pendingData = false; | |
htmx.onLoad(elt => { | |
// Find and process the canvas element | |
const elements = htmx.findAll(elt, '#drawingCanvas'); | |
if (elt.matches('#drawingCanvas')) elements.unshift(elt); | |
elements.forEach(c => { | |
canvas = c; | |
context = canvas.getContext('2d'); | |
// Create video element for webcam feed | |
video = document.createElement('video'); | |
video.setAttribute('playsinline', ''); | |
video.setAttribute('autoplay', ''); | |
video.setAttribute('muted', ''); | |
// Start webcam feed | |
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { | |
navigator.mediaDevices.getUserMedia({ video: true }) | |
.then(stream => { | |
video.srcObject = stream; | |
video.play(); | |
}) | |
.catch(err => { | |
console.error("Error accessing the webcam", err); | |
}); | |
} | |
// Update canvas with video feed | |
function updateCanvas() { | |
if (video.readyState === video.HAVE_ENOUGH_DATA) { | |
context.save(); // Save the current context state | |
context.translate(canvas.width, canvas.height); // Move to far corner | |
context.rotate(Math.PI); // Rotate 180 degrees (π radians) | |
context.drawImage(video, 0, 0, canvas.width, canvas.height); | |
context.restore(); // Restore the context state | |
} | |
requestAnimationFrame(updateCanvas); | |
} | |
video.addEventListener('loadedmetadata', updateCanvas); | |
// Start sending frames | |
setInterval(throttledSendCanvasData, throttleInterval); | |
}); | |
// If elt is a guess, log it and update latest-guess | |
if (elt.matches('.guess')) { | |
console.log("Got guess:", elt); | |
const latestGuess = document.getElementById('latest-guess'); | |
let correct = elt.innerText.includes(" (correct!)"); | |
let guess = elt.innerText.split(": ")[1].split(" ")[0]; | |
let guesser = elt.innerText.split(": ")[0]; | |
let text = elt.innerText.split(" (")[0]; | |
if (latestGuess) { | |
latestGuess.innerText = text; //correct ? text + " (correct!)" : text; | |
latestGuess.classList.add('guess-animation'); | |
if (correct) { | |
latestGuess.classList.add('correct-guess'); | |
// End game means 3 seconds before the game ends and moves to new screen | |
// TODO pause and animate fireworks | |
let tl = document.getElementById("time-left") | |
if (tl){ | |
console.log("Stopping countdown"); | |
document.getElementById("active-header").innerText = "Game Over!"; | |
document.getElementById("active-subheader").innerText = guesser + " correctly guessed the word "+ guess + "!"; | |
window.confetti(ticks=100); | |
tl.remove();// Stops the countdown | |
} | |
} else { | |
latestGuess.classList.remove('correct-guess'); | |
} | |
// Remove animation class after it's done | |
setTimeout(() => { | |
latestGuess.classList.remove('guess-animation'); | |
}, 500); | |
} | |
} | |
}); | |
function throttledSendCanvasData() { | |
const now = Date.now(); | |
if (now - lastSendTime >= throttleInterval) { | |
sendCanvasData(); | |
lastSendTime = now; | |
if (throttleTimeout) { | |
clearTimeout(throttleTimeout); | |
throttleTimeout = null; | |
} | |
if (pendingData) { | |
throttleTimeout = setTimeout(() => { | |
sendCanvasData(); | |
lastSendTime = Date.now(); | |
pendingData = false; | |
}, throttleInterval); | |
} | |
} else { | |
pendingData = true; | |
if (!throttleTimeout) { | |
throttleTimeout = setTimeout(() => { | |
sendCanvasData(); | |
lastSendTime = Date.now(); | |
pendingData = false; | |
throttleTimeout = null; | |
}, throttleInterval - (now - lastSendTime)); | |
} | |
} | |
} | |
function sendCanvasData() { | |
canvas.toBlob((blob) => { | |
const formData = new FormData(); | |
formData.append('image', blob, 'webcam.png'); | |
console.log("Sending webcam frame"); | |
fetch('/process-canvas', { | |
method: 'POST', | |
body: formData, | |
}).then(response => response.json()) | |
.then(data => { | |
console.log("Received data"); | |
if (data['active_game'] == "no") { | |
// if the canvas exists, update the active area to remove it | |
setTimeout(() => { | |
if (document.getElementById('drawingCanvas')) { | |
htmx.ajax('GET', '/active_area', {target:'#active-area', swap:'outerHTML'}); | |
} | |
}, 4000); | |
} | |
console.log(data); | |
}) | |
.catch(error => console.error('Error:', error)); | |
}); | |
} | |
// Countdonw timer | |
setInterval(() => { | |
let timeLeftElement = document.getElementById("time-left"); | |
let progressElement = document.getElementById("progress"); | |
let containerElement = document.getElementById("countdown-container"); | |
// do nothing if it doesn't exist | |
if (!timeLeftElement || !progressElement) return; | |
let start = parseFloat(document.getElementById("start-time").value); | |
let elapsed = (Date.now() / 1000) - start; | |
let max_duration = parseFloat(document.getElementById("game-max-duration").value); | |
let time = Math.max(0, max_duration - elapsed); | |
let percentage = (time / max_duration) * 100; | |
// console.log(start, elapsed, max_duration, time, percentage); | |
timeLeftElement.innerText = `Time left: ${time} seconds`; | |
progressElement.style.width = `${percentage}%`; | |
// Add urgency effects | |
if (percentage <= 25) { | |
progressElement.classList.add('urgent'); | |
containerElement.classList.add('urgency'); | |
} else { | |
progressElement.classList.remove('urgent'); | |
containerElement.classList.remove('urgency'); | |
} | |
if (time <= 0) { | |
// Check the game hasn't already been ended | |
let timeLeftElement = document.getElementById("time-left"); | |
if (timeLeftElement){ | |
console.log("Time's up!"); | |
// remove the time-left element | |
timeLeftElement.remove(); | |
// trigger HTMX to end the game and replace the active area | |
htmx.ajax('GET', '/endgame', {target:'#active-area', swap:'outerHTML'}); | |
} | |
} | |
}, 100); // every 100ms |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment