183 lines
6.1 KiB
JavaScript
183 lines
6.1 KiB
JavaScript
// Leaderboard & High Score Registry System
|
|
window.LeaderboardSystem = class LeaderboardSystem {
|
|
constructor() {
|
|
this.selectedMode = 'survival'; // default tab
|
|
|
|
// High score records per mode
|
|
this.records = {
|
|
adventure: [
|
|
{ name: 'Безумный Дэйв', score: 10, detail: 'Уровень 10 (Пройдено)', date: '2026-09-01' },
|
|
{ name: 'Сосед Боб', score: 8, detail: 'Уровень 8', date: '2026-09-10' },
|
|
{ name: 'Пенни', score: 5, detail: 'Уровень 5', date: '2026-09-12' }
|
|
],
|
|
survival: [
|
|
{ name: 'Безумный Дэйв', score: 32, detail: '32 Флага', date: '2026-09-02' },
|
|
{ name: 'Зомби Йети', score: 18, detail: '18 Флагов', date: '2026-09-05' },
|
|
{ name: 'Пенни', score: 12, detail: '12 Флагов', date: '2026-09-11' }
|
|
],
|
|
roguelike: [
|
|
{ name: 'Безумный Дэйв', score: 14, detail: '14 Этажей (6 Реликвий)', date: '2026-09-03' },
|
|
{ name: 'Сосед Боб', score: 9, detail: '9 Этажей', date: '2026-09-08' },
|
|
{ name: 'Пенни', score: 6, detail: '6 Этажей', date: '2026-09-14' }
|
|
],
|
|
izombie: [
|
|
{ name: 'Доктор Зомбосс', score: 50, detail: '50 Мозгов съедено', date: '2026-09-04' },
|
|
{ name: 'Футболист Зомби', score: 25, detail: '25 Мозгов', date: '2026-09-09' },
|
|
{ name: 'Обычный Зомби', score: 15, detail: '15 Мозгов', date: '2026-09-13' }
|
|
],
|
|
bowling: [
|
|
{ name: 'Безумный Дэйв', score: 6800, detail: '6800 очков (Страйк x8)', date: '2026-09-06' },
|
|
{ name: 'Пенни', score: 4500, detail: '4500 очков', date: '2026-09-07' },
|
|
{ name: 'Сосед Боб', score: 3200, detail: '3200 очков', date: '2026-09-15' }
|
|
],
|
|
boss: [
|
|
{ name: 'Безумный Дэйв', score: 1, detail: 'Побежден за 2 мин 15 сек', date: '2026-09-15' }
|
|
],
|
|
bossrush: [
|
|
{ name: 'Безумный Дэйв', score: 3, detail: 'Boss Rush завершен! (3 волны)', date: '2026-09-16' },
|
|
{ name: 'Сосед Боб', score: 2, detail: '2 волны', date: '2026-09-16' }
|
|
]
|
|
};
|
|
|
|
this.loadFromAuth();
|
|
this.fetchAllLeaderboards();
|
|
}
|
|
|
|
loadFromAuth() {
|
|
if (window.PvZAuth) {
|
|
const uData = window.PvZAuth.getUserData();
|
|
if (uData.leaderboards) {
|
|
this.records = { ...this.records, ...uData.leaderboards };
|
|
}
|
|
}
|
|
}
|
|
|
|
saveToAuth() {
|
|
if (window.PvZAuth && !window.PvZAuth.isGuest()) {
|
|
window.PvZAuth.saveUserData({
|
|
leaderboards: this.records
|
|
});
|
|
}
|
|
}
|
|
|
|
async fetchAllLeaderboards() {
|
|
try {
|
|
const resp = await fetch('/api/leaderboard');
|
|
if (resp.ok) {
|
|
const json = await resp.json();
|
|
if (json && json.success && json.records) {
|
|
for (const [mode, rows] of Object.entries(json.records)) {
|
|
if (Array.isArray(rows) && rows.length > 0) {
|
|
this.mergeServerRecords(mode, rows);
|
|
}
|
|
}
|
|
if (window._pvzEngine && typeof window._pvzEngine.renderLeaderboardUI === 'function') {
|
|
window._pvzEngine.renderLeaderboardUI(this.selectedMode);
|
|
}
|
|
}
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
async fetchLeaderboard(mode) {
|
|
try {
|
|
const resp = await fetch(`/api/leaderboard?mode=${encodeURIComponent(mode)}`);
|
|
if (resp.ok) {
|
|
const json = await resp.json();
|
|
if (json && json.success && Array.isArray(json.records)) {
|
|
this.mergeServerRecords(mode, json.records);
|
|
if (window._pvzEngine && typeof window._pvzEngine.renderLeaderboardUI === 'function') {
|
|
window._pvzEngine.renderLeaderboardUI(mode);
|
|
}
|
|
}
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
mergeServerRecords(mode, serverRows) {
|
|
if (!this.records[mode]) {
|
|
this.records[mode] = [];
|
|
}
|
|
const combined = [...this.records[mode]];
|
|
for (const sr of serverRows) {
|
|
const existing = combined.find(r => r.name === sr.name);
|
|
if (existing) {
|
|
if (sr.score > existing.score) {
|
|
existing.score = sr.score;
|
|
existing.detail = sr.detail;
|
|
existing.date = sr.date;
|
|
}
|
|
} else {
|
|
combined.push({
|
|
name: sr.name,
|
|
score: sr.score,
|
|
detail: sr.detail,
|
|
date: sr.date
|
|
});
|
|
}
|
|
}
|
|
combined.sort((a, b) => b.score - a.score);
|
|
this.records[mode] = combined.slice(0, 10);
|
|
}
|
|
|
|
submitScore(mode, score, detail = '') {
|
|
if (!this.records[mode]) {
|
|
this.records[mode] = [];
|
|
}
|
|
|
|
let playerName = 'Игрок';
|
|
let token = null;
|
|
if (window.PvZAuth) {
|
|
playerName = window.PvZAuth.getCurrentUser();
|
|
if (window.PvZAuth.isGuest()) playerName = 'Гость';
|
|
token = window.PvZAuth.getToken();
|
|
}
|
|
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
|
|
// Check if player already on this board
|
|
const existing = this.records[mode].find(r => r.name === playerName);
|
|
if (existing) {
|
|
if (score > existing.score) {
|
|
existing.score = score;
|
|
existing.detail = detail || `${score} очков`;
|
|
existing.date = today;
|
|
}
|
|
} else {
|
|
this.records[mode].push({
|
|
name: playerName,
|
|
score,
|
|
detail: detail || `${score} очков`,
|
|
date: today
|
|
});
|
|
}
|
|
|
|
// Sort descending
|
|
this.records[mode].sort((a, b) => b.score - a.score);
|
|
// Keep top 10
|
|
this.records[mode] = this.records[mode].slice(0, 10);
|
|
this.saveToAuth();
|
|
|
|
// Submit to server leaderboard asynchronously
|
|
try {
|
|
const headers = { 'Content-Type': 'application/json' };
|
|
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
fetch('/api/leaderboard', {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({
|
|
mode,
|
|
score,
|
|
detail: detail || `${score} очков`,
|
|
name: playerName,
|
|
token
|
|
})
|
|
}).then(r => r.json()).catch(() => {});
|
|
} catch (_) {}
|
|
}
|
|
|
|
getEntries(mode) {
|
|
return this.records[mode] || [];
|
|
}
|
|
};
|