Created
November 23, 2025 18:00
-
-
Save Leibinger015/0558f39fe73be6d59d7c1da73729bedf to your computer and use it in GitHub Desktop.
WebApp: Für Smartphones und Tablet
This file contains hidden or 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
| <!-- Script by anb030.de / v1.8 (20251123) --> | |
| <!DOCTYPE html> | |
| <html lang="de"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>SuperBall Touch</title> | |
| <link rel="apple-touch-icon" href="img/apple-touch-icon.png" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, viewport-fit=cover"> | |
| <style> | |
| @font-face { | |
| font-family: 'DigiLCD'; | |
| src: url('data/digi_lcd.ttf') format('truetype'); | |
| font-weight: normal; | |
| font-style: normal; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| -webkit-user-select: none; | |
| user-select: none; | |
| } | |
| html, body { | |
| margin: 0; | |
| padding: 0; | |
| height: 100%; | |
| overflow: hidden; | |
| background: #000; | |
| color: #eee; | |
| font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; | |
| touch-action: none; | |
| } | |
| .app { | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| max-width: 500px; | |
| margin: 0 auto; | |
| background: #000; | |
| } | |
| .game-area { | |
| height: calc(100vh - 260px); | |
| flex: none; | |
| position: relative; | |
| } | |
| canvas { | |
| width: 100%; | |
| height: 100%; | |
| display: block; | |
| background: #000; | |
| } | |
| .controls { | |
| min-height: 260px; | |
| flex: none; | |
| background: #111; | |
| display: grid; | |
| grid-template-columns: 1.3fr 0.8fr 0.8fr 1.3fr; | |
| grid-template-rows: auto 1fr; | |
| gap: 10px; | |
| align-items: center; | |
| padding: 0 10px calc(26px + env(safe-area-inset-bottom)) 10px; | |
| } | |
| .btn { | |
| border: none; | |
| border-radius: 12px; | |
| background: #222; | |
| color: #eee; | |
| font-weight: 600; | |
| font-size: clamp(14px, 3.2vw, 18px); | |
| padding: 8px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| box-shadow: 0 0 8px rgba(0,0,0,0.6); | |
| } | |
| .btn:active { | |
| transform: scale(0.97); | |
| background: #333; | |
| } | |
| .btn-round { | |
| border-radius: 50%; | |
| aspect-ratio: 1 / 1; | |
| width: 90%; | |
| justify-self: center; | |
| font-size: clamp(22px, 5vw, 30px); | |
| padding: 0; | |
| } | |
| .btn-left { | |
| background: #263238; | |
| } | |
| .btn-right { | |
| background: #263238; | |
| } | |
| .btn-start, | |
| .btn-pause { | |
| font-size: clamp(12px, 2.8vw, 16px); | |
| padding: 6px; | |
| margin-top: 15px; | |
| border-radius: 10px; | |
| } | |
| .btn-start { | |
| background: #1b5e20; | |
| } | |
| .btn-pause { | |
| background: #b71c1c; | |
| } | |
| .btn-label { | |
| pointer-events: none; | |
| } | |
| .game-logo-container { | |
| grid-column: 1 / -1; | |
| grid-row: 2; | |
| display: flex; | |
| justify-content: center; | |
| align-items: flex-start; | |
| padding-top: 10px; | |
| padding-bottom: 10px; | |
| min-height: 60px; | |
| } | |
| .game-logo { | |
| max-width: 80%; | |
| max-height: 80px; | |
| height: auto; | |
| object-fit: contain; | |
| filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.4)); | |
| } | |
| .orientation-warning { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100vw; | |
| height: 100vh; | |
| background: #000; | |
| color: #fff; | |
| display: none; | |
| align-items: center; | |
| justify-content: center; | |
| flex-direction: column; | |
| text-align: center; | |
| z-index: 99999; | |
| font-size: 1.5rem; | |
| padding: 20px; | |
| } | |
| .landscape-image { | |
| max-height: 50vh; | |
| width: auto; | |
| max-width: 50vw; | |
| height: auto; | |
| display: block; | |
| margin-bottom: 20px; | |
| } | |
| @media (orientation: landscape) { | |
| .orientation-warning { | |
| display: flex; | |
| } | |
| .app { | |
| display: none !important; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="orientation-warning"> | |
| <img src="img/no_landscape.png" alt="Bitte in den Hochformat-Modus drehen" class="landscape-image"> | |
| <p>Dieses Spiel kann nur im <br>Porträtformat gespielt werden.</p> | |
| </div> | |
| <div class="app"> | |
| <div class="game-area"> | |
| <canvas id="game" width="360" height="540"></canvas> | |
| </div> | |
| <div class="controls"> | |
| <button id="btn-left" class="btn btn-round btn-left"> | |
| <span class="btn-label">L</span> | |
| </button> | |
| <button id="btn-start" class="btn btn-start"> | |
| <span class="btn-label">Start</span> | |
| </button> | |
| <button id="btn-pause" class="btn btn-pause"> | |
| <span class="btn-label" id="pause-label">Pause</span> | |
| </button> | |
| <button id="btn-right" class="btn btn-round btn-right"> | |
| <span class="btn-label">R</span> | |
| </button> | |
| <div class="game-logo-container"> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const canvas = document.getElementById("game"); | |
| const ctx = canvas.getContext("2d"); | |
| const WIDTH = canvas.width; | |
| const HEIGHT = canvas.height; | |
| const ballImage = new Image(); | |
| ballImage.src = "img/s_ball.png"; | |
| const obstacleImage = new Image(); | |
| obstacleImage.src = "img/x_ball.png"; | |
| const gameLogoImage = new Image(); | |
| gameLogoImage.src = "img/game_logo.png"; | |
| const PLAYFIELD_WIDTH = WIDTH * 0.55; | |
| const PLAYFIELD_X = (WIDTH - PLAYFIELD_WIDTH) / 2; | |
| const PLAYFIELD_Y = 40; | |
| const PLAYFIELD_HEIGHT = HEIGHT - 100; | |
| const stars = []; | |
| for (let i = 0; i < 60; i++) { | |
| stars.push({ | |
| x: Math.random() * WIDTH, | |
| y: Math.random() * HEIGHT, | |
| r: Math.random() * 1.2 + 0.3 | |
| }); | |
| } | |
| const STATE_TITLE = 0; | |
| const STATE_RUNNING = 1; | |
| const STATE_GAMEOVER= 2; | |
| const STATE_PAUSED = 3; | |
| let gameState = STATE_TITLE; | |
| let level = 1; | |
| const LEVEL_DURATION = 20; | |
| let elapsedTime = 0; | |
| let highscoreLevel = 0; | |
| let highscoreTime = 0.0; | |
| const ball = { | |
| x: WIDTH / 2, | |
| y: PLAYFIELD_Y + PLAYFIELD_HEIGHT - 40, | |
| radius: 16, | |
| speedX: 230 | |
| }; | |
| let obstacles = []; | |
| const BASE_OBSTACLE_SPEED = 120; | |
| let obstacleSpeed = BASE_OBSTACLE_SPEED; | |
| let spawnInterval = 0.9; | |
| let spawnTimer = 0; | |
| const input = { | |
| left: false, | |
| right: false | |
| }; | |
| let lastTimestamp = performance.now(); | |
| let infoLinkArea = null; | |
| function loadHighscores() { | |
| const storedLevel = localStorage.getItem('superball_highscore_level'); | |
| const storedTime = localStorage.getItem('superball_highscore_time'); | |
| highscoreLevel = storedLevel ? parseInt(storedLevel, 10) : 0; | |
| highscoreTime = storedTime ? parseFloat(storedTime) : 0.0; | |
| } | |
| function saveHighscore(currentLevel, currentTime) { | |
| let updateNeeded = false; | |
| if (currentLevel > highscoreLevel) { | |
| highscoreLevel = currentLevel; | |
| highscoreTime = currentTime; | |
| updateNeeded = true; | |
| } | |
| else if (currentLevel > 0 && currentLevel === highscoreLevel && currentTime > highscoreTime) { | |
| highscoreTime = currentTime; | |
| updateNeeded = true; | |
| } | |
| if (updateNeeded) { | |
| localStorage.setItem('superball_highscore_level', highscoreLevel); | |
| localStorage.setItem('superball_highscore_time', highscoreTime.toFixed(1)); | |
| } | |
| } | |
| function resetDifficulty() { | |
| level = 1; | |
| elapsedTime = 0; | |
| obstacleSpeed = BASE_OBSTACLE_SPEED; | |
| spawnInterval = 0.9; | |
| spawnTimer = 0; | |
| } | |
| function applyLevelSettings() { | |
| obstacleSpeed = BASE_OBSTACLE_SPEED + (level - 1) * 40; | |
| spawnInterval = Math.max(0.4, 0.9 - (level - 1) * 0.08); | |
| } | |
| function resetGame() { | |
| ball.x = WIDTH / 2; | |
| ball.y = PLAYFIELD_Y + PLAYFIELD_HEIGHT - 40; | |
| obstacles = []; | |
| resetDifficulty(); | |
| gameState = STATE_RUNNING; | |
| } | |
| function spawnObstacle() { | |
| const minR = 10; | |
| const maxR = 18; | |
| const r = minR + Math.random() * (maxR - minR); | |
| const x = PLAYFIELD_X + r + Math.random() * (PLAYFIELD_WIDTH - 2 * r); | |
| obstacles.push({ | |
| x: x, | |
| y: PLAYFIELD_Y - r, | |
| r: r, | |
| speed: obstacleSpeed + Math.random() * 25 | |
| }); | |
| } | |
| function update(dt) { | |
| if (gameState !== STATE_RUNNING) return; | |
| elapsedTime += dt; | |
| const newLevel = Math.floor(elapsedTime / LEVEL_DURATION) + 1; | |
| if (newLevel > level) { | |
| level = newLevel; | |
| applyLevelSettings(); | |
| } | |
| let dir = 0; | |
| if (input.left) dir -= 1; | |
| if (input.right) dir += 1; | |
| ball.x += dir * ball.speedX * dt; | |
| const leftLimit = PLAYFIELD_X + ball.radius + 4; | |
| const rightLimit = PLAYFIELD_X + PLAYFIELD_WIDTH - ball.radius - 4; | |
| if (ball.x < leftLimit) ball.x = leftLimit; | |
| if (ball.x > rightLimit) ball.x = rightLimit; | |
| spawnTimer += dt; | |
| if (spawnTimer >= spawnInterval) { | |
| spawnTimer -= spawnInterval; | |
| spawnObstacle(); | |
| } | |
| for (let i = obstacles.length - 1; i >= 0; i--) { | |
| const o = obstacles[i]; | |
| o.y += o.speed * dt; | |
| if (o.y - o.r > PLAYFIELD_Y + PLAYFIELD_HEIGHT) { | |
| obstacles.splice(i, 1); | |
| } | |
| } | |
| for (const o of obstacles) { | |
| const dx = ball.x - o.x; | |
| const dy = ball.y - o.y; | |
| const rSum = ball.radius + o.r; | |
| if (dx * dx + dy * dy <= rSum * rSum) { | |
| gameOver(); | |
| break; | |
| } | |
| } | |
| } | |
| function gameOver() { | |
| saveHighscore(level, elapsedTime); | |
| gameState = STATE_GAMEOVER; | |
| } | |
| function drawBackground() { | |
| const grd = ctx.createLinearGradient(0, 0, 0, HEIGHT); | |
| grd.addColorStop(0, "#001b3d"); | |
| grd.addColorStop(1, "#000014"); | |
| ctx.fillStyle = grd; | |
| ctx.fillRect(0, 0, WIDTH, HEIGHT); | |
| ctx.fillStyle = "#ffffff"; | |
| for (const s of stars) { | |
| ctx.beginPath(); | |
| ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| ctx.fillStyle = "#123c8c"; | |
| ctx.fillRect(PLAYFIELD_X - 6, PLAYFIELD_Y - 6, PLAYFIELD_WIDTH + 12, PLAYFIELD_HEIGHT + 12); | |
| ctx.fillStyle = "#001a33"; | |
| ctx.fillRect(PLAYFIELD_X, PLAYFIELD_Y, PLAYFIELD_WIDTH, PLAYFIELD_HEIGHT); | |
| ctx.strokeStyle = "#4f7fbf"; | |
| ctx.lineWidth = 1; | |
| const gridSpacing = 24; | |
| for (let x = PLAYFIELD_X; x <= PLAYFIELD_X + PLAYFIELD_WIDTH; x += gridSpacing) { | |
| ctx.beginPath(); | |
| ctx.moveTo(x + 0.5, PLAYFIELD_Y); | |
| ctx.lineTo(x + 0.5, PLAYFIELD_Y + PLAYFIELD_HEIGHT); | |
| ctx.stroke(); | |
| } | |
| for (let y = PLAYFIELD_Y; y <= PLAYFIELD_Y + PLAYFIELD_HEIGHT; y += gridSpacing) { | |
| ctx.beginPath(); | |
| ctx.moveTo(PLAYFIELD_X, y + 0.5); | |
| ctx.lineTo(PLAYFIELD_X + PLAYFIELD_WIDTH, y + 0.5); | |
| ctx.stroke(); | |
| } | |
| } | |
| function drawBall() { | |
| const diameter = ball.radius * 2; | |
| const drawX = ball.x - ball.radius; | |
| const drawY = ball.y - ball.radius; | |
| if (ballImage.complete && ballImage.naturalWidth > 0) { | |
| ctx.drawImage(ballImage, drawX, drawY, diameter, diameter); | |
| } else { | |
| ctx.beginPath(); | |
| ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2); | |
| ctx.fillStyle = "#ffcc00"; | |
| ctx.fill(); | |
| ctx.lineWidth = 2; | |
| ctx.strokeStyle = "#ff9900"; | |
| ctx.stroke(); | |
| } | |
| } | |
| function drawObstacles() { | |
| for (const o of obstacles) { | |
| const diameter = o.r * 2; | |
| const drawX = o.x - o.r; | |
| const drawY = o.y - o.r; | |
| if (obstacleImage.complete && obstacleImage.naturalWidth > 0) { | |
| ctx.drawImage(obstacleImage, drawX, drawY, diameter, diameter); | |
| } else { | |
| ctx.beginPath(); | |
| ctx.arc(o.x, o.y, o.r, 0, Math.PI * 2); | |
| ctx.fillStyle = "#5cff4a"; | |
| ctx.fill(); | |
| ctx.lineWidth = 2; | |
| ctx.strokeStyle = "#2cbf1e"; | |
| ctx.stroke(); | |
| } | |
| } | |
| } | |
| function drawHUD() { | |
| const timeSec = elapsedTime.toFixed(1); | |
| ctx.textAlign = "right"; | |
| const fontPrimary = 'DigiLCD, system-ui, sans-serif'; | |
| const fontSecondary = 'system-ui, sans-serif'; | |
| ctx.fillStyle = "#ffffff"; | |
| ctx.font = "bold 22px " + fontPrimary; | |
| ctx.fillText(timeSec + " s", WIDTH - 8, 28); | |
| ctx.fillStyle = "#a6ff4d"; | |
| ctx.font = "14px " + fontSecondary; | |
| ctx.fillText("Level", WIDTH - 8, 56); | |
| ctx.font = "bold 32px " + fontPrimary; | |
| ctx.fillText(level.toString(), WIDTH - 8, 88); | |
| ctx.fillStyle = "#ffffff"; | |
| ctx.font = "14px " + fontSecondary; | |
| ctx.fillText("Rekord", WIDTH - 8, 116); | |
| ctx.fillStyle = "#ffcc00"; | |
| ctx.font = "bold 24px " + fontPrimary; | |
| ctx.fillText(highscoreLevel.toString(), WIDTH - 8, 144); | |
| if (highscoreLevel > 0) { | |
| const timeText = "(" + highscoreTime.toFixed(1) + " s)"; | |
| ctx.fillStyle = "#aaaaaa"; | |
| ctx.font = "12px " + fontSecondary; | |
| ctx.fillText(timeText, WIDTH - 8, 162); | |
| } | |
| } | |
| function drawTitleScreen() { | |
| drawBackground(); | |
| drawBall(); | |
| drawHUD(); | |
| ctx.fillStyle = "rgba(0,0,0,0.6)"; | |
| ctx.fillRect(0, 0, WIDTH, HEIGHT); | |
| if (gameLogoImage.complete && gameLogoImage.naturalWidth > 0) { | |
| const logoWidth = WIDTH * 0.8; | |
| const logoHeight = (gameLogoImage.naturalHeight / gameLogoImage.naturalWidth) * logoWidth; | |
| const logoX = (WIDTH - logoWidth) / 2; | |
| const logoY = HEIGHT / 2 - logoHeight / 2 - 60; | |
| ctx.drawImage(gameLogoImage, logoX, logoY, logoWidth, logoHeight); | |
| } else { | |
| ctx.fillStyle = "#ffcc00"; | |
| ctx.font = "bold 26px DigiLCD, system-ui, sans-serif"; | |
| ctx.textAlign = "center"; | |
| ctx.fillText("SUPERBALL TOUCH", WIDTH / 2, HEIGHT / 2 - 40); | |
| } | |
| ctx.fillStyle = "#ffffff"; | |
| ctx.font = "16px system-ui, sans-serif"; | |
| ctx.textAlign = "center"; | |
| ctx.fillText("Weiche den Kugeln aus!", WIDTH / 2, HEIGHT / 2 + 30); | |
| ctx.font = "20px system-ui, sans-serif"; | |
| ctx.fillText("Tippe unten auf START, um zu beginnen.", WIDTH / 2, HEIGHT / 2 + 60); | |
| const infoText = "ⓘ Spielregeln & FAQ"; | |
| const infoFont = "16px system-ui, sans-serif"; | |
| ctx.font = infoFont; | |
| ctx.fillStyle = "#ffffff"; | |
| const infoY = HEIGHT / 2 + 86; | |
| ctx.fillText(infoText, WIDTH / 2, infoY); | |
| const metrics = ctx.measureText(infoText); | |
| const textWidth = metrics.width; | |
| const boxPaddingX = 10; | |
| const boxPaddingY = 6; | |
| infoLinkArea = { | |
| x: WIDTH / 2 - textWidth / 2 - boxPaddingX, | |
| y: infoY - 12 - boxPaddingY, | |
| w: textWidth + boxPaddingX * 2, | |
| h: 24 + boxPaddingY * 2 | |
| }; | |
| ctx.strokeStyle = "rgba(187,187,187,0.5)"; | |
| ctx.lineWidth = 1; | |
| ctx.beginPath(); | |
| ctx.moveTo(WIDTH / 2 - textWidth / 2, infoY + 4); | |
| ctx.lineTo(WIDTH / 2 + textWidth / 2, infoY + 4); | |
| ctx.stroke(); | |
| } | |
| function drawGameOverScreen() { | |
| ctx.fillStyle = "rgba(0,0,0,0.65)"; | |
| ctx.fillRect(0, 0, WIDTH, HEIGHT); | |
| ctx.fillStyle = "#ff4444"; | |
| ctx.font = "bold 26px DigiLCD, system-ui, sans-serif"; | |
| ctx.textAlign = "center"; | |
| ctx.fillText("GAME OVER", WIDTH / 2, HEIGHT / 2 - 40); | |
| ctx.fillStyle = "#ffffff"; | |
| ctx.font = "16px system-ui, sans-serif"; | |
| ctx.fillText("Zeit: " + elapsedTime.toFixed(1) + " s", WIDTH / 2, HEIGHT / 2); | |
| ctx.fillText("Level: " + level, WIDTH / 2, HEIGHT / 2 + 26); | |
| ctx.fillStyle = "#ffcc00"; | |
| ctx.font = "16px system-ui, sans-serif"; | |
| ctx.fillText("Rekord: " + highscoreLevel + " (" + highscoreTime.toFixed(1) + " s)", WIDTH / 2, HEIGHT / 2 + 52); | |
| ctx.fillStyle = "#ffffff"; | |
| ctx.font = "14px system-ui, sans-serif"; | |
| ctx.fillText("Tippe auf START für einen neuen Versuch.", WIDTH / 2, HEIGHT / 2 + 80); | |
| } | |
| function drawPauseOverlay() { | |
| ctx.fillStyle = "rgba(0,0,0,0.5)"; | |
| ctx.fillRect(0, 0, WIDTH, HEIGHT); | |
| ctx.fillStyle = "#ffeb3b"; | |
| ctx.font = "bold 24px DigiLCD, system-ui, sans-serif"; | |
| ctx.textAlign = "center"; | |
| ctx.fillText("PAUSE", WIDTH / 2, HEIGHT / 2); | |
| } | |
| function render() { | |
| drawBackground(); | |
| drawObstacles(); | |
| drawBall(); | |
| drawHUD(); | |
| if (gameState === STATE_TITLE) { | |
| drawTitleScreen(); | |
| } else if (gameState === STATE_GAMEOVER) { | |
| drawGameOverScreen(); | |
| } else if (gameState === STATE_PAUSED) { | |
| drawPauseOverlay(); | |
| } | |
| } | |
| function loop(timestamp) { | |
| let dt = (timestamp - lastTimestamp) / 1000; | |
| lastTimestamp = timestamp; | |
| if (dt > 0.05) dt = 0.05; | |
| if (gameState === STATE_RUNNING) { | |
| update(dt); | |
| } | |
| render(); | |
| requestAnimationFrame(loop); | |
| } | |
| const btnLeft = document.getElementById("btn-left"); | |
| const btnRight = document.getElementById("btn-right"); | |
| const btnStart = document.getElementById("btn-start"); | |
| const btnPause = document.getElementById("btn-pause"); | |
| const pauseLabel = document.getElementById("pause-label"); | |
| function bindHoldButton(elem, onDown, onUp) { | |
| elem.addEventListener("pointerdown", (e) => { | |
| e.preventDefault(); | |
| onDown(); | |
| }); | |
| ["pointerup", "pointercancel", "pointerleave"].forEach(ev => { | |
| elem.addEventListener(ev, (e) => { | |
| e.preventDefault(); | |
| if (onUp) onUp(); | |
| }); | |
| }); | |
| } | |
| bindHoldButton(btnLeft, | |
| () => { input.left = true; input.right = false; }, | |
| () => { input.left = false; } | |
| ); | |
| bindHoldButton(btnRight, | |
| () => { input.right = true; input.left = false; }, | |
| () => { input.right = false; } | |
| ); | |
| btnStart.addEventListener("pointerdown", (e) => { | |
| e.preventDefault(); | |
| resetGame(); | |
| pauseLabel.textContent = "Pause"; | |
| }); | |
| btnPause.addEventListener("pointerdown", (e) => { | |
| e.preventDefault(); | |
| if (gameState === STATE_RUNNING) { | |
| gameState = STATE_PAUSED; | |
| pauseLabel.textContent = "Weiter"; | |
| } else if (gameState === STATE_PAUSED) { | |
| gameState = STATE_RUNNING; | |
| pauseLabel.textContent = "Pause"; | |
| } | |
| }); | |
| canvas.addEventListener("pointerdown", (e) => { | |
| if (gameState !== STATE_TITLE || !infoLinkArea) return; | |
| const rect = canvas.getBoundingClientRect(); | |
| const scaleX = canvas.width / rect.width; | |
| const scaleY = canvas.height / rect.height; | |
| const x = (e.clientX - rect.left) * scaleX; | |
| const y = (e.clientY - rect.top) * scaleY; | |
| if ( | |
| x >= infoLinkArea.x && | |
| x <= infoLinkArea.x + infoLinkArea.w && | |
| y >= infoLinkArea.y && | |
| y <= infoLinkArea.y + infoLinkArea.h | |
| ) { | |
| e.preventDefault(); | |
| window.location.href = "data/info.html"; | |
| } | |
| }); | |
| window.addEventListener("keydown", (e) => { | |
| const key = e.key; | |
| if (key === "ArrowLeft") { | |
| e.preventDefault(); | |
| input.left = true; | |
| input.right = false; | |
| } else if (key === "ArrowRight") { | |
| e.preventDefault(); | |
| input.right = true; | |
| input.left = false; | |
| } else if (key === " " || key === "Spacebar") { | |
| e.preventDefault(); | |
| resetGame(); | |
| pauseLabel.textContent = "Pause"; | |
| } else if (key === "p" || key === "P") { | |
| e.preventDefault(); | |
| if (gameState === STATE_RUNNING) { | |
| gameState = STATE_PAUSED; | |
| pauseLabel.textContent = "Weiter"; | |
| } else if (gameState === STATE_PAUSED) { | |
| gameState = STATE_RUNNING; | |
| pauseLabel.textContent = "Pause"; | |
| } | |
| } | |
| }); | |
| window.addEventListener("keyup", (e) => { | |
| const key = e.key; | |
| if (key === "ArrowLeft") { | |
| e.preventDefault(); | |
| input.left = false; | |
| } else if (key === "ArrowRight") { | |
| e.preventDefault(); | |
| input.right = false; | |
| } | |
| }); | |
| loadHighscores(); | |
| lastTimestamp = performance.now(); | |
| requestAnimationFrame(loop); | |
| </script> | |
| </body> | |
| </html> |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Artikel zum Spiel: https://anb030.de/a/webapp-superball-touch/
index.html
App-Icon:
Logo:
Ball‘s: