feat: add game over stats

This commit is contained in:
2026-06-25 05:39:39 +05:00
parent 5d4c8196be
commit ddc8b93804
2 changed files with 128 additions and 9 deletions
+64 -9
View File
@@ -28,6 +28,7 @@
<div id="gameover-screen" class="hidden"> <div id="gameover-screen" class="hidden">
<div class="screen-title" id="gameover-title">ЧАТ, ВЫ ПРОИГРАЛИ!</div> <div class="screen-title" id="gameover-title">ЧАТ, ВЫ ПРОИГРАЛИ!</div>
<div class="screen-subtitle">Final Score: <span id="final-score">0</span></div> <div class="screen-subtitle">Final Score: <span id="final-score">0</span></div>
<div id="player-stats"></div>
<div class="screen-subtitle" id="gameover-subtitle">Введи !play чтобы играть</div> <div class="screen-subtitle" id="gameover-subtitle">Введи !play чтобы играть</div>
</div> </div>
</div> </div>
@@ -448,7 +449,7 @@
const InputModule = { const InputModule = {
currentLine: "", currentLine: "",
submitLine(text) { submitLine(text, username) {
const line = text.trim().toLowerCase(); const line = text.trim().toLowerCase();
if (!line) return; if (!line) return;
if (introPlaying) return; if (introPlaying) return;
@@ -468,10 +469,10 @@
const enemy = this.findEnemy(line); const enemy = this.findEnemy(line);
// АНИМАЦИЯ: слово вылетает из поля ввода, потом запускается снаряд // АНИМАЦИЯ: слово вылетает из поля ввода, потом запускается снаряд
this.animateShoot(line, enemy); this.animateShoot(line, enemy, username);
}, },
animateShoot(line, enemy) { animateShoot(line, enemy, username) {
const el = document.getElementById("typed-text"); const el = document.getElementById("typed-text");
// Добавляем CSS-класс для анимации вылета // Добавляем CSS-класс для анимации вылета
@@ -484,9 +485,9 @@
this.updateDisplay(""); this.updateDisplay("");
if (enemy) { if (enemy) {
game.launchProjectile(line, enemy, true); game.launchProjectile(line, enemy, true, username);
} else { } else {
game.launchProjectile(line, null, false); game.launchProjectile(line, null, false, username);
} }
}, S.inputShootDelay); }, S.inputShootDelay);
}, },
@@ -656,9 +657,11 @@
let gameLoopStarted = false; let gameLoopStarted = false;
let introPlaying = false; let introPlaying = false;
let playerStats = {};
const game = { const game = {
launchProjectile(word, targetEnemy, isHit) { launchProjectile(word, targetEnemy, isHit, username) {
projectiles.push(new Projectile(word, targetEnemy, isHit)); projectiles.push(new Projectile(word, targetEnemy, isHit, username));
}, },
onProjectileHit(projectile, enemy) { onProjectileHit(projectile, enemy) {
@@ -667,6 +670,10 @@
if (dead) { if (dead) {
score += enemy.isHeavy ? CONFIG.game.scoreHeavy : CONFIG.game.scoreSimple; score += enemy.isHeavy ? CONFIG.game.scoreHeavy : CONFIG.game.scoreSimple;
document.getElementById("score").textContent = score; document.getElementById("score").textContent = score;
if (projectile.username) {
if (!playerStats[projectile.username]) playerStats[projectile.username] = { kills: 0, misses: 0 };
playerStats[projectile.username].kills++;
}
this.destroyEnemy(enemy); this.destroyEnemy(enemy);
} else { } else {
spawnParticles(enemy.x, enemy.y, CONFIG.colors.heavy, S.particleArmorBreak); spawnParticles(enemy.x, enemy.y, CONFIG.colors.heavy, S.particleArmorBreak);
@@ -677,6 +684,10 @@
onProjectileMiss(projectile) { onProjectileMiss(projectile) {
spawnParticles(projectile.targetX, projectile.targetY, CONFIG.colors.miss, S.particleMiss); spawnParticles(projectile.targetX, projectile.targetY, CONFIG.colors.miss, S.particleMiss);
triggerShake(S.shakeMiss, S.shakeMissDuration); triggerShake(S.shakeMiss, S.shakeMissDuration);
if (projectile.username) {
if (!playerStats[projectile.username]) playerStats[projectile.username] = { kills: 0, misses: 0 };
playerStats[projectile.username].misses++;
}
}, },
destroyEnemy(enemy) { destroyEnemy(enemy) {
@@ -696,9 +707,10 @@
// PROJECTILE — вылетает из поля ввода (низ экрана, центр) // PROJECTILE — вылетает из поля ввода (низ экрана, центр)
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
class Projectile { class Projectile {
constructor(word, targetEnemy, isHit) { constructor(word, targetEnemy, isHit, username) {
this.word = word; this.word = word;
this.isHit = isHit; this.isHit = isHit;
this.username = username;
this.x = W / 2; this.x = W / 2;
this.y = H - S.shooterOffset; this.y = H - S.shooterOffset;
this.speed = S.projectileSpeed; this.speed = S.projectileSpeed;
@@ -1040,9 +1052,50 @@
let gameOverTimeout = null; let gameOverTimeout = null;
function renderPlayerStats() {
const container = document.getElementById("player-stats");
container.innerHTML = "";
const names = Object.keys(playerStats);
if (names.length === 0) return;
const byKills = names.slice().sort((a, b) => playerStats[b].kills - playerStats[a].kills);
const top3 = byKills.slice(0, 3);
const sectionTitle = document.createElement("div");
sectionTitle.className = "stats-section-title";
sectionTitle.textContent = "УБИЙЦЫ";
container.appendChild(sectionTitle);
top3.forEach((name, i) => {
const row = document.createElement("div");
row.className = "stats-row" + (i === 0 ? " stats-top1" : i === 1 ? " stats-top2" : " stats-top3");
const place = i === 0 ? "1" : i === 1 ? "2" : "3";
row.innerHTML = `<span class="stats-place">#${place}</span> <span class="stats-name">${name}</span> <span class="stats-kills">${playerStats[name].kills}</span>`;
container.appendChild(row);
});
let maxMisses = 0;
let mazilaName = "";
for (const name of names) {
if (playerStats[name].misses > maxMisses) {
maxMisses = playerStats[name].misses;
mazilaName = name;
}
}
if (mazilaName && maxMisses > 0) {
const mazila = document.createElement("div");
mazila.className = "stats-mazila";
mazila.innerHTML = `<span class="mazila-title">Мазила:</span> ${mazilaName} <span class="mazila-count">(${maxMisses})</span>`;
container.appendChild(mazila);
}
}
function gameOver() { function gameOver() {
gameState = "gameover"; gameState = "gameover";
document.getElementById("final-score").textContent = score; document.getElementById("final-score").textContent = score;
renderPlayerStats();
document.getElementById("gameover-screen").classList.remove("hidden"); document.getElementById("gameover-screen").classList.remove("hidden");
document.getElementById("gameover-subtitle").classList.add("hidden"); document.getElementById("gameover-subtitle").classList.add("hidden");
@@ -1070,6 +1123,7 @@
enemies = []; enemies = [];
particles = []; particles = [];
projectiles = []; projectiles = [];
playerStats = {};
spawnTimer = CONFIG.game.spawnIntervalBase; spawnTimer = CONFIG.game.spawnIntervalBase;
spawnInterval = CONFIG.game.spawnIntervalBase; spawnInterval = CONFIG.game.spawnIntervalBase;
waveTimer = 0; waveTimer = 0;
@@ -1143,7 +1197,8 @@
client.connect(); client.connect();
client.on("message", (channel, tags, message, self) => { client.on("message", (channel, tags, message, self) => {
InputModule.submitLine(message); const username = tags["display-name"] || tags.username || "anonymous";
InputModule.submitLine(message, username);
}); });
function update() { function update() {
+64
View File
@@ -111,6 +111,70 @@ body {
margin-bottom: 30px; margin-bottom: 30px;
opacity: 0.8; opacity: 0.8;
} }
#player-stats {
margin-bottom: 20px;
width: 80%;
max-width: 400px;
}
.stats-section-title {
color: #00ff41;
font-size: 16px;
letter-spacing: 3px;
margin-bottom: 10px;
text-shadow: 0 0 10px #00ff41;
}
.stats-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 12px;
margin-bottom: 4px;
border-left: 2px solid #00ff41;
opacity: 0.8;
}
.stats-top1 {
font-size: 18px;
font-weight: bold;
color: #00ff41;
border-left-width: 4px;
opacity: 1;
text-shadow: 0 0 12px #00ff41;
}
.stats-top2 {
font-size: 15px;
font-weight: 600;
color: #00dd33;
opacity: 0.9;
}
.stats-top3 {
font-size: 13px;
color: #00bb22;
}
.stats-place {
min-width: 28px;
}
.stats-name {
flex: 1;
text-align: left;
margin-left: 8px;
}
.stats-kills {
min-width: 24px;
text-align: right;
}
.stats-mazila {
margin-top: 14px;
font-size: 14px;
color: #ff6644;
opacity: 0.85;
}
.mazila-title {
font-weight: bold;
text-shadow: 0 0 8px #ff6644;
}
.mazila-count {
opacity: 0.6;
}
.hidden { .hidden {
display: none !important; display: none !important;
} }