bildbearbeitung / _res /miniGameButton.js
Sebastiankay's picture
Update _res/miniGameButton.js
5412de8 verified
/**
* Interactive Mini-Game Button
*/
class GameButton {
constructor(buttonId) {
this.button = document.getElementById(buttonId)
this.runButton = document.getElementById("runBtn")
if (!this.button) {
console.error("GameButton: Target button not found")
return
}
// --- Injection Logic ---
this.button.classList.add("game-btn") // Add our styling class
// Wrap existing text to animate it
if (!this.button.querySelector(".btn-text")) {
const textSpan = document.createElement("span")
textSpan.className = "btn-text"
while (this.button.firstChild) {
textSpan.appendChild(this.button.firstChild)
}
this.button.appendChild(textSpan)
}
// Create Game Container
if (!this.button.querySelector(".game-container")) {
this.gameContainer = document.createElement("div")
this.gameContainer.className = "game-container"
this.gameContainer.id = "gameContainer"
this.button.appendChild(this.gameContainer)
} else {
this.gameContainer = this.button.querySelector(".game-container")
}
// Create Progress Bar
if (!this.button.querySelector(".game-button-progress-bar")) {
const progBar = document.createElement("div")
progBar.className = "game-button-progress-bar"
this.progressFill = document.createElement("div")
this.progressFill.className = "progress-fill"
this.progressFill.id = "progressFill"
progBar.appendChild(this.progressFill)
this.button.appendChild(progBar)
} else {
this.progressFill = this.button.querySelector(".progress-fill")
}
// Create Completion Controls
if (!this.button.querySelector(".completion-controls")) {
const controls = document.createElement("div")
controls.className = "completion-controls"
this.compText = document.createElement("span")
this.compText.className = "completion-text"
this.compText.innerText = "Fertig!"
controls.appendChild(this.compText)
this.closeBtn = document.createElement("button")
this.closeBtn.className = "close-game-btn"
this.closeBtn.title = "Close Game"
// Use innerHTML for SVG icon
this.closeBtn.innerHTML = '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>'
// Prevent close button click from triggering main button
this.closeBtn.addEventListener("click", (e) => {
e.stopPropagation()
this.reset()
})
controls.appendChild(this.closeBtn)
this.button.appendChild(controls)
} else {
this.closeBtn = this.button.querySelector(".close-game-btn")
this.compText = this.button.querySelector(".completion-text")
this.closeBtn.addEventListener("click", (e) => {
e.stopPropagation()
this.reset()
})
}
if (!this.button.querySelector(".points-wrapper")) {
const pointsWrapper = document.createElement("div")
pointsWrapper.className = "points-wrapper"
const pointsTextWrapper = document.createElement("span")
pointsTextWrapper.className = "points-text-wrapper"
const pointsText = document.createElement("span")
pointsText.className = "points-text"
pointsText.innerText = "p"
this.points = document.createElement("span")
this.points.className = "points"
this.points.innerText = "0"
pointsTextWrapper.appendChild(this.points)
pointsTextWrapper.appendChild(pointsText)
pointsWrapper.appendChild(pointsTextWrapper)
this.button.appendChild(pointsWrapper)
} else {
this.points = this.button.querySelector(".points")
}
// -----------------------
this.durationInput = document.getElementById("durationInput")
this.state = "IDLE"
this.processDuration = 5000
this.startTime = 0
this.rafId = null
this.gamePoints = 0
this.games = ["snake", "memory", "simon"]
this.currentGame = null
this.button.addEventListener("click", (e) => {
if (this.button.classList.contains("active")) e.preventDefault()
if (this.state === "IDLE") this.activate()
})
// Pause on blur
this.button.addEventListener("blur", () => {
// if (this.currentGame && (this.state === "ACTIVE" || this.state === "COMPLETE")) {
// this.currentGame.pause()
// this.button.classList.add("paused")
// }
})
// Resume on focus
this.button.addEventListener("focus", () => {
if (this.currentGame && (this.state === "ACTIVE" || this.state === "COMPLETE")) {
this.currentGame.resume()
this.button.classList.remove("paused")
}
})
this.button.addEventListener("focusout", (e) => {
if (this.currentGame && (this.state === "ACTIVE" || this.state === "COMPLETE")) {
if (e.currentTarget.contains(e.relatedTarget)) {
/* Focus will still be within the container */
this.currentGame.resume()
this.button.classList.remove("paused")
} else {
/* Focus will leave the container */
this.currentGame.pause()
this.button.classList.add("paused")
}
}
})
}
activate() {
if (this.state !== "IDLE") return
this.runButton.click()
// Get duration from input if present
if (this.durationInput) {
const val = parseInt(this.durationInput.value)
if (val && val > 0) this.processDuration = val
}
this.state = "ACTIVE"
this.button.classList.add("active", "process-running")
this.button.classList.remove("complete")
// Start the fake process
this.startTime = Date.now()
this.progressFill.style.width = "0%"
setTimeout(() => this.updateProgress(), 200)
// Pick a random game
setTimeout(() => {
if (this.state === "ACTIVE") {
this.launchRandomGame()
}
}, 500)
}
updateProgress() {
if (this.state === "IDLE") return
const elapsed = Date.now() - this.startTime
const firstActiveProgressBar = document.querySelectorAll("div.progress-bar")[0]
const progressError = document.querySelectorAll('div[data-testid="status-tracker"] span.error')[0]
if (firstActiveProgressBar) {
const progress = firstActiveProgressBar.style.width
this.progressFill.style.width = `${progress}`
}
// if (progress >= 100 && this.state !== "COMPLETE") {
// this.complete()
// }
if (progressError) {
console.log("complete error")
this.completeError()
}
if (this.button.classList.contains("process-running") === false && this.state !== "COMPLETE") {
console.log("complete")
this.progressFill.style.width = "100%"
this.complete()
}
if (this.state !== "IDLE") {
if (this.button.classList.contains("process-running")) {
this.rafId = requestAnimationFrame(() => this.updateProgress())
}
}
}
completeError() {
this.state = "COMPLETE"
this.compText.innerText = "ERROR!"
this.button.classList.add("complete", "error")
this.button.classList.remove("process-running")
// Game continues running! No cleanup here.
}
complete() {
this.state = "COMPLETE"
this.button.classList.add("complete")
// Game continues running! No cleanup here.
}
reset() {
this.state = "IDLE"
this.button.classList.remove("active")
this.button.classList.remove("complete", "error")
this.compText.innerText = "Fertig!"
this.progressFill.style.width = "0%"
if (this.currentGame) {
this.currentGame.cleanup()
this.currentGame = null
}
this.gameContainer.innerHTML = ""
// Remove paused class if present
this.button.classList.remove("paused")
}
launchRandomGame() {
// Simple random
const gameType = this.games[Math.floor(Math.random() * this.games.length)]
// const gameType = "snake"
this.gameContainer.innerHTML = ""
const w = this.button.offsetWidth
const h = this.button.offsetHeight
if (gameType === "snake") {
this.currentGame = new SnakeGame(this.gameContainer, w, h)
} else if (gameType === "memory") {
this.currentGame = new MemoryGame(this.gameContainer)
} else if (gameType === "simon") {
this.currentGame = new SimonGame(this.gameContainer)
}
}
}
/**
* Snake Game Implementation
*/
class SnakeGame {
constructor(container, width, height) {
this.canvas = document.createElement("canvas")
this.canvas.id = "snakeCanvas"
this.canvas.width = Math.min(width - 40, 400)
this.canvas.height = 70
container.appendChild(this.canvas)
this.ctx = this.canvas.getContext("2d")
this.gridSize = 10
const startX = Math.floor(this.canvas.width / this.gridSize / 2)
const startY = Math.floor(this.canvas.height / this.gridSize / 2)
this.snake = [{ x: startX, y: startY }]
this.dx = 1
this.dy = 0
this.food = this.spawnFood()
this.score = 0
this.gameOver = false
this.interval = setInterval(() => this.loop(), 120)
this.handleKey = this.handleKey.bind(this)
document.addEventListener("keydown", this.handleKey)
this.canvas.addEventListener("mousedown", (e) => this.handleClick(e))
}
spawnFood() {
return {
x: Math.floor(Math.random() * (this.canvas.width / this.gridSize)),
y: Math.floor(Math.random() * (this.canvas.height / this.gridSize)),
}
}
handleKey(e) {
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
e.preventDefault()
}
switch (e.key) {
case "ArrowUp":
if (this.dy === 0) {
this.dx = 0
this.dy = -1
}
break
case "ArrowDown":
if (this.dy === 0) {
this.dx = 0
this.dy = 1
}
break
case "ArrowLeft":
if (this.dx === 0) {
this.dx = -1
this.dy = 0
}
break
case "ArrowRight":
if (this.dx === 0) {
this.dx = 1
this.dy = 0
}
break
}
}
handleClick(e) {
e.stopPropagation()
const rect = this.canvas.getBoundingClientRect()
const clickX = e.clientX - rect.left
const clickY = e.clientY - rect.top
const headScreenX = this.snake[0].x * this.gridSize + this.gridSize / 2
const headScreenY = this.snake[0].y * this.gridSize + this.gridSize / 2
const diffX = clickX - headScreenX
const diffY = clickY - headScreenY
if (Math.abs(diffX) > Math.abs(diffY)) {
if (diffX > 0 && this.dx === 0) {
this.dx = 1
this.dy = 0
} else if (diffX < 0 && this.dx === 0) {
this.dx = -1
this.dy = 0
}
} else {
if (diffY > 0 && this.dy === 0) {
this.dx = 0
this.dy = 1
} else if (diffY < 0 && this.dy === 0) {
this.dx = 0
this.dy = -1
}
}
}
loop() {
if (this.gameOver) return
const head = { x: this.snake[0].x + this.dx, y: this.snake[0].y + this.dy }
const cols = Math.floor(this.canvas.width / this.gridSize)
const rows = Math.floor(this.canvas.height / this.gridSize)
if (head.x < 0) head.x = cols - 1
if (head.x >= cols) head.x = 0
if (head.y < 0) head.y = rows - 1
if (head.y >= rows) head.y = 0
if (this.snake.some((s) => s.x === head.x && s.y === head.y)) {
this.snake = [{ x: Math.floor(cols / 2), y: Math.floor(rows / 2) }]
this.gameOver = true
this.canvas.classList.add("game-over")
this.restart(4000)
return
}
this.snake.unshift(head)
if (head.x === this.food.x && head.y === this.food.y) {
this.score += 10
document.querySelector(".points").innerText = this.score
this.food = this.spawnFood()
} else {
this.snake.pop()
}
this.draw()
}
draw() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
this.ctx.fillStyle = "#10b981"
this.snake.forEach((s) => {
this.ctx.fillRect(s.x * this.gridSize, s.y * this.gridSize, this.gridSize - 1, this.gridSize - 1)
})
this.ctx.fillStyle = "#ef4444"
this.ctx.fillRect(this.food.x * this.gridSize, this.food.y * this.gridSize, this.gridSize - 1, this.gridSize - 1)
}
cleanup() {
clearInterval(this.interval)
this.restart()
document.removeEventListener("keydown", this.handleKey)
}
pause() {
clearInterval(this.interval)
}
resume() {
clearInterval(this.interval)
this.canvas.classList.remove("game-over")
this.interval = setInterval(() => this.loop(), 120)
}
restart(time) {
setTimeout(() => {
this.snake = [{ x: Math.floor(this.canvas.width / this.gridSize / 2), y: Math.floor(this.canvas.height / this.gridSize / 2) }]
this.dx = 1
this.dy = 0
this.food = this.spawnFood()
this.score = 0
document.querySelector(".points").innerText = this.score
this.gameOver = false
}, time || 0)
}
}
/**
* Memory Game Implementation
*/
class MemoryGame {
constructor(container) {
this.grid = document.createElement("div")
this.grid.className = "memory-grid"
container.appendChild(this.grid)
const icons = ["🐌", "🦕", "♥️", "⚡", "🌱", "🤖"]
this.cards = [...icons, ...icons]
this.cards.sort(() => Math.random() - 0.5)
this.flipped = []
this.matched = []
this.render()
}
render() {
this.cards.forEach((icon, index) => {
const { r, g, b } = getAverageColor(icon)
const card = document.createElement("div")
card.style.setProperty("--card-color", `rgb(${r},${g},${b})`)
// card.style.setProperty("--card-color", `rgba(${r},${g},${b}, 0.5)`)
// card.style.setProperty("--card-color", `color(from rgb(${r},${g},${b}) h calc(from rgb(${r},${g},${b}) h + 180)`)
card.className = "memory-card"
card.dataset.icon = icon
card.addEventListener("mousedown", (e) => e.preventDefault())
card.addEventListener("click", (e) => {
e.stopPropagation()
this.flip(card, index)
})
this.grid.appendChild(card)
})
}
flip(card, index) {
if (this.flipped.length >= 2 || this.flipped.includes(index) || this.matched.includes(index)) return
card.innerText = this.cards[index]
card.classList.add("flipped")
this.flipped.push(index)
if (this.flipped.length === 2) {
this.checkMatch()
}
}
checkMatch() {
const [idx1, idx2] = this.flipped
const card1 = this.grid.children[idx1]
const card2 = this.grid.children[idx2]
if (this.cards[idx1] === this.cards[idx2]) {
this.matched.push(idx1, idx2)
card1.classList.add("matched")
card2.classList.add("matched")
this.flipped = []
if (this.matched.length === this.cards.length) {
setTimeout(() => {
this.matched = []
this.flipped = []
this.cards.sort(() => Math.random() - 0.5)
this.grid.innerHTML = ""
this.render()
}, 2000)
}
} else {
setTimeout(() => {
card1.classList.remove("flipped")
card1.innerText = ""
card2.classList.remove("flipped")
card2.innerText = ""
this.flipped = []
}, 800)
}
}
cleanup() {}
pause() {}
resume() {}
}
/**
* Simon Game Implementation
*/
class SimonGame {
constructor(container) {
this.board = document.createElement("div")
this.board.className = "simon-board"
container.appendChild(this.board)
const colors = ["simon-green", "simon-red", "simon-yellow", "simon-blue"]
this.sequence = []
this.playerSequence = []
this.buttons = []
this.score = 0
colors.forEach((color, i) => {
const btn = document.createElement("div")
btn.className = `simon-btn ${color}`
btn.dataset.id = i
btn.addEventListener("mousedown", (e) => e.preventDefault())
btn.addEventListener("click", (e) => {
e.stopPropagation()
this.handleInput(i)
})
this.board.appendChild(btn)
this.buttons.push(btn)
})
this.isActive = false
this.timer = setTimeout(() => this.nextRound(), 600)
}
nextRound() {
this.sequence.push(Math.floor(Math.random() * 4))
this.playerSequence = []
this.playSequence()
}
playSequence() {
this.isActive = false
let i = 0
this.interval = setInterval(() => {
this.flash(this.sequence[i])
i++
if (i >= this.sequence.length) {
clearInterval(this.interval)
this.isActive = true
}
}, 800)
}
flash(btnIndex) {
if (!this.buttons[btnIndex]) return
const btn = this.buttons[btnIndex]
btn.classList.add("lit")
setTimeout(() => btn.classList.remove("lit"), 300)
}
handleInput(index) {
if (!this.isActive) return
this.flash(index)
this.playerSequence.push(index)
const currentStep = this.playerSequence.length - 1
if (this.playerSequence[currentStep] !== this.sequence[currentStep]) {
// Fail
this.sequence = []
this.isActive = false
this.board.classList.add("game-over")
setTimeout(() => {
this.score = 0
document.querySelector(".points").innerText = this.score
this.board.classList.remove("game-over")
this.nextRound()
}, 4000)
return
}
if (this.playerSequence.length === this.sequence.length) {
this.isActive = false
this.score += 10
document.querySelector(".points").innerText = this.score
setTimeout(() => this.nextRound(), 1000)
}
}
cleanup() {
clearTimeout(this.timer)
this.score = 0
document.querySelector(".points").innerText = this.score
this.board.classList.remove("game-over")
if (this.interval) clearInterval(this.interval)
}
pause() {
this.paused = true
clearTimeout(this.timer)
if (this.interval) clearInterval(this.interval)
this.buttons.forEach((b) => b.classList.remove("lit"))
}
resume() {
if (!this.paused) return
this.paused = false
this.playSequence()
}
}
// Initialize
const initInterval = setInterval(() => {
if (document.querySelector("#gameBtn")) {
console.log("Game Button found!")
const btn = new GameButton("gameBtn")
clearInterval(initInterval)
}
}, 250)
const getAverageColor = (emoji) => {
const canvas = document.createElement("canvas")
const ctx = canvas.getContext("2d")
const size = 30
canvas.width = size
canvas.height = size
ctx.textBaseline = "middle"
ctx.textAlign = "center"
ctx.font = `${size - 4}px sans-serif`
ctx.fillText(emoji, size / 2, size / 2)
const data = ctx.getImageData(0, 0, size, size).data
let r = 0,
g = 0,
b = 0,
count = 0
for (let i = 0; i < data.length; i += 4) {
const alpha = data[i + 3]
if (alpha > 50) {
r += data[i]
g += data[i + 1]
b += data[i + 2]
count++
}
}
if (count > 0) {
r = Math.floor(r / count)
g = Math.floor(g / count)
b = Math.floor(b / count)
} else {
r = 255
g = 255
b = 255
}
return { r, g, b }
}