Piano Fire
by SonicBear351500 lines48.1 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<title>Piano Fire</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
}
body {
background: #0a0a0a;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
color: white;
overflow: hidden;
touch-action: none;
position: fixed;
width: 100%;
height: 100%;
}
#app {
width: 100%;
max-width: 420px;
height: 100vh;
max-height: 780px;
background: linear-gradient(145deg, #0f0a1a, #1a0a2a);
border-radius: 0px;
padding: 4px;
box-shadow: 0 20px 60px rgba(0,0,0,0.9);
position: relative;
overflow: hidden;
border: 2px solid #4a2a6a;
display: flex;
flex-direction: column;
touch-action: none;
}
@media (orientation: landscape) {
#app {
max-width: 100%;
max-height: 100vh;
height: 100vh;
border-radius: 0;
flex-direction: row;
padding: 4px;
}
.game-container {
flex-direction: row !important;
flex-wrap: wrap !important;
}
#gameCanvas {
height: 100% !important;
width: 58% !important;
flex: 1 !important;
}
.game-controls {
flex-direction: column !important;
width: 38% !important;
height: 100% !important;
position: relative !important;
bottom: auto !important;
left: auto !important;
right: auto !important;
padding: 6px !important;
display: flex !important;
justify-content: center !important;
gap: 4px !important;
flex-wrap: wrap !important;
}
.game-controls button {
min-width: 50px !important;
min-height: 40px !important;
font-size: 9px !important;
}
#pauseOverlay {
width: 58% !important;
left: 0 !important;
}
}
@media (min-width: 421px) and (orientation: portrait) {
#app {
border-radius: 40px;
height: 780px;
}
}
#loadingScreen {
display: flex;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at 50% 30%, #1a0a2a, #0a0a0a);
z-index: 100;
justify-content: center;
align-items: center;
flex-direction: column;
border-radius: 0px;
animation: fadeIn 0.5s ease;
}
#loadingScreen .logo {
font-size: 60px;
animation: bounce 1s ease infinite;
}
#loadingScreen .title {
font-size: 32px;
font-weight: 900;
color: #ff44aa;
text-shadow: 0 0 30px #ff0088, 0 0 60px #880044;
margin-top: 5px;
letter-spacing: 4px;
}
#loadingScreen .sub {
font-size: 14px;
color: #88dd88;
margin-top: 5px;
}
#loadingScreen .tap-text {
font-size: 12px;
color: #557755;
margin-top: 15px;
animation: pulse 1.5s ease infinite;
}
#loadingScreen .load-bar {
width: 200px;
height: 4px;
background: #1a1a1a;
border-radius: 4px;
margin-top: 10px;
overflow: hidden;
border: 1px solid #4a2a6a;
}
#loadingScreen .load-fill {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #ff44aa, #ff8800);
border-radius: 4px;
transition: width 0.3s;
}
#loadingScreen .load-percent {
font-size: 12px;
color: #ff44aa;
margin-top: 4px;
}
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes bounce {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-15px) scale(1.05); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.screen {
display: none;
flex-direction: column;
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
padding: 5px;
background: linear-gradient(145deg, #0f0a1a, #1a0a2a);
overflow-y: auto;
}
.screen.active {
display: flex;
}
#homeScreen {
justify-content: center;
align-items: center;
gap: 3px;
background: radial-gradient(circle at 50% 30%, #1a0a2a, #0a0a0a);
}
#homeScreen .logo {
font-size: 34px;
font-weight: 900;
color: #ff44aa;
text-shadow: 0 0 30px #ff0088, 0 0 60px #880044;
letter-spacing: 3px;
}
#homeScreen .sub {
font-size: 10px;
color: #88dd88;
letter-spacing: 4px;
margin-bottom: 8px;
}
.menu-btn {
background: linear-gradient(145deg, #2a1a3a, #1a0a2a);
border: 2px solid #4a2a6a;
color: white;
padding: 6px 16px;
border-radius: 60px;
font-size: 12px;
font-weight: bold;
width: 85%;
text-align: center;
margin: 2px 0;
cursor: pointer;
transition: 0.15s;
box-shadow: 0 2px 0 #0a0a0a;
letter-spacing: 1px;
touch-action: manipulation;
}
.menu-btn:active {
transform: translateY(2px);
box-shadow: none;
}
.menu-btn.pink {
border-color: #ff44aa;
background: linear-gradient(145deg, #3a1a4a, #1a0a2a);
}
.menu-btn.gold {
border-color: #ffdd44;
background: linear-gradient(145deg, #3a3a1a, #1a1a0a);
}
.small-btn {
padding: 2px 6px;
font-size: 7px;
width: auto;
}
.back-btn {
background: #2a1a3a;
border: none;
color: white;
padding: 2px 6px;
border-radius: 30px;
cursor: pointer;
font-size: 10px;
touch-action: manipulation;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 3px;
background: #1a1a1a;
padding: 2px 5px;
border-radius: 30px;
border: 1px solid #4a2a6a;
}
#gameScreen {
padding: 0;
background: #0a0a0a;
position: relative;
}
.game-container {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
#gameCanvas {
width: 100%;
height: 100%;
background: #0a0a1a;
border-radius: 10px;
display: block;
touch-action: none;
cursor: pointer;
flex: 1;
}
.game-controls {
position: absolute;
bottom: 2px;
left: 0;
right: 0;
padding: 0 2px;
display: flex;
justify-content: space-between;
pointer-events: none;
z-index: 20;
flex-wrap: wrap;
}
.game-controls button {
pointer-events: auto;
background: rgba(0,0,0,0.85);
border: 2px solid #4a2a6a;
color: white;
border-radius: 30px;
padding: 2px 6px;
font-size: 7px;
font-weight: bold;
backdrop-filter: blur(4px);
cursor: pointer;
touch-action: manipulation;
min-width: 28px;
min-height: 28px;
}
.game-controls button:active {
transform: scale(0.9);
}
.game-controls .pause-btn {
border-color: #ffdd44;
background: rgba(255, 200, 50, 0.2);
}
.game-controls .quit-btn {
border-color: #ff4444;
background: rgba(255, 50, 50, 0.2);
}
#pauseOverlay {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.85);
z-index: 30;
justify-content: center;
align-items: center;
flex-direction: column;
border-radius: 10px;
animation: fadeIn 0.3s ease;
}
#pauseOverlay .pause-text {
font-size: 32px;
font-weight: bold;
color: #ffdd44;
text-shadow: 0 0 30px #ff8800;
text-align: center;
}
#pauseOverlay .pause-sub {
font-size: 14px;
color: #88dd88;
margin-top: 5px;
text-align: center;
}
#pauseOverlay .pause-buttons {
display: flex;
gap: 10px;
margin-top: 15px;
}
#pauseOverlay .pause-buttons button {
padding: 8px 20px;
border-radius: 30px;
font-size: 12px;
font-weight: bold;
cursor: pointer;
touch-action: manipulation;
border: 2px solid #4a2a6a;
background: #1a1a1a;
color: white;
}
#pauseOverlay .pause-buttons button:active {
transform: scale(0.95);
}
#pauseOverlay .pause-buttons .resume-btn {
border-color: #44ff88;
background: rgba(68, 255, 136, 0.2);
}
#pauseOverlay .pause-buttons .quit-btn {
border-color: #ff4444;
background: rgba(255, 50, 50, 0.2);
}
#resultOverlay {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.9);
z-index: 35;
justify-content: center;
align-items: center;
flex-direction: column;
border-radius: 10px;
animation: fadeIn 0.5s ease;
}
#resultOverlay .result-text {
font-size: 28px;
font-weight: bold;
color: #ffdd44;
text-shadow: 0 0 30px #ff8800;
text-align: center;
}
#resultOverlay .result-stars {
font-size: 40px;
margin-top: 5px;
text-align: center;
}
#resultOverlay .result-score {
font-size: 16px;
color: #88dd88;
margin-top: 5px;
text-align: center;
}
#resultOverlay .result-buttons {
display: flex;
gap: 10px;
margin-top: 15px;
}
#resultOverlay .result-buttons button {
padding: 8px 20px;
border-radius: 30px;
font-size: 12px;
font-weight: bold;
cursor: pointer;
touch-action: manipulation;
border: 2px solid #4a2a6a;
background: #1a1a1a;
color: white;
}
#resultOverlay .result-buttons button:active {
transform: scale(0.95);
}
.music-control {
background: #1a1a1a;
border: 1px solid #4a2a6a;
border-radius: 8px;
padding: 4px;
margin: 3px 0;
display: flex;
align-items: center;
gap: 3px;
flex-wrap: wrap;
}
.music-control input[type="file"] {
display: none;
}
.music-control .file-label {
background: #2a1a3a;
padding: 2px 6px;
border-radius: 10px;
font-size: 7px;
cursor: pointer;
border: 1px solid #4a2a6a;
color: white;
}
.music-control .file-label:active {
background: #3a2a4a;
}
.music-control .song-name {
color: #88dd88;
font-size: 6px;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 80px;
}
.music-instruction {
font-size: 6px;
color: #88dd88;
padding: 4px;
background: #0a0a1a;
border-radius: 6px;
margin-top: 2px;
text-align: center;
border: 1px solid #2a1a3a;
line-height: 1.5;
}
.music-instruction .highlight {
color: #ff44aa;
}
.difficulty-select {
display: flex;
gap: 4px;
margin: 4px 0;
flex-wrap: wrap;
justify-content: center;
}
.difficulty-select .diff-btn {
padding: 4px 12px;
border-radius: 30px;
font-size: 8px;
font-weight: bold;
cursor: pointer;
border: 2px solid #4a2a6a;
background: #1a1a1a;
color: white;
touch-action: manipulation;
}
.difficulty-select .diff-btn.active {
border-color: #ff44aa;
background: #2a1a3a;
}
@media (max-width: 420px) {
#app {
border-radius: 0;
height: 100vh;
max-height: 100vh;
padding: 2px;
}
.menu-btn {
padding: 4px 10px;
font-size: 10px;
}
.game-controls button {
padding: 1px 4px;
font-size: 6px;
min-width: 22px;
min-height: 22px;
}
#loadingScreen .logo {
font-size: 40px;
}
#loadingScreen .title {
font-size: 24px;
}
#resultOverlay .result-text {
font-size: 22px;
}
#resultOverlay .result-stars {
font-size: 30px;
}
#pauseOverlay .pause-text {
font-size: 24px;
}
}
@media (max-height: 600px) {
.game-controls button {
min-width: 20px;
min-height: 20px;
font-size: 5px;
padding: 1px 3px;
}
#pauseOverlay .pause-text {
font-size: 20px;
}
#pauseOverlay .pause-buttons button {
padding: 4px 12px;
font-size: 10px;
}
}
</style>
</head>
<body>
<div id="app">
<!-- LOADING SCREEN -->
<div id="loadingScreen">
<div class="logo">🎹</div>
<div class="title">PIANO FIRE</div>
<div class="sub">TAP THE TILES</div>
<div class="load-bar">
<div class="load-fill" id="loadFill"></div>
</div>
<div class="load-percent" id="loadPercent">0%</div>
<div class="tap-text">👆 TAP TO START</div>
</div>
<!-- HOME SCREEN -->
<div id="homeScreen" class="screen">
<div class="logo">🎹 PIANO FIRE</div>
<div class="sub">TAP THE TILES</div>
<div style="display:flex; gap:2px; margin-bottom:4px; flex-wrap:wrap; justify-content:center;">
<span class="coin-display" style="border-color:#ff44aa;">🎵 PLAY</span>
</div>
<button class="menu-btn pink" onclick="showScreen('songSelectScreen')">▶ SELECT SONG</button>
<button class="menu-btn" onclick="startGame()">⚡ QUICK PLAY</button>
<button class="menu-btn small-btn" onclick="showScreen('settingsScreen')">⚙️ SETTINGS</button>
<div style="margin-top:6px; font-size:6px; color:#557755;">"TAP THE TILES!"</div>
</div>
<!-- SONG SELECT -->
<div id="songSelectScreen" class="screen">
<div class="header">
<button class="back-btn" onclick="showScreen('homeScreen')">←</button>
<span style="font-size:10px;">🎵 SELECT SONG</span>
<span style="font-size:8px; color:#88dd88;" id="songStatus">No song loaded</span>
</div>
<div class="music-control">
<span style="font-size:7px; color:#88dd88;">🎵 MUSIC:</span>
<label class="file-label" for="musicFile">📁 UPLOAD MP3</label>
<input type="file" id="musicFile" accept=".mp3,.wav,.ogg" onchange="uploadMusic(event)" />
<span class="song-name" id="songName">No song selected</span>
</div>
<div class="music-instruction">
📌 <span class="highlight">HOW TO ADD MUSIC:</span><br>
1️⃣ Download MP3 from <span class="highlight">audio.com</span> or any MP3 site<br>
2️⃣ Click <span class="highlight">"UPLOAD MP3"</span> and select your file<br>
3️⃣ Choose difficulty and play!
</div>
<div style="font-size:8px; color:#88dd88; text-align:center; margin-top:4px;">🎯 DIFFICULTY</div>
<div class="difficulty-select">
<button class="diff-btn" onclick="setDifficulty('easy')" id="diffEasy">🟢 EASY</button>
<button class="diff-btn active" onclick="setDifficulty('medium')" id="diffMedium">🟡 MEDIUM</button>
<button class="diff-btn" onclick="setDifficulty('hard')" id="diffHard">🔴 HARD</button>
<button class="diff-btn" onclick="setDifficulty('expert')" id="diffExpert">🔵 EXPERT</button>
</div>
<button class="menu-btn pink" style="margin-top:4px;" onclick="startGame()">▶ START GAME</button>
<div style="font-size:6px; color:#557755; text-align:center; margin-top:2px;">
🎵 Tiles change color at the extreme part of the song!
</div>
</div>
<!-- GAME SCREEN -->
<div id="gameScreen" class="screen">
<div class="game-container">
<canvas id="gameCanvas"></canvas>
<div id="pauseOverlay">
<div class="pause-text">⏸️ PAUSED</div>
<div class="pause-sub">Game is paused</div>
<div class="pause-buttons">
<button class="resume-btn" onclick="togglePause()">▶ RESUME</button>
<button class="quit-btn" onclick="quitGame()">🚪 QUIT</button>
</div>
</div>
<div id="resultOverlay">
<div class="result-text" id="resultText">🎉 GREAT!</div>
<div class="result-stars" id="resultStars">⭐⭐⭐</div>
<div class="result-score" id="resultScore">Score: 0</div>
<div class="result-buttons">
<button onclick="closeResult()">🎮 MENU</button>
<button onclick="restartGame()">🔄 RETRY</button>
</div>
</div>
<div class="game-controls">
<div class="btn-row">
<button class="quit-btn" onclick="quitGame()">🚪</button>
<button class="pause-btn" onclick="togglePause()">⏸️</button>
</div>
<div class="btn-row">
<span style="color:#ffdd44; font-size:8px; padding:4px;" id="scoreDisplay">⭐ 0</span>
<span style="color:#ff44aa; font-size:8px; padding:4px;" id="comboDisplay">🔥 0</span>
</div>
</div>
</div>
</div>
<!-- SETTINGS -->
<div id="settingsScreen" class="screen">
<div class="header">
<button class="back-btn" onclick="showScreen('homeScreen')">←</button>
<span style="font-size:10px;">⚙️ SETTINGS</span>
<span></span>
</div>
<div style="margin-top:8px; font-size:10px;">
<div>🎵 Sound: <span class="text-gold" id="soundSetting">ON</span></div>
<div>🎮 Controls: <span class="text-gold">Touch Anywhere</span></div>
<div>🎯 Difficulty: <span class="text-gold" id="diffSetting">Medium</span></div>
<div>⭐ High Score: <span class="text-gold" id="highScoreDisplay">0</span></div>
<button class="menu-btn small-btn" style="margin-top:4px;" onclick="toggleSound()">🔊 TOGGLE SOUND</button>
<button class="menu-btn small-btn" style="margin-top:2px;" onclick="showComingSoon('More Songs')">🎵 MORE SONGS</button>
</div>
<div style="margin-top:8px; font-size:6px; color:#557755; text-align:center;">
💡 Download songs from <span style="color:#ff44aa;">audio.com</span> or any MP3 site
</div>
</div>
</div>
<script>
// ========== LOADING SCREEN ==========
let loadingProgress = 0;
const loadFill = document.getElementById('loadFill');
const loadPercent = document.getElementById('loadPercent');
const loadingScreen = document.getElementById('loadingScreen');
const loadInterval = setInterval(() => {
loadingProgress += Math.random() * 8 + 2;
if (loadingProgress > 100) loadingProgress = 100;
loadFill.style.width = loadingProgress + '%';
loadPercent.textContent = Math.floor(loadingProgress) + '%';
if (loadingProgress >= 100) {
clearInterval(loadInterval);
document.querySelector('.tap-text').style.display = 'block';
}
}, 100);
loadingScreen.addEventListener('click', function() {
if (loadingProgress >= 100) {
this.style.display = 'none';
document.getElementById('homeScreen').classList.add('active');
updateHighScore();
}
});
// ========== GAME STATE ==========
let gameActive = false;
let gamePaused = false;
let score = 0;
let combo = 0;
let highScore = 0;
let tiles = [];
let tileSpeed = 2;
let tileWidth = 80;
let tileHeight = 80;
let nextTileY = 0;
let tileGap = 30;
let gameTime = 0;
let difficulty = 'medium';
let audioElement = null;
let musicLoaded = false;
let soundEnabled = true;
let gameLoop = null;
let frameId = null;
let colorChangeTimer = 0;
let isExtremeMode = false;
let totalTiles = 0;
let hitTiles = 0;
let missedTiles = 0;
let stars = 0;
let canvas = null;
let ctx = null;
let W = 400;
let H = 700;
let spawnTimer = 0;
let spawnInterval = 30;
let maxMissed = 15;
let extremeTimer = 0;
let extremeDuration = 0;
let extremeActive = false;
let tileSpawnCount = 0;
let lastTileLane = -1;
// Colors
const colors = {
normal: '#ff44aa',
normalDark: '#cc2288',
normalLight: '#ff66cc',
extreme: '#ff00ff',
extremeDark: '#cc00cc',
extremeLight: '#ff44ff',
background: '#0a0a1a',
laneBg: 'rgba(255,255,255,0.05)',
hit: '#44ff88',
miss: '#ff4444',
combo: '#ffdd44'
};
// ========== MUSIC SYSTEM ==========
function uploadMusic(event) {
const file = event.target.files[0];
if (file) {
const url = URL.createObjectURL(file);
if (audioElement) {
audioElement.src = url;
audioElement.load();
} else {
audioElement = new Audio(url);
}
musicLoaded = true;
document.getElementById('songName').textContent = file.name;
document.getElementById('songStatus').textContent = `✅ ${file.name}`;
document.getElementById('songStatus').style.color = '#88dd88';
try {
const reader = new FileReader();
reader.onload = function(e) {
localStorage.setItem('pianoFireMusic', e.target.result);
localStorage.setItem('pianoFireMusicName', file.name);
};
reader.readAsDataURL(file);
} catch(e) {}
}
}
function loadStoredMusic() {
try {
const stored = localStorage.getItem('pianoFireMusic');
const name = localStorage.getItem('pianoFireMusicName');
if (stored && name) {
if (audioElement) {
audioElement.src = stored;
audioElement.load();
} else {
audioElement = new Audio(stored);
}
musicLoaded = true;
document.getElementById('songName').textContent = name;
document.getElementById('songStatus').textContent = `✅ ${name}`;
document.getElementById('songStatus').style.color = '#88dd88';
}
} catch(e) {}
}
function playMusic() {
if (audioElement && musicLoaded) {
try {
audioElement.currentTime = 0;
audioElement.play();
} catch(e) {}
}
}
function stopMusic() {
if (audioElement) {
try {
audioElement.pause();
} catch(e) {}
}
}
function pauseMusic() {
if (audioElement && !audioElement.paused) {
try {
audioElement.pause();
} catch(e) {}
}
}
function resumeMusic() {
if (audioElement && audioElement.paused && gameActive && !gamePaused) {
try {
audioElement.play();
} catch(e) {}
}
}
// ========== DIFFICULTY ==========
function setDifficulty(diff) {
difficulty = diff;
document.querySelectorAll('.diff-btn').forEach(b => b.classList.remove('active'));
const map = { easy: 'diffEasy', medium: 'diffMedium', hard: 'diffHard', expert: 'diffExpert' };
const el = document.getElementById(map[diff]);
if (el) el.classList.add('active');
document.getElementById('diffSetting').textContent = diff.charAt(0).toUpperCase() + diff.slice(1);
const speedMap = { easy: 0.8, medium: 1.5, hard: 2.5, expert: 4 };
tileSpeed = speedMap[diff] || 1.5;
const spawnMap = { easy: 45, medium: 35, hard: 25, expert: 18 };
spawnInterval = spawnMap[diff] || 35;
maxMissed = { easy: 25, medium: 20, hard: 15, expert: 10 }[diff] || 20;
tileGap = { easy: 40, medium: 30, hard: 25, expert: 20 }[diff] || 30;
}
// ========== HIGH SCORE ==========
function updateHighScore() {
const saved = localStorage.getItem('pianoFireHighScore');
if (saved) {
highScore = parseInt(saved) || 0;
}
document.getElementById('highScoreDisplay').textContent = highScore;
}
function saveHighScore() {
if (score > highScore) {
highScore = score;
localStorage.setItem('pianoFireHighScore', String(highScore));
document.getElementById('highScoreDisplay').textContent = highScore;
}
}
// ========== START GAME ==========
function startGame() {
showScreen('gameScreen');
initGameCanvas();
resetGame();
startGameLoop();
if (musicLoaded) {
playMusic();
}
}
function resetGame() {
score = 0;
combo = 0;
tiles = [];
nextTileY = 0;
gameTime = 0;
colorChangeTimer = 0;
isExtremeMode = false;
extremeTimer = 0;
extremeActive = false;
spawnTimer = 0;
tileSpawnCount = 0;
totalTiles = 0;
hitTiles = 0;
missedTiles = 0;
stars = 0;
gameActive = true;
gamePaused = false;
lastTileLane = -1;
document.getElementById('pauseOverlay').style.display = 'none';
document.getElementById('resultOverlay').style.display = 'none';
document.getElementById('scoreDisplay').textContent = '⭐ 0';
document.getElementById('comboDisplay').textContent = '🔥 0';
// Generate initial tiles
generateTiles(10);
}
function generateTiles(count) {
for (let i = 0; i < count; i++) {
// Avoid same lane consecutively
let lane;
do {
lane = Math.floor(Math.random() * 4);
} while (lane === lastTileLane && tiles.length > 0);
lastTileLane = lane;
const y = nextTileY;
nextTileY += tileHeight + tileGap;
// Determine if this tile should be in extreme mode
const useExtreme = isExtremeMode || (extremeActive && Math.random() < 0.6);
tiles.push({
lane: lane,
x: lane * (tileWidth + 5) + 10,
y: y,
width: tileWidth,
height: tileHeight,
hit: false,
missed: false,
color: useExtreme ? colors.extreme : colors.normal,
darkColor: useExtreme ? colors.extremeDark : colors.normalDark,
isExtreme: useExtreme
});
totalTiles++;
tileSpawnCount++;
}
}
function initGameCanvas() {
canvas = document.getElementById('gameCanvas');
if (canvas) {
W = canvas.width = canvas.clientWidth || 400;
H = canvas.height = canvas.clientHeight || 700;
tileWidth = (W - 20) / 4 - 5;
tileHeight = tileWidth * 0.9;
ctx = canvas.getContext('2d');
}
}
// ========== GAME LOOP ==========
function startGameLoop() {
if (gameLoop) clearInterval(gameLoop);
if (frameId) cancelAnimationFrame(frameId);
gameLoop = setInterval(() => {
if (!gameActive || gamePaused) return;
gameTime++;
colorChangeTimer++;
spawnTimer++;
extremeTimer++;
// EXTREME MODE - triggers periodically and lasts for a duration
if (extremeTimer > 200) {
extremeTimer = 0;
extremeActive = true;
isExtremeMode = true;
// Update existing tiles to extreme colors
tiles.forEach(t => {
if (!t.hit && !t.missed) {
t.color = colors.extreme;
t.darkColor = colors.extremeDark;
t.isExtreme = true;
}
});
// Extreme mode lasts for 60-100 frames
extremeDuration = 60 + Math.floor(Math.random() * 40);
}
// End extreme mode
if (extremeActive && extremeTimer > extremeDuration) {
extremeActive = false;
isExtremeMode = false;
// Revert tiles to normal colors
tiles.forEach(t => {
if (!t.hit && !t.missed) {
t.color = colors.normal;
t.darkColor = colors.normalDark;
t.isExtreme = false;
}
});
}
// Move tiles down
tiles.forEach(t => {
if (!t.hit && !t.missed) {
t.y += tileSpeed;
// Check if tile passed bottom
if (t.y > H + 50) {
t.missed = true;
missedTiles++;
combo = 0;
document.getElementById('comboDisplay').textContent = '🔥 0';
// Check game over
if (missedTiles >= maxMissed) {
gameActive = false;
clearInterval(gameLoop);
gameOver();
}
}
}
});
// Remove missed tiles that are far gone
tiles = tiles.filter(t => !t.missed || t.y < H + 100);
// Spawn new tiles
if (spawnTimer >= spawnInterval) {
spawnTimer = 0;
const lastTile = tiles[tiles.length - 1];
if (!lastTile || lastTile.y > 50) {
// Spawn more tiles during extreme mode
const count = isExtremeMode ? 5 : 3;
generateTiles(count);
}
}
// Check if no tiles left and game should end
if (tiles.length === 0 && gameActive && totalTiles > 20) {
gameActive = false;
clearInterval(gameLoop);
gameOver();
}
drawGame();
}, 50);
function animate() {
if (!gameActive) return;
drawGame();
frameId = requestAnimationFrame(animate);
}
animate();
}
// ========== DRAW GAME ==========
function drawGame() {
if (!canvas || !ctx) return;
ctx.clearRect(0, 0, W, H);
// Background
ctx.fillStyle = colors.background;
ctx.fillRect(0, 0, W, H);
// Lane dividers
const laneWidth = (W - 10) / 4;
for (let i = 0; i < 4; i++) {
ctx.fillStyle = colors.laneBg;
ctx.fillRect(5 + i * laneWidth, 0, laneWidth - 2, H);
}
// Draw tiles
tiles.forEach(t => {
if (t.hit) return;
const color = t.color || colors.normal;
const darkColor = t.darkColor || colors.normalDark;
// Glow effect for extreme tiles
if (t.isExtreme || isExtremeMode) {
ctx.shadowColor = color;
ctx.shadowBlur = 25;
}
const radius = 10;
const x = t.x;
const y = t.y;
const w = t.width || tileWidth;
const h = t.height || tileHeight;
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + w - radius, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + radius);
ctx.lineTo(x + w, y + h - radius);
ctx.quadraticCurveTo(x + w, y + h, x + w - radius, y + h);
ctx.lineTo(x + radius, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
// Gradient
const gradient = ctx.createLinearGradient(x, y, x, y + h);
gradient.addColorStop(0, color);
gradient.addColorStop(0.6, color);
gradient.addColorStop(1, darkColor);
ctx.fillStyle = gradient;
ctx.fill();
// Shine effect
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255,255,255,0.1)';
ctx.fillRect(x + 8, y + 8, w - 16, h * 0.25);
// Border
ctx.strokeStyle = 'rgba(255,255,255,0.1)';
ctx.lineWidth = 1;
ctx.stroke();
});
// Bottom line (hit zone)
const lineY = H - 70;
ctx.strokeStyle = 'rgba(255,68,170,0.4)';
ctx.lineWidth = 2;
ctx.setLineDash([8, 8]);
ctx.beginPath();
ctx.moveTo(10, lineY);
ctx.lineTo(W - 10, lineY);
ctx.stroke();
ctx.setLineDash([]);
// Tap zone (full width)
ctx.fillStyle = 'rgba(255,68,170,0.06)';
ctx.fillRect(10, lineY + 2, W - 20, 40);
// Tap zone text
ctx.fillStyle = 'rgba(255,68,170,0.25)';
ctx.font = '8px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('▼ TAP ANYWHERE TO HIT ▼', W/2, lineY + 25);
ctx.textAlign = 'left';
// Extreme mode indicator
if (isExtremeMode || extremeActive) {
ctx.fillStyle = 'rgba(255,0,255,0.08)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#ff00ff';
ctx.font = 'bold 16px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('⚡ EXTREME MODE ⚡', W/2, 30);
ctx.textAlign = 'left';
// Glow pulse
ctx.shadowColor = '#ff00ff';
ctx.shadowBlur = 50;
ctx.fillStyle = 'rgba(255,0,255,0.02)';
ctx.fillRect(0, 0, W, H);
ctx.shadowBlur = 0;
}
// Difficulty indicator
const diffColors = { easy: '#44ff44', medium: '#ffdd44', hard: '#ff4444', expert: '#4444ff' };
ctx.fillStyle = diffColors[difficulty] || '#ffffff';
ctx.font = '8px sans-serif';
ctx.fillText(`🎯 ${difficulty.toUpperCase()}`, 10, 15);
// Missed count
ctx.fillStyle = 'rgba(255,68,68,0.6)';
ctx.font = '8px sans-serif';
ctx.textAlign = 'right';
ctx.fillText(`❌ ${missedTiles}/${maxMissed}`, W - 10, 15);
ctx.textAlign = 'left';
// Tile count
const activeTiles = tiles.filter(t => !t.hit && !t.missed).length;
ctx.fillStyle = 'rgba(255,255,255,0.2)';
ctx.font = '7px sans-serif';
ctx.textAlign = 'right';
ctx.fillText(`🎵 ${activeTiles}`, W - 10, 28);
ctx.textAlign = 'left';
}
// ========== TAP HANDLING - ANYWHERE ON SCREEN ==========
function handleTap(x, y) {
if (!gameActive || gamePaused) return;
if (!canvas) return;
// Get canvas coordinates
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const canvasX = (x - rect.left) * scaleX;
const canvasY = (y - rect.top) * scaleY;
// Ignore taps at the very top (UI area)
if (canvasY < 20) return;
// Find the closest tile to the tap position
let closestTile = null;
let closestDist = Infinity;
const lineY = H - 70;
tiles.forEach(t => {
if (t.hit || t.missed) return;
// Check if tap is roughly above the tile
const tileCenterY = t.y + t.height / 2;
const distY = Math.abs(tileCenterY - (lineY + 20));
const distX = Math.abs((t.x + t.width/2) - canvasX);
// Only consider tiles near the bottom zone
if (distY < tileHeight + 40 && distX < tileWidth) {
const totalDist = Math.sqrt(distX * distX + distY * distY);
if (totalDist < closestDist) {
closestDist = totalDist;
closestTile = t;
}
}
});
// Also check any tile regardless of position (for extreme mode)
if (!closestTile) {
tiles.forEach(t => {
if (t.hit || t.missed) return;
const tileCenterY = t.y + t.height / 2;
const distY = Math.abs(tileCenterY - (lineY + 20));
const distX = Math.abs((t.x + t.width/2) - canvasX);
if (distY < tileHeight * 1.5 && distX < tileWidth * 1.2) {
const totalDist = Math.sqrt(distX * distX + distY * distY);
if (totalDist < closestDist) {
closestDist = totalDist;
closestTile = t;
}
}
});
}
if (closestTile) {
// Hit!
closestTile.hit = true;
hitTiles++;
combo++;
const comboBonus = Math.min(combo, 5);
score += 10 * comboBonus;
// Update displays
document.getElementById('scoreDisplay').textContent = `⭐ ${score}`;
document.getElementById('comboDisplay').textContent = `🔥 ${combo}`;
// Sound effect
if (soundEnabled) {
try {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.frequency.value = 440 + (closestTile.lane * 100);
oscillator.type = 'sine';
gainNode.gain.value = 0.08;
oscillator.start();
oscillator.stop(audioCtx.currentTime + 0.08);
} catch(e) {}
}
// Remove tile after delay
setTimeout(() => {
tiles = tiles.filter(t => t !== closestTile);
}, 150);
} else {
// Miss - but only if tap is in the bottom area
if (canvasY > H - 100) {
combo = 0;
document.getElementById('comboDisplay').textContent = '🔥 0';
if (soundEnabled) {
try {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.frequency.value = 200;
oscillator.type = 'sawtooth';
gainNode.gain.value = 0.04;
oscillator.start();
oscillator.stop(audioCtx.currentTime + 0.1);
} catch(e) {}
}
}
}
}
// ========== CANVAS TOUCH EVENTS ==========
function setupCanvasTouch() {
const canvas = document.getElementById('gameCanvas');
if (!canvas) return;
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch = e.touches[0];
if (touch) handleTap(touch.clientX, touch.clientY);
}, { passive: false });
canvas.addEventListener('mousedown', (e) => {
handleTap(e.clientX, e.clientY);
});
}
// ========== PAUSE / QUIT ==========
function togglePause() {
if (!gameActive) return;
gamePaused = !gamePaused;
document.getElementById('pauseOverlay').style.display = gamePaused ? 'flex' : 'none';
if (gamePaused) {
pauseMusic();
} else {
resumeMusic();
}
}
function quitGame() {
gameActive = false;
if (gameLoop) clearInterval(gameLoop);
if (frameId) cancelAnimationFrame(frameId);
stopMusic();
document.getElementById('pauseOverlay').style.display = 'none';
document.getElementById('resultOverlay').style.display = 'none';
showScreen('homeScreen');
}
function restartGame() {
document.getElementById('resultOverlay').style.display = 'none';
resetGame();
if (musicLoaded) {
playMusic();
}
}
function closeResult() {
document.getElementById('resultOverlay').style.display = 'none';
gameActive = false;
stopMusic();
showScreen('songSelectScreen');
}
// ========== GAME OVER ==========
function gameOver() {
if (!gameActive) return;
gameActive = false;
if (gameLoop) clearInterval(gameLoop);
if (frameId) cancelAnimationFrame(frameId);
stopMusic();
// Calculate stars
const accuracy = totalTiles > 0 ? hitTiles / totalTiles : 0;
if (accuracy > 0.85) stars = 3;
else if (accuracy > 0.65) stars = 2;
else if (accuracy > 0.45) stars = 1;
else stars = 0;
// Save high score
saveHighScore();
// Show result
const resultText = document.getElementById('resultText');
const resultStars = document.getElementById('resultStars');
const resultScore = document.getElementById('resultScore');
if (accuracy > 0.85) resultText.textContent = '🎉 PERFECT!';
else if (accuracy > 0.65) resultText.textContent = '🌟 GREAT!';
else if (accuracy > 0.45) resultText.textContent = '👍 GOOD!';
else resultText.textContent = '💪 KEEP PRACTICING!';
resultStars.textContent = '⭐'.repeat(stars) + '☆'.repeat(3 - stars);
resultScore.textContent = `Score: ${score} | Hit: ${hitTiles}/${totalTiles} | Combo: ${combo}`;
document.getElementById('resultOverlay').style.display = 'flex';
}
// ========== UI HELPERS ==========
function showScreen(id) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
const target = document.getElementById(id);
if (target) target.classList.add('active');
if (id === 'gameScreen') {
setTimeout(() => {
initGameCanvas();
setupCanvasTouch();
}, 100);
}
if (id === 'settingsScreen') {
document.getElementById('soundSetting').textContent = soundEnabled ? 'ON' : 'OFF';
updateHighScore();
}
if (id === 'songSelectScreen') {
loadStoredMusic();
}
}
function toggleSound() {
soundEnabled = !soundEnabled;
document.getElementById('soundSetting').textContent = soundEnabled ? 'ON' : 'OFF';
}
function showComingSoon(feature) {
alert(`🔜 ${feature} coming soon!\nStay tuned for updates!`);
}
// ========== INIT ==========
setDifficulty('medium');
loadStoredMusic();
updateHighScore();
// Handle resize
function handleResize() {
const canvas = document.getElementById('gameCanvas');
if (canvas) {
W = canvas.width = canvas.clientWidth || 400;
H = canvas.height = canvas.clientHeight || 700;
tileWidth = (W - 20) / 4 - 5;
tileHeight = tileWidth * 0.9;
ctx = canvas.getContext('2d');
}
}
window.addEventListener('resize', handleResize);
window.addEventListener('orientationchange', () => {
setTimeout(handleResize, 400);
});
document.addEventListener('DOMContentLoaded', () => {
setTimeout(handleResize, 300);
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === ' ' || e.key === 'p') {
if (document.getElementById('gameScreen').classList.contains('active')) {
togglePause();
}
}
if (e.key === 'q' || e.key === 'Q') {
if (document.getElementById('gameScreen').classList.contains('active')) {
quitGame();
}
}
// Tap with keyboard for testing (A key)
if (e.key === 'a' || e.key === 'A') {
if (document.getElementById('gameScreen').classList.contains('active') && gameActive) {
handleTap(200, 600);
}
}
});
console.log('🎹 Piano Fire - Tap the tiles!');
console.log('📌 Download music from audio.com or any MP3 site');
console.log('🎯 Difficulty: Easy, Medium, Hard, Expert');
console.log('⭐ Get 3 stars for perfect performance!');
console.log('⏸️ Press Space/P to pause, Q to quit');
console.log('👆 Tap anywhere on the screen to hit tiles!');
</script>
</body>
</html>Game Source: Piano Fire
Creator: SonicBear35
Libraries: none
Complexity: complex (1500 lines, 48.1 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: piano-fire-sonicbear35" to link back to the original. Then publish at arcadelab.ai/publish.