Mini-Scrabble – Prinzessinnen-Edition
Spieler 1
0
SCRABBLE
KINDER-EDITION
Spieler 2
0
Willkommen! Lege ein Wort in die Mitte ★
Mit dem Zauberer überprüfen?
Papa/Mama bestätigen?
Abbrechen
Tipp
BESTÄTIGEN
BUCHSTABEN VON SPIELER 1
` : text; msgBox.className = `p-3 rounded-xl text-center font-bold text-sm sm:text-base shadow-sm flex flex-col items-center gap-1 transition-all duration-500 `; if (type === 'error') msgBox.classList.add('bg-red-100', 'text-red-600'); else if (type === 'warning') msgBox.classList.add('bg-orange-100', 'text-orange-700'); else if (type === 'success') msgBox.classList.add('bg-green-100', 'text-green-700'); else if (type === 'funfact') msgBox.classList.add('bg-yellow-50', 'text-pink-600', 'ring-4', 'ring-yellow-200'); else msgBox.classList.add('bg-white', 'text-pink-500'); if (showActions && !state.aiLoading) { actions.classList.remove('hidden'); state.lastUnknownWord = word; } else { actions.classList.add('hidden'); state.lastUnknownWord = null; } lucide.createIcons(); } // --- INTERACTION --- function handleHandClick(index) { if (state.selectedHandIndex === index) state.selectedHandIndex = null; else state.selectedHandIndex = index; render(); } function handleGridClick(r, c) { if (state.grid[r][c]) return; // Placer une lettre if (state.selectedHandIndex !== null) { const player = state.players[state.currentPlayerIdx]; const letterObj = player.hand[state.selectedHandIndex]; // Pas d'écrasement de temp if (state.tempPlacements.some(t => t.r === r && t.c === c)) return; state.tempPlacements.push({ r, c, ...letterObj }); player.hand.splice(state.selectedHandIndex, 1); state.selectedHandIndex = null; render(); } // Reprendre une lettre else { const tempIdx = state.tempPlacements.findIndex(t => t.r === r && t.c === c); if (tempIdx !== -1) { const item = state.tempPlacements[tempIdx]; state.players[state.currentPlayerIdx].hand.push({ letter: item.letter, score: item.score, id: item.id }); state.tempPlacements.splice(tempIdx, 1); render(); } } } function recallLetters() { if (state.tempPlacements.length === 0) return; const player = state.players[state.currentPlayerIdx]; state.tempPlacements.forEach(p => { player.hand.push({ letter: p.letter, score: p.score, id: p.id }); }); state.tempPlacements = []; state.selectedHandIndex = null; render(); } // --- VALIDATION --- function getFullWordAt(r, c, dr, dc) { let startR = r, startC = c; // Reculer while ( startR - dr >= 0 && startC - dc >= 0 && startR - dr t.r === startR - dr && t.c === startC - dc)) ) { startR -= dr; startC -= dc; } // Avancer let wordStr = \"\"; let currR = startR, currC = startC; while (currR >= 0 && currC >= 0 && currR t.r === currR && t.c === currC); if (!cell && !temp) break; wordStr += (cell ? cell.letter : temp.letter); currR += dr; currC += dc; } return wordStr; } async function validateTurn(force = false, forcedWord = null) { if (state.tempPlacements.length === 0) return updateMessage(\"Tu n'as rien posé !\", \"error\"); // 1. Alignement const rows = state.tempPlacements.map(t => t.r); const cols = state.tempPlacements.map(t => t.c); const isRow = new Set(rows).size === 1; const isCol = new Set(cols).size === 1; if (state.tempPlacements.length > 1 && !isRow && !isCol) return updateMessage(\"Les lettres doivent être alignées.\", \"error\"); // 2. Connexion const isGridEmpty = state.grid[4][4] === null; if (isGridEmpty) { if (!state.tempPlacements.some(t => t.r === 4 && t.c === 4)) return updateMessage(\"Le premier mot doit passer par l'étoile centrale ★\", \"error\"); } else { const isConnected = state.tempPlacements.some(t => { const neighbors = [{r:t.r-1,c:t.c},{r:t.r+1,c:t.c},{r:t.r,c:t.c-1},{r:t.r,c:t.c+1}]; return neighbors.some(n => n.r >= 0 && n.r = 0 && n.c 1) ? wordH : ((wordV.length > 1) ? wordV : state.tempPlacements[0].letter); } else { const first = state.tempPlacements[0]; word = isRow ? getFullWordAt(first.r, first.c, 0, 1) : getFullWordAt(first.r, first.c, 1, 0); } if(forcedWord) word = forcedWord; // 4. Dictionnaire const isValid = FULL_DICTIONARY.includes(word); if (!isValid && !force) { if (word.length { let score = p.score; const special = SPECIAL_CELLS[`${p.r},${p.c}`]; if (special === 'DL') score *= 2; if (special === 'TW') wordMultiplier *= 3; if (special === 'DW') wordMultiplier *= 2; turnScore += score; }); if (state.tempPlacements.length === 7) { turnScore += 10; updateMessage(\"BINGO ! +10 points !\", \"success\"); } turnScore *= wordMultiplier; // Appliquer state.tempPlacements.forEach(p => state.grid[p.r][p.c] = { letter: p.letter, score: p.score }); state.players[state.currentPlayerIdx].score += turnScore; // Piocher const needed = 7 - state.players[state.currentPlayerIdx].hand.length; if (needed > 0) { const drawn = drawLetters(needed, state.players[state.currentPlayerIdx].hand); state.players[state.currentPlayerIdx].hand.push(...drawn); } state.tempPlacements = []; state.selectedHandIndex = null; // Fun Definition (Lance l'animation de chargement) getFunDefinition(word); // Switch Player state.currentPlayerIdx = state.currentPlayerIdx === 0 ? 1 : 0; render(); } function forceValidate() { if(state.lastUnknownWord) validateTurn(true, state.lastUnknownWord); } // --- IA LOGIC --- async function checkWordWithAI() { if(!state.lastUnknownWord) return; const word = state.lastUnknownWord; state.aiLoading = true; updateMessage(\"Je demande au Magicien... ✨\", \"info\"); render(); // Afficher loader const prompt = `Est-ce que \"${word}\" est un mot français valide existant qui convient à un enfant de 7 ans ? Réponds uniquement par OUI ou NON.`; const result = await callGeminiFast(prompt); state.aiLoading = false; if (result && result.trim().toUpperCase().includes(\"OUI\")) { validateTurn(true, word); } else { updateMessage(`Le Magicien dit que \"${word}\" n'existe pas !`, \"error\"); render(); } } async function getFunDefinition(word) { state.aiLoading = true; updateMessage(\"Le Magicien cherche... ⚡\", \"info\"); render(); // Prompt simplifié const prompt = `Explique le mot \"${word}\" à une enfant de 7 ans. Une phrase drôle. Emoji fin.`; // Utilisation de la version rapide const def = await callGeminiFast(prompt); state.aiLoading = false; if(def) updateMessage(def, \"funfact\"); else updateMessage(`Bravo ! \"${word}\" est un super mot ! 🌟`, \"success\"); render(); } // --- HINT LOGIC (SMART) --- // Vérifie si placer un mot à une position donnée est valide (limites, conflits, mots croisés) function isValidPlacement(word, r, c, isHorizontal, handLetters) { let tempHand = [...handLetters]; let lettersNeeded = 0; // 1. Vérification Principale (Limites + Conflits + Disponibilité lettres) for (let i = 0; i = GRID_SIZE || cc = GRID_SIZE) return false; let cell = state.grid[cr][cc]; if (cell) { if (cell.letter !== word[i]) return false; } else { let idx = tempHand.indexOf(word[i]); if (idx === -1) return false; tempHand.splice(idx, 1); lettersNeeded++; } } if (lettersNeeded === 0) return false; // Ne doit pas utiliser que des lettres du plateau // 2. Vérification Mots Croisés for (let i = 0; i = 0 && nc >= 0 && state.grid[nr][nc]) { perpWord = state.grid[nr][nc].letter + perpWord; pBefore++; } else break; } // Scan Avant while (true) { let nr = isHorizontal ? cr + pAfter : cr; let nc = isHorizontal ? cc : cc + pAfter; if (nr 1) { if (!FULL_DICTIONARY.includes(perpWord)) return false; } } return true; } // Système d'indice intelligent function giveSmartHint() { const player = state.players[state.currentPlayerIdx]; const hand = player.hand.map(l => l.letter); const isGridEmpty = state.grid[4][4] === null; let found = null; const shuffledDict = [...FULL_DICTIONARY].sort(() => Math.random() - 0.5); if (isGridEmpty) { for (let word of shuffledDict) { let tempHand = [...hand]; let possible = true; for(let char of word) { let idx = tempHand.indexOf(char); if(idx !== -1) tempHand.splice(idx, 1); else { possible = false; break; } } if(!possible) continue; // Essayer de le placer pour qu'il touche le centre for(let i = 0; i
w.includes(anchor)); for (let word of candidates) { for (let i = 0; i
${letter}
${score}
`; } function render() { const p1 = state.players[0]; const p2 = state.players[1]; const currentP = state.players[state.currentPlayerIdx]; const isP1 = state.currentPlayerIdx === 0; // Scores & Header Styles document.getElementById('p1-score').textContent = p1.score; document.getElementById('p2-score').textContent = p2.score; const p1Box = document.getElementById('p1-box'); const p2Box = document.getElementById('p2-box'); if(isP1) { p1Box.className = \"flex flex-col items-center p-2 px-4 rounded-xl transition-all duration-300 bg-pink-100 ring-4 ring-pink-300 scale-110 shadow-md z-10\"; p2Box.className = \"flex flex-col items-center p-2 px-4 rounded-xl transition-all duration-300 opacity-40 grayscale scale-90\"; } else { p2Box.className = \"flex flex-col items-center p-2 px-4 rounded-xl transition-all duration-300 bg-blue-100 ring-4 ring-blue-300 scale-110 shadow-md z-10\"; p1Box.className = \"flex flex-col items-center p-2 px-4 rounded-xl transition-all duration-300 opacity-40 grayscale scale-90\"; } // Opponent Rack const otherP = isP1 ? p2 : p1; document.getElementById('opponent-rack').innerHTML = otherP.hand.map(l => `
${l.letter}
` ).join(''); // Board const boardEl = document.getElementById('board'); boardEl.innerHTML = ''; for(let r=0; r
t.r === r && t.c === c); const content = cellData || tempData; const special = SPECIAL_CELLS[`${r},${c}`]; let bgClass = \"bg-white\"; if(special === 'TW') bgClass = \"bg-red-200 text-red-600\"; else if(special === 'DL') bgClass = \"bg-blue-200 text-blue-600\"; else if(special === 'DW') bgClass = \"bg-pink-300 text-pink-700\"; else if(special === 'CENTER') bgClass = \"bg-pink-400 text-white\"; if(content) bgClass = \"bg-amber-100\"; cell.className = `w-full h-full aspect-square border-2 border-pink-200 rounded-md flex items-center justify-center relative ${bgClass} ${(!content && state.selectedHandIndex !== null) ? 'cursor-pointer hover:bg-pink-100 animate-pulse' : ''}`; cell.onclick = () => handleGridClick(r, c); if(!content && special) { cell.innerHTML = `
${CELL_LABELS[special]}
`; } if(content) { cell.innerHTML = renderTile(content.letter, content.score, !!tempData, false); } boardEl.appendChild(cell); } } // Validate Button State const valBtn = document.getElementById('btn-validate'); const canValidate = state.tempPlacements.length > 0; if(canValidate) { valBtn.className = `flex-1 py-3 rounded-2xl shadow-md font-bold text-white text-lg flex items-center justify-center gap-2 transition-all active:scale-95 duration-300 ${isP1 ? 'bg-gradient-to-r from-pink-400 to-pink-500 shadow-pink-200' : 'bg-gradient-to-r from-blue-400 to-blue-500 shadow-blue-200'}`; } else { valBtn.className = \"flex-1 py-3 rounded-2xl shadow-md font-bold text-white text-lg flex items-center justify-center gap-2 transition-all active:scale-95 duration-300 bg-gray-300 cursor-not-allowed\"; } // Player Rack const playerArea = document.getElementById('player-area'); const rackLabel = document.getElementById('rack-label'); const indicator = document.getElementById('turn-indicator'); playerArea.className = `fixed bottom-0 w-full bg-white border-t-4 p-4 pb-6 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)] transition-colors duration-500 ${isP1 ? 'border-pink-200' : 'border-blue-200'}`; rackLabel.textContent = `LETTRES DE ${currentP.name}`; rackLabel.className = `text-xs font-bold uppercase tracking-widest transition-colors duration-300 ${isP1 ? 'text-pink-500' : 'text-blue-500'}`; indicator.className = `w-3 h-3 rounded-full shadow-sm transition-colors duration-300 ${isP1 ? 'bg-pink-500' : 'bg-blue-500'}`; const handEl = document.getElementById('player-hand'); handEl.innerHTML = currentP.hand.map((tile, i) => `
${renderTile(tile.letter, tile.score, false, state.selectedHandIndex === i)}
`).join('') + Array(7 - currentP.hand.length).fill('
').join(''); // Icons refresh lucide.createIcons(); } // Init startNewGame();