| <!DOCTYPE html> |
| <html lang="ja"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> |
| <title>Tsum Tsum Style - Skill Gauge Fix</title> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script> |
| <style> |
| :root { |
| --bg-color: #fce4ec; |
| --game-bg: #2c3e50; |
| --fever-bg: #1a1a1a; |
| --panel-color: rgba(255, 255, 255, 0.95); |
| } |
|
|
| body { |
| margin: 0; |
| padding: 0; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| height: 100vh; |
| background-color: var(--bg-color); |
| font-family: 'Hiragino Sans', 'Meiryo', sans-serif; |
| overflow: hidden; |
| touch-action: none; |
| transition: background-color 0.5s; |
| } |
|
|
| #game-container { |
| position: relative; |
| box-shadow: 0 20px 50px rgba(0,0,0,0.4); |
| border-radius: 20px; |
| overflow: hidden; |
| background-color: var(--game-bg); |
| border: 4px solid #fff; |
| transition: background-color 0.5s, border-color 0.5s; |
| } |
|
|
| #game-container.fever-active { |
| background-color: var(--fever-bg); |
| border-color: #ffeb3b; |
| } |
|
|
| canvas { |
| display: block; |
| } |
|
|
| #ui-layer { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| pointer-events: none; |
| } |
|
|
| .hud { |
| padding: 10px 15px; |
| display: flex; |
| justify-content: space-between; |
| color: white; |
| font-size: 18px; |
| font-weight: 900; |
| text-shadow: 2px 2px 0px #000; |
| } |
|
|
| #fever-bar-container { |
| position: absolute; |
| top: 50px; |
| left: 50%; |
| transform: translateX(-50%); |
| width: 60%; |
| height: 10px; |
| background: rgba(255,255,255,0.2); |
| border-radius: 5px; |
| overflow: hidden; |
| } |
|
|
| #fever-bar-fill { |
| width: 0%; |
| height: 100%; |
| background: #00e5ff; |
| transition: width 0.2s; |
| } |
|
|
| #fever-label { |
| position: absolute; |
| top: 65px; |
| left: 50%; |
| transform: translateX(-50%); |
| color: #00e5ff; |
| font-weight: bold; |
| font-size: 12px; |
| text-shadow: 1px 1px 2px #000; |
| } |
|
|
| #skill-button-container { |
| position: absolute; |
| bottom: 15px; |
| left: 15px; |
| width: 80px; |
| height: 80px; |
| pointer-events: auto; |
| cursor: pointer; |
| z-index: 70; |
| } |
|
|
| #skill-canvas { |
| width: 100%; |
| height: 100%; |
| } |
|
|
| #fever-notice { |
| position: absolute; |
| top: 30%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| color: #ffeb3b; |
| font-size: 48px; |
| font-weight: 900; |
| text-shadow: 4px 4px 0px #e91e63; |
| display: none; |
| z-index: 80; |
| } |
|
|
| #message-overlay { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| background: var(--panel-color); |
| color: #333; |
| padding: 30px; |
| border-radius: 20px; |
| text-align: center; |
| display: none; |
| pointer-events: auto; |
| box-shadow: 0 10px 30px rgba(0,0,0,0.5); |
| z-index: 100; |
| } |
|
|
| button { |
| margin-top: 15px; |
| padding: 10px 30px; |
| font-size: 18px; |
| background: #ff4081; |
| border: none; |
| color: white; |
| border-radius: 50px; |
| font-weight: bold; |
| cursor: pointer; |
| box-shadow: 0 4px 0 #c2185b; |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <div id="game-container"> |
| <canvas id="gameCanvas"></canvas> |
| <div id="ui-layer"> |
| <div class="hud"> |
| <div>SCORE: <span id="score-val">0</span></div> |
| <div id="timer">TIME: <span id="time-val">60</span></div> |
| </div> |
| <div id="fever-bar-container"><div id="fever-bar-fill"></div></div> |
| <div id="fever-label">FEVER NEXT: 29</div> |
| <div id="fever-notice">FEVER!</div> |
| <div id="skill-button-container"><canvas id="skill-canvas" width="120" height="120"></canvas></div> |
| <div id="message-overlay"> |
| <h1 id="overlay-title">TIME UP!</h1> |
| <p>FINAL SCORE<br><strong id="final-score" style="font-size: 2em; color: #ff4081">0</strong></p> |
| <button onclick="resetGame()">RETRY</button> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| // --- Config --- |
| const ASPECT_RATIO = 3 / 4; |
| const MAX_TSUMS = 75; |
| const GAME_DURATION = 60; |
| const SKILL_MAX = 13; |
| const FEVER_THRESHOLD = 29; |
| const FEVER_DURATION = 10000; |
| const MY_TSUM_INDEX = 0; // 赤色ツムがマイツム |
| |
| // 消去速度(ミリ秒) |
| const POP_INTERVAL_NORMAL = 320; |
| const POP_INTERVAL_FEVER = 160; |
|
|
| const TSUM_TYPES = [ |
| { color: '#FF5E5E', highlight: '#FFADAD' }, // Smile (My Tsum) |
| { color: '#54A0FF', highlight: '#A0CFFF' }, // Sleepy |
| { color: '#1DD1A1', highlight: '#88F2D5' }, // Wink |
| { color: '#FECA57', highlight: '#FFEB99' } // Surprise |
| ]; |
|
|
| // --- State --- |
| let width, height, tsumRadius; |
| let score = 0; |
| let timeLeft = GAME_DURATION; |
| let isGameOver = false; |
| let selectedTsums = []; |
| let isDragging = false; |
| let skillCount = 0; |
| let feverCount = 0; |
| let isFever = false; |
| let feverEndTime = 0; |
| let timerInterval; |
| let rainbowAngle = 0; |
| let particles = []; |
| let shockwaves = []; |
| |
| // 消去待機キュー |
| let poppingQueue = []; |
| let lastPopTime = 0; |
|
|
| const { Engine, Runner, World, Bodies, Composite, Body } = Matter; |
| let engine, runner; |
| const canvas = document.getElementById('gameCanvas'); |
| const ctx = canvas.getContext('2d'); |
| const skillCanvas = document.getElementById('skill-canvas'); |
| const sctx = skillCanvas.getContext('2d'); |
|
|
| function initLayout() { |
| const windowHeight = Math.min(window.innerHeight * 0.85, 750); |
| height = windowHeight; |
| width = height * ASPECT_RATIO; |
| canvas.width = width; |
| canvas.height = height; |
| const container = document.getElementById('game-container'); |
| container.style.width = width + 'px'; |
| container.style.height = height + 'px'; |
| tsumRadius = width / 14; |
| } |
|
|
| function initPhysics() { |
| engine = Engine.create(); |
| engine.gravity.y = 1.5; |
| |
| const wallThickness = 100; |
| const wallOptions = { isStatic: true, friction: 0.1, restitution: 0.1 }; |
| const ground = Bodies.rectangle(width / 2, height + wallThickness/2, width, wallThickness, wallOptions); |
| const leftWall = Bodies.rectangle(-wallThickness/2, height / 2, wallThickness, height * 2, wallOptions); |
| const rightWall = Bodies.rectangle(width + wallThickness/2, height / 2, wallThickness, height * 2, wallOptions); |
| World.add(engine.world, [ground, leftWall, rightWall]); |
| |
| runner = Runner.create(); |
| Runner.run(runner, engine); |
| } |
|
|
| function createTsum(x, y) { |
| const typeIndex = Math.floor(Math.random() * TSUM_TYPES.length); |
| return Bodies.circle(x, y, tsumRadius, { |
| friction: 0.2, restitution: 0.1, density: 0.001, label: 'tsum', slop: 0.05, |
| plugin: { type: typeIndex } |
| }); |
| } |
|
|
| function spawnTsum(yOffset = -50) { |
| const x = Math.random() * (width - tsumRadius * 2) + tsumRadius; |
| World.add(engine.world, createTsum(x, yOffset)); |
| } |
|
|
| // --- Fever Logic --- |
| function updateFeverProgress(count) { |
| if (isFever) return; |
| feverCount += count; |
| const progress = Math.min((feverCount / FEVER_THRESHOLD) * 100, 100); |
| document.getElementById('fever-bar-fill').style.width = progress + '%'; |
| document.getElementById('fever-label').innerText = `FEVER NEXT: ${Math.max(0, FEVER_THRESHOLD - feverCount)}`; |
| if (feverCount >= FEVER_THRESHOLD) startFever(); |
| } |
|
|
| function startFever() { |
| isFever = true; feverCount = 0; |
| feverEndTime = Date.now() + FEVER_DURATION; |
| timeLeft += 5; |
| document.getElementById('game-container').classList.add('fever-active'); |
| const notice = document.getElementById('fever-notice'); |
| notice.style.display = 'block'; setTimeout(() => notice.style.display = 'none', 1000); |
| document.getElementById('fever-bar-fill').style.backgroundColor = '#ffeb3b'; |
| document.getElementById('fever-label').innerText = 'FEVER TIME!!'; |
| } |
|
|
| function endFever() { |
| isFever = false; |
| document.getElementById('game-container').classList.remove('fever-active'); |
| document.getElementById('fever-bar-fill').style.backgroundColor = '#00e5ff'; |
| updateFeverProgress(0); |
| } |
|
|
| // --- Effects --- |
| function addParticles(x, y, color, count = 10) { |
| for (let i = 0; i < count; i++) { |
| particles.push({ |
| x, y, vx: (Math.random() - 0.5) * 15, vy: (Math.random() - 0.5) * 15, |
| life: 1.0, color, size: Math.random() * 6 + 2 |
| }); |
| } |
| } |
|
|
| function addShockwave(x, y, maxRadius, color) { |
| shockwaves.push({ x, y, radius: 0, maxRadius, life: 1.0, color }); |
| } |
|
|
| // --- Sequential Popping Logic --- |
| function processPoppingQueue() { |
| if (poppingQueue.length === 0) return; |
| |
| const now = Date.now(); |
| const interval = isFever ? POP_INTERVAL_FEVER : POP_INTERVAL_NORMAL; |
|
|
| if (now - lastPopTime >= interval) { |
| lastPopTime = now; |
| popNextTsum(); |
| } |
| } |
|
|
| function popNextTsum() { |
| const item = poppingQueue.shift(); |
| if (!item) return; |
|
|
| if (Composite.allBodies(engine.world).includes(item.body)) { |
| const x = item.body.position.x; |
| const y = item.body.position.y; |
| |
| addParticles(x, y, item.color, 8); |
| World.remove(engine.world, item.body); |
| spawnTsum(); |
| |
| const multiplier = isFever ? 3 : 1; |
| score += 120 * multiplier; |
| document.getElementById('score-val').innerText = score; |
| updateFeverProgress(1); |
|
|
| // 通常消去時のマイツム判定 |
| if (item.type === MY_TSUM_INDEX) { |
| skillCount = Math.min(SKILL_MAX, skillCount + 1); |
| } |
|
|
| // ボム生成 |
| if (item.isLast && item.shouldSpawnBomb) { |
| World.add(engine.world, Bodies.circle(x, y, tsumRadius * 1.5, { |
| label: 'bomb', |
| friction: 0.05, |
| restitution: 0.2 |
| })); |
| } |
| } |
| } |
|
|
| function flushPoppingQueue() { |
| while (poppingQueue.length > 0) { |
| popNextTsum(); |
| } |
| } |
|
|
| // --- Drawing --- |
| function drawFace(x, y, r, type) { |
| ctx.save(); ctx.translate(x, y); ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; ctx.strokeStyle = 'rgba(0, 0, 0, 0.7)'; |
| ctx.lineWidth = 2.5; const eyeSize = r * 0.15; const eyeOff = r * 0.4; |
| if (type === 0) { |
| ctx.beginPath(); ctx.arc(-eyeOff, -r*0.1, eyeSize, 0, Math.PI*2); ctx.fill(); |
| ctx.beginPath(); ctx.arc(eyeOff, -r*0.1, eyeSize, 0, Math.PI*2); ctx.fill(); |
| ctx.beginPath(); ctx.arc(0, r*0.15, r*0.4, 0.4, Math.PI-0.4); ctx.stroke(); |
| } else if (type === 1) { |
| ctx.beginPath(); ctx.moveTo(-eyeOff-eyeSize, -r*0.1); ctx.lineTo(-eyeOff+eyeSize, -r*0.1); ctx.stroke(); |
| ctx.beginPath(); ctx.moveTo(eyeOff-eyeSize, -r*0.1); ctx.lineTo(eyeOff+eyeSize, -r*0.1); ctx.stroke(); |
| ctx.beginPath(); ctx.arc(0, r*0.35, r*0.2, 0, Math.PI*2); ctx.fill(); |
| } else if (type === 2) { |
| ctx.beginPath(); ctx.arc(-eyeOff, -r*0.1, eyeSize, 0, Math.PI*2); ctx.fill(); |
| ctx.beginPath(); ctx.arc(eyeOff, -r*0.1, eyeSize, Math.PI, 0); ctx.stroke(); |
| ctx.fillStyle = '#ff80ab'; ctx.beginPath(); ctx.arc(0, r*0.25, eyeSize, 0, Math.PI*2); ctx.fill(); |
| } else { |
| ctx.beginPath(); ctx.arc(-eyeOff, -r*0.1, eyeSize*1.3, 0, Math.PI*2); ctx.fill(); |
| ctx.beginPath(); ctx.arc(eyeOff, -r*0.1, eyeSize*1.3, 0, Math.PI*2); ctx.fill(); |
| ctx.beginPath(); ctx.arc(0, r*0.35, r*0.2, 0, Math.PI*2); ctx.stroke(); |
| } |
| ctx.restore(); |
| } |
|
|
| function draw() { |
| ctx.clearRect(0, 0, width, height); |
| rainbowAngle += 0.05; |
| if (isFever) { |
| if (Date.now() > feverEndTime) endFever(); |
| else { |
| const remaining = feverEndTime - Date.now(); |
| document.getElementById('fever-bar-fill').style.width = (remaining / FEVER_DURATION * 100) + '%'; |
| } |
| } |
|
|
| processPoppingQueue(); |
|
|
| const bodies = Composite.allBodies(engine.world); |
| if (selectedTsums.length > 1) { |
| ctx.beginPath(); ctx.strokeStyle = isFever ? '#ffeb3b' : 'rgba(255, 255, 255, 0.8)'; |
| ctx.lineWidth = 14; ctx.lineJoin = 'round'; |
| selectedTsums.forEach((t, i) => { |
| if (i === 0) ctx.moveTo(t.position.x, t.position.y); |
| else ctx.lineTo(t.position.x, t.position.y); |
| }); |
| ctx.stroke(); |
| } |
|
|
| bodies.forEach(body => { |
| if (body.label === 'tsum') { |
| const type = body.plugin.type; |
| const isSelected = selectedTsums.includes(body); |
| const isPopping = poppingQueue.some(p => p.body === body); |
| |
| ctx.globalAlpha = isPopping ? 0.7 + Math.sin(Date.now()*0.008)*0.2 : 1.0; |
| |
| ctx.beginPath(); ctx.arc(body.position.x, body.position.y, tsumRadius, 0, Math.PI * 2); |
| ctx.fillStyle = isSelected ? TSUM_TYPES[type].highlight : TSUM_TYPES[type].color; |
| ctx.fill(); |
| if (isSelected) { ctx.strokeStyle = '#fff'; ctx.lineWidth = 5; ctx.stroke(); } |
| drawFace(body.position.x, body.position.y, tsumRadius, type); |
| ctx.globalAlpha = 1.0; |
| } else if (body.label === 'bomb') { |
| const grad = ctx.createRadialGradient(body.position.x, body.position.y, 0, body.position.x, body.position.y, tsumRadius*1.5); |
| grad.addColorStop(0, `hsl(${(rainbowAngle*100)%360}, 100%, 85%)`); grad.addColorStop(1, `hsl(${(rainbowAngle*100+180)%360}, 100%, 55%)`); |
| ctx.beginPath(); ctx.arc(body.position.x, body.position.y, tsumRadius * 1.5, 0, Math.PI*2); |
| ctx.fillStyle = grad; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 3; ctx.stroke(); |
| ctx.fillStyle = '#fff'; ctx.font = 'bold 24px Arial'; ctx.textAlign = 'center'; ctx.textBaseline='middle'; ctx.fillText('B', body.position.x, body.position.y); |
| } |
| }); |
|
|
| for (let i = particles.length - 1; i >= 0; i--) { |
| const p = particles[i]; p.x += p.vx; p.y += p.vy; p.vy += 0.4; p.life -= 0.03; |
| if (p.life <= 0) { particles.splice(i, 1); continue; } |
| ctx.globalAlpha = p.life; ctx.fillStyle = p.color; |
| ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI*2); ctx.fill(); |
| } |
| for (let i = shockwaves.length - 1; i >= 0; i--) { |
| const s = shockwaves[i]; s.radius += (s.maxRadius - s.radius) * 0.2; s.life -= 0.05; |
| if (s.life <= 0) { shockwaves.splice(i, 1); continue; } |
| ctx.globalAlpha = s.life; ctx.strokeStyle = s.color; ctx.lineWidth = 8; |
| ctx.beginPath(); ctx.arc(s.x, s.y, s.radius, 0, Math.PI*2); ctx.stroke(); |
| } |
| ctx.globalAlpha = 1.0; |
|
|
| sctx.clearRect(0, 0, 120, 120); sctx.beginPath(); sctx.arc(60, 60, 52, 0, Math.PI*2); sctx.fillStyle = '#1a252f'; sctx.fill(); |
| const ratio = Math.min(skillCount / SKILL_MAX, 1); |
| sctx.beginPath(); sctx.moveTo(60, 60); sctx.arc(60, 60, 52, -Math.PI/2, -Math.PI/2 + (Math.PI*2*ratio)); |
| sctx.fillStyle = ratio >= 1 ? `hsl(${(rainbowAngle*120)%360}, 95%, 70%)` : '#ff4081'; sctx.fill(); |
| sctx.strokeStyle = '#fff'; sctx.lineWidth = 5; sctx.stroke(); |
| sctx.fillStyle = '#fff'; sctx.font = 'bold 22px Arial'; sctx.textAlign='center'; |
| sctx.fillText(ratio >= 1 ? 'OK!' : 'SKILL', 60, 68); |
|
|
| if (!isGameOver) requestAnimationFrame(draw); |
| } |
|
|
| // --- Actions --- |
| function useSkill() { |
| if (skillCount < SKILL_MAX || isGameOver) return; |
| flushPoppingQueue(); |
| |
| skillCount = 0; |
| const cx = width / 2; const cy = height / 2; const range = width * 0.55; |
| addShockwave(cx, cy, range * 1.8, '#fff'); |
| const bodies = Composite.allBodies(engine.world); |
| let count = 0; |
| let myTsumCount = 0; |
|
|
| bodies.forEach(b => { |
| if (b.label === 'tsum') { |
| const d = Math.sqrt((b.position.x - cx)**2 + (b.position.y - cy)**2); |
| if (d < range) { |
| // スキル範囲内のマイツムをカウント |
| if (b.plugin.type === MY_TSUM_INDEX) myTsumCount++; |
| |
| addParticles(b.position.x, b.position.y, TSUM_TYPES[b.plugin.type].color, 5); |
| World.remove(engine.world, b); count++; |
| } |
| } |
| }); |
|
|
| // スキルで消したマイツムをゲージに反映 |
| skillCount = Math.min(SKILL_MAX, myTsumCount); |
|
|
| const multiplier = isFever ? 3 : 1; |
| score += count * 250 * multiplier; |
| document.getElementById('score-val').innerText = score; |
| updateFeverProgress(count); |
| for (let i = 0; i < count; i++) spawnTsum(-50 - (i*10)); |
| } |
|
|
| function explodeBomb(bomb) { |
| flushPoppingQueue(); |
| |
| const bPos = { x: bomb.position.x, y: bomb.position.y }; |
| addShockwave(bPos.x, bPos.y, tsumRadius * 7, 'rgba(255,255,255,0.9)'); |
| const bodies = Composite.allBodies(engine.world); |
| let count = 0; |
| let myTsumInBlast = 0; |
|
|
| bodies.forEach(b => { |
| if (b.label === 'tsum') { |
| const d = Math.sqrt((b.position.x-bPos.x)**2 + (b.position.y-bPos.y)**2); |
| if (d < tsumRadius * 5) { |
| // 爆発範囲内のマイツムをカウントしてゲージへ |
| if (b.plugin.type === MY_TSUM_INDEX) myTsumInBlast++; |
| |
| addParticles(b.position.x, b.position.y, TSUM_TYPES[b.plugin.type].color, 5); |
| World.remove(engine.world, b); count++; |
| } |
| } |
| }); |
| |
| // ボムで消したマイツムをゲージに加算 |
| skillCount = Math.min(SKILL_MAX, skillCount + myTsumInBlast); |
|
|
| World.remove(engine.world, bomb); |
| const multiplier = isFever ? 3 : 1; |
| score += count * 180 * multiplier; |
| document.getElementById('score-val').innerText = score; |
| updateFeverProgress(count); |
| for (let i = 0; i < count; i++) spawnTsum(); |
| } |
|
|
| function handleStart(x, y) { |
| if (isGameOver) return; |
| const bodies = Composite.allBodies(engine.world); |
| const bomb = bodies.find(b => b.label === 'bomb' && Math.sqrt((b.position.x-x)**2 + (b.position.y-y)**2) < tsumRadius * 2.5); |
| if (bomb) { explodeBomb(bomb); return; } |
| isDragging = true; selectedTsums = []; handleMove(x, y); |
| } |
|
|
| function handleMove(x, y) { |
| if (!isDragging || isGameOver) return; |
| const bodies = Composite.allBodies(engine.world); |
| const tsum = bodies.find(b => b.label === 'tsum' && Math.sqrt((b.position.x-x)**2 + (b.position.y-y)**2) < tsumRadius * 1.5); |
| |
| if (tsum && !poppingQueue.some(p => p.body === tsum)) { |
| if (selectedTsums.length === 0) selectedTsums.push(tsum); |
| else { |
| const last = selectedTsums[selectedTsums.length - 1]; |
| if (selectedTsums.length > 1 && tsum === selectedTsums[selectedTsums.length-2]) selectedTsums.pop(); |
| else if (!selectedTsums.includes(tsum) && tsum.plugin.type === last.plugin.type) { |
| const d = Math.sqrt((tsum.position.x-last.position.x)**2 + (tsum.position.y-last.position.y)**2); |
| if (d < tsumRadius * 3.2) selectedTsums.push(tsum); |
| } |
| } |
| } |
| } |
|
|
| function handleEnd() { |
| if (!isDragging) return; |
| isDragging = false; |
| const count = selectedTsums.length; |
| if (count >= 3) { |
| const shouldSpawnBomb = count >= 7; |
| |
| selectedTsums.forEach((t, index) => { |
| poppingQueue.push({ |
| body: t, |
| color: TSUM_TYPES[t.plugin.type].color, |
| type: t.plugin.type, |
| isLast: index === count - 1, |
| shouldSpawnBomb: shouldSpawnBomb |
| }); |
| }); |
| } |
| selectedTsums = []; |
| } |
|
|
| function resetGame() { |
| score = 0; skillCount = 0; feverCount = 0; isFever = false; timeLeft = GAME_DURATION; |
| particles = []; shockwaves = []; poppingQueue = []; lastPopTime = Date.now(); |
| document.getElementById('score-val').innerText = '0'; |
| document.getElementById('message-overlay').style.display = 'none'; |
| document.getElementById('game-container').classList.remove('fever-active'); |
| const bodies = Composite.allBodies(engine.world); |
| bodies.forEach(b => { if (b.label === 'tsum' || b.label === 'bomb') World.remove(engine.world, b); }); |
| for (let i = 0; i < MAX_TSUMS; i++) spawnTsum(-Math.random() * 1000); |
| isGameOver = false; |
| clearInterval(timerInterval); |
| timerInterval = setInterval(() => { |
| timeLeft--; document.getElementById('time-val').innerText = timeLeft; |
| if (timeLeft <= 0) { |
| isGameOver = true; clearInterval(timerInterval); |
| document.getElementById('final-score').innerText = score; |
| document.getElementById('message-overlay').style.display = 'block'; |
| } |
| }, 1000); |
| draw(); |
| } |
|
|
| canvas.addEventListener('mousedown', e => { const r = canvas.getBoundingClientRect(); handleStart(e.clientX-r.left, e.clientY-r.top); }); |
| window.addEventListener('mousemove', e => { const r = canvas.getBoundingClientRect(); handleMove(e.clientX-r.left, e.clientY-r.top); }); |
| window.addEventListener('mouseup', handleEnd); |
| canvas.addEventListener('touchstart', e => { e.preventDefault(); const r = canvas.getBoundingClientRect(); const t = e.touches[0]; handleStart(t.clientX-r.left, t.clientY-r.top); }, {passive: false}); |
| canvas.addEventListener('touchmove', e => { e.preventDefault(); const r = canvas.getBoundingClientRect(); const t = e.touches[0]; handleMove(t.clientX-r.left, t.clientY-r.top); }, {passive: false}); |
| canvas.addEventListener('touchend', e => { e.preventDefault(); handleEnd(); }, {passive: false}); |
| document.getElementById('skill-button-container').addEventListener('mousedown', (e) => { e.stopPropagation(); useSkill(); }); |
| document.getElementById('skill-button-container').addEventListener('touchstart', (e) => { e.stopPropagation(); e.preventDefault(); useSkill(); }); |
|
|
| window.onload = () => { initLayout(); initPhysics(); resetGame(); }; |
| </script> |
| </body> |
| </html> |