From 4e4b8e625001fbbfabf1f0c55cd3be934962689f Mon Sep 17 00:00:00 2001 From: Afham Fardeen Date: Sun, 1 Feb 2026 17:58:41 +0530 Subject: [PATCH 1/3] fear: adding guessing mode --- src/App.js | 61 ++-- src/components/common/FooterMenu.js | 25 +- .../features/WordsCard/WordsCard.js | 27 +- src/components/features/game/index.js | 279 ++++++++++++++++++ src/components/features/game/util.js | 78 +++++ src/constants/Constants.js | 10 +- src/style/global.js | 19 ++ 7 files changed, 455 insertions(+), 44 deletions(-) create mode 100644 src/components/features/game/index.js create mode 100644 src/components/features/game/util.js diff --git a/src/App.js b/src/App.js index 1ef1d24..ffe1ff2 100644 --- a/src/App.js +++ b/src/App.js @@ -23,6 +23,7 @@ import { DEFAULT_SOUND_TYPE_KEY, } from "./components/features/sound/sound"; import DynamicBackground from "./components/common/DynamicBackground"; +import GameComponent from "./components/features/game"; function App() { // localStorage persist theme setting @@ -82,17 +83,27 @@ function App() { false, "IsInWordsCardMode" ); + const [isGameMode, setIsGameMode] = useLocalPersistState( + false, + "IsInGameMode" + ); const isWordGameMode = gameMode === GAME_MODE_DEFAULT && !isCoffeeMode && !isTrainerMode && + !isWordsCardMode && !isGameMode; + const isInGameMode = + gameMode === GAME_MODE && + !isCoffeeMode && + !isTrainerMode && !isWordsCardMode; const isSentenceGameMode = gameMode === GAME_MODE_SENTENCE && !isCoffeeMode && !isTrainerMode && - !isWordsCardMode; + !isWordsCardMode && + !isGameMode; const handleThemeChange = (e) => { window.localStorage.setItem("theme", JSON.stringify(e.value)); @@ -119,22 +130,11 @@ function App() { setIsUltraZenMode(!isUltraZenMode); }; - const toggleCoffeeMode = () => { - setIsCoffeeMode(!isCoffeeMode); - setIsTrainerMode(false); - setIsWordsCardMode(false); - }; - - const toggleTrainerMode = () => { - setIsTrainerMode(!isTrainerMode); - setIsCoffeeMode(false); - setIsWordsCardMode(false); - }; - - const toggleWordsCardMode = () => { - setIsTrainerMode(false); - setIsCoffeeMode(false); - setIsWordsCardMode(!isWordsCardMode); + const toggleMode = (mode) => { + setIsTrainerMode( mode === "trainer" && !isTrainerMode); + setIsCoffeeMode( mode === "coffee" && !isCoffeeMode); + setIsWordsCardMode( mode === "wordsCard" && !isWordsCardMode); + setIsGameMode( mode === "game" && !isGameMode); }; useEffect(() => { @@ -159,6 +159,9 @@ function App() { const focusSentenceInput = () => { sentenceInputRef.current && sentenceInputRef.current.focus(); }; + const focusGameInput = () => { + textInputRef.current && textInputRef.current.focus(); + }; useEffect(() => { if (isWordGameMode) { @@ -173,6 +176,10 @@ function App() { focusTextArea(); return; } + if (isInGameMode) { + focusGameInput(); + return; + } return; }, [ theme, @@ -183,6 +190,7 @@ function App() { isSentenceGameMode, soundMode, soundType, + isInGameMode ]); return ( @@ -204,6 +212,20 @@ function App() { handleInputFocus={() => focusTextInput()} > )} + { + isGameMode && ( + focusTextInput()} + > + ) + } {isSentenceGameMode && ( { const isSiteInfoDisabled = isMusicMode || isFocusedMode; const isBottomLogoEnabled = isFocusedMode && !isMusicMode; - const isTypeTestEnabled = !isCoffeeMode && !isTrainerMode && !isWordsCardMode; + const isTypeTestEnabled = !isCoffeeMode && !isTrainerMode && !isWordsCardMode && !isGameMode; const getModeButtonClassName = (mode) => { if (mode) { @@ -96,7 +97,6 @@ const FooterMenu = ({ onChange={handleThemeChange} menuPlacement="top" > - @@ -122,7 +122,7 @@ const FooterMenu = ({ menuPlacement="top" > )} - + {toggleMode("wordsCard")}}> @@ -135,7 +135,7 @@ const FooterMenu = ({ - + toggleMode("coffee")}> {FREE_MODE} @@ -146,7 +146,7 @@ const FooterMenu = ({ - + toggleMode("trainer")}> @@ -160,6 +160,15 @@ const FooterMenu = ({ {" "} + toggleMode("game")}> + {GAME_MODE} + }> + + + + + {isTypeTestEnabled && ( <> { const [mode, setMode] = useLocalPersistState("vocab", "mode"); // selective, vocab - const [selectiveWord, setSelectiveWord] = useLocalPersistState("word",""); + const [selectiveWord, setSelectiveWord] = useLocalPersistState("","word"); const [play] = useSound(SOUND_MAP[soundType], { volume: 0.5 }); @@ -489,18 +489,17 @@ const WordsCard = ({ soundType, soundMode }) => { controlsList="nodownload nofullscreen noremoteplayback" /> - { - mode === "vocab" && -
- {"Chapter " + currChapter.toUpperCase() + ": "} {index + 1} /{" "} - {currChapterCount} -
- } - { - mode === "selective" && -

Selected Keys:

- } - + { + mode === "vocab" && +
+ {"Chapter " + currChapter.toUpperCase() + ": "} {index + 1} /{" "} + {currChapterCount} +
+ } + { + mode === "selective" && +

Selected Keys:

+ }
{ @@ -567,7 +566,7 @@ const WordsCard = ({ soundType, soundMode }) => { } - + setMode("vocab")}> Vocab diff --git a/src/components/features/game/index.js b/src/components/features/game/index.js new file mode 100644 index 0000000..362949b --- /dev/null +++ b/src/components/features/game/index.js @@ -0,0 +1,279 @@ +import useSound from "use-sound"; +import { SOUND_MAP } from "../sound/sound"; +import { useState, useEffect, useRef } from "react"; +import { getRandomWord, initData, isWordPresent } from "./util"; +import useLocalPersistState from "../../../hooks/useLocalPersistState"; +import { Box, Grid, Tooltip } from "@mui/material"; +import IconButton from "../../utils/IconButton"; +import RestartAltIcon from "@mui/icons-material/RestartAlt"; +import LightbulbIcon from '@mui/icons-material/Lightbulb'; +import { HINT_BUTTON_TOOLTIP_TITLE, HINT_LIMIT, RESET_BUTTON_TOOLTIP_TITLE } from "../../../constants/Constants"; +import LinearProgress from "@mui/material/LinearProgress"; + + +const GameComponent = ({ soundType, soundMode }) => { + const [play] = useSound(SOUND_MAP[soundType], { volume: 0.5 }); + //easy, medium, hard + const [difficulty, setDifficulty] = useLocalPersistState("easy", "game-difficulty"); + + const [guessWord, setGuessWord] = useLocalPersistState("","guessWord"); + + // set up game loop status state + const [status, setStatus] = useState("waiting"); + const [visibleIndex, setVisibleIndex] = useState([]); + + const [currInput, setCurrInput] = useState(""); + + const hiddenInputRef = useRef(); + + const start = () => { + if (status === "finished") { + return; + } + if (status !== "started") { + setStatus("started"); + } + }; + const currWord = guessWord; + const handleInputBlur = (event) => { + hiddenInputRef.current && hiddenInputRef.current.focus(); + }; + + const handleInputChange = (e) => { + setCurrInput(e.target.value); + hiddenInputRef.current.value = e.target.value; + e.preventDefault(); + }; + useEffect(() => { + hiddenInputRef.current && hiddenInputRef.current.focus(); + initData(); + requestWord(); + }, []); + useEffect(() => { + // Call requestWord whenever difficulty changes + requestWord(); + }, [difficulty]); + + useEffect(() => { + hiddenInputRef.current.value = ""; + setCurrInput(""); + let random = 0 + while (random===0 && guessWord){ + random = Math.floor(guessWord.length * Math.random()) + } + setVisibleIndex([random]); + }, [guessWord]); + + const allVisibleRevealed = () => { + for (let i = 0; i < currWord.length; i++) { + if ((i === 0 || visibleIndex.includes(i)) && currInput[i] !== currWord[i]) { + return false; + } + } + return true; + } + + const getCharClassName = (idx, char) => { + if (currWord.length <= currInput.length) { + if (currInput.length === currWord.length ) { + if(allVisibleRevealed() && isWordPresent(currInput)) + return "correct-wordcard-char"; + return "wordcard-error-char"; + } + return "wordcard-error-char"; + } + if(idx === 0 || visibleIndex.includes(idx)){ + if(currInput[idx] && char !== currInput[idx]){ + return "wordcard-error-char"; + } + } + if (idx < currInput.length) { + if (char === " ") { + return "error-wordcard-space-char"; + } + return "correct-wordcard-char"; + } + return "wordcard-char"; + }; + const getExtraCharClassName = (char) => { + if (char === " ") { + return "wordcard-error-char-space-char"; + } + return "wordcard-error-char"; + }; + + const extra = currInput.slice(guessWord.length, currInput.length).split(""); + const getCharDisplay = (idx, char) => { + if ( visibleIndex.includes(idx) || idx === 0) { + return char; + } + if(idx < currInput.length){ + return currInput[idx]; + } + return "_"; + }; + + const handleReset = () => { + setStatus("waiting"); + setCurrInput(""); + requestWord(); + hiddenInputRef.current.value = ""; + } + + const requestWord = () => { + const difficultyRanges = { + easy: { min: 3, max: 4 }, + medium: { min: 5, max: 7 }, + hard: { min: 8, max: 20 }, + }; + const { min, max } = difficultyRanges[difficulty] || difficultyRanges["easy"]; + const newWord = getRandomWord(min, max); + + if (newWord !== guessWord) { + setGuessWord(newWord); + } + } + + const handleDisable = () =>{ + return visibleIndex.length > HINT_LIMIT || visibleIndex.length + 2 === currWord.length; + } + const handleHint = () => { + if (visibleIndex.length > HINT_LIMIT || visibleIndex.length === currWord.length - 2) { + return; + } + let newVisibleIndex = [...visibleIndex]; + let random = 0 + while (currWord && (newVisibleIndex.includes(random) || random === 0)) { + random = Math.floor(currWord.length * Math.random()) + } + newVisibleIndex.push(random); + setVisibleIndex(newVisibleIndex); + } + const getModeActivation = (type) => { + // return "active-button" ; + return difficulty === type ? "active-button" : "inactive-button" + } + + const handleKeyDown = (e) => { + if (soundMode) { + play(); + } + const keyCode = e.keyCode; + + // disable tab key + if (keyCode === 9) { + e.preventDefault(); + return; + } + + if (status === "finished") { + e.preventDefault(); + return; + } + + // start the game by typing any thing + if (status !== "started" && status !== "finished") { + start(); + return; + } + + // Handle word completion + if (currInput.length >= guessWord.length) { + if (keyCode === 13 || keyCode === 32) { + if (guessWord === currInput || isWordPresent(currInput)) { + e.preventDefault(); + requestWord(); + setCurrInput(""); + hiddenInputRef.current.value = ""; + } + return; + } + return; + } + }; + + return ( +
+
+ handleKeyDown(e)} + > +
+ {currWord.split("").map((char, idx) => ( + + {getCharDisplay(idx, char)} + + ))} + {extra.map((char, idx) => ( + + {char} + + ))} +
+
+
+ + + + + + + + + + + + + + + setDifficulty("easy")}> + + Easy + + + setDifficulty("medium")}> + + Medium + + + setDifficulty("hard")}> + + Hard + + + + + + + + + +
+
+
+
+ ); +}; + +export default GameComponent; \ No newline at end of file diff --git a/src/components/features/game/util.js b/src/components/features/game/util.js new file mode 100644 index 0000000..33e5d7f --- /dev/null +++ b/src/components/features/game/util.js @@ -0,0 +1,78 @@ +const result = {}; +// Parse CET4Words + +function initData(){ + ParseCET4Words(); + ParseCET6Words(); + ParseGREWords(); +} + +// Parse CET4Words +const ParseCET4Words = () => { + const CET4Words = require('../../../assets/Vocab/CET4Words.json'); + Object.keys(CET4Words).forEach((key) => { + separator(CET4Words[key]?.key); + }); +}; + +// Parse CET6Words +const ParseCET6Words = () => { + const CET6Words = require('../../../assets/Vocab/CET6Words.json'); + Object.keys(CET6Words).forEach((key) => { + separator(CET6Words[key]?.key); + }); +}; + +// Parse GREWords +const ParseGREWords = () => { + const GREWords = require('../../../assets/Vocab/GREWords.json'); + Object.keys(GREWords).forEach((key) => { + separator(GREWords[key]?.key); + }); +}; + +// Function to create a data structure of separate words based on first character and length. +function separator(word) { + if(word.includes('.') || !word) return; + const firstChar = word.charAt(0).toLowerCase(); + const len = word.length; + if (!result[firstChar]) { + result[firstChar] = {}; + } + if(!result[firstChar][len]){ + result[firstChar][len] = new Set(); + } + result[firstChar][len].add(word); +} + +// Function to check if a word exists in the result +function isWordPresent(word) { + if (!word) return false; + const firstChar = word[0].toLowerCase(); + const len = word.length; + return result[firstChar]?.[len]?.has(word) || false; +} + +// Function to get a random word from the result +function getRandomWord(min, max) { + + // Pick a random first character + const randomFirstChar = String.fromCharCode(Math.floor(Math.random() * 26) + 97); + const lengths = Object.keys(result[randomFirstChar]); + const randomLength = lengths[Math.floor(Math.random() * lengths.length)]; + + // Get a random word from the set + const wordsSet = result[randomFirstChar][randomLength]; + const wordsArray = Array.from(wordsSet); // Convert Set to Array temporarily + let word = wordsArray[Math.floor(Math.random() * wordsArray.length)] + if (word.length >= min && word.length <= max) { + return word; + } + return getRandomWord(min, max); +} + +export { isWordPresent, result, getRandomWord, initData }; + +// console.log(result); +// console.log(getRandomWord()); +// console.log(isWordPresent("adult")); \ No newline at end of file diff --git a/src/constants/Constants.js b/src/constants/Constants.js index ebcf289..c45edea 100644 --- a/src/constants/Constants.js +++ b/src/constants/Constants.js @@ -55,7 +55,9 @@ const FREE_MODE = const ENGLISH_MODE = "ENGLISH_MODE"; const CHINESE_MODE = "CHINESE_MODE"; -const GAME_MODE = "GAME_MODE"; +const GAME_MODE = "Time based word guess mode"; +const HINT_BUTTON_TOOLTIP_TITLE = "Show Hint"; +const RESET_BUTTON_TOOLTIP_TITLE = "New word for guessing"; const GAME_MODE_DEFAULT = "WORD_MODE"; const GAME_MODE_SENTENCE = "SENTENCE_MODE"; const WORD_MODE_LABEL = "word"; @@ -65,6 +67,7 @@ const TRAINER_MODE = "QWERTY keyboard practice mode"; const DEFAULT_SENTENCES_COUNT = 5; const TEN_SENTENCES_COUNT = 10; const FIFTEEN_SENTENCES_COUNT = 15; +const HINT_LIMIT = 2; const ENGLISH_SENTENCE_MODE_TOOLTIP_TITLE = "English Sentence Mode"; const CHINESE_SENTENCE_MODE_TOOLTIP_TITLE = "Chinese Sentence Mode"; @@ -137,5 +140,8 @@ export { SYMBOL_ADDON_KEY, ULTRA_ZEN_MODE, VOCAB_MODE, - SELECTIVE_MODE + SELECTIVE_MODE, + HINT_BUTTON_TOOLTIP_TITLE, + RESET_BUTTON_TOOLTIP_TITLE, + HINT_LIMIT }; diff --git a/src/style/global.js b/src/style/global.js index cf4e83e..10b1742 100644 --- a/src/style/global.js +++ b/src/style/global.js @@ -368,6 +368,15 @@ width: 8em transform:scale(1.18); transition:0.3s; } +.restart-button-game{ +margin-left: auto; +margin-right: auto; +width: 11em +} +.restart-button-game button:hover{ +transform:scale(1.18); +transition:0.3s; +} .alert{ opacity: 0.3; background-image: ${({ theme }) => theme.gradient}; @@ -687,9 +696,19 @@ transform: translate(0); .CorrectKeyDowns{ color: inherit; } +.center-row{ +display: flex; +justify-content: center; +} .IncorrectKeyDowns{ color: red; } +.game-card-container{ +display: flex; +justify-content: center; +width: 100%; +height: 100%; +} .words-card-container{ display: block; width: 100%; From 1d0687366beadfe954abb054df85bf2c16c6621c Mon Sep 17 00:00:00 2001 From: Afham Fardeen Date: Thu, 5 Feb 2026 11:10:45 +0530 Subject: [PATCH 2/3] feat: adding timer --- src/components/features/game/index.js | 84 +++++++++++++++++++++++---- src/components/features/game/util.js | 15 ++++- 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/components/features/game/index.js b/src/components/features/game/index.js index 362949b..7bb2140 100644 --- a/src/components/features/game/index.js +++ b/src/components/features/game/index.js @@ -3,20 +3,24 @@ import { SOUND_MAP } from "../sound/sound"; import { useState, useEffect, useRef } from "react"; import { getRandomWord, initData, isWordPresent } from "./util"; import useLocalPersistState from "../../../hooks/useLocalPersistState"; -import { Box, Grid, Tooltip } from "@mui/material"; +import { Box, Dialog, DialogActions, DialogTitle, Grid, Tooltip, Button } from "@mui/material"; import IconButton from "../../utils/IconButton"; import RestartAltIcon from "@mui/icons-material/RestartAlt"; import LightbulbIcon from '@mui/icons-material/Lightbulb'; import { HINT_BUTTON_TOOLTIP_TITLE, HINT_LIMIT, RESET_BUTTON_TOOLTIP_TITLE } from "../../../constants/Constants"; import LinearProgress from "@mui/material/LinearProgress"; +import RestoreIcon from '@mui/icons-material/Restore'; const GameComponent = ({ soundType, soundMode }) => { const [play] = useSound(SOUND_MAP[soundType], { volume: 0.5 }); //easy, medium, hard const [difficulty, setDifficulty] = useLocalPersistState("easy", "game-difficulty"); - const [guessWord, setGuessWord] = useLocalPersistState("","guessWord"); + const [progress, setProgress] = useState(100); // Progress bar value + const [timer, setTimer] = useState(null); // Timer reference + const [gameOverDialogOpen, setGameOverDialogOpen] = useState(false); // State for game over dialog + const [guessedWordsCount, setGuessedWordsCount] = useState(0); // set up game loop status state const [status, setStatus] = useState("waiting"); @@ -32,8 +36,38 @@ const GameComponent = ({ soundType, soundMode }) => { } if (status !== "started") { setStatus("started"); + startTimer(); } }; + + const restartGame = () => { + setStatus("waiting"); + setCurrInput(""); + setProgress(100); // Reset progress bar + setVisibleIndex([]); + requestWord(); + hiddenInputRef.current.value = ""; + clearInterval(timer); // Clear the timer + setTimer(null) + setGameOverDialogOpen(false); // Close the game over dialog + }; + const startTimer = () => { + clearInterval(timer); // Clear any existing timer + const newTimer = setInterval(() => { + setProgress((prev) => { + if (prev <= 0) { + clearInterval(newTimer); + setStatus("finished"); // End the game when the timer reaches 0 + return 0; + } + return prev - 1; // Decrease progress by 1% every second + }); + }, 1000); + setTimer(newTimer); + }; + + + const currWord = guessWord; const handleInputBlur = (event) => { hiddenInputRef.current && hiddenInputRef.current.focus(); @@ -53,6 +87,12 @@ const GameComponent = ({ soundType, soundMode }) => { // Call requestWord whenever difficulty changes requestWord(); }, [difficulty]); + // Show game over dialog when the game ends + useEffect(() => { + if (status === "finished") { + setGameOverDialogOpen(true); // Open the game over dialog + } + }, [status]); useEffect(() => { hiddenInputRef.current.value = ""; @@ -114,10 +154,9 @@ const GameComponent = ({ soundType, soundMode }) => { }; const handleReset = () => { - setStatus("waiting"); - setCurrInput(""); requestWord(); hiddenInputRef.current.value = ""; + setProgress((prev) => Math.min(prev - 2, 100)); // Increase progress by 2% for reset } const requestWord = () => { @@ -148,6 +187,7 @@ const GameComponent = ({ soundType, soundMode }) => { } newVisibleIndex.push(random); setVisibleIndex(newVisibleIndex); + setProgress((prev) => Math.min(prev - 1, 100)); // Increase progress by 1% for hint } const getModeActivation = (type) => { // return "active-button" ; @@ -183,8 +223,10 @@ const GameComponent = ({ soundType, soundMode }) => { if (guessWord === currInput || isWordPresent(currInput)) { e.preventDefault(); requestWord(); + setProgress((prev) => Math.min(prev + 2, 100)); setCurrInput(""); hiddenInputRef.current.value = ""; + setGuessedWordsCount((prev) => prev + 1); // Increment guessed words count } return; } @@ -242,6 +284,16 @@ const GameComponent = ({ soundType, soundMode }) => {
+ + + + +
setDifficulty("easy")}> @@ -260,18 +312,28 @@ const GameComponent = ({ soundType, soundMode }) => { - - - - + +

You guessed {guessedWordsCount} words correctly!

+
+ + + + setGameOverDialogOpen(false)}> + Game Over + + + + ); }; diff --git a/src/components/features/game/util.js b/src/components/features/game/util.js index 33e5d7f..03c8dad 100644 --- a/src/components/features/game/util.js +++ b/src/components/features/game/util.js @@ -58,14 +58,27 @@ function getRandomWord(min, max) { // Pick a random first character const randomFirstChar = String.fromCharCode(Math.floor(Math.random() * 26) + 97); + // Check if the character exists in the result + if (!result[randomFirstChar]) { + return getRandomWord(min, max); // Retry if no words exist for the character + } + const lengths = Object.keys(result[randomFirstChar]); const randomLength = lengths[Math.floor(Math.random() * lengths.length)]; // Get a random word from the set const wordsSet = result[randomFirstChar][randomLength]; const wordsArray = Array.from(wordsSet); // Convert Set to Array temporarily - let word = wordsArray[Math.floor(Math.random() * wordsArray.length)] + if (wordsArray.length === 0) { + delete result[randomFirstChar][randomLength]; // Remove empty length set + if (Object.keys(result[randomFirstChar]).length === 0) { + delete result[randomFirstChar]; // Remove empty character entry + } + return getRandomWord(min, max); // Retry if no words are available + } + const word = wordsArray[Math.floor(Math.random() * wordsArray.length)] if (word.length >= min && word.length <= max) { + wordsSet.delete(word); // Remove the guessed word from the set return word; } return getRandomWord(min, max); From 7e9a94f80fc19af602355f39c64aaa9f8a0e674c Mon Sep 17 00:00:00 2001 From: Afham Fardeen Date: Sat, 14 Feb 2026 12:27:05 +0530 Subject: [PATCH 3/3] feat: high score --- src/components/features/game/index.js | 71 +++++++++++++++++++-------- src/components/features/game/util.js | 48 +++++++++--------- 2 files changed, 74 insertions(+), 45 deletions(-) diff --git a/src/components/features/game/index.js b/src/components/features/game/index.js index 7bb2140..09bc440 100644 --- a/src/components/features/game/index.js +++ b/src/components/features/game/index.js @@ -21,6 +21,7 @@ const GameComponent = ({ soundType, soundMode }) => { const [timer, setTimer] = useState(null); // Timer reference const [gameOverDialogOpen, setGameOverDialogOpen] = useState(false); // State for game over dialog const [guessedWordsCount, setGuessedWordsCount] = useState(0); + const [highScore, setHighScore] = useLocalPersistState(0, "highscore"); // High score tracker // set up game loop status state const [status, setStatus] = useState("waiting"); @@ -45,12 +46,17 @@ const GameComponent = ({ soundType, soundMode }) => { setCurrInput(""); setProgress(100); // Reset progress bar setVisibleIndex([]); + setGuessedWordsCount(0); // Reset guessed words count requestWord(); - hiddenInputRef.current.value = ""; + if (hiddenInputRef.current) { + hiddenInputRef.current.value = ""; + hiddenInputRef.current.focus(); // Refocus the input field + } clearInterval(timer); // Clear the timer - setTimer(null) + setTimer(null); setGameOverDialogOpen(false); // Close the game over dialog }; + const startTimer = () => { clearInterval(timer); // Clear any existing timer const newTimer = setInterval(() => { @@ -66,8 +72,6 @@ const GameComponent = ({ soundType, soundMode }) => { setTimer(newTimer); }; - - const currWord = guessWord; const handleInputBlur = (event) => { hiddenInputRef.current && hiddenInputRef.current.focus(); @@ -104,37 +108,48 @@ const GameComponent = ({ soundType, soundMode }) => { setVisibleIndex([random]); }, [guessWord]); + useEffect(() => { + // Reset guessed words count and request a new word when difficulty changes + setGuessedWordsCount(0); + requestWord(); + }, [difficulty]); + const allVisibleRevealed = () => { for (let i = 0; i < currWord.length; i++) { if ((i === 0 || visibleIndex.includes(i)) && currInput[i] !== currWord[i]) { return false; } } - return true; + return isWordPresent(currInput); } const getCharClassName = (idx, char) => { - if (currWord.length <= currInput.length) { - if (currInput.length === currWord.length ) { - if(allVisibleRevealed() && isWordPresent(currInput)) - return "correct-wordcard-char"; - return "wordcard-error-char"; - } - return "wordcard-error-char"; + const wordClass = ["wordcard-error-char", "correct-wordcard-char", "wordcard-char", "error-wordcard-space-char"]; + + // case 1. If the input is longer than or equal to the word length, all chars are wrong. + if(currInput.length > currWord.length){ + return wordClass[0]; // error char + } + + // Case 2: If the input length equal to the word length. + if (currWord.length === currInput.length) { + // if all visible chars are correct and the word is valid, show correct char, otherwise show error char. + return allVisibleRevealed() ? wordClass[1] : wordClass[0]; } + + // Case 3: If the input length is less than the word length, check the visible chars. If the char is visible and not correct, show error char. If the char is typed but not visible, show correct char if it's correct, otherwise show error char. If the char is not typed, show default char. if(idx === 0 || visibleIndex.includes(idx)){ if(currInput[idx] && char !== currInput[idx]){ - return "wordcard-error-char"; + return wordClass[0]; // error char } } if (idx < currInput.length) { - if (char === " ") { - return "error-wordcard-space-char"; - } - return "correct-wordcard-char"; + // if the char is space, show error space char, otherwise show correct char + return char === " " ? wordClass[3] : wordClass[1]; } - return "wordcard-char"; + return wordClass[2]; // default char }; + const getExtraCharClassName = (char) => { if (char === " ") { return "wordcard-error-char-space-char"; @@ -154,6 +169,9 @@ const GameComponent = ({ soundType, soundMode }) => { }; const handleReset = () => { + if (status !== "started") { + start(); // Start the game if it's not already started + } requestWord(); hiddenInputRef.current.value = ""; setProgress((prev) => Math.min(prev - 2, 100)); // Increase progress by 2% for reset @@ -177,6 +195,9 @@ const GameComponent = ({ soundType, soundMode }) => { return visibleIndex.length > HINT_LIMIT || visibleIndex.length + 2 === currWord.length; } const handleHint = () => { + if (status !== "started") { + start(); // Start the game if it's not already started + } if (visibleIndex.length > HINT_LIMIT || visibleIndex.length === currWord.length - 2) { return; } @@ -220,13 +241,17 @@ const GameComponent = ({ soundType, soundMode }) => { // Handle word completion if (currInput.length >= guessWord.length) { if (keyCode === 13 || keyCode === 32) { - if (guessWord === currInput || isWordPresent(currInput)) { + if (guessWord === currInput || (currWord.length === currInput.length && allVisibleRevealed())) { e.preventDefault(); requestWord(); setProgress((prev) => Math.min(prev + 2, 100)); setCurrInput(""); hiddenInputRef.current.value = ""; - setGuessedWordsCount((prev) => prev + 1); // Increment guessed words count + setGuessedWordsCount((prev) => { + const newCount = prev + 1; + setHighScore((highScore) => Math.max(highScore, newCount)); // Update high score if needed + return newCount; + }); } return; } @@ -314,6 +339,7 @@ const GameComponent = ({ soundType, soundMode }) => {

You guessed {guessedWordsCount} words correctly!

+

High Score: {highScore}

@@ -328,6 +354,11 @@ const GameComponent = ({ soundType, soundMode }) => { setGameOverDialogOpen(false)}> Game Over + +

+ The word was: {currWord} +

+