diff --git a/game.html b/game.html index 92348fb..d39275a 100644 --- a/game.html +++ b/game.html @@ -28,6 +28,7 @@ @@ -448,7 +449,7 @@ const InputModule = { currentLine: "", - submitLine(text) { + submitLine(text, username) { const line = text.trim().toLowerCase(); if (!line) return; if (introPlaying) return; @@ -468,10 +469,10 @@ 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"); // Добавляем CSS-класс для анимации вылета @@ -484,9 +485,9 @@ this.updateDisplay(""); if (enemy) { - game.launchProjectile(line, enemy, true); + game.launchProjectile(line, enemy, true, username); } else { - game.launchProjectile(line, null, false); + game.launchProjectile(line, null, false, username); } }, S.inputShootDelay); }, @@ -656,9 +657,11 @@ let gameLoopStarted = false; let introPlaying = false; + let playerStats = {}; + const game = { - launchProjectile(word, targetEnemy, isHit) { - projectiles.push(new Projectile(word, targetEnemy, isHit)); + launchProjectile(word, targetEnemy, isHit, username) { + projectiles.push(new Projectile(word, targetEnemy, isHit, username)); }, onProjectileHit(projectile, enemy) { @@ -667,6 +670,10 @@ if (dead) { score += enemy.isHeavy ? CONFIG.game.scoreHeavy : CONFIG.game.scoreSimple; 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); } else { spawnParticles(enemy.x, enemy.y, CONFIG.colors.heavy, S.particleArmorBreak); @@ -677,6 +684,10 @@ onProjectileMiss(projectile) { spawnParticles(projectile.targetX, projectile.targetY, CONFIG.colors.miss, S.particleMiss); 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) { @@ -696,9 +707,10 @@ // PROJECTILE — вылетает из поля ввода (низ экрана, центр) // ═══════════════════════════════════════════════════════════════ class Projectile { - constructor(word, targetEnemy, isHit) { + constructor(word, targetEnemy, isHit, username) { this.word = word; this.isHit = isHit; + this.username = username; this.x = W / 2; this.y = H - S.shooterOffset; this.speed = S.projectileSpeed; @@ -1040,9 +1052,50 @@ 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 = `#${place} ${name} ${playerStats[name].kills}`; + 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 = `Мазила: ${mazilaName} (${maxMisses})`; + container.appendChild(mazila); + } + } + function gameOver() { gameState = "gameover"; document.getElementById("final-score").textContent = score; + renderPlayerStats(); document.getElementById("gameover-screen").classList.remove("hidden"); document.getElementById("gameover-subtitle").classList.add("hidden"); @@ -1070,6 +1123,7 @@ enemies = []; particles = []; projectiles = []; + playerStats = {}; spawnTimer = CONFIG.game.spawnIntervalBase; spawnInterval = CONFIG.game.spawnIntervalBase; waveTimer = 0; @@ -1143,7 +1197,8 @@ client.connect(); client.on("message", (channel, tags, message, self) => { - InputModule.submitLine(message); + const username = tags["display-name"] || tags.username || "anonymous"; + InputModule.submitLine(message, username); }); function update() { diff --git a/style.css b/style.css index a805614..eb78492 100644 --- a/style.css +++ b/style.css @@ -111,6 +111,70 @@ body { margin-bottom: 30px; 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 { display: none !important; }