diff --git a/script.js b/script.js index 7fe9c9f..2c7a18e 100644 --- a/script.js +++ b/script.js @@ -4,6 +4,8 @@ let score = 0; let running = false; let ball, paddle, bricks, rows, cols, speed; let currentDiff = "easy"; +let balls = []; // Array untuk multiple balls +let powerUps = []; // Array untuk power-ups const brickColors = ["#FF5733", "#33FF57", "#3357FF", "#FF33F5", "#33FFF5", "#F5FF33"]; @@ -31,13 +33,14 @@ if (saveScoreBtn) { }); } -function lockSpeed() { - let currentSpeedSq = ball.dx * ball.dx + ball.dy * ball.dy; +// Modifikasi: lockSpeed sekarang menerima objek bola +function lockSpeed(b) { + let currentSpeedSq = b.dx * b.dx + b.dy * b.dy; if (currentSpeedSq > 0.0001) { let currentSpeed = Math.sqrt(currentSpeedSq); let ratio = speed / currentSpeed; - ball.dx *= ratio; - ball.dy *= ratio; + b.dx *= ratio; + b.dy *= ratio; } } @@ -64,7 +67,9 @@ function startGame(){ paddle = { x:200, w:80, h:10 }; ball = { x:240, y:200, dx:speed, dy:-speed, r:6 }; + balls = [ball]; // Reset ke satu bola bricks = []; + powerUps = []; // Reset power-ups let brickW = (canvas.width - cols * 5) / cols; let brickH = 15; @@ -99,57 +104,85 @@ function update(){ ctx.clearRect(0,0,canvas.width,canvas.height); - ball.x += ball.dx; - ball.y += ball.dy; - + // Gambar paddle ctx.fillStyle = "#ffffff"; ctx.fillRect(paddle.x, canvas.height-paddle.h, paddle.w, paddle.h); - ctx.beginPath(); - ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2); - ctx.fillStyle = "#ffffff"; - ctx.fill(); - ctx.closePath(); + // Update semua bola + balls.forEach((b, index) => { + b.x += b.dx; + b.y += b.dy; - if(ball.x + ball.r > canvas.width || ball.x - ball.r < 0){ - ball.dx *= -1; lockSpeed(); - wallSound.currentTime = 0; wallSound.play(); - } - if(ball.y - ball.r < 0){ - ball.dy *= -1; lockSpeed(); - wallSound.currentTime = 0; wallSound.play(); - } + // Gambar bola + ctx.beginPath(); + ctx.arc(b.x, b.y, b.r, 0, Math.PI*2); + ctx.fillStyle = "#ffffff"; + ctx.fill(); + ctx.closePath(); - if(ball.y + ball.r > canvas.height - paddle.h && ball.x > paddle.x && ball.x < paddle.x + paddle.w){ - let deltaX = ball.x - (paddle.x + paddle.w/2); - let dy_abs = Math.abs(speed) * -1; - ball.dx = deltaX * 0.15; - ball.dy = dy_abs; - lockSpeed(); - wallSound.currentTime = 0; wallSound.play(); - } + // Collision dengan dinding + if(b.x + b.r > canvas.width || b.x - b.r < 0){ + b.dx *= -1; lockSpeed(b); + // wallSound.currentTime = 0; wallSound.play(); + } + if(b.y - b.r < 0){ + b.dy *= -1; lockSpeed(b); + // wallSound.currentTime = 0; wallSound.play(); + } - bricks.forEach(b =>{ - if(!b.hit && ball.x > b.x && ball.x < b.x+b.w && ball.y > b.y && ball.y < b.y+b.h){ - b.health--; - ball.dy *= -1; - lockSpeed(); + // Collision dengan paddle + if(b.y + b.r > canvas.height - paddle.h && b.x > paddle.x && b.x < paddle.x + paddle.w){ + let deltaX = b.x - (paddle.x + paddle.w/2); + let dy_abs = Math.abs(speed) * -1; + b.dx = deltaX * 0.15; + b.dy = dy_abs; + lockSpeed(b); + // wallSound.currentTime = 0; wallSound.play(); + } - if (b.health <= 0) { - b.hit = true; - score += 10; - winSound.currentTime = 0; winSound.play(); - } else { - hitSound.currentTime = 0; hitSound.play(); - if (b.health === 4) b.color = '#B22222'; - if (b.health === 3) b.color = '#CD853F'; - if (b.health === 2) b.color = '#FFA07A'; - if (b.health === 1) b.color = '#F08080'; - } - document.getElementById("score").textContent = score; + // Jika bola jatuh, hapus + if(b.y > canvas.height){ + balls.splice(index, 1); } }); + // Jika tidak ada bola lagi, game over + if(balls.length === 0){ + loseSound.currentTime = 0; loseSound.play(); + running = false; + showModal("GAME OVER!", score); + return; + } + + // Collision bola dengan bricks + balls.forEach(b => { + bricks.forEach(br => { + if(!br.hit && b.x > br.x && b.x < br.x+br.w && b.y > br.y && b.y < br.y+br.h){ + br.health--; + b.dy *= -1; + lockSpeed(b); + + if (br.health <= 0) { + br.hit = true; + score += 10; + // winSound.currentTime = 0; winSound.play(); + // Spawn power-up + if (Math.random() < 0.3) { + spawnPowerUp(br.x + br.w/2, br.y + br.h/2); + } + } else { + // hitSound.currentTime = 0; hitSound.play(); + if (br.health === 4) br.color = '#B22222'; + if (br.health === 3) br.color = '#CD853F'; + if (br.health === 2) br.color = '#FFA07A'; + if (br.health === 1) br.color = '#F08080'; + } + document.getElementById("score").textContent = score; + } + }); + }); + + // Gambar bricks bricks.forEach(b =>{ if(!b.hit){ ctx.fillStyle = b.color; @@ -158,18 +191,27 @@ function update(){ ctx.strokeRect(b.x,b.y,b.w,b.h); } }); + + // Update power-ups + powerUps.forEach((pu, index) => { + pu.y += 2; + ctx.fillStyle = pu.color; + ctx.fillRect(pu.x - 5, pu.y - 5, 10, 10); + if (pu.y + 5 > canvas.height - paddle.h && pu.x > paddle.x && pu.x < paddle.x + paddle.w) { + activatePowerUp(pu.type); + powerUps.splice(index, 1); + } + if (pu.y > canvas.height) { + powerUps.splice(index, 1); + } + }); if(bricks.every(b => b.hit)){ - winSound.currentTime = 0; winSound.play(); + winSound.currentTime = 0; + winSound.play(); running = false; showModal("YOU WIN!", score); } - - if(ball.y > canvas.height){ - loseSound.currentTime = 0; loseSound.play(); - running = false; - showModal("GAME OVER!", score); - } } canvas.addEventListener("mousemove", e =>{ @@ -205,7 +247,6 @@ function saveScore(fromModal = false){ saveScoreBtn.style.opacity = '0.7'; } - // Perubahan: Menambahkan &diff=... ke URL fetch(`save.php?score=${score}&diff=${currentDiff}`) .then(response => { if (response.ok) { @@ -232,4 +273,41 @@ function saveScore(fromModal = false){ }); } } +} + +// Fungsi spawn power-up +function spawnPowerUp(x, y) { + const types = ['multiBall', 'largeBall', 'largePaddle', 'fastBall']; + const type = types[Math.floor(Math.random() * types.length)]; + let color; + switch (type) { + case 'multiBall': color = '#FF0000'; break; + case 'largeBall': color = '#00FF00'; break; + case 'largePaddle': color = '#0000FF'; break; + case 'fastBall': color = '#FFFF00'; break; + } + powerUps.push({ x: x, y: y, type: type, color: color }); +} + +// Fungsi activate power-up +function activatePowerUp(type) { + switch (type) { + case 'multiBall': + // Tambah bola baru dari posisi bola pertama + if (balls.length > 0) { + let newBall = { x: balls[0].x, y: balls[0].y, dx: -balls[0].dx, dy: balls[0].dy, r: balls[0].r }; + balls.push(newBall); + } + break; + case 'largeBall': + balls.forEach(b => b.r += 2); + break; + case 'largePaddle': + paddle.w += 20; + break; + case 'fastBall': + speed += 1; + balls.forEach(b => lockSpeed(b)); + break; + } } \ No newline at end of file diff --git a/style.css b/style.css index 08b0a34..73d4ba2 100644 --- a/style.css +++ b/style.css @@ -1,28 +1,39 @@ +/* Import pixelated font from Google Fonts */ +@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); + +/* General body styles with pixelated feel */ body { - font-family: 'Poppins', sans-serif; + font-family: 'Press Start 2P', monospace; /* Pixelated font for retro look */ margin: 0; padding: 0; - background: radial-gradient(circle at top, #6a85f1, #4834d4); - color: white; + background: #000; /* Solid black background for pixel style */ + color: #FFF; /* White text */ display: flex; flex-direction: column; align-items: center; min-height: 100vh; + image-rendering: pixelated; /* Force pixelated rendering */ + image-rendering: -moz-crisp-edges; /* Firefox */ + image-rendering: crisp-edges; /* Safari */ } +/* Header with pixel shadow */ h1 { font-size: 2.7rem; margin-top: 25px; - text-shadow: 3px 3px 10px rgba(0,0,0,0.4); + text-shadow: 2px 2px 0px #FF0000; /* Red pixel shadow */ letter-spacing: 1px; + color: #FFFF00; /* Yellow for retro feel */ } h2 { font-size: 2.5rem; margin-top: 20px; - text-shadow: 3px 3px 10px rgba(0,0,0,0.4); + text-shadow: 2px 2px 0px #FF0000; + color: #FFFF00; } +/* Menu with pixelated buttons */ .menu { margin-top: 10px; display: flex; @@ -33,21 +44,22 @@ h2 { .menu a { text-decoration: none; - color: #fff; + color: #FFF; padding: 10px 18px; - background: rgba(255,255,255,0.18); - border-radius: 30px; - backdrop-filter: blur(10px); - border: 1px solid rgba(255,255,255,0.3); - transition: 0.3s ease; + background: #333; /* Dark gray for pixel button */ + border: 2px solid #FFF; /* White border for pixel edge */ + border-radius: 0; /* No rounded corners for pixel style */ + box-shadow: 4px 4px 0px #000; /* Raised pixel effect */ + transition: none; /* Remove smooth transitions for instant pixel feel */ display: flex; align-items: center; gap: 7px; + font-size: 0.8rem; /* Smaller font for pixel look */ } .menu a:hover { - transform: translateY(-3px); - background: rgba(255,255,255,0.35); + background: #555; /* Lighter gray on hover */ + box-shadow: 2px 2px 0px #000; /* Slightly less raised */ } .user-info { @@ -56,34 +68,40 @@ h2 { text-align: center; width: 100%; display: block; + color: #00FF00; /* Green for user info */ } +/* Game container with pixelated border */ .game-container { margin-top: 20px; - background: rgba(255,255,255,0.08); - backdrop-filter: blur(12px); + background: #111; /* Dark background */ padding: 25px; - border-radius: 20px; - border: 1px solid rgba(255,255,255,0.25); - box-shadow: 0px 10px 25px rgba(0,0,0,0.25); + border: 4px solid #FFF; /* Thick white border for pixel */ + border-radius: 0; /* Square corners */ + box-shadow: 8px 8px 0px #000; /* Heavy shadow for 3D pixel effect */ text-align: center; width: 95%; max-width: 520px; } canvas { - border-radius: 12px; - border: 3px solid rgba(255,255,255,0.8); - background: #111; - box-shadow: 0px 8px 25px rgba(0,0,0,0.6); + border: 4px solid #FFF; /* Pixel border */ + border-radius: 0; /* Square */ + background: #000; /* Black canvas background */ + box-shadow: 4px 4px 0px #000; /* Pixel shadow */ + image-rendering: pixelated; /* Ensure canvas renders pixelated */ + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; } #score { font-size: 1.6rem; font-weight: bold; margin: 10px 0; + color: #FFFF00; /* Yellow score */ } +/* Controls with pixel buttons */ .controls { display: flex; justify-content: center; @@ -94,29 +112,34 @@ canvas { select { padding: 8px 12px; - border-radius: 8px; - border: none; + border: 2px solid #FFF; + border-radius: 0; font-size: 1rem; outline: none; + background: #333; + color: #FFF; + font-family: 'Press Start 2P', monospace; } button { padding: 10px 22px; font-size: 1rem; - border: none; - border-radius: 25px; - color: white; - background: linear-gradient(45deg, #ff6b6b, #ff4757); - box-shadow: 0px 5px 15px rgba(255,76,76,0.4); - transition: 0.25s ease; + border: 2px solid #FFF; + border-radius: 0; + color: #FFF; + background: #FF0000; /* Red button */ + box-shadow: 4px 4px 0px #000; + transition: none; cursor: pointer; + font-family: 'Press Start 2P', monospace; } button:hover { - transform: scale(1.05); - box-shadow: 0px 7px 18px rgba(255,76,76,0.55); + background: #CC0000; /* Darker red */ + box-shadow: 2px 2px 0px #000; } +/* Leaderboard body */ .leaderboard-body { padding: 20px; text-align: center; @@ -137,12 +160,11 @@ button:hover { .lb-column { flex: 1; min-width: 300px; - background: rgba(255,255,255,0.1); + background: #222; /* Dark gray */ padding: 20px; - border-radius: 18px; - backdrop-filter: blur(12px); - border: 1px solid rgba(255,255,255,0.25); - box-shadow: 0px 10px 25px rgba(0,0,0,0.25); + border: 4px solid #FFF; + border-radius: 0; + box-shadow: 8px 8px 0px #000; } .lb-column h3 { @@ -151,115 +173,121 @@ button:hover { font-size: 1.5rem; letter-spacing: 2px; margin-bottom: 15px; - text-shadow: 2px 2px 8px rgba(0,0,0,0.3); + text-shadow: 2px 2px 0px #000; } -.easy-title { color: #33FF57; border-bottom: 2px solid #33FF57; display: inline-block; padding-bottom: 5px; } -.medium-title { color: #3357FF; border-bottom: 2px solid #3357FF; display: inline-block; padding-bottom: 5px; } -.hard-title { color: #FF33F5; border-bottom: 2px solid #FF33F5; display: inline-block; padding-bottom: 5px; } +.easy-title { color: #00FF00; border-bottom: 4px solid #00FF00; display: inline-block; padding-bottom: 5px; } +.medium-title { color: #0000FF; border-bottom: 4px solid #0000FF; display: inline-block; padding-bottom: 5px; } +.hard-title { color: #FF00FF; border-bottom: 4px solid #FF00FF; display: inline-block; padding-bottom: 5px; } table { width: 100%; border-collapse: collapse; - color: white; - border-radius: 12px; + color: #FFF; + border: 2px solid #FFF; + border-radius: 0; overflow: hidden; } th { - background: rgba(255,255,255,0.2); + background: #444; padding: 12px; font-weight: 600; letter-spacing: 1px; + border: 2px solid #FFF; } td { padding: 12px; - background: rgba(255,255,255,0.07); - border-bottom: 1px solid rgba(255,255,255,0.15); + background: #333; + border: 1px solid #FFF; } tr:nth-child(even) td { - background: rgba(255,255,255,0.12); + background: #555; } tr:hover td { - background: rgba(255,255,255,0.22); - transition: 0.25s; + background: #777; + transition: none; } .back-button { display: inline-block; margin: 25px auto; padding: 10px 22px; - background: linear-gradient(45deg, #28a745, #21c064); - border-radius: 25px; - color: white; + background: #00FF00; /* Green */ + border: 2px solid #FFF; + border-radius: 0; + color: #000; /* Black text for contrast */ font-weight: bold; text-decoration: none; font-size: 1rem; - box-shadow: 0px 5px 15px rgba(0,0,0,0.25); - transition: 0.25s ease; + box-shadow: 4px 4px 0px #000; + transition: none; } .back-button:hover { - transform: scale(1.05); - box-shadow: 0px 7px 18px rgba(0,0,0,0.45); + background: #00CC00; + box-shadow: 2px 2px 0px #000; } +/* Login body */ .login-body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; + background: #000; } .login-box { position: relative; width: 380px; - background: rgba(255, 255, 255, 0.15); - backdrop-filter: blur(12px); - border-radius: 15px; + background: #333; + border: 4px solid #FFF; + border-radius: 0; padding: 25px; - box-shadow: 0 8px 32px rgba(255, 255, 255, 0.86); - animation: popup 0.4s ease; -} - -@keyframes popup { - from { opacity: 0; transform: scale(0.85); } - to { opacity: 1; transform: scale(1); } + box-shadow: 8px 8px 0px #000; + animation: none; /* Remove animation for pixel feel */ } .login-box input, .login-box button { width: 100%; padding: 10px; margin-bottom: 15px; - border: none; - border-radius: 8px; + border: 2px solid #FFF; + border-radius: 0; box-sizing: border-box; font-size: 1rem; + background: #000; + color: #FFF; + font-family: 'Press Start 2P', monospace; } .login-box button { - background: linear-gradient(45deg, #ff6b6b, #ff4757); - color: white; + background: #FF0000; + color: #FFF; font-weight: bold; cursor: pointer; - transition: 0.3s ease; + transition: none; + box-shadow: 4px 4px 0px #000; } .login-box button:hover { - background: linear-gradient(45deg, #ff4757, #ff6b6b); + background: #CC0000; + box-shadow: 2px 2px 0px #000; } .login-box hr { margin: 25px 0; - border: 1px solid rgba(255, 255, 255, 0.3); + border: 2px solid #FFF; width: 90%; margin-left: 5%; } +/* Game modal with pixel style */ .game-modal { display: none; position: fixed; @@ -269,35 +297,35 @@ tr:hover td { width: 100%; height: 100%; overflow: auto; - background-color: rgba(0,0,0,0.5); + background-color: rgba(0,0,0,0.8); display: flex; justify-content: center; align-items: center; - backdrop-filter: blur(5px); } .modal-content { - background: rgba(255, 255, 255, 0.2); - backdrop-filter: blur(15px); + background: #222; padding: 40px; - border-radius: 20px; + border: 4px solid #FFF; + border-radius: 0; text-align: center; width: 90%; max-width: 350px; - box-shadow: 0 15px 40px rgba(0, 0, 0, 0.4); - animation: popup 0.4s ease-out; - border: 1px solid rgba(255, 255, 255, 0.4); + box-shadow: 8px 8px 0px #000; + animation: none; } .modal-content h2 { font-size: 2.5rem; margin-bottom: 10px; + color: #FFFF00; } .modal-content p { font-size: 1.5rem; font-weight: bold; margin-bottom: 25px; + color: #FFF; } .modal-buttons { @@ -311,28 +339,39 @@ tr:hover td { .modal-buttons button { padding: 10px 20px; font-size: 1rem; + border: 2px solid #FFF; + border-radius: 0; + box-shadow: 4px 4px 0px #000; + font-family: 'Press Start 2P', monospace; } #saveScoreBtn { - background: linear-gradient(45deg, #28a745, #21c064); - box-shadow: 0px 5px 15px rgba(33, 192, 100, 0.4); + background: #00FF00; + color: #000; + box-shadow: 4px 4px 0px #000; } .leaderboard-link, .back-link { display: block; margin-top: 10px; text-decoration: none; - color: #fff; + color: #FFF; font-size: 0.9rem; padding: 8px; - border-radius: 10px; - transition: 0.3s ease; + border: 2px solid #FFF; + border-radius: 0; + background: #333; + box-shadow: 4px 4px 0px #000; + transition: none; + font-family: 'Press Start 2P', monospace; } .leaderboard-link:hover, .back-link:hover { - background: rgba(255, 255, 255, 0.15); + background: #555; + box-shadow: 2px 2px 0px #000; } +/* Media queries for mobile */ @media (max-width: 600px) { h1 { font-size: 2.2rem; } canvas { width: 100%; height: auto; } diff --git a/win.wav b/win.wav index 4f1db12..650e6c9 100644 Binary files a/win.wav and b/win.wav differ