test
This commit is contained in:
parent
27fdee7566
commit
19d3eb26cd
@ -281,6 +281,45 @@ body {
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// === SIMPAN SKOR KE LEADERBOARD ===
|
||||
function saveScoreToLeaderboard(totalScore) {
|
||||
let leaderboard = JSON.parse(localStorage.getItem("leaderboard")) || [];
|
||||
|
||||
let record = {
|
||||
player: localStorage.getItem("username") || "Guest",
|
||||
score: totalScore,
|
||||
date: new Date().toLocaleString()
|
||||
};
|
||||
|
||||
leaderboard.push(record);
|
||||
leaderboard.sort((a, b) => b.score - a.score);
|
||||
leaderboard = leaderboard.slice(0, 10); // max 10 skor terbaik
|
||||
|
||||
localStorage.setItem("leaderboard", JSON.stringify(leaderboard));
|
||||
}
|
||||
|
||||
// === GANTI BAGIAN showEndScreen() DENGAN TAMBAHAN SAVE ===
|
||||
function showEndScreen() {
|
||||
clearInterval(countdown);
|
||||
let baseScore = score;
|
||||
let timeBonus = time * 5;
|
||||
let moveBonus = Math.max(0, 200 - moves * 10);
|
||||
let total = baseScore + timeBonus + moveBonus;
|
||||
|
||||
document.getElementById("baseScoreEnd").textContent = baseScore;
|
||||
document.getElementById("timeBonusEnd").textContent = "+" + timeBonus;
|
||||
document.getElementById("moveBonusEnd").textContent = "+" + moveBonus;
|
||||
document.getElementById("totalScoreEnd").textContent = total;
|
||||
document.getElementById("endScreen").style.display = "flex";
|
||||
|
||||
saveScoreToLeaderboard(total); // ⬅ SIMPAN SKOR
|
||||
}
|
||||
|
||||
// === KLIK TOMBOL LEADERBOARD KE HALAMAN LEADERBOARD ===
|
||||
document.querySelector(".leaderboard").addEventListener("click", () => {
|
||||
window.location.href = "leaderboard.html";
|
||||
});
|
||||
|
||||
const images = [
|
||||
"images/fruit1.png",
|
||||
"images/fruit2.png",
|
||||
|
||||
@ -200,6 +200,16 @@ body {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.gameboard {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
padding: 20px 50px;
|
||||
box-sizing: border-box;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.play-again { background: #b700ff; color: white; }
|
||||
.leaderboard { background: gold; }
|
||||
.back-menu { background: #444; color: white; }
|
||||
|
||||
@ -7,8 +7,6 @@ body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
/* Pink → Ungu Gradient */
|
||||
background: linear-gradient(135deg, #ff9ed1, #d784ff);
|
||||
}
|
||||
|
||||
|
||||
109
leaderboard.css
Normal file
109
leaderboard.css
Normal file
@ -0,0 +1,109 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Poppins, Arial, sans-serif;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, #ff9ed1, #d784ff);
|
||||
}
|
||||
/* animasi fruit */
|
||||
.fruit {
|
||||
position: absolute;
|
||||
width: 95px;
|
||||
opacity: 0.85;
|
||||
animation: float 7s infinite ease-in-out;
|
||||
pointer-events: none;
|
||||
filter: drop-shadow(0 4px 6px rgba(0,0,0,0.12));
|
||||
}
|
||||
|
||||
/* Posisi lebih seimbang & tidak menutupi teks */
|
||||
.f1 { top: 6%; left: 12%; animation-delay: .3s; }
|
||||
.f2 { top: 12%; right: 18%; animation-delay: 1.2s; }
|
||||
.f3 { top: 48%; right: 8%; animation-delay: .7s; }
|
||||
.f4 { top: 72%; left: 20%; animation-delay: 1.6s; }
|
||||
.f5 { top: 28%; left: 60%; animation-delay: .5s; }
|
||||
.f6 { top: 68%; right: 22%; animation-delay: 1s; }
|
||||
.f7 { top: 40%; left: 10%; animation-delay: .9s; }
|
||||
.f8 { top: 82%; left: 70%; animation-delay: 1.4s; }
|
||||
.f9 { top: 18%; right: 46%; animation-delay: .6s; }
|
||||
.f10 { top: 80%; right: 10%; animation-delay: 1.8s; }
|
||||
|
||||
/* Smooth floating animation */
|
||||
@keyframes float {
|
||||
0% { transform: translateY(0) rotate(0deg); }
|
||||
50% { transform: translateY(-18px) rotate(6deg); }
|
||||
100% { transform: translateY(0) rotate(0deg); }
|
||||
}
|
||||
|
||||
@keyframes drift {
|
||||
0% { transform: translateX(0); }
|
||||
50% { transform: translateX(22px); }
|
||||
100% { transform: translateX(0); }
|
||||
}
|
||||
|
||||
.fruit {
|
||||
width: clamp(70px, 7vw, 105px);
|
||||
}
|
||||
|
||||
/* leaderboard */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.rank-up {
|
||||
animation: rankUp 0.6s ease;
|
||||
background: #d2ffdf !important; /* hijau */
|
||||
}
|
||||
|
||||
.rank-down {
|
||||
animation: rankDown 0.6s ease;
|
||||
background: #ffd2d2 !important; /* merah */
|
||||
}
|
||||
|
||||
@keyframes rankUp {
|
||||
from { transform: translateY(10px); opacity: 0.4; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes rankDown {
|
||||
from { transform: translateY(-10px); opacity: 0.4; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes rankFlashUp {
|
||||
0% { background: rgba(0,255,0,0.7); }
|
||||
100% { background: rgba(0,255,0,0.2); }
|
||||
}
|
||||
|
||||
@keyframes rankFlashDown {
|
||||
0% { background: rgba(255,0,0,0.7); }
|
||||
100% { background: rgba(255,0,0,0.2); }
|
||||
}
|
||||
|
||||
/* Difficulty Tabs */
|
||||
.difficulty-tabs {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.difficulty-tabs button {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
background: #d2b6ff;
|
||||
font-weight: 600;
|
||||
transition: 0.25s;
|
||||
}
|
||||
|
||||
.difficulty-tabs button.active {
|
||||
background: #8e00ff;
|
||||
color: white;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
36
leaderboard.html
Normal file
36
leaderboard.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Leaderboard – Memory Card</title>
|
||||
<link rel="stylesheet" href="leaderboard.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="leaderboardPopup" class="leaderboard-popup">
|
||||
<div class="leaderboard-box">
|
||||
<h2>🏆 Leaderboard</h2>
|
||||
<div class="difficulty-tabs">
|
||||
<button onclick="changeDifficulty('easy')" id="tab-easy">Easy</button>
|
||||
<button onclick="changeDifficulty('medium')" id="tab-medium">Medium</button>
|
||||
<button onclick="changeDifficulty('hard')" id="tab-hard">Hard</button>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<th>Nama</th>
|
||||
<th>Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="leaderboard-body"></tbody>
|
||||
</table>
|
||||
<button class="close-leaderboard" onclick="closeLeaderboard()">✖ Tutup</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="leaderboard.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
108
leaderboard.js
Normal file
108
leaderboard.js
Normal file
@ -0,0 +1,108 @@
|
||||
const leaderboardFile = "leaderboard.json";
|
||||
|
||||
// 🔹 Buka popup leaderboard
|
||||
function openLeaderboard() {
|
||||
document.getElementById("leaderboardPopup").style.display = "flex";
|
||||
loadLeaderboard();
|
||||
}
|
||||
|
||||
// 🔹 Tutup popup leaderboard
|
||||
function closeLeaderboard() {
|
||||
document.getElementById("leaderboardPopup").style.display = "none";
|
||||
}
|
||||
|
||||
|
||||
// 🔹 Muat leaderboard dari JSON
|
||||
async function loadLeaderboard() {
|
||||
const difficulty = localStorage.getItem("difficulty") || "easy";
|
||||
|
||||
try {
|
||||
const res = await fetch(leaderboardFile + "?v=" + Date.now());
|
||||
const data = await res.json();
|
||||
|
||||
// Ambil list sesuai difficulty
|
||||
let list = data[difficulty] || [];
|
||||
|
||||
// Urutkan score dari paling tinggi
|
||||
list.sort((a, b) => b.score - a.score);
|
||||
|
||||
const tbody = document.getElementById("leaderboard-body");
|
||||
tbody.innerHTML = "";
|
||||
|
||||
newOrder.length = 0;
|
||||
list.forEach((p, i) => {
|
||||
newOrder.push(p.name);
|
||||
|
||||
let movementClass = "";
|
||||
if (previousOrder.length > 0) {
|
||||
const oldIndex = previousOrder.indexOf(p.name);
|
||||
if (oldIndex !== -1) {
|
||||
if (oldIndex > i) movementClass = "rank-up";
|
||||
if (oldIndex < i) movementClass = "rank-down";
|
||||
}
|
||||
}
|
||||
|
||||
tbody.innerHTML += `
|
||||
<tr class="${movementClass}">
|
||||
<td class="rank">#${i + 1}</td>
|
||||
<td>${p.name}</td>
|
||||
<td>${p.score}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
highlightTab(difficulty);
|
||||
|
||||
// Simpan posisi lama → jadi perbandingan load berikutnya
|
||||
localStorage.setItem("prev_ranking", JSON.stringify(newOrder));
|
||||
|
||||
} catch (error) {
|
||||
document.getElementById("leaderboard-body").innerHTML =
|
||||
`<tr><td colspan="3">⚠ Gagal memuat leaderboard</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 🔹 Menyimpan skor ke leaderboard.json
|
||||
async function saveScore(name, score, difficulty) {
|
||||
const res = await fetch(leaderboardFile);
|
||||
let data = await res.json();
|
||||
let board = data[difficulty];
|
||||
|
||||
const player = board.find(e => e.name.toLowerCase() === name.toLowerCase());
|
||||
|
||||
if (player) {
|
||||
if (score > player.score) player.score = score;
|
||||
} else {
|
||||
board.push({ name, score });
|
||||
}
|
||||
|
||||
await fetch(leaderboardFile, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data, null, 2),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const difficulty = localStorage.getItem("difficulty") || "easy";
|
||||
localStorage.setItem("playerName", username);
|
||||
|
||||
let previousOrder = JSON.parse(localStorage.getItem("prev_ranking") || "[]");
|
||||
let newOrder = [];
|
||||
|
||||
function highlightTab(diff) {
|
||||
document.querySelectorAll(".difficulty-tabs button").forEach(btn => btn.classList.remove("active"));
|
||||
document.getElementById("tab-" + diff).classList.add("active");
|
||||
}
|
||||
|
||||
function changeDifficulty(diff) {
|
||||
localStorage.setItem("difficulty", diff);
|
||||
previousOrder = JSON.parse(localStorage.getItem("prev_ranking")) || [];
|
||||
loadLeaderboard();
|
||||
highlightTab(diff);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
5
leaderboard.json
Normal file
5
leaderboard.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"easy": [],
|
||||
"medium": [],
|
||||
"hard": []
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user