// 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.closest("[data-key]")) {
pressKey(e.target.dataset.key)
return
}
if (e.target.closest("[data-enter]")) {
submitGuess()
return
}
if (e.target.closest("[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 }
)
})
}
// The CELEBRATION SOUND EFFECT
function playCelebrationSound() {
const audio = new Audio('celebration.mp3')
audio.volume = 0.5 // Adjust volume as needed
audio.play().catch(error => {
console.error('Error playing celebration sound:', error)
})
}
// IT'S RIGHT HERE YOU BLIND MOFO"
function checkWinLose(guess, tiles) {
if (guess === targetWord) {
playCelebrationSound()
showAlert("Congratulations, You win!", 5000)
danceTiles(tiles)
celebrateWithConfetti()
setTimeout(() => {
showWordDefinition(targetWord, true)
},3000)
stopInteraction()
return
}
const remainingTiles = guessGrid.querySelectorAll(":not([data-letter])")
if (remainingTiles.length === 0) {
showAlert(targetWord.toUpperCase(), null)
showWordDefinition(targetWord, false)
stopInteraction()
}
}
// Function to fetch and display word definition
async function showWordDefinition(word, isWin = true) {
try {
console.log(`Fetching definition for: ${word}`)
const response = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`)
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`)
}
const data = await response.json()
// Extract the first definition
const firstMeaning = data[0]?.meanings?.[0]
const definition = firstMeaning?.definitions?.[0]?.definition
const partOfSpeech = firstMeaning?.partOfSpeech
const example = firstMeaning?.definitions?.[0]?.example
if (definition) {
// Create definition display
let definitionText = `š **${word.toUpperCase()}** (${partOfSpeech || 'word'})\n${definition}`
if (example) {
definitionText += `\n\nš” Example: "${example}"`
}
// Show definition in a styled alert
showDefinitionAlert(definitionText, isWin)
} else {
throw new Error('No definition found')
}
} catch (error) {
console.error('Error fetching definition:', error)
// Fallback message
const fallbackMsg = `š **${word.toUpperCase()}**\nDefinition not available at the moment.`
showDefinitionAlert(fallbackMsg, isWin)
}
}
// Special alert function for definitions
function showDefinitionAlert(message, isWin = true) {
const alert = document.createElement("div")
alert.innerHTML = message.replace(/\n/g, '
').replace(/\*\*(.*?)\*\*/g, '$1')
alert.classList.add("alert", "definition-alert")
if (isWin) {
alert.classList.add("win-definition")
} else {
alert.classList.add("lose-definition")
}
alertContainer.prepend(alert)
// Auto-hide after 8 seconds (longer for reading)
setTimeout(() => {
alert.classList.add("hide")
alert.addEventListener("transitionend", () => {
alert.remove()
})
}, 8000)
// Click to dismiss
alert.addEventListener('click', () => {
alert.classList.add("hide")
alert.addEventListener("transitionend", () => {
alert.remove()
})
})
}
// Confetti celebration function
function celebrateWithConfetti() {
console.log('Confetti celebration triggered!')
// Check if confetti library is loaded
if (typeof confetti === 'undefined') {
console.error('Confetti library not loaded! Make sure to include the script tag.')
return
}
try {
// Initial burst from the center
confetti({
particleCount: 100,
spread: 70,
origin: { y: 0.6 }
})
// Side cannons effect
setTimeout(() => {
confetti({
particleCount: 50,
angle: 60,
spread: 55,
origin: { x: 0 }
})
confetti({
particleCount: 50,
angle: 120,
spread: 55,
origin: { x: 1 }
})
}, 200)
// Stars effect
setTimeout(() => {
confetti({
particleCount: 30,
spread: 360,
startVelocity: 30,
decay: 0.9,
scalar: 1.2,
shapes: ['star'],
colors: ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#ffeaa7']
})
}, 400)
// Final cascade
setTimeout(() => {
confetti({
particleCount: 200,
spread: 100,
origin: { y: 0.4 },
colors: ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#ffeaa7', '#dda0dd']
})
}, 600)
console.log('Confetti animation started successfully!')
} catch (error) {
console.error('Error triggering confetti:', error)
}
}
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)