diff --git a/login.php b/login.php index 1b8c9c9..2d4b347 100644 --- a/login.php +++ b/login.php @@ -3,18 +3,23 @@ session_start(); $conn = new mysqli("localhost","root","","breakout_db"); if($conn->connect_error){ die("DB Error"); } -// Register if(isset($_POST['register'])){ $u = $_POST['user']; $p = password_hash($_POST['pass'], PASSWORD_DEFAULT); - $conn->query("INSERT INTO users(username,password,highscore) VALUES('$u','$p',0)"); + $u_safe = $conn->real_escape_string($u); + $conn->query("INSERT INTO users(username,password,highscore) VALUES('$u_safe','$p',0)"); + if($conn->affected_rows > 0) { + $_SESSION['user'] = $u; + header("Location: index.php"); + exit; + } } -// Login if(isset($_POST['login'])){ $u = $_POST['user']; $p = $_POST['pass']; - $res = $conn->query("SELECT * FROM users WHERE username='$u'"); + $u_safe = $conn->real_escape_string($u); + $res = $conn->query("SELECT * FROM users WHERE username='$u_safe'"); if($row = $res->fetch_assoc()){ if(password_verify($p, $row['password'])){ $_SESSION['user'] = $u; @@ -29,48 +34,14 @@ if(isset($_POST['login'])){ Login & Register - - - + - +
@@ -81,11 +52,7 @@ body { -
+

Register

@@ -94,12 +61,12 @@ body {
+ - Back - + Back +
- - + \ No newline at end of file diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..6d302a6 --- /dev/null +++ b/logout.php @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/script.js b/script.js index b2a3cc9..26a7ce3 100644 --- a/script.js +++ b/script.js @@ -10,69 +10,168 @@ let loseSound = new Audio("lose.wav"); let wallSound = new Audio("wall.wav"); let winSound = new Audio("win.wav"); +// Ambil elemen modal baru +const gameModal = document.getElementById("gameModal"); +const modalTitle = document.getElementById("modalTitle"); +const modalScore = document.getElementById("modalScore"); +const saveScoreBtn = document.getElementById("saveScoreBtn"); +const playAgainBtn = document.getElementById("playAgainBtn"); + +// Event Listener untuk tombol Play Again di modal +if (playAgainBtn) { + playAgainBtn.addEventListener('click', () => { + gameModal.style.display = 'none'; + startGame(); + }); +} + +// Event Listener untuk tombol Save Score di modal (jika user login) +if (saveScoreBtn) { + saveScoreBtn.addEventListener('click', () => { + saveScore(true); + }); +} + +function lockSpeed() { + let currentSpeedSq = ball.dx * ball.dx + ball.dy * ball.dy; + if (currentSpeedSq > 0.0001) { + let currentSpeed = Math.sqrt(currentSpeedSq); + let ratio = speed / currentSpeed; + ball.dx *= ratio; + ball.dy *= ratio; + } +} + function startGame(){ score = 0; document.getElementById("score").textContent = score; running = true; let diff = document.getElementById("diff").value; - if(diff === "easy"){ rows = 3; cols = 5; speed = 3; } - if(diff === "medium"){ rows = 4; cols = 7; speed = 4; } - if(diff === "hard"){ rows = 6; cols = 9; speed = 5; } + let initialHealth = 1; + + if(diff === "easy"){ + rows = 3; + cols = 5; + speed = 2; + initialHealth = 1; + } + if(diff === "medium"){ + rows = 4; + cols = 7; + speed = 4; + initialHealth = 3; + } + if(diff === "hard"){ + rows = 6; + cols = 9; + speed = 5; + initialHealth = 5; + } paddle = { x:200, w:80, h:10 }; - ball = { x:240, y:200, dx:speed, dy:-speed, r:6 }; + ball = { x:240, y:200, dx:speed, dy:-speed, r:6 }; bricks = []; + let brickW = (canvas.width - cols * 5) / cols; + let brickH = 15; + for(let r=0;r 480-ball.r || ball.y < ball.r){ - if(ball.x < ball.r || ball.x > 480-ball.r) ball.dx *= -1; - if (ball.y < ball.r) ball.dy *= -1; - - wallSound.currentTime = 0; + 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(); + + // Pantulan Dinding + if(ball.x + ball.r > canvas.width || ball.x - ball.r < 0){ + ball.dx *= -1; + lockSpeed(); + wallSound.currentTime = 0; wallSound.play(); } - - if(ball.y > 294 && ball.x > paddle.x && ball.x < paddle.x+paddle.w){ - let deltaX = ball.x - (paddle.x + paddle.w/2); - ball.dx = deltaX * 0.1; + if(ball.y - ball.r < 0){ ball.dy *= -1; - + lockSpeed(); wallSound.currentTime = 0; wallSound.play(); } + // Tabrakan Paddle + 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(); + } + + // Tabrakan Brick 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.hit = true; + + b.health--; ball.dy *= -1; - score += 10; + + lockSpeed(); + + if (b.health <= 0) { + b.hit = true; + score += 10; + winSound.currentTime = 0; + winSound.play(); + } else { + hitSound.currentTime = 0; + hitSound.play(); + // Perubahan warna berdasarkan sisa health + 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; - hitSound.currentTime = 0; - hitSound.play(); } }); @@ -85,34 +184,93 @@ function loop(){ } }); + // Cek Kemenangan - Menampilkan Modal if(bricks.every(b => b.hit)){ winSound.currentTime = 0; winSound.play(); running = false; - alert("YOU WIN! Total Score: " + score); - saveScore(); - return; + showModal("YOU WIN!", score); } - if(ball.y > 330){ + // Cek Game Over - Menampilkan Modal + if(ball.y > canvas.height){ loseSound.currentTime = 0; loseSound.play(); running = false; - saveScore(); - alert("Game Over"); - return; + showModal("GAME OVER!", score); } - - requestAnimationFrame(loop); } -window.addEventListener("mousemove", e =>{ - let newX = e.clientX - canvas.offsetLeft - paddle.w/2; +// Pengendalian Paddle +canvas.addEventListener("mousemove", e =>{ + if (!paddle || !canvas) return; + + const canvasRect = canvas.getBoundingClientRect(); + let newX = e.clientX - canvasRect.left - paddle.w/2; + if(newX < 0) newX = 0; if(newX > canvas.width - paddle.w) newX = canvas.width - paddle.w; + paddle.x = newX; }); -function saveScore(){ - fetch("save.php?score="+score); +// Fungsi untuk menampilkan modal +function showModal(title, finalScore) { + modalTitle.textContent = title; + modalScore.innerHTML = `Final Score: ${finalScore}`; + gameModal.style.display = 'flex'; + + // Reset tombol save score + if (saveScoreBtn) { + saveScoreBtn.disabled = false; + saveScoreBtn.textContent = 'Save Score'; + saveScoreBtn.style.background = 'linear-gradient(45deg, #28a745, #21c064)'; + saveScoreBtn.style.opacity = '1'; + } +} + +function saveScore(fromModal = false){ + let isUserLoggedIn = document.querySelector('.user-info'); + + if (isUserLoggedIn) { + if (fromModal) { + + if (saveScoreBtn) { + saveScoreBtn.disabled = true; + saveScoreBtn.textContent = 'Saving...'; + saveScoreBtn.style.opacity = '0.7'; + } + + fetch(`save.php?score=${score}`) + .then(response => { + if (response.ok) { + console.log("Score saved successfully!"); + if (saveScoreBtn) { + saveScoreBtn.textContent = 'Saved! ✅'; + saveScoreBtn.style.background = 'gray'; + saveScoreBtn.style.opacity = '1'; + } + } else { + console.error("Failed to save score."); + if (saveScoreBtn) { + saveScoreBtn.textContent = 'Failed ❌'; + saveScoreBtn.disabled = false; + saveScoreBtn.style.opacity = '1'; + } + } + }) + .catch(error => { + console.error("Error saving score:", error); + if (saveScoreBtn) { + saveScoreBtn.textContent = 'Error ❌'; + saveScoreBtn.disabled = false; + saveScoreBtn.style.opacity = '1'; + } + }); + } else { + console.log("Score ready to be saved via modal."); + } + } else { + console.log("Score not saved. User is not logged in."); + } } \ No newline at end of file diff --git a/style.css b/style.css index 7e8f4d4..9fc0bad 100644 --- a/style.css +++ b/style.css @@ -1,69 +1,319 @@ -body{ - text-align:center; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background: linear-gradient(to bottom, #4a00e0, #8e2de2); - color: white; +body { + font-family: 'Poppins', sans-serif; margin: 0; - padding-top: 20px; + padding: 0; + background: radial-gradient(circle at top, #6a85f1, #4834d4); + color: white; + display: flex; + flex-direction: column; + align-items: center; + min-height: 100vh; } + h1 { - color: #ffcc00; - text-shadow: 2px 2px 4px #000000; + font-size: 2.7rem; + margin-top: 25px; + text-shadow: 3px 3px 10px rgba(0,0,0,0.4); + letter-spacing: 1px; } -.menu a{ - margin: 15px; - padding: 8px 15px; - color: #ffcc00; + +.menu { + margin-top: 10px; + display: flex; + gap: 15px; + flex-wrap: wrap; +} + +.menu a { text-decoration: none; - border: 1px solid #ffcc00; - border-radius: 5px; - transition: background 0.3s; + 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; + display: flex; + align-items: center; + gap: 7px; } + .menu a:hover { - background: #ffcc00; - color: #4a00e0; + transform: translateY(-3px); + background: rgba(255,255,255,0.35); } -canvas{ - background:#000000; - display:block; - margin:auto; - border:5px solid #ffcc00; - box-shadow: 0 0 20px rgba(255, 204, 0, 0.5); + +.user-info { + font-size: 1.1rem; + margin-bottom: 5px; + text-align: center; } -p { - margin-top: 15px; - font-size: 1.2em; + +.game-container { + margin-top: 20px; + background: rgba(255,255,255,0.08); + backdrop-filter: blur(12px); + 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); + 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); +} + #score { + font-size: 1.6rem; font-weight: bold; - color: #00ff7f; + margin: 10px 0; } -select, button { - padding: 8px 15px; + +.controls { + display: flex; + justify-content: center; + gap: 15px; + margin-top: 15px; + flex-wrap: wrap; +} + +select { + padding: 8px 12px; + border-radius: 8px; border: none; - border-radius: 5px; - font-size: 1em; - cursor: pointer; - transition: background 0.3s; + font-size: 1rem; + outline: none; } + button { - background: #00ff7f; - color: black; - margin-left: 10px; + 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; + cursor: pointer; } + button:hover { - background: #00e676; + transform: scale(1.05); + box-shadow: 0px 7px 18px rgba(255,76,76,0.55); } -.login-box, table{ - background: rgba(255, 255, 255, 0.9); - color: black; + +@media (max-width: 600px) { + h1 { font-size: 2.2rem; } + canvas { width: 100%; height: auto; } } -.login-box input, .login-box button{ - color: black; + +.leaderboard-body { + padding: 20px; + text-align: center; + min-height: auto; + display: block; } -table{ - border: 1px solid #ccc; + +h2 { + font-size: 2.5rem; + margin-top: 20px; + text-shadow: 3px 3px 10px rgba(0,0,0,0.4); } -td{ - padding:5px 20px; + +.board-container { + margin: 25px auto; + width: 95%; + max-width: 650px; + background: rgba(255,255,255,0.10); + padding: 25px; + 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); +} + +table { + width: 100%; + border-collapse: collapse; + color: white; + border-radius: 12px; + overflow: hidden; +} + +th { + background: rgba(255,255,255,0.2); + padding: 12px; + font-weight: 600; + letter-spacing: 1px; +} + +td { + padding: 12px; + background: rgba(255,255,255,0.07); + border-bottom: 1px solid rgba(255,255,255,0.15); +} + +tr:nth-child(even) td { + background: rgba(255,255,255,0.12); +} + +tr:hover td { + background: rgba(255,255,255,0.22); + transition: 0.25s; +} + +.back-button { + display: inline-block; + margin-top: 25px; + padding: 10px 22px; + background: linear-gradient(45deg, #28a745, #21c064); + border-radius: 25px; + color: white; + font-weight: bold; + text-decoration: none; + font-size: 1rem; + box-shadow: 0px 5px 15px rgba(0,0,0,0.25); + transition: 0.25s ease; +} + +.back-button:hover { + transform: scale(1.05); + box-shadow: 0px 7px 18px rgba(0,0,0,0.45); +} + +.login-body { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; +} + +.login-box { + position: relative; + width: 380px; + background: rgba(255, 255, 255, 0.15); + backdrop-filter: blur(12px); + border-radius: 15px; + 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); + } +} + +.login-box input, .login-box button { + width: 100%; + padding: 10px; + margin-bottom: 15px; + border: none; + border-radius: 8px; + box-sizing: border-box; + font-size: 1rem; +} + +.login-box button { + background: linear-gradient(45deg, #ff6b6b, #ff4757); + color: white; + font-weight: bold; + cursor: pointer; + transition: 0.3s ease; +} + +.login-box button:hover { + background: linear-gradient(45deg, #ff4757, #ff6b6b); +} + +.login-box hr { + margin: 25px 0; + border: 1px solid rgba(255, 255, 255, 0.3); + width: 90%; + margin-left: 5%; +} +.game-modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0,0,0,0.5); + 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); + padding: 40px; + border-radius: 20px; + 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); +} + +.modal-content h2 { + font-size: 2.5rem; + margin-bottom: 10px; +} + +.modal-content p { + font-size: 1.5rem; + font-weight: bold; + margin-bottom: 25px; +} + +.modal-buttons { + display: flex; + justify-content: center; + gap: 15px; + margin-bottom: 25px; + flex-wrap: wrap; +} + +.modal-buttons button { + padding: 10px 20px; + font-size: 1rem; +} + +#saveScoreBtn { + background: linear-gradient(45deg, #28a745, #21c064); + box-shadow: 0px 5px 15px rgba(33, 192, 100, 0.4); +} + +.leaderboard-link, .back-link { + display: block; + margin-top: 10px; + text-decoration: none; + color: #fff; + font-size: 0.9rem; + padding: 8px; + border-radius: 10px; + transition: 0.3s ease; +} + +.leaderboard-link:hover, .back-link:hover { + background: rgba(255, 255, 255, 0.15); } \ No newline at end of file