You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/4-typing-game/solution/index.js

113 lines
3.6 KiB

// Typing Game - Modern ES6+ Version
// Quotes pool
const quotes = [
"When you have eliminated the impossible, whatever remains, however improbable, must be the truth.",
"There is nothing more deceptive than an obvious fact.",
"I ought to know by this time that when a fact appears to be opposed to a long train of deductions it invariably proves to be capable of bearing some other interpretation.",
"I never make exceptions. An exception disproves the rule.",
"What one man can invent another can discover.",
"Nothing clears up a case so much as stating it to another person.",
"Education never ends, Watson. It is a series of lessons, with the greatest for the last."
];
// State
let words = [];
let wordIndex = 0;
let startTime = 0;
// UI Elements
const quoteElement = document.querySelector("#quote");
const messageElement = document.querySelector("#message");
const typedValueElement = document.querySelector("#typed-value");
const startButton = document.querySelector("#start");
// Messages system
const messages = {
success: (seconds, wpm) => `🎉 Congratulations! You finished in ${seconds.toFixed(2)} seconds (${wpm} WPM).`,
error: "⚠️ Oops! There's a mistake.",
start: "⌨️ Start typing to begin the game."
};
// Utility: pick random quote
const getRandomQuote = () => quotes[Math.floor(Math.random() * quotes.length)];
// Utility: render quote as spans
const renderQuote = (quote) => {
quoteElement.innerHTML = quote
.split(" ")
.map((word, i) => `<span ${i === 0 ? 'class="highlight"' : ""}>${word}</span>`)
.join(" ");
};
// Utility: highlight current word
const highlightWord = (index) => {
[...quoteElement.children].forEach((el, i) => {
el.classList.toggle("highlight", i === index);
});
};
// Game start
const startGame = () => {
const quote = getRandomQuote();
words = quote.split(" ");
wordIndex = 0;
renderQuote(quote);
messageElement.textContent = messages.start;
typedValueElement.value = "";
typedValueElement.focus();
startTime = Date.now();
};
// Typing logic
const handleTyping = () => {
const currentWord = words[wordIndex];
const typedValue = typedValueElement.value;
if (typedValue === currentWord && wordIndex === words.length - 1) {
// Game finished
const elapsedTime = (Date.now() - startTime) / 1000;
const elapsedMinutes = elapsedTime / 60;
const wordCount = words.length;
const wpm = (wordCount / elapsedMinutes).toFixed(2);
messageElement.textContent = messages.success(elapsedTime, wpm);
typedValueElement.disabled = true;
document.getElementById("reset").style.display = "inline-block";
document.getElementById("start").style.display = "none";
} else if (typedValue.endsWith(" ") && typedValue.trim() === currentWord) {
// Word completed
typedValueElement.value = "";
wordIndex++;
highlightWord(wordIndex);
} else if (currentWord.startsWith(typedValue)) {
// Correct so far
typedValueElement.classList.remove("error");
} else {
// Error
typedValueElement.classList.add("error");
messageElement.textContent = messages.error;
}
};
// Event Listeners
startButton.addEventListener("click", startGame);
typedValueElement.addEventListener("input", handleTyping);
// Default state
messageElement.textContent = "👉 Click Start to begin!";
const resetButton = document.getElementById("reset");
resetButton.addEventListener("click", () => {
// Reset game state
typedValueElement.disabled = false;
typedValueElement.value = "";
messageElement.textContent = "👉 Click Start to begin!";
quoteElement.innerHTML = "";
startButton.style.display = "inline-block";
resetButton.style.display = "none";
});