Fruit Slice Game Canvas

Windows Tip: After downloading the ZIP, right-click the folder → Properties → check "Unblock" (if visible) → Apply → OK. This removes the "file came from another computer" warning so everything works perfectly!
HTML
CSS
JavaScript
<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>Fruit Slice Game</title> <style> body { margin: 0; overflow: hidden; background: linear-gradient(to bottom, #87CEEB 0%, #98D8C8 70%, #F8F1E9 100%); font-family: 'Arial Black', sans-serif; touch-action: none; user-select: none; } canvas { display: block; cursor: none; } #ui { position: absolute; top: 20px; left: 20px; right: 20px; display: flex; justify-content: space-between; color: #333; font-size: 1.8em; text-shadow: 2px 2px 4px rgba(255,255,255,0.8); z-index: 10; pointer-events: none; } #combo { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 4em; color: #ff6b6b; text-shadow: 3px 3px 6px rgba(0,0,0,0.5); display: none; z-index: 15; pointer-events: none; } #startScreen, #gameOver { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); color: white; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 2.8em; text-align: center; z-index: 20; } button { margin-top: 30px; padding: 18px 45px; font-size: 1.3em; background: linear-gradient(45deg, #ff6b6b, #feca57); color: white; border: none; border-radius: 50px; cursor: pointer; box-shadow: 0 12px 25px rgba(0,0,0,0.4); transition: all 0.3s; font-family: inherit; font-weight: bold; } button:hover, button:active { transform: scale(1.05) translateY(-3px); box-shadow: 0 15px 30px rgba(0,0,0,0.5); } #finalScore { font-size: 1.5em; margin: 20px 0; } </style>
</head>
<body> <div id="ui"> <div>Score: <span id="score">0</span></div> <div>Lives: <span id="lives">5</span></div> </div> <div id="combo">COMBO!</div> <div id="startScreen"> <h1>Fruit Slice!</h1> <p>Swipe to slice fruits!<br>Avoid bombs!</p> <button id="startBtn">SLICE NOW!</button> </div> <div id="gameOver" style="display:none"> <h1>Game Over!</h1> <div id="finalScore">Score: 0</div> <button id="restartBtn">Play Again</button> </div> <canvas id="canvas"></canvas> <script> const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); const scoreEl = document.getElementById("score"); const livesEl = document.getElementById("lives"); const comboEl = document.getElementById("combo"); const startScreen = document.getElementById("startScreen"); const gameOverScreen = document.getElementById("gameOver"); const finalScoreEl = document.getElementById("finalScore"); let width, height; function resize() { width = canvas.width = window.innerWidth; height = canvas.height = window.innerHeight; } window.addEventListener("resize", resize); resize(); // Game state let score = 0; let lives = 5; let combo = 0; let lastSliceTime = 0; let fruits = []; let particles = []; let trail = []; let isPlaying = false; let mouse = { x: 0, y: 0, down: false }; let spawnInterval; class Fruit { constructor(x, y, radius, color, isBomb) { this.x = x; this.y = y; this.radius = radius; this.color = color; this.isBomb = isBomb; this.vx = (Math.random() - 0.5) * 4; this.vy = 2 + Math.random() * 2 + Math.min(score * 0.005, 0.5); this.angle = 0; this.sliced = false; this.rotationSpeed = (Math.random() - 0.5) * 0.3; } update() { if (this.sliced) return false; this.x += this.vx; this.y += this.vy; this.vy += 0.08; this.angle += this.rotationSpeed; this.vx *= 0.99; if (this.x - this.radius < 0 || this.x + this.radius > width) { this.vx = -this.vx * 0.8; this.x = Math.max(this.radius, Math.min(width - this.radius, this.x)); } if (this.y > height + this.radius * 2) { lives--; livesEl.textContent = lives; if (lives <= 0) gameOver(); return true; } return false; } slice() { this.sliced = true; for (let i = 0; i < 12; i++) { const angle = (Math.PI * 2 * i) / 12; const speed = 4 + Math.random() * 3; particles.push({ x: this.x, y: this.y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed - 2, size: 4 + Math.random() * 6, color: this.isBomb ? '#ff4757' : `hsl(${Math.random()*60 + 20}, 70%, 60%)`, life: 1 }); } const now = Date.now(); if (now - lastSliceTime < 800) combo++; else combo = 1; lastSliceTime = now; const points = this.isBomb ? -10 : (10 * combo); score += points; scoreEl.textContent = Math.max(0, score); if (combo > 1) { comboEl.textContent = `${combo}x COMBO!`; comboEl.style.display = 'block'; setTimeout(() => { comboEl.style.display = 'none'; }, 800); } if (this.isBomb) { lives--; livesEl.textContent = lives; if (lives <= 0) gameOver(); } } draw() { ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle); const grad = ctx.createRadialGradient(-10, -10, 0, 0, 0, this.radius); grad.addColorStop(0, 'rgba(255,255,255,0.8)'); grad.addColorStop(0.7, this.color); grad.addColorStop(1, this.color); ctx.beginPath(); ctx.arc(0, 0, this.radius, 0, Math.PI * 2); ctx.fillStyle = grad; ctx.fill(); ctx.strokeStyle = 'rgba(0,0,0,0.2)'; ctx.lineWidth = 2; ctx.stroke(); ctx.beginPath(); ctx.arc(-this.radius * 0.3, -this.radius * 0.3, this.radius * 0.3, 0, Math.PI * 2); ctx.fillStyle = 'rgba(255,255,255,0.5)'; ctx.fill(); if (!this.isBomb) { ctx.fillStyle = '#228B22'; ctx.fillRect(-3, -this.radius - 8, 6, 12); ctx.fillStyle = '#8B4513'; ctx.fillRect(-1.5, -this.radius - 4, 3, 6); } else { ctx.strokeStyle = '#ff9500'; ctx.lineWidth = 4; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(0, -this.radius + 5); ctx.lineTo(0, -this.radius - 15); ctx.stroke(); ctx.beginPath(); ctx.arc(0, -this.radius - 20, 4, 0, Math.PI * 2); ctx.fillStyle = '#ff4757'; ctx.fill(); ctx.strokeStyle = '#ff4757'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(-this.radius * 0.7, -this.radius * 0.7); ctx.lineTo(this.radius * 0.7, this.radius * 0.7); ctx.moveTo(this.radius * 0.7, -this.radius * 0.7); ctx.lineTo(-this.radius * 0.7, this.radius * 0.7); ctx.stroke(); } ctx.restore(); } } class Particle { constructor(x, y, vx, vy, size, color) { this.x = x; this.y = y; this.vx = vx; this.vy = vy; this.size = size; this.color = color; this.life = 1; } update() { this.x += this.vx; this.y += this.vy; this.vy += 0.15; this.vx *= 0.98; this.life -= 0.025; this.size *= 0.98; return this.life > 0; } draw() { ctx.save(); ctx.globalAlpha = this.life; ctx.fillStyle = this.color; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } } function getMousePos(e) { const rect = canvas.getBoundingClientRect(); const clientX = e.clientX || (e.touches?.[0]?.clientX || 0); const clientY = e.clientY || (e.touches?.[0]?.clientY || 0); return { x: clientX - rect.left, y: clientY - rect.top }; } function addTrail(e) { const pos = getMousePos(e); mouse.x = pos.x; mouse.y = pos.y; trail.push({...pos}); if (trail.length > 15) trail.shift(); for (let i = 0; i < trail.length - 1; i++) { const p1 = trail[i]; const p2 = trail[i+1]; fruits.forEach((fruit, idx) => { if (fruit.sliced) return; const dist = distanceToLine(fruit.x, fruit.y, p1.x, p1.y, p2.x, p2.y); if (dist < fruit.radius + 20) { fruit.slice(); fruits.splice(idx, 1); } }); } } function distanceToLine(x, y, x1, y1, x2, y2) { const A = x - x1; const B = y - y1; const C = x2 - x1; const D = y2 - y1; const dot = A * C + B * D; const lenSq = C * C + D * D; if (lenSq === 0) return Math.sqrt(A * A + B * B); const param = dot / lenSq; let xx, yy; if (param < 0) { xx = x1; yy = y1; } else if (param > 1) { xx = x2; yy = y2; } else { xx = x1 + param * C; yy = y1 + param * D; } const dx = x - xx; const dy = y - yy; return Math.sqrt(dx * dx + dy * dy); } function handleStart(e) { e.preventDefault(); mouse.down = true; trail = []; addTrail(e); } function handleMove(e) { e.preventDefault(); if (mouse.down) addTrail(e); } function handleEnd(e) { e.preventDefault(); mouse.down = false; trail = []; } canvas.addEventListener('mousedown', handleStart); canvas.addEventListener('mousemove', handleMove); canvas.addEventListener('mouseup', handleEnd); canvas.addEventListener('touchstart', handleStart, { passive: false }); canvas.addEventListener('touchmove', handleMove, { passive: false }); canvas.addEventListener('touchend', handleEnd, { passive: false }); function spawnFruit() { if (!isPlaying || fruits.length > 6) return; const colors = ['#ff6b6b', '#feca57', '#48dbfb', '#ff9ff3', '#54a0ff', '#5f27cd', '#00b894', '#fdcb6e']; const isBomb = Math.random() < 0.15; const color = isBomb ? '#2d3436' : colors[Math.floor(Math.random() * colors.length)]; const x = Math.random() * (width - 200) + 100; const y = -50 - Math.random() * 50; const radius = 28 + Math.random() * 15; fruits.push(new Fruit(x, y, radius, color, isBomb)); } function animate() { ctx.clearRect(0, 0, width, height); for (let i = fruits.length - 1; i >= 0; i--) { if (fruits[i].update()) fruits.splice(i, 1); else fruits[i].draw(); } for (let i = particles.length - 1; i >= 0; i--) { if (!particles[i].update()) particles.splice(i, 1); else particles[i].draw(); } if (mouse.down && trail.length > 1) { ctx.lineCap = ctx.lineJoin = 'round'; ctx.lineWidth = 50; ctx.strokeStyle = 'rgba(255,255,255,0.4)'; ctx.beginPath(); ctx.moveTo(trail[0].x, trail[0].y); for (let i = 1; i < trail.length; i++) ctx.lineTo(trail[i].x, trail[i].y); ctx.stroke(); ctx.lineWidth = 25; ctx.strokeStyle = 'rgba(255,255,255,0.9)'; ctx.beginPath(); ctx.moveTo(trail[0].x, trail[0].y); for (let i = 1; i < trail.length; i++) ctx.lineTo(trail[i].x, trail[i].y); ctx.stroke(); } if (isPlaying) requestAnimationFrame(animate); } function startGame() { score = 0; lives = 5; combo = 0; fruits = []; particles = []; trail = []; scoreEl.textContent = '0'; livesEl.textContent = '5'; startScreen.style.display = 'none'; gameOverScreen.style.display = 'none'; isPlaying = true; animate(); if (spawnInterval) clearInterval(spawnInterval); spawnInterval = setInterval(spawnFruit, 1200 - Math.min(score * 3, 600)); spawnFruit(); } function gameOver() { isPlaying = false; if (spawnInterval) clearInterval(spawnInterval); finalScoreEl.textContent = `Final Score: ${score}`; gameOverScreen.style.display = 'flex'; } document.getElementById("startBtn").addEventListener("click", startGame); document.getElementById("restartBtn").addEventListener("click", startGame); document.addEventListener('touchmove', e => e.preventDefault(), { passive: false }); document.addEventListener('gesturestart', e => e.preventDefault()); document.addEventListener('gesturechange', e => e.preventDefault()); </script>
</body>
</html>