Merge branch 'main' of https://git-eng.ukwms.ac.id/2526-web/kelompok06-2048
This commit is contained in:
commit
cd0dccc729
8
2048.css
8
2048.css
@ -1047,14 +1047,14 @@ h1 {
|
||||
}
|
||||
}
|
||||
|
||||
/* Best Score Display - ORANGE gradient */
|
||||
.best-score-display {
|
||||
/* High Score Display - ORANGE gradient */
|
||||
.high-score-display {
|
||||
margin-top: 28px;
|
||||
padding-top: 28px;
|
||||
border-top: 2px solid rgba(255, 140, 0, 0.25);
|
||||
}
|
||||
|
||||
.best-score-label {
|
||||
.high-score-label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 160, 50, 0.7);
|
||||
@ -1063,7 +1063,7 @@ h1 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.best-score-value {
|
||||
.high-score-value {
|
||||
font-size: clamp(34px, 5vw, 42px);
|
||||
font-weight: 900;
|
||||
background: linear-gradient(135deg, #ff8c00 0%, #ffa500 50%, #ffb347 100%);
|
||||
|
||||
12
2048.html
12
2048.html
@ -210,7 +210,7 @@
|
||||
</div>
|
||||
<div class="score-box">
|
||||
<div class="score-label">HIGH SCORE</div>
|
||||
<div class="score-value" id="best-score">0</div>
|
||||
<div class="score-value" id="high-score">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -325,14 +325,14 @@
|
||||
New High Score
|
||||
</div>
|
||||
|
||||
<!-- Best Score Display - show if NOT new record -->
|
||||
<!-- high Score Display - show if NOT new record -->
|
||||
<div
|
||||
class="best-score-display"
|
||||
id="best-score-display"
|
||||
class="high-score-display"
|
||||
id="high-score-display"
|
||||
style="display: none"
|
||||
>
|
||||
<div class="best-score-label">High Score</div>
|
||||
<div class="best-score-value" id="modal-best-score">0</div>
|
||||
<div class="high-score-label">High Score</div>
|
||||
<div class="high-score-value" id="modal-high-score">0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
46
2048.js
46
2048.js
@ -1,11 +1,18 @@
|
||||
|
||||
|
||||
/* ------------------------
|
||||
State & Variables
|
||||
------------------------ */
|
||||
let board = [];
|
||||
let currentScore = 0;
|
||||
let bestScore = parseInt(localStorage.getItem('bestScore2048')) || 0;
|
||||
|
||||
// 1. Ambil username dari sessionStorage (sesuai sistem login kamu)
|
||||
const currentUser = sessionStorage.getItem("loggedInUser") || "guest";
|
||||
|
||||
// 2. Buat nama kunci unik, misal: "highScore2048_budi"
|
||||
const storageKey = 'highScore2048_' + currentUser;
|
||||
|
||||
// 3. Ambil skor milik user tersebut saja
|
||||
|
||||
let highScore = parseInt(localStorage.getItem(storageKey)) || 0;
|
||||
let lastMoveDir = null;
|
||||
let isMoving = false;
|
||||
let mergesInCurrentMove = 0;
|
||||
@ -71,7 +78,7 @@ function checkAndShowTutorial() {
|
||||
DOM Ready
|
||||
------------------------ */
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
updateBestScoreDisplay();
|
||||
updateHighScoreDisplay();
|
||||
setupBoard();
|
||||
addNewTile();
|
||||
addNewTile();
|
||||
@ -223,17 +230,18 @@ function updateScoreDisplay() {
|
||||
scoreEl.textContent = currentScore;
|
||||
}
|
||||
|
||||
if (currentScore > bestScore) {
|
||||
bestScore = currentScore;
|
||||
localStorage.setItem('bestScore2048', bestScore);
|
||||
updateBestScoreDisplay();
|
||||
}
|
||||
if (currentScore > highScore) {
|
||||
highScore = currentScore;
|
||||
// Gunakan storageKey yang sudah kita buat di atas (dinamis sesuai user)
|
||||
localStorage.setItem(storageKey, highScore);
|
||||
updateHighScoreDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
function updateBestScoreDisplay() {
|
||||
const bestScoreEl = document.getElementById('best-score');
|
||||
if (bestScoreEl) {
|
||||
bestScoreEl.textContent = bestScore;
|
||||
function updateHighScoreDisplay() {
|
||||
const highScoreEl = document.getElementById('high-score');
|
||||
if (highScoreEl) {
|
||||
highScoreEl.textContent = highScore;
|
||||
}
|
||||
}
|
||||
|
||||
@ -601,7 +609,7 @@ function showGameOver() {
|
||||
console.error("Fungsi saveScore tidak ditemukan! Pastikan Score_Request.js sudah diload.");
|
||||
}
|
||||
|
||||
const isNewHighScore = finalScore >= bestScore && finalScore > 0;
|
||||
const isNewHighScore = finalScore >= highScore && finalScore > 0;
|
||||
|
||||
const finalScoreEl = document.getElementById('final-score');
|
||||
if (finalScoreEl) {
|
||||
@ -609,16 +617,16 @@ function showGameOver() {
|
||||
}
|
||||
|
||||
const newHighScoreBadge = document.getElementById('new-high-score-badge');
|
||||
const bestScoreDisplay = document.getElementById('best-score-display');
|
||||
const highScoreDisplay = document.getElementById('high-score-display');
|
||||
|
||||
if (isNewHighScore) {
|
||||
if (newHighScoreBadge) newHighScoreBadge.style.display = 'inline-block';
|
||||
if (bestScoreDisplay) bestScoreDisplay.style.display = 'none';
|
||||
if (highScoreDisplay) highScoreDisplay.style.display = 'none';
|
||||
} else {
|
||||
if (newHighScoreBadge) newHighScoreBadge.style.display = 'none';
|
||||
if (bestScoreDisplay) bestScoreDisplay.style.display = 'block';
|
||||
const modalBestScore = document.getElementById('modal-best-score');
|
||||
if (modalBestScore) modalBestScore.textContent = bestScore;
|
||||
if (highScoreDisplay) highScoreDisplay.style.display = 'block';
|
||||
const modalHighScore = document.getElementById('modal-high-score');
|
||||
if (modalHighScore) modalHighScore.textContent = highScore;
|
||||
}
|
||||
|
||||
const gameOverOverlay = document.getElementById('game-over-overlay');
|
||||
|
||||
233
Leaderboard.css
233
Leaderboard.css
@ -198,7 +198,9 @@ h1::before {
|
||||
box-shadow: 0 0 15px rgba(0, 234, 255, 0.8);
|
||||
}
|
||||
|
||||
/* Leaderboard Item */
|
||||
/* --- LEADERBOARD ITEMS & COLORS (HARMONISASI) --- */
|
||||
|
||||
/* Base Item Style */
|
||||
.leaderboard-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -206,7 +208,7 @@ h1::before {
|
||||
padding: 18px 25px;
|
||||
border-radius: 14px;
|
||||
background: rgba(30, 0, 50, 0.5);
|
||||
border: 1px solid rgba(0, 255, 255, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
@ -217,57 +219,105 @@ h1::before {
|
||||
box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Rank Styles */
|
||||
.leaderboard-item.rank-1 {
|
||||
background: linear-gradient(135deg, rgba(0, 234, 255, 0.25), rgba(0, 255, 136, 0.2));
|
||||
border: 2px solid rgba(0, 234, 255, 0.6);
|
||||
box-shadow:
|
||||
0 0 25px rgba(0, 234, 255, 0.4),
|
||||
inset 0 0 25px rgba(0, 234, 255, 0.15);
|
||||
}
|
||||
|
||||
.leaderboard-item.rank-2 {
|
||||
background: linear-gradient(135deg, rgba(255, 0, 255, 0.2), rgba(204, 0, 255, 0.15));
|
||||
border: 2px solid rgba(255, 0, 255, 0.5);
|
||||
box-shadow: 0 0 18px rgba(255, 0, 255, 0.3);
|
||||
}
|
||||
|
||||
.leaderboard-item.rank-3 {
|
||||
background: linear-gradient(135deg, rgba(138, 43, 226, 0.25), rgba(75, 0, 130, 0.2));
|
||||
border: 2px solid rgba(138, 43, 226, 0.5);
|
||||
box-shadow: 0 0 15px rgba(138, 43, 226, 0.3);
|
||||
}
|
||||
|
||||
/* Rank Badge */
|
||||
.rank-badge {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 17px;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
/* === RANK 1: CYAN NEON (Juara) === */
|
||||
.leaderboard-item.rank-1,
|
||||
.your-rank-container.rank-1 .your-rank-item {
|
||||
background: linear-gradient(90deg, rgba(0, 234, 255, 0.25) 0%, rgba(0, 234, 255, 0.05) 100%);
|
||||
border: 2px solid #00eaff;
|
||||
box-shadow: 0 0 15px rgba(0, 234, 255, 0.3);
|
||||
}
|
||||
|
||||
.rank-1 .rank-badge {
|
||||
background: linear-gradient(135deg, #00eaff, #00ff88);
|
||||
color: #0c001a;
|
||||
box-shadow: 0 0 20px rgba(0, 234, 255, 0.8);
|
||||
background: #00eaff;
|
||||
color: #000;
|
||||
box-shadow: 0 0 15px #00eaff;
|
||||
animation: pulseBadge 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.rank-2 .rank-badge {
|
||||
background: linear-gradient(135deg, #ff00ff, #cc00ff);
|
||||
.rank-1 .player-name {
|
||||
/* 1. Matikan efek gradient aneh pada teks */
|
||||
background: none;
|
||||
-webkit-text-fill-color: initial;
|
||||
background-clip: border-box;
|
||||
|
||||
/* 2. Samakan ukuran font dengan yang lain (sebelumnya 22px jadi 20px) */
|
||||
font-size: 20px;
|
||||
|
||||
/* 3. Warna Putih Solid + Sedikit Glow Cyan biar tetap spesial tapi rapi */
|
||||
color: #ffffff;
|
||||
box-shadow: 0 0 18px rgba(255, 0, 255, 0.7);
|
||||
text-shadow: 0 0 10px rgba(0, 234, 255, 0.8);
|
||||
|
||||
/* Hapus filter drop-shadow berlebih */
|
||||
filter: none;
|
||||
}
|
||||
|
||||
/* FIX: Warna Score Rank 1 jadi Putih Solid (Anti Silau) */
|
||||
.rank-1 .score-value {
|
||||
/* 1. Ukuran disamakan dengan yang lain */
|
||||
font-size: 22px !important;
|
||||
|
||||
/* 2. Warna Teks Putih Solid */
|
||||
background: none !important;
|
||||
-webkit-text-fill-color: initial !important;
|
||||
color: #ffffff !important;
|
||||
|
||||
/* 3. GLOWING: Ganti bayangan hitam jadi cahaya Cyan */
|
||||
text-shadow: 0 0 10px #00eaff, 0 0 20px rgba(0, 234, 255, 0.4) !important;
|
||||
|
||||
/* Reset filter */
|
||||
filter: none !important;
|
||||
}
|
||||
|
||||
/* === RANK 2: MAGENTA NEON (Runner Up) === */
|
||||
.leaderboard-item.rank-2,
|
||||
.your-rank-container.rank-2 .your-rank-item {
|
||||
background: linear-gradient(90deg, rgba(255, 0, 255, 0.25) 0%, rgba(255, 0, 255, 0.05) 100%);
|
||||
border: 2px solid #ff00ff;
|
||||
box-shadow: 0 0 15px rgba(255, 0, 255, 0.3);
|
||||
}
|
||||
|
||||
.rank-2 .rank-badge {
|
||||
background: #ff00ff;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 15px #ff00ff;
|
||||
}
|
||||
|
||||
.rank-2 .score-value {
|
||||
color: #fff;
|
||||
text-shadow: 0 0 10px #ff00ff;
|
||||
}
|
||||
|
||||
.rank-2 .player-score {
|
||||
color: #ff00ff;
|
||||
}
|
||||
|
||||
/* === RANK 3: VIOLET NEON (Third) === */
|
||||
.leaderboard-item.rank-3,
|
||||
.your-rank-container.rank-3 .your-rank-item {
|
||||
background: linear-gradient(90deg, rgba(138, 43, 226, 0.25) 0%, rgba(138, 43, 226, 0.05) 100%);
|
||||
border: 2px solid #8a2be2;
|
||||
box-shadow: 0 0 15px rgba(138, 43, 226, 0.3);
|
||||
}
|
||||
|
||||
.rank-3 .rank-badge {
|
||||
background: linear-gradient(135deg, #8a2be2, #4b0082);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 0 15px rgba(138, 43, 226, 0.6);
|
||||
background: #8a2be2;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 15px #8a2be2;
|
||||
}
|
||||
|
||||
.rank-3 .score-value {
|
||||
color: #fff;
|
||||
text-shadow: 0 0 10px #8a2be2;
|
||||
}
|
||||
|
||||
.rank-3 .player-score {
|
||||
color: #a855f7;
|
||||
}
|
||||
|
||||
/* === RANK OTHER === */
|
||||
.leaderboard-item.rank-other {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.rank-other .rank-badge {
|
||||
@ -276,6 +326,7 @@ h1::before {
|
||||
border: 1px solid rgba(0, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
@keyframes pulseBadge {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
@ -287,7 +338,7 @@ h1::before {
|
||||
}
|
||||
}
|
||||
|
||||
/* Player Info */
|
||||
/* Player Info Common */
|
||||
.player-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@ -305,22 +356,11 @@ h1::before {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.rank-1 .player-name {
|
||||
background: linear-gradient(90deg, #00eaff, #00ff88);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-shadow: none;
|
||||
filter: drop-shadow(0 0 6px rgba(0, 234, 255, 0.5));
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
/* Score */
|
||||
/* Score Common */
|
||||
.player-score {
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
color: #00eaff;
|
||||
text-shadow: 0 0 12px rgba(0, 234, 255, 0.7);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
@ -332,25 +372,6 @@ h1::before {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.rank-1 .score-value {
|
||||
font-size: 25px;
|
||||
background: linear-gradient(90deg, #00eaff, #ff00ff);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
filter: drop-shadow(0 0 10px rgba(0, 234, 255, 0.8));
|
||||
}
|
||||
|
||||
.rank-2 .player-score {
|
||||
color: #ff00ff;
|
||||
text-shadow: 0 0 12px rgba(255, 0, 255, 0.7);
|
||||
}
|
||||
|
||||
.rank-3 .player-score {
|
||||
color: #a855f7;
|
||||
text-shadow: 0 0 12px rgba(138, 43, 226, 0.6);
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
@ -358,15 +379,26 @@ h1::before {
|
||||
color: rgba(200, 200, 255, 0.5);
|
||||
}
|
||||
|
||||
/* Your Rank Fixed Section */
|
||||
/* Badge Size Common */
|
||||
.rank-badge {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 17px;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Your Rank Section Structure */
|
||||
.your-rank-container {
|
||||
background: rgba(20, 0, 40, 0.9);
|
||||
border-radius: 14px;
|
||||
padding: 15px 25px;
|
||||
border: 2px solid rgba(0, 255, 136, 0.6);
|
||||
box-shadow:
|
||||
0 0 25px rgba(0, 255, 136, 0.4),
|
||||
inset 0 0 20px rgba(0, 255, 136, 0.15);
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.your-rank-item {
|
||||
@ -375,15 +407,34 @@ h1::before {
|
||||
gap: 20px;
|
||||
padding: 18px 25px;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(135deg, rgba(0, 255, 136, 0.15), rgba(0, 234, 255, 0.1));
|
||||
/* Default style if not top 3 */
|
||||
background: linear-gradient(135deg, rgba(0, 255, 136, 0.15), rgba(0, 0, 0, 0.5));
|
||||
border: 2px solid rgba(0, 255, 136, 0.5);
|
||||
box-shadow: 0 0 20px rgba(0, 255, 136, 0.3);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
/* --- RESPONSIVE FIX (MODE LAPTOP/LAYAR KECIL) --- */
|
||||
|
||||
@media screen and (max-width: 1440px) {
|
||||
/* Mencegah container nabrak tombol back */
|
||||
.container {
|
||||
width: 88%;
|
||||
padding: 30px 50px;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
.btn-back {
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 35px 30px;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@ -394,18 +445,6 @@ h1::before {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-back {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.btn-back svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
|
||||
@ -21,6 +21,8 @@
|
||||
|
||||
<ul class="leaderboard-list" id="leaderboardList"></ul>
|
||||
|
||||
<div id="userRankContainer"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="Leaderboard.js"></script>
|
||||
|
||||
@ -7,12 +7,53 @@ function loadLeaderboard() {
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === "success") {
|
||||
// Render Top 10
|
||||
renderLeaderboard(data.leaderboard);
|
||||
|
||||
// Render Ranking Saya (User Rank)
|
||||
if (data.user_rank) {
|
||||
renderUserRank(data.user_rank);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => console.error("Error loading leaderboard:", error));
|
||||
}
|
||||
|
||||
// ... fungsi renderLeaderboard tetap sama ...
|
||||
|
||||
// TAMBAHKAN FUNGSI INI DI BAWAH
|
||||
function renderUserRank(user) {
|
||||
const container = document.getElementById('userRankContainer');
|
||||
if (!container) return;
|
||||
|
||||
// Format angka skor
|
||||
const formattedScore = new Intl.NumberFormat().format(user.score);
|
||||
|
||||
// HTML structure sesuai CSS .your-rank-container
|
||||
const html = `
|
||||
<div class="your-rank-container">
|
||||
<div style="color: #00ff88; margin-bottom: 10px; font-weight: bold; text-transform: uppercase; letter-spacing: 2px;">
|
||||
</div>
|
||||
<div class="your-rank-item">
|
||||
<div class="rank-badge" style="background: #00ff88; color: #000; box-shadow: 0 0 15px #00ff88;">
|
||||
${user.rank}
|
||||
</div>
|
||||
<div class="player-info">
|
||||
<div class="player-name">${escapeHtml(user.username)}</div>
|
||||
</div>
|
||||
<div class="player-score">
|
||||
<div class="score-value">${formattedScore}</div>
|
||||
<div class="score-label">Points</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
// ... fungsi escapeHtml tetap sama ...
|
||||
|
||||
function renderLeaderboard(players) {
|
||||
const listContainer = document.getElementById('leaderboardList');
|
||||
if (!listContainer) return; // Safety check
|
||||
|
||||
@ -1,23 +1,76 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require 'Connection.php';
|
||||
session_start();
|
||||
|
||||
$query = "SELECT username, score FROM leaderboard ORDER BY score DESC LIMIT 10";
|
||||
$response = [
|
||||
"status" => "error",
|
||||
"leaderboard" => [],
|
||||
"user_rank" => null
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 1. Ambil Top 10 Global (DIPERBAIKI)
|
||||
// Ditambahkan "user_id ASC" agar urutannya PASTI (Konsisten)
|
||||
// Jika skor sama, user dengan ID lebih kecil (daftar duluan) akan di atas
|
||||
// ---------------------------------------------------------
|
||||
$query = "SELECT username, score FROM leaderboard ORDER BY score DESC, user_id ASC LIMIT 10";
|
||||
$result = $conn->query($query);
|
||||
|
||||
$leaderboard = [];
|
||||
|
||||
if ($result && $result->num_rows > 0) {
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$leaderboard[] = $row;
|
||||
$response['leaderboard'][] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
// Kirim data ke frontend dalam bentuk JSON
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"leaderboard" => $leaderboard
|
||||
]);
|
||||
// ---------------------------------------------------------
|
||||
// 2. Ambil Ranking User Login (DIPERBAIKI)
|
||||
// ---------------------------------------------------------
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
$my_id = $_SESSION['user_id'];
|
||||
|
||||
// Ambil score user saat ini
|
||||
$scoreQuery = $conn->prepare("SELECT username, score FROM leaderboard WHERE user_id = ?");
|
||||
$scoreQuery->bind_param("i", $my_id);
|
||||
$scoreQuery->execute();
|
||||
$scoreResult = $scoreQuery->get_result();
|
||||
|
||||
if ($scoreRow = $scoreResult->fetch_assoc()) {
|
||||
$myScore = $scoreRow['score'];
|
||||
$myUsername = $scoreRow['username'];
|
||||
|
||||
// --- LOGIKA BARU ---
|
||||
// Hitung ranking dengan Tie-Breaker.
|
||||
// Rank = Jumlah orang yang skornya LEBIH BESAR
|
||||
// DITAMBAH Jumlah orang yang skornya SAMA tapi ID-nya LEBIH KECIL (dia di atas kita)
|
||||
|
||||
$rankQuery = $conn->prepare("
|
||||
SELECT COUNT(*) as rank_above
|
||||
FROM leaderboard
|
||||
WHERE score > ?
|
||||
OR (score = ? AND user_id < ?)
|
||||
");
|
||||
|
||||
// Kita bind 3 parameter: score, score, id
|
||||
$rankQuery->bind_param("iii", $myScore, $myScore, $my_id);
|
||||
|
||||
$rankQuery->execute();
|
||||
$rankResult = $rankQuery->get_result();
|
||||
$rankRow = $rankResult->fetch_assoc();
|
||||
|
||||
// Rank adalah jumlah orang di atas kita + 1
|
||||
$myRank = $rankRow['rank_above'] + 1;
|
||||
|
||||
$response['user_rank'] = [
|
||||
"username" => $myUsername,
|
||||
"score" => $myScore,
|
||||
"rank" => $myRank
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$response['status'] = "success";
|
||||
echo json_encode($response);
|
||||
|
||||
$conn->close();
|
||||
?>
|
||||
18
Login.php
18
Login.php
@ -1,12 +1,11 @@
|
||||
<?php
|
||||
// ✅ CORS Headers HARUS di paling atas sebelum apapun
|
||||
// ... (Header CORS tetap sama) ...
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||
header('Access-Control-Max-Age: 86400');
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// ✅ Handle preflight OPTIONS request
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit();
|
||||
@ -15,17 +14,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
session_start();
|
||||
include 'Connection.php';
|
||||
|
||||
// Ambil data dari JSON body
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$username = $input['username'] ?? '';
|
||||
$password = $input['password'] ?? '';
|
||||
|
||||
if (empty($username) || empty($password)) {
|
||||
echo json_encode(["success" => false, "message" => "Username dan password wajib diisi"]);
|
||||
exit;
|
||||
}
|
||||
// ... (Validasi input kosong tetap sama) ...
|
||||
|
||||
$stmt = $conn->prepare("SELECT password FROM users WHERE username = ?");
|
||||
// 🔴 PERBAIKAN 1: Tambahkan 'id' di dalam SELECT
|
||||
$stmt = $conn->prepare("SELECT id, password FROM users WHERE username = ?");
|
||||
$stmt->bind_param("s", $username);
|
||||
$stmt->execute();
|
||||
$stmt->store_result();
|
||||
@ -37,11 +33,15 @@ if ($stmt->num_rows === 0) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt->bind_result($hashedPassword);
|
||||
// 🔴 PERBAIKAN 2: Bind result untuk menangkap 'id' dan 'password'
|
||||
$stmt->bind_result($userId, $hashedPassword);
|
||||
$stmt->fetch();
|
||||
|
||||
if (password_verify($password, $hashedPassword)) {
|
||||
// 🔴 PERBAIKAN 3: Simpan 'user_id' ke dalam SESSION
|
||||
$_SESSION['user_id'] = $userId;
|
||||
$_SESSION['username'] = $username;
|
||||
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"message" => "Login berhasil",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="Score_Request.js"></script>
|
||||
<script type="module" src="Register.js"></script>
|
||||
<script src="Animation_Register.js"></script>
|
||||
</body>
|
||||
|
||||
27
Register.js
27
Register.js
@ -28,17 +28,34 @@ document.getElementById("registerForm").addEventListener("submit", async functio
|
||||
|
||||
// Button loading state
|
||||
const submitBtn = this.querySelector("button[type='submit']");
|
||||
const originalText = submitBtn.textContent;
|
||||
|
||||
submitBtn.disabled = true;
|
||||
|
||||
try {
|
||||
const data = await registerRequest(username, password);
|
||||
|
||||
// API temenmu return: { status: "success", message: "..." }
|
||||
if (data.status === "success") {
|
||||
// 1. Tampilkan modal sukses
|
||||
showModal("success", "Register Successful!", data.message);
|
||||
|
||||
// 🔥 PERBAIKAN: SAVE SCORE & REDIRECT 🔥
|
||||
|
||||
// Coba simpan skor jika variable 'score' atau 'gameScore' tersedia di global scope
|
||||
// Atau jika kamu menyimpan skor sementara di localStorage
|
||||
const pendingScore = localStorage.getItem('lastScore'); // Contoh jika pakai localStorage
|
||||
|
||||
if (pendingScore && typeof saveScore === "function") {
|
||||
console.log("Menyimpan skor pending: " + pendingScore);
|
||||
saveScore(pendingScore);
|
||||
} else if (typeof score !== 'undefined') {
|
||||
// Jika variabel global 'score' ada (dari file game logic)
|
||||
saveScore(score);
|
||||
}
|
||||
|
||||
// Redirect ke halaman utama setelah 1.5 detik
|
||||
setTimeout(() => {
|
||||
window.location.href = "index.html"; // Ubah sesuai halaman game/menu kamu
|
||||
}, 1500);
|
||||
|
||||
} else {
|
||||
showModal("error", "Register Failed!", data.message || "An error occurred.");
|
||||
setInputError("username");
|
||||
@ -48,11 +65,9 @@ document.getElementById("registerForm").addEventListener("submit", async functio
|
||||
console.error("Register Error:", error);
|
||||
|
||||
let msg = "A connection error occurred.";
|
||||
|
||||
if (error.message === "Failed to fetch") {
|
||||
msg = "Unable to connect to server.";
|
||||
}
|
||||
|
||||
showModal("error", "Error!", msg);
|
||||
|
||||
} finally {
|
||||
@ -68,8 +83,10 @@ document.getElementById("confirmPassword").addEventListener("input", clearInputS
|
||||
// Efek error merah neon
|
||||
function setInputError(id) {
|
||||
const el = document.getElementById(id);
|
||||
if(el) {
|
||||
el.style.borderColor = "#ff0080";
|
||||
el.style.boxShadow = "0 0 20px rgba(255, 0, 128, 0.3)";
|
||||
}
|
||||
}
|
||||
|
||||
// Hilangkan error visual
|
||||
|
||||
24
Register.php
24
Register.php
@ -2,12 +2,12 @@
|
||||
// ✅ Set timezone Indonesia (WIB)
|
||||
date_default_timezone_set('Asia/Jakarta');
|
||||
|
||||
// ✅ CORS Headers - di paling atas
|
||||
// ✅ CORS Headers
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||
header('Access-Control-Max-Age: 86400');
|
||||
header('Content-Type: application/json'); // Cukup 1x saja
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// ✅ Handle preflight OPTIONS
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
@ -17,7 +17,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
|
||||
include 'Connection.php';
|
||||
|
||||
// ✅ Handle input dari JSON body atau POST form
|
||||
// ✅ Handle input
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$username = trim($input['username'] ?? $_POST['username'] ?? '');
|
||||
$password = $input['password'] ?? $_POST['password'] ?? '';
|
||||
@ -31,7 +31,7 @@ if (empty($username) || empty($password)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// ✅ Validasi panjang password minimal
|
||||
// ✅ Validasi panjang password
|
||||
if (strlen($password) < 6) {
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
@ -68,15 +68,23 @@ $check->close();
|
||||
|
||||
// ✅ Hash password dan insert ke database
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
$created_at = date("Y-m-d H:i:s");
|
||||
|
||||
$stmt = $conn->prepare("INSERT INTO users (username, password) VALUES (?, ?)");
|
||||
$stmt->bind_param("ss", $username, $hashedPassword);
|
||||
$stmt = $conn->prepare("INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)");
|
||||
$stmt->bind_param("sss", $username, $hashedPassword, $created_at);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
// 🔥 PERBAIKAN UTAMA DI SINI (AUTO-LOGIN) 🔥
|
||||
$new_user_id = $stmt->insert_id; // Ambil ID user baru
|
||||
|
||||
session_start();
|
||||
$_SESSION['user_id'] = $new_user_id; // Set Session ID
|
||||
$_SESSION['username'] = $username; // Set Session Username
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"message" => "Pendaftaran berhasil",
|
||||
"registered_at" => date("Y-m-d H:i:s") // timestamp sudah WIB
|
||||
"message" => "Pendaftaran berhasil & Auto-login",
|
||||
"registered_at" => $created_at
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user