443 lines
13 KiB
JavaScript
443 lines
13 KiB
JavaScript
// Mobile-Optimized Crossword Game
|
|
class MobileCrosswordGame {
|
|
constructor() {
|
|
this.currentLevel = 1
|
|
this.coins = 100
|
|
this.hints = 3
|
|
this.currentPuzzle = null
|
|
this.userAnswers = {}
|
|
this.selectedCell = null
|
|
this.gridElement = document.getElementById('crosswordGrid')
|
|
|
|
this.init()
|
|
}
|
|
|
|
init() {
|
|
this.loadPuzzle(this.currentLevel)
|
|
this.setupEventListeners()
|
|
this.updateUI()
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Menu
|
|
document.getElementById('menuBtn').addEventListener('click', () => {
|
|
document.getElementById('sideMenu').classList.add('active')
|
|
})
|
|
document.getElementById('closeMenu').addEventListener('click', () => {
|
|
document.getElementById('sideMenu').classList.remove('active')
|
|
})
|
|
|
|
// Controls
|
|
document.getElementById('checkBtn').addEventListener('click', () => this.checkAnswers())
|
|
document.getElementById('clearBtn').addEventListener('click', () => this.clearGrid())
|
|
document.getElementById('hintBtn').addEventListener('click', () => this.showHintModal())
|
|
document.getElementById('skipBtn').addEventListener('click', () => this.skipPuzzle())
|
|
|
|
// Modals
|
|
document.getElementById('useHintBtn').addEventListener('click', () => this.useHint())
|
|
document.getElementById('cancelHintBtn').addEventListener('click', () => this.hideHintModal())
|
|
document.getElementById('nextBtn').addEventListener('click', () => this.nextPuzzle())
|
|
|
|
// Menu items
|
|
document.getElementById('dailyPuzzle').addEventListener('click', () => {
|
|
this.showFeedback('Dagelijkse puzzel komt binnenkort!')
|
|
document.getElementById('sideMenu').classList.remove('active')
|
|
})
|
|
document.getElementById('levelSelect').addEventListener('click', () => {
|
|
this.showFeedback('Niveau selectie komt binnenkort!')
|
|
document.getElementById('sideMenu').classList.remove('active')
|
|
})
|
|
}
|
|
|
|
loadPuzzle(level) {
|
|
// Use puzzles from puzzleData.js
|
|
if (typeof DUTCH_PUZZLES !== 'undefined') {
|
|
this.currentPuzzle = DUTCH_PUZZLES[`level${level}`] || DUTCH_PUZZLES.level1
|
|
}
|
|
|
|
this.userAnswers = {}
|
|
this.renderGrid()
|
|
this.updateProgress()
|
|
this.updateUI()
|
|
}
|
|
|
|
renderGrid() {
|
|
if (!this.currentPuzzle) return
|
|
|
|
const grid = this.currentPuzzle.grid
|
|
const words = this.currentPuzzle.words
|
|
|
|
this.gridElement.innerHTML = ''
|
|
|
|
// Calculate grid dimensions (add 1 column for clues)
|
|
const rows = grid.length
|
|
const cols = Math.max(...grid.map(row => row.length)) + 1
|
|
|
|
// Calculate optimal cell size for mobile
|
|
const cellSize = this.calculateCellSize(rows, cols)
|
|
|
|
// Set grid styles
|
|
this.gridElement.style.gridTemplateColumns = `repeat(${cols}, ${cellSize}px)`
|
|
this.gridElement.style.gridTemplateRows = `repeat(${rows}, ${cellSize}px)`
|
|
|
|
// Create grid cells
|
|
for (let row = 0; row < rows; row++) {
|
|
for (let col = 0; col < cols; col++) {
|
|
if (col === 0) {
|
|
// First column: clue cells
|
|
const clueData = this.getClueForRow(row, words)
|
|
this.gridElement.appendChild(this.createClueCell(clueData, cellSize))
|
|
} else {
|
|
// Data columns
|
|
const gridCol = col - 1
|
|
const cellValue = grid[row]?.[gridCol] || '#'
|
|
this.gridElement.appendChild(this.createCell(cellValue, row, gridCol, words, cellSize))
|
|
}
|
|
}
|
|
}
|
|
|
|
this.attachCellListeners()
|
|
}
|
|
|
|
calculateCellSize(rows, cols) {
|
|
// Mobile-specific calculation
|
|
const headerHeight = 50
|
|
const progressHeight = 35
|
|
const controlsHeight = 75
|
|
const padding = 16
|
|
|
|
const availableHeight = window.innerHeight - headerHeight - progressHeight - controlsHeight - padding
|
|
const availableWidth = window.innerWidth - padding
|
|
|
|
const maxFromHeight = Math.floor(availableHeight / rows)
|
|
const maxFromWidth = Math.floor(availableWidth / cols)
|
|
|
|
let cellSize = Math.min(maxFromHeight, maxFromWidth)
|
|
|
|
// Constrain to 30-42px for mobile readability
|
|
cellSize = Math.max(30, Math.min(42, cellSize))
|
|
|
|
return cellSize
|
|
}
|
|
|
|
getClueForRow(row, words) {
|
|
return words.find(w => w.direction === 'horizontal' && w.startRow === row)
|
|
}
|
|
|
|
createClueCell(clueData, cellSize) {
|
|
const cell = document.createElement('div')
|
|
cell.className = 'grid-cell clue-cell'
|
|
cell.style.width = `${cellSize}px`
|
|
cell.style.height = `${cellSize}px`
|
|
|
|
if (clueData) {
|
|
const text = document.createElement('div')
|
|
text.className = 'clue-text'
|
|
text.textContent = clueData.clue
|
|
|
|
const arrow = document.createElement('div')
|
|
arrow.className = 'arrow arrow-right'
|
|
arrow.textContent = '→'
|
|
|
|
cell.appendChild(text)
|
|
cell.appendChild(arrow)
|
|
} else {
|
|
cell.classList.add('blocked-cell')
|
|
}
|
|
|
|
return cell
|
|
}
|
|
|
|
createCell(value, row, col, words, cellSize) {
|
|
const cell = document.createElement('div')
|
|
cell.className = 'grid-cell'
|
|
cell.style.width = `${cellSize}px`
|
|
cell.style.height = `${cellSize}px`
|
|
cell.dataset.row = row
|
|
cell.dataset.col = col
|
|
|
|
if (value === '#') {
|
|
// Check if it's a clue cell for second word
|
|
const clueData = this.getClueAtPosition(row, col, words)
|
|
if (clueData) {
|
|
cell.classList.add('clue-cell')
|
|
|
|
const text = document.createElement('div')
|
|
text.className = 'clue-text'
|
|
text.textContent = clueData.clue
|
|
|
|
const arrow = document.createElement('div')
|
|
arrow.className = 'arrow arrow-right'
|
|
arrow.textContent = '→'
|
|
|
|
cell.appendChild(text)
|
|
cell.appendChild(arrow)
|
|
} else {
|
|
cell.classList.add('blocked-cell')
|
|
}
|
|
} else {
|
|
// Input cell
|
|
cell.classList.add('input-cell')
|
|
const input = document.createElement('input')
|
|
input.type = 'text'
|
|
input.maxLength = 1
|
|
input.setAttribute('inputmode', 'text')
|
|
input.dataset.row = row
|
|
input.dataset.col = col
|
|
|
|
const fontSize = Math.max(14, Math.min(22, cellSize * 0.5))
|
|
input.style.fontSize = `${fontSize}px`
|
|
|
|
const key = `${row}-${col}`
|
|
if (this.userAnswers[key]) {
|
|
input.value = this.userAnswers[key]
|
|
}
|
|
|
|
cell.appendChild(input)
|
|
}
|
|
|
|
return cell
|
|
}
|
|
|
|
getClueAtPosition(row, col, words) {
|
|
for (const word of words) {
|
|
if (word.direction === 'horizontal' && word.startRow === row && word.startCol - 1 === col) {
|
|
return word
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
attachCellListeners() {
|
|
const inputs = this.gridElement.querySelectorAll('input')
|
|
|
|
inputs.forEach(input => {
|
|
input.addEventListener('input', (e) => this.handleInput(e))
|
|
input.addEventListener('focus', (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
|
|
|
|
if (value && value.length === 1) {
|
|
this.moveToNextCell(input)
|
|
}
|
|
|
|
this.updateProgress()
|
|
}
|
|
|
|
selectCell(input) {
|
|
document.querySelectorAll('.grid-cell.input-cell').forEach(cell => {
|
|
cell.classList.remove('active')
|
|
})
|
|
input.parentElement.classList.add('active')
|
|
this.selectedCell = input
|
|
}
|
|
|
|
moveToNextCell(currentInput) {
|
|
const inputs = Array.from(this.gridElement.querySelectorAll('input'))
|
|
const currentIndex = inputs.indexOf(currentInput)
|
|
|
|
if (currentIndex !== -1 && currentIndex < inputs.length - 1) {
|
|
const nextInput = inputs[currentIndex + 1]
|
|
nextInput.focus()
|
|
}
|
|
}
|
|
|
|
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 userAnswer = input.value.toUpperCase()
|
|
const correctAnswer = this.getCorrectAnswer(row, col)
|
|
|
|
if (correctAnswer) {
|
|
total++
|
|
const cell = input.parentElement
|
|
if (userAnswer === correctAnswer) {
|
|
correct++
|
|
cell.classList.add('correct')
|
|
cell.classList.remove('incorrect')
|
|
} else if (userAnswer) {
|
|
cell.classList.add('incorrect')
|
|
cell.classList.remove('correct')
|
|
}
|
|
}
|
|
})
|
|
|
|
const percentage = total > 0 ? Math.round((correct / total) * 100) : 0
|
|
|
|
if (percentage === 100) {
|
|
this.showSuccess()
|
|
} else {
|
|
this.showFeedback(`${correct}/${total} correct (${percentage}%)`)
|
|
}
|
|
}
|
|
|
|
getCorrectAnswer(row, col) {
|
|
for (const word of this.currentPuzzle.words) {
|
|
if (word.direction === 'horizontal') {
|
|
if (word.startRow === row && col >= word.startCol && col < word.startCol + word.answer.length) {
|
|
return word.answer[col - word.startCol]
|
|
}
|
|
} else if (word.direction === 'vertical') {
|
|
if (word.startCol === col && row >= word.startRow && row < word.startRow + word.answer.length) {
|
|
return word.answer[row - word.startRow]
|
|
}
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
clearGrid() {
|
|
if (confirm('Alles wissen?')) {
|
|
this.userAnswers = {}
|
|
const inputs = this.gridElement.querySelectorAll('input')
|
|
inputs.forEach(input => {
|
|
input.value = ''
|
|
input.parentElement.classList.remove('correct', 'incorrect')
|
|
})
|
|
this.updateProgress()
|
|
this.showFeedback('Gewist')
|
|
}
|
|
}
|
|
|
|
showHintModal() {
|
|
if (this.hints <= 0) {
|
|
this.showFeedback('Geen hints meer!')
|
|
return
|
|
}
|
|
document.getElementById('hintModal').classList.add('active')
|
|
}
|
|
|
|
hideHintModal() {
|
|
document.getElementById('hintModal').classList.remove('active')
|
|
}
|
|
|
|
useHint() {
|
|
if (this.hints <= 0) return
|
|
|
|
const inputs = Array.from(this.gridElement.querySelectorAll('input'))
|
|
const emptyInputs = inputs.filter(input => !input.value)
|
|
|
|
if (emptyInputs.length === 0) {
|
|
this.showFeedback('Alles al ingevuld!')
|
|
this.hideHintModal()
|
|
return
|
|
}
|
|
|
|
const randomInput = emptyInputs[Math.floor(Math.random() * emptyInputs.length)]
|
|
const row = parseInt(randomInput.dataset.row)
|
|
const col = parseInt(randomInput.dataset.col)
|
|
const correctAnswer = this.getCorrectAnswer(row, col)
|
|
|
|
if (correctAnswer) {
|
|
randomInput.value = correctAnswer
|
|
this.userAnswers[`${row}-${col}`] = correctAnswer
|
|
|
|
this.hints--
|
|
this.updateUI()
|
|
this.updateProgress()
|
|
this.showFeedback('Letter onthuld!')
|
|
this.hideHintModal()
|
|
}
|
|
}
|
|
|
|
showSuccess() {
|
|
this.coins += 50
|
|
this.hints += 1
|
|
this.updateUI()
|
|
document.getElementById('successModal').classList.add('active')
|
|
}
|
|
|
|
nextPuzzle() {
|
|
document.getElementById('successModal').classList.remove('active')
|
|
this.currentLevel++
|
|
this.loadPuzzle(this.currentLevel)
|
|
this.showFeedback(`Niveau ${this.currentLevel}`)
|
|
}
|
|
|
|
skipPuzzle() {
|
|
if (confirm('Overslaan?')) {
|
|
this.currentLevel++
|
|
this.loadPuzzle(this.currentLevel)
|
|
this.showFeedback('Overgeslagen')
|
|
}
|
|
}
|
|
|
|
updateProgress() {
|
|
const inputs = this.gridElement.querySelectorAll('input')
|
|
let filled = 0
|
|
let total = 0
|
|
|
|
inputs.forEach(input => {
|
|
total++
|
|
if (input.value) filled++
|
|
})
|
|
|
|
const progress = total > 0 ? Math.round((filled / total) * 100) : 0
|
|
document.getElementById('progressFill').style.width = `${progress}%`
|
|
document.getElementById('progressText').textContent = `${progress}%`
|
|
}
|
|
|
|
updateUI() {
|
|
document.getElementById('coinsCount').textContent = this.coins
|
|
document.getElementById('hintsCount').textContent = this.hints
|
|
document.getElementById('levelNum').textContent = this.currentLevel
|
|
}
|
|
|
|
showFeedback(message) {
|
|
// Simple alert for mobile
|
|
const feedback = document.createElement('div')
|
|
feedback.style.cssText = `
|
|
position: fixed;
|
|
top: 60px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background: rgba(0,0,0,0.8);
|
|
color: white;
|
|
padding: 10px 20px;
|
|
border-radius: 20px;
|
|
font-size: 14px;
|
|
z-index: 9999;
|
|
animation: fadeInOut 2s ease;
|
|
`
|
|
feedback.textContent = message
|
|
document.body.appendChild(feedback)
|
|
|
|
setTimeout(() => {
|
|
feedback.remove()
|
|
}, 2000)
|
|
}
|
|
}
|
|
|
|
// Add fade animation
|
|
const style = document.createElement('style')
|
|
style.textContent = `
|
|
@keyframes fadeInOut {
|
|
0% { opacity: 0; transform: translateX(-50%) translateY(-10px); }
|
|
20% { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
80% { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
100% { opacity: 0; transform: translateX(-50%) translateY(-10px); }
|
|
}
|
|
`
|
|
document.head.appendChild(style)
|
|
|
|
// Initialize game
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
new MobileCrosswordGame()
|
|
})
|