| | document.addEventListener('DOMContentLoaded', function() { |
| | |
| | const textDisplay = document.getElementById('textDisplay'); |
| | const inputArea = document.getElementById('inputArea'); |
| | const wpmDisplay = document.getElementById('wpm'); |
| | const accuracyDisplay = document.getElementById('accuracy'); |
| | const timeDisplay = document.getElementById('time'); |
| | const startBtn = document.getElementById('startBtn'); |
| | const resultContainer = document.getElementById('resultContainer'); |
| | const finalWpm = document.getElementById('finalWpm'); |
| | const finalAccuracy = document.getElementById('finalAccuracy'); |
| | const finalTime = document.getElementById('finalTime'); |
| | const restartBtn = document.getElementById('restartBtn'); |
| | const overseer = document.getElementById('overseer'); |
| | const gunshot = document.getElementById('gunshot'); |
| | const usernameInput = document.getElementById('username'); |
| | |
| | |
| | let text = ''; |
| | let startTime = null; |
| | let endTime = null; |
| | let timer = null; |
| | let mistakes = 0; |
| | let totalChars = 0; |
| | let currentPosition = 0; |
| | let gameActive = false; |
| | let timeLimit = 60; |
| | let timeRemaining = timeLimit; |
| | |
| | |
| | function fetchText() { |
| | fetch('/get_text') |
| | .then(response => response.json()) |
| | .then(data => { |
| | text = data.text; |
| | displayText(); |
| | }) |
| | .catch(error => console.error('Error fetching text:', error)); |
| | } |
| | |
| | |
| | function displayText() { |
| | textDisplay.innerHTML = ''; |
| | for (let i = 0; i < text.length; i++) { |
| | const span = document.createElement('span'); |
| | span.textContent = text[i]; |
| | textDisplay.appendChild(span); |
| | } |
| | |
| | if (textDisplay.firstChild) { |
| | textDisplay.firstChild.classList.add('current'); |
| | } |
| | } |
| | |
| | |
| | function startGame() { |
| | if (gameActive) return; |
| | |
| | |
| | startBtn.disabled = true; |
| | |
| | |
| | startCountdown(); |
| | } |
| | |
| | |
| | function startCountdown() { |
| | const countdown = document.getElementById('countdown'); |
| | countdown.style.display = 'block'; |
| | let count = 3; |
| | |
| | countdown.textContent = count; |
| | countdown.classList.add('pulse'); |
| | |
| | const countdownTimer = setInterval(() => { |
| | count--; |
| | |
| | if (count > 0) { |
| | countdown.textContent = count; |
| | countdown.classList.remove('pulse'); |
| | void countdown.offsetWidth; |
| | countdown.classList.add('pulse'); |
| | } else if (count === 0) { |
| | countdown.textContent = 'Старт!'; |
| | countdown.classList.remove('pulse'); |
| | void countdown.offsetWidth; |
| | countdown.classList.add('pulse'); |
| | } else { |
| | clearInterval(countdownTimer); |
| | countdown.style.display = 'none'; |
| | initializeGame(); |
| | } |
| | }, 1000); |
| | } |
| | |
| | |
| | function initializeGame() { |
| | fetchText(); |
| | inputArea.value = ''; |
| | inputArea.disabled = false; |
| | inputArea.focus(); |
| | |
| | mistakes = 0; |
| | totalChars = 0; |
| | currentPosition = 0; |
| | timeRemaining = timeLimit; |
| | |
| | startTime = new Date(); |
| | gameActive = true; |
| | |
| | |
| | timer = setInterval(updateTimer, 1000); |
| | |
| | |
| | resultContainer.style.display = 'none'; |
| | overseer.classList.remove('show'); |
| | |
| | |
| | startBtn.disabled = false; |
| | |
| | |
| | updateStats(); |
| | } |
| | |
| | |
| | function updateTimer() { |
| | timeRemaining--; |
| | timeDisplay.textContent = timeRemaining; |
| | |
| | if (timeRemaining <= 0) { |
| | endGame(false); |
| | } |
| | } |
| | |
| | |
| | function updateStats() { |
| | if (!gameActive) return; |
| | |
| | const currentTime = new Date(); |
| | const timeElapsed = (currentTime - startTime) / 1000; |
| | |
| | |
| | |
| | const wpm = Math.round((currentPosition / 5) / (timeElapsed / 60)); |
| | |
| | |
| | const accuracy = totalChars > 0 ? Math.round(((totalChars - mistakes) / totalChars) * 100) : 100; |
| | |
| | wpmDisplay.textContent = wpm; |
| | accuracyDisplay.textContent = accuracy + '%'; |
| | } |
| | |
| | |
| | function endGame(completed = true) { |
| | clearInterval(timer); |
| | gameActive = false; |
| | endTime = new Date(); |
| | |
| | inputArea.disabled = true; |
| | |
| | const timeElapsed = Math.round((endTime - startTime) / 1000); |
| | const wpm = Math.round((currentPosition / 5) / (timeElapsed / 60)); |
| | const accuracy = totalChars > 0 ? Math.round(((totalChars - mistakes) / totalChars) * 100) : 100; |
| | |
| | finalWpm.textContent = wpm; |
| | finalAccuracy.textContent = accuracy + '%'; |
| | finalTime.textContent = timeElapsed + 's'; |
| | |
| | resultContainer.style.display = 'block'; |
| | |
| | |
| | if (!completed || accuracy < 70) { |
| | showOverseer(); |
| | } |
| | |
| | |
| | if (usernameInput && usernameInput.value) { |
| | saveResult(usernameInput.value, wpm, accuracy); |
| | } |
| | } |
| | |
| | |
| | function showOverseer() { |
| | overseer.classList.add('show'); |
| | |
| | |
| | createBloodSplatters(); |
| | |
| | |
| | setTimeout(() => { |
| | overseer.classList.add('firing'); |
| | gunshot.style.display = 'block'; |
| | bloodSplatter.classList.add('show'); |
| | |
| | |
| | deathMessage.classList.add('show'); |
| | |
| | |
| | const audio = new Audio('/static/sounds/gunshot.mp3'); |
| | audio.play().catch(e => console.log('Audio play failed:', e)); |
| | |
| | |
| | document.body.classList.add('shake'); |
| | setTimeout(() => { |
| | document.body.classList.remove('shake'); |
| | }, 500); |
| | |
| | |
| | setTimeout(() => { |
| | gunshot.style.display = 'none'; |
| | }, 500); |
| | }, 2000); |
| | } |
| | |
| | |
| | function createBloodSplatters() { |
| | const bloodSplatter = document.getElementById('bloodSplatter'); |
| | bloodSplatter.innerHTML = ''; |
| | |
| | |
| | for (let i = 0; i < 30; i++) { |
| | const drop = document.createElement('div'); |
| | drop.classList.add('blood-drop'); |
| | |
| | |
| | const size = Math.random() * 20 + 5; |
| | drop.style.width = `${size}px`; |
| | drop.style.height = `${size}px`; |
| | |
| | |
| | drop.style.left = `${Math.random() * 100}%`; |
| | drop.style.top = `${Math.random() * 50}%`; |
| | |
| | |
| | drop.style.animationDelay = `${Math.random() * 1.5}s`; |
| | |
| | bloodSplatter.appendChild(drop); |
| | } |
| | } |
| | |
| | |
| | function saveResult(username, wpm, accuracy) { |
| | fetch('/save_result', { |
| | method: 'POST', |
| | headers: { |
| | 'Content-Type': 'application/json', |
| | }, |
| | body: JSON.stringify({ |
| | username: username, |
| | wpm: wpm, |
| | accuracy: accuracy |
| | }) |
| | }) |
| | .then(response => response.json()) |
| | .then(data => console.log('Result saved:', data)) |
| | .catch(error => console.error('Error saving result:', error)); |
| | } |
| | |
| | |
| | inputArea.addEventListener('input', function(e) { |
| | if (!gameActive) return; |
| | |
| | const inputValue = e.target.value; |
| | const currentChar = text[currentPosition]; |
| | |
| | |
| | if (inputValue.charAt(inputValue.length - 1) === currentChar) { |
| | |
| | const spans = textDisplay.querySelectorAll('span'); |
| | spans[currentPosition].classList.remove('current'); |
| | spans[currentPosition].classList.add('correct'); |
| | |
| | currentPosition++; |
| | totalChars++; |
| | |
| | |
| | if (currentPosition < text.length) { |
| | spans[currentPosition].classList.add('current'); |
| | } else { |
| | |
| | endGame(true); |
| | } |
| | } else { |
| | |
| | mistakes++; |
| | totalChars++; |
| | |
| | const spans = textDisplay.querySelectorAll('span'); |
| | spans[currentPosition].classList.add('incorrect'); |
| | } |
| | |
| | |
| | updateStats(); |
| | }); |
| | |
| | |
| | startBtn.addEventListener('click', startGame); |
| | restartBtn.addEventListener('click', startGame); |
| | |
| | |
| | timeDisplay.textContent = timeLimit; |
| | }); |