// Global variables let targetWords = [] let dictionary = [] let targetWord = "" const WORD_LENGTH = 5 const FLIP_ANIMATION_DURATION = 500 const DANCE_ANIMATION_DURATION = 500 const keyboard = document.querySelector("[data-keyboard]") const alertContainer = document.querySelector("[data-alert-container]") const guessGrid = document.querySelector("[data-guess-grid]") // Initialize the game by loading JSON data async function initializeGame() { try { // Load both JSON files const [targetWordsResponse, dictionaryResponse] = await Promise.all([ fetch('targetWords.json'), fetch('dictionary.json') ]) // Check if responses are ok if (!targetWordsResponse.ok) { throw new Error(`Failed to load targetWords.json: ${targetWordsResponse.status}`) } if (!dictionaryResponse.ok) { throw new Error(`Failed to load dictionary.json: ${dictionaryResponse.status}`) } // Parse JSON data targetWords = await targetWordsResponse.json() dictionary = await dictionaryResponse.json() // Validate data if (!Array.isArray(targetWords) || targetWords.length === 0) { throw new Error('Target words must be a non-empty array') } if (!Array.isArray(dictionary) || dictionary.length === 0) { throw new Error('Dictionary must be a non-empty array') } // Select random target word targetWord = targetWords[Math.floor(Math.random() * targetWords.length)] // Start the game startInteraction() console.log('Game initialized successfully!') console.log(`Target word selected from ${targetWords.length} possible words`) console.log(`Dictionary loaded with ${dictionary.length} words`) } catch (error) { console.error('Failed to initialize game:', error) showAlert(`Error loading game data: ${error.message}`, 5000) } } // Alternative initialization with date-based word selection (uncomment if needed) /* async function initializeGameWithDate() { try { const [targetWordsResponse, dictionaryResponse] = await Promise.all([ fetch('targetWords.json'), fetch('dictionary.json') ]) if (!targetWordsResponse.ok || !dictionaryResponse.ok) { throw new Error('Failed to load game data files') } targetWords = await targetWordsResponse.json() dictionary = await dictionaryResponse.json() // Date-based word selection const offsetFromDate = new Date(2022, 0, 1) const msOffset = Date.now() - offsetFromDate const dayOffset = Math.floor(msOffset / 1000 / 60 / 60 / 24) targetWord = targetWords[dayOffset % targetWords.length] startInteraction() } catch (error) { console.error('Failed to initialize game:', error) showAlert(`Error loading game data: ${error.message}`, 5000) } } */ function startInteraction() { document.addEventListener("click", handleMouseClick) document.addEventListener("keydown", handleKeyPress) } function stopInteraction() { document.removeEventListener("click", handleMouseClick) document.removeEventListener("keydown", handleKeyPress) } function handleMouseClick(e) { if (e.target.matches("[data-key]")) { pressKey(e.target.dataset.key) return } if (e.target.matches("[data-enter]")) { submitGuess() return } if (e.target.matches("[data-delete]")) { deleteKey() return } } function handleKeyPress(e) { if (e.key === "Enter") { submitGuess() return } if (e.key === "Backspace" || e.key === "Delete") { deleteKey() return } if (e.key.match(/^[a-z]$/)) { pressKey(e.key) return } } function pressKey(key) { const activeTiles = getActiveTiles() if (activeTiles.length >= WORD_LENGTH) return const nextTile = guessGrid.querySelector(":not([data-letter])") nextTile.dataset.letter = key.toLowerCase() nextTile.textContent = key nextTile.dataset.state = "active" } function deleteKey() { const activeTiles = getActiveTiles() const lastTile = activeTiles[activeTiles.length - 1] if (lastTile == null) return lastTile.textContent = "" delete lastTile.dataset.state delete lastTile.dataset.letter } function submitGuess() { const activeTiles = [...getActiveTiles()] if (activeTiles.length !== WORD_LENGTH) { showAlert("Not enough letters, Sara") shakeTiles(activeTiles) return } const guess = activeTiles.reduce((word, tile) => { return word + tile.dataset.letter }, "") if (!dictionary.includes(guess)) { showAlert("Not in dictionary") shakeTiles(activeTiles) return } stopInteraction() activeTiles.forEach((...params) => flipTile(...params, guess)) } function flipTile(tile, index, array, guess) { const letter = tile.dataset.letter const key = keyboard.querySelector(`[data-key="${letter}"i]`) setTimeout(() => { tile.classList.add("flip") }, (index * FLIP_ANIMATION_DURATION) / 2) tile.addEventListener( "transitionend", () => { tile.classList.remove("flip") if (targetWord[index] === letter) { tile.dataset.state = "correct" key.classList.add("correct") } else if (targetWord.includes(letter)) { tile.dataset.state = "wrong-position" key.classList.add("wrong-position") } else { tile.dataset.state = "wrong" key.classList.add("wrong") } if (index === array.length - 1) { tile.addEventListener( "transitionend", () => { startInteraction() checkWinLose(guess, array) }, { once: true } ) } }, { once: true } ) } function getActiveTiles() { return guessGrid.querySelectorAll('[data-state="active"]') } function showAlert(message, duration = 1000) { const alert = document.createElement("div") alert.textContent = message alert.classList.add("alert") alertContainer.prepend(alert) if (duration == null) return setTimeout(() => { alert.classList.add("hide") alert.addEventListener("transitionend", () => { alert.remove() }) }, duration) } function shakeTiles(tiles) { tiles.forEach(tile => { tile.classList.add("shake") tile.addEventListener( "animationend", () => { tile.classList.remove("shake") }, { once: true } ) }) } function checkWinLose(guess, tiles) { if (guess === targetWord) { showAlert("Congratulations Sara, You win!", 5000) danceTiles(tiles) stopInteraction() return } const remainingTiles = guessGrid.querySelectorAll(":not([data-letter])") if (remainingTiles.length === 0) { showAlert(targetWord.toUpperCase(), null) stopInteraction() } } function danceTiles(tiles) { tiles.forEach((tile, index) => { setTimeout(() => { tile.classList.add("dance") tile.addEventListener( "animationend", () => { tile.classList.remove("dance") }, { once: true } ) }, (index * DANCE_ANIMATION_DURATION) / 5) }) } /*==================== DARK LIGHT THEME ====================*/ const themeButton = document.getElementById('theme-button') const darkTheme = 'dark-theme' const iconTheme = 'bx-sun' // Previously selected topic (if user selected) const selectedTheme = localStorage.getItem('selected-theme') const selectedIcon = localStorage.getItem('selected-icon') // We obtain the current theme that the interface has by validating the dark-theme class const getCurrentTheme = () => document.body.classList.contains(darkTheme) ? 'dark' : 'light' const getCurrentIcon = () => themeButton.classList.contains(iconTheme) ? 'bx-moon' : 'bx-sun' // We validate if the user previously chose a topic if (selectedTheme) { // If the validation is fulfilled, we ask what the issue was to know if we activated or deactivated the dark document.body.classList[selectedTheme === 'dark' ? 'add' : 'remove'](darkTheme) themeButton.classList[selectedIcon === 'bx-moon' ? 'add' : 'remove'](iconTheme) } // Activate / deactivate the theme manually with the button themeButton.addEventListener('click', () => { // Add or remove the dark / icon theme document.body.classList.toggle(darkTheme) themeButton.classList.toggle(iconTheme) // We save the theme and the current icon that the user chose localStorage.setItem('selected-theme', getCurrentTheme()) localStorage.setItem('selected-icon', getCurrentIcon()) }) // Initialize the game when the page loads document.addEventListener('DOMContentLoaded', initializeGame)