// Crossword Game Controller class CrosswordGame { constructor() { this.currentPuzzle = null; this.userAnswers = {}; this.selectedCell = null; this.gridElement = document.getElementById('crosswordGrid'); this.progress = 0; this.initializeEventListeners(); this.loadNewPuzzle(); } initializeEventListeners() { // New puzzle button document.getElementById('newPuzzleBtn').addEventListener('click', () => { this.loadNewPuzzle(); }); // Settings button document.getElementById('settingsBtn').addEventListener('click', () => { document.getElementById('settingsModal').style.display = 'block'; }); // Close modal document.querySelector('.close').addEventListener('click', () => { document.getElementById('settingsModal').style.display = 'none'; }); // Game control buttons document.getElementById('checkAnswersBtn').addEventListener('click', () => { this.checkAnswers(); }); document.getElementById('revealLetterBtn').addEventListener('click', () => { this.revealLetter(); }); document.getElementById('clearGridBtn').addEventListener('click', () => { this.clearGrid(); }); // Settings changes document.getElementById('difficultySelect').addEventListener('change', (e) => { this.loadNewPuzzle(e.target.value); }); // Keyboard navigation document.addEventListener('keydown', (e) => { this.handleKeyboardNavigation(e); }); } async loadNewPuzzle(difficulty = 'medium') { try { this.showLoading(); this.currentPuzzle = await PuzzleDatabase.getPuzzle(difficulty); this.renderGrid(); this.userAnswers = {}; this.updateProgress(); } catch (error) { console.error('Error loading puzzle:', error); this.showError('Er is een fout opgetreden bij het laden van de puzzel.'); } } renderGrid() { if (!this.currentPuzzle) return; const grid = this.currentPuzzle.grid; const words = this.currentPuzzle.words; this.gridElement.innerHTML = ''; this.gridElement.style.gridTemplateColumns = `repeat(${grid[0].length}, 1fr)`; // Create grid cells for (let row = 0; row < grid.length; row++) { for (let col = 0; col < grid[row].length; col++) { const cell = grid[row][row]; const cellElement = this.createCellElement(cell, row, col, words); this.gridElement.appendChild(cellElement); } } // Add event listeners to input cells this.attachInputListeners(); } createCellElement(cellType, row, col, words) { const cellDiv = document.createElement('div'); cellDiv.className = 'grid-cell'; cellDiv.dataset.row = row; cellDiv.dataset.col = col; switch (cellType) { case '#': cellDiv.classList.add('blocked-cell'); break; case 'C': case 'L': case 'U': case 'E': // This is a clue cell (example from template) const clueData = this.findClueForPosition(row, col, words); if (clueData) { cellDiv.classList.add('clue-cell', clueData.direction); cellDiv.innerHTML = `
${clueData.clue}
${clueData.direction === 'horizontal' ? '→' : '↓'}
`; } break; default: // This should be an input cell cellDiv.classList.add('input-cell'); const input = document.createElement('input'); input.type = 'text'; input.maxLength = 1; input.dataset.row = row; input.dataset.col = col; // Add existing answer if available const key = `${row}-${col}`; if (this.userAnswers[key]) { input.value = this.userAnswers[key]; } cellDiv.appendChild(input); } return cellDiv; } findClueForPosition(row, col, words) { for (const word of words) { if (word.startRow === row && word.startCol === col) { return { clue: word.clue, direction: word.direction }; } } return null; } attachInputListeners() { const inputs = this.gridElement.querySelectorAll('input'); inputs.forEach(input => { input.addEventListener('input', (e) => { this.handleInput(e); }); input.addEventListener('focus', (e) => { this.selectCell(e.target); }); input.addEventListener('click', (e) => { this.selectCell(e.target); }); }); } handleInput(e) { const input = e.target; const value = input.value.toUpperCase(); input.value = value; const row = parseInt(input.dataset.row); const col = parseInt(input.dataset.col); const key = `${row}-${col}`; this.userAnswers[key] = value; // Move to next cell automatically if (value && value.length === 1) { this.moveToNextCell(input); } this.updateProgress(); } selectCell(input) { // Remove previous selection document.querySelectorAll('.grid-cell').forEach(cell => { cell.classList.remove('active'); }); // Add selection to current cell input.parentElement.classList.add('active'); this.selectedCell = input; // Highlight related word this.highlightWord(input); } highlightWord(input) { // Remove previous highlights document.querySelectorAll('.grid-cell').forEach(cell => { cell.classList.remove('highlighted'); }); // Find and highlight the entire word const row = parseInt(input.dataset.row); const col = parseInt(input.dataset.col); // This is a simplified version - in a real app you'd track word boundaries const word = this.findWordAtPosition(row, col); if (word) { // Highlight all cells in this word // Implementation depends on your word tracking system } } moveToNextCell(currentInput) { const row = parseInt(currentInput.dataset.row); const col = parseInt(currentInput.dataset.col); // Try to find next input cell in row first let nextInput = null; const currentCell = currentInput.parentElement; const nextCell = currentCell.nextElementSibling; if (nextCell && nextCell.querySelector('input')) { nextInput = nextCell.querySelector('input'); } else { // Move to next row const currentRow = currentCell.parentElement; const nextRow = currentRow.nextElementSibling; if (nextRow) { const firstInput = nextRow.querySelector('input'); if (firstInput) { nextInput = firstInput; } } } if (nextInput) { nextInput.focus(); nextInput.select(); } } handleKeyboardNavigation(e) { if (!this.selectedCell) return; const row = parseInt(this.selectedCell.dataset.row); const col = parseInt(this.selectedCell.dataset.col); let targetRow = row; let targetCol = col; switch (e.key) { case 'ArrowUp': targetRow = Math.max(0, row - 1); break; case 'ArrowDown': targetRow = Math.min(this.currentPuzzle.grid.length - 1, row + 1); break; case 'ArrowLeft': targetCol = Math.max(0, col - 1); break; case 'ArrowRight': targetCol = Math.min(this.currentPuzzle.grid[0].length - 1, col + 1); break; case 'Backspace': this.selectedCell.value = ''; delete this.userAnswers[`${row}-${col}`]; this.updateProgress(); break; default: return; } e.preventDefault(); const targetInput = this.gridElement.querySelector(`input[data-row="${targetRow}"][data-col="${targetCol}"]`); if (targetInput) { targetInput.focus(); targetInput.select(); } } checkAnswers() { let correct = 0; let total = 0; const inputs = this.gridElement.querySelectorAll('input'); inputs.forEach(input => { const row = parseInt(input.dataset.row); const col = parseInt(input.dataset.col); const key = `${row}-${col}`; const userAnswer = input.value.toUpperCase(); // Find the correct answer for this position const correctAnswer = this.findCorrectAnswer(row, col); if (correctAnswer) { total++; if (userAnswer === correctAnswer) { correct++; input.parentElement.classList.add('correct'); input.parentElement.classList.remove('incorrect'); } else if (userAnswer) { input.parentElement.classList.add('incorrect'); input.parentElement.classList.remove('correct'); } } }); const percentage = total > 0 ? Math.round((correct / total) * 100) : 0; // Show feedback if (percentage === 100) { this.showSuccess('Gefeliciteerd! Alle antwoorden zijn correct!'); } else { this.showFeedback(`Je hebt ${correct} van de ${total} letters correct (${percentage}%).`); } } findCorrectAnswer(row, col) { // Find the correct letter for this position for (const word of this.currentPuzzle.words) { if (word.direction === 'horizontal') { if (word.startRow === row && col >= word.startCol && col < word.startCol + word.answer.length) { const letterIndex = col - word.startCol; return word.answer[letterIndex]; } } else if (word.direction === 'vertical') { if (word.startCol === col && row >= word.startRow && row < word.startRow + word.answer.length) { const letterIndex = row - word.startRow; return word.answer[letterIndex]; } } } return null; } revealLetter() { if (!this.selectedCell) { this.showError('Selecteer eerst een cel om een letter te onthullen.'); return; } const row = parseInt(this.selectedCell.dataset.row); const col = parseInt(this.selectedCell.dataset.col); const correctAnswer = this.findCorrectAnswer(row, col); if (correctAnswer) { this.selectedCell.value = correctAnswer; const key = `${row}-${col}`; this.userAnswers[key] = correctAnswer; this.updateProgress(); this.showFeedback('Letter onthuld!'); } } clearGrid() { if (confirm('Weet je zeker dat je het hele raster wilt wissen?')) { const inputs = this.gridElement.querySelectorAll('input'); inputs.forEach(input => { input.value = ''; input.parentElement.classList.remove('correct', 'incorrect'); }); this.userAnswers = {}; this.updateProgress(); this.showFeedback('Raster gewist.'); } } updateProgress() { const inputs = this.gridElement.querySelectorAll('input'); let filled = 0; let total = 0; inputs.forEach(input => { const row = parseInt(input.dataset.row); const col = parseInt(input.dataset.col); const correctAnswer = this.findCorrectAnswer(row, col); if (correctAnswer) { total++; if (input.value) { filled++; } } }); this.progress = total > 0 ? Math.round((filled / total) * 100) : 0; const progressFill = document.getElementById('progressFill'); const progressText = document.getElementById('progressText'); progressFill.style.width = `${this.progress}%`; progressText.textContent = `${this.progress}% voltooid`; if (this.progress === 100) { this.checkAnswers(); } } showLoading() { this.gridElement.innerHTML = '

Laden...

'; } showError(message) { this.showFeedback(message, 'error'); } showSuccess(message) { this.showFeedback(message, 'success'); } showFeedback(message, type = 'info') { // Create feedback element const feedback = document.createElement('div'); feedback.className = `feedback feedback-${type}`; feedback.textContent = message; // Style the feedback feedback.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 15px 20px; border-radius: 8px; color: white; font-weight: 600; z-index: 1000; animation: slideIn 0.3s ease; max-width: 300px; word-wrap: break-word; `; // Set background color based on type switch (type) { case 'error': feedback.style.background = 'linear-gradient(135deg, #f56565, #e53e3e)'; break; case 'success': feedback.style.background = 'linear-gradient(135deg, #48bb78, #38a169)'; break; default: feedback.style.background = 'linear-gradient(135deg, #4299e1, #3182ce)'; } document.body.appendChild(feedback); // Remove after 3 seconds setTimeout(() => { feedback.style.animation = 'slideOut 0.3s ease'; setTimeout(() => { if (feedback.parentNode) { feedback.parentNode.removeChild(feedback); } }, 300); }, 3000); } } // Add CSS animations for feedback const style = document.createElement('style'); style.textContent = ` @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } `; document.head.appendChild(style); // Initialize the game when DOM is loaded document.addEventListener('DOMContentLoaded', () => { const game = new CrosswordGame(); });