This repository has been archived on 2025-12-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
word/crossword.js
2025-12-15 22:33:12 +01:00

482 lines
13 KiB
JavaScript

// 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 = `
<div class="clue-text">${clueData.clue}</div>
<div class="arrow ${clueData.direction === 'horizontal' ? 'arrow-right' : 'arrow-down'}">
${clueData.direction === 'horizontal' ? '→' : '↓'}
</div>
`;
}
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 = '<div style="text-align: center; padding: 50px;"><div class="loading"></div><p>Laden...</p></div>';
}
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();
});