diff --git a/app/Helpers/RoomHelper.php b/app/Helpers/RoomHelper.php index e9f6bb9..e9d3cc6 100644 --- a/app/Helpers/RoomHelper.php +++ b/app/Helpers/RoomHelper.php @@ -1,12 +1,15 @@ session()->get('current_room', 1); - $completedRooms = $request->session()->get('completed_rooms', []); + $progress = RoomProgress::getProgress(); + + $currentRoom = $progress->current_room; + $completedRooms = $progress->completed_rooms; return $roomId === $currentRoom || in_array($roomId, $completedRooms); } diff --git a/app/Http/Controllers/Api/RoomProgressController.php b/app/Http/Controllers/Api/RoomProgressController.php index e546989..1980d9f 100644 --- a/app/Http/Controllers/Api/RoomProgressController.php +++ b/app/Http/Controllers/Api/RoomProgressController.php @@ -21,7 +21,7 @@ public function show() public function complete(Request $request) { $request->validate([ - 'roomId' => 'required|integer|min:1|max:10', + 'roomId' => 'required|integer|min:1|max:9', ]); $roomId = $request->input('roomId'); @@ -49,6 +49,7 @@ public function complete(Request $request) 'currentRoom' => $progress->current_room, 'completedRooms' => $progress->completed_rooms, 'message' => 'Salle complétée avec succès', + 'allRoomsCompleted' => count($progress->completed_rooms) === 9, ]); } diff --git a/app/Models/RoomProgress.php b/app/Models/RoomProgress.php index a4929e2..61499e9 100644 --- a/app/Models/RoomProgress.php +++ b/app/Models/RoomProgress.php @@ -41,13 +41,13 @@ public function completeRoom(int $roomId): bool } // Ajouter la salle aux salles complétées si pas déjà dedans - $completedRooms = $this->completed_rooms; + $completedRooms = (array) $this->completed_rooms; if (! in_array($roomId, $completedRooms)) { $completedRooms[] = $roomId; } - // Passer à la salle suivante (max 10 salles) - $nextRoom = $roomId < 10 ? $roomId + 1 : $roomId; + // Passer à la salle suivante (max 9 salles) + $nextRoom = $roomId < 9 ? $roomId + 1 : $roomId; $this->update([ 'completed_rooms' => $completedRooms, diff --git a/laravel b/laravel index 1c31bd8..772dd2b 100644 Binary files a/laravel and b/laravel differ diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx index afb994a..eebd5de 100644 --- a/resources/js/pages/dashboard.tsx +++ b/resources/js/pages/dashboard.tsx @@ -6,13 +6,12 @@ const rooms = [ { id: 1, name: "La salle des Lumières", status: "locked", icon: "💡" }, { id: 2, name: "La Salle des Symboles", status: "locked", icon: "🔣" }, { id: 3, name: "La Bibliothèque mystérieuse", status: "locked", icon: "📚" }, - { id: 4, name: "Le Labyrinthe", status: "locked", icon: "🌀" }, - { id: 5, name: "La Salle du Code", status: "locked", icon: "🔐" }, - { id: 6, name: "Le Puzzle de cable", status: "locked", icon: "🔌" }, - { id: 7, name: "Après la pluie voila le soleil", status: "locked", icon: "🌈" }, - { id: 8, name: "Le Protocole Simon", status: "locked", icon: "🎮" }, - { id: 9, name: "Le Plateau Mystère", status: "locked", icon: "🎲" }, - { id: 10, name: "La Synchronisation Finale", status: "locked", icon: "⚡" }, + { id: 4, name: "La Grille Mystérieuse", status: "locked", icon: "🎨" }, + { id: 5, name: "Le Puzzle de cable", status: "locked", icon: "🔌" }, + { id: 6, name: "Le Mot Mêlé du Manoir", status: "locked", icon: "🔤" }, + { id: 7, name: "Le Protocole Simon", status: "locked", icon: "🎮" }, + { id: 8, name: "Le Plateau Mystère", status: "locked", icon: "🎲" }, + { id: 9, name: "Le Coffre-Fort du Voleur", status: "locked", icon: "🔐" }, ]; interface RoomProgress { @@ -150,19 +149,21 @@ export default function Dashboard() { {/* Actions */} - {room.id === currentRoom && ( + {isRoomAccessible(room.id) && (
- - OUVRIR CANAL SÉCURISÉ - + {room.id === currentRoom && ( + + OUVRIR CANAL SÉCURISÉ + + )} - ENTRER DANS LA SALLE → + {completedRooms.includes(room.id) ? 'REVOIR LA SALLE ↻' : 'ENTRER DANS LA SALLE →'}
)} diff --git a/resources/js/pages/rooms/room-1.tsx b/resources/js/pages/rooms/room-1.tsx index c597852..3e06e5d 100644 --- a/resources/js/pages/rooms/room-1.tsx +++ b/resources/js/pages/rooms/room-1.tsx @@ -1,16 +1,15 @@ import { Head, Link } from '@inertiajs/react'; -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import ChatPanel from '@/components/ChatPanel'; import CompleteRoomButton from '@/components/CompleteRoomButton'; import { Message } from '@/types'; -// Code Morse: "LUMIERE" = .-.. ..- -- .. . .-. . +// Code Morse: "OEUVRE" = --- . ..- ...- .-. . const morseCode = [ - { char: 'L', pattern: [1, 0, 2, 0, 1, 0, 1] }, // .-.. - { char: 'U', pattern: [1, 0, 1, 0, 2] }, // ..- - { char: 'M', pattern: [2, 0, 2] }, // -- - { char: 'I', pattern: [1, 0, 1] }, // .. + { char: 'O', pattern: [2, 0, 2, 0, 2] }, // --- { char: 'E', pattern: [1] }, // . + { char: 'U', pattern: [1, 0, 1, 0, 2] }, // ..- + { char: 'V', pattern: [1, 0, 1, 0, 1, 0, 2] }, // ...- { char: 'R', pattern: [1, 0, 2, 0, 1] }, // .-. { char: 'E', pattern: [1] }, // . ]; @@ -62,7 +61,7 @@ export default function Room1({ messages, sessionId }: { messages: Message[]; se const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - if (userInput.toUpperCase() === 'LUMIERE') { + if (userInput.toUpperCase() === 'OEUVRE') { setSolved(true); } else { alert('Code incorrect ! Observez bien les lumières.'); @@ -100,18 +99,19 @@ export default function Room1({ messages, sessionId }: { messages: Message[]; se

- "Décodez le message lumineux..." + "Chaque clignotement éclaire un secret."

{/* Instructions */}

📋 INSTRUCTIONS :

-

Joueur WEB : Observez les lumières qui clignotent

+

Agent Wilson (WEB) : Observez les lumières qui clignotent

• Lumière COURTE = point (.)

• Lumière LONGUE = trait (-)

Pause = nouvelle lettre

-

• Décrivez les signaux au joueur MOBILE qui décode le morse

+

• Décrivez précisément les signaux à Agent Owen (MOBILE)

+

Agent Owen possède le tableau de décodage Morse

@@ -136,18 +136,6 @@ export default function Room1({ messages, sessionId }: { messages: Message[]; se - {/* Morse Reference */} -
-
- 📖 AIDE: Table du code Morse -
- {Object.entries(morseToText).map(([morse, letter]) => ( -
{letter}: {morse}
- ))} -
-
-
- {/* Input form */} {!solved && (
@@ -179,10 +167,10 @@ export default function Room1({ messages, sessionId }: { messages: Message[]; se ✨ CODE DÉCODÉ! ✨

- Message: LUMIERE + Message: OEUVRE

-

- Code de la salle: 4729 +

+ "L'art est lumière, et la lumière guide les esprits."

)} diff --git a/resources/js/pages/rooms/room-10.tsx b/resources/js/pages/rooms/room-10.tsx deleted file mode 100644 index b9e4507..0000000 --- a/resources/js/pages/rooms/room-10.tsx +++ /dev/null @@ -1,301 +0,0 @@ -import { Head, Link } from '@inertiajs/react'; -import { useState, useEffect } from 'react'; -import ChatPanel from '@/components/ChatPanel'; -import CompleteRoomButton from '@/components/CompleteRoomButton'; -import { Message } from '@/types'; - -interface Module { - id: number; - name: string; - icon: string; - activated: boolean; -} - -const initialModules: Module[] = [ - { id: 1, name: 'Lumière', icon: '💡', activated: false }, - { id: 2, name: 'Mouvement', icon: '🏃', activated: false }, - { id: 3, name: 'Communication', icon: '📡', activated: false }, - { id: 4, name: 'Stabilité', icon: '⚖️', activated: false }, -]; - -export default function Room10({ messages, sessionId }: { messages: Message[]; sessionId: string }) { - const [modules, setModules] = useState(initialModules); - const [timeLeft, setTimeLeft] = useState(60); // 60 seconds countdown - const [timerRunning, setTimerRunning] = useState(false); - const [solved, setSolved] = useState(false); - - useEffect(() => { - let interval: NodeJS.Timeout; - if (timerRunning && timeLeft > 0 && !solved) { - interval = setInterval(() => { - setTimeLeft((prev) => { - if (prev <= 1) { - setTimerRunning(false); - return 0; - } - return prev - 1; - }); - }, 1000); - } - return () => clearInterval(interval); - }, [timerRunning, timeLeft, solved]); - - const startTimer = () => { - if (!timerRunning && timeLeft > 0) { - setTimerRunning(true); - } - }; - - const toggleModule = (id: number) => { - if (solved) return; - - const newModules = modules.map(m => - m.id === id ? { ...m, activated: !m.activated } : m - ); - setModules(newModules); - - // Start timer on first module activation - if (!timerRunning && newModules.some(m => m.activated)) { - setTimerRunning(true); - } - - // Check if all modules are activated - if (newModules.every(m => m.activated)) { - setSolved(true); - setTimerRunning(false); - } - }; - - const reset = () => { - setModules(initialModules); - setTimeLeft(60); - setTimerRunning(false); - setSolved(false); - }; - - const formatTime = (seconds: number) => { - const mins = Math.floor(seconds / 60); - const secs = seconds % 60; - return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; - }; - - return ( - <> - -
- {/* Pulsing energy waves */} -
- {Array.from({ length: 4 }, (_, i) => ( -
-
-
- ))} -
- - {/* Floating gears */} -
- {Array.from({ length: 6 }, (_, i) => ( -
- ⚙ -
- ))} -
- - {/* Header */} -
-
-
- - - SALLE 10: LA SYNCHRONISATION FINALE - -
-
- - {'<'} RETOUR - -
-
-
- - {/* Main content */} -
-
-
-

- ⚡ LA SYNCHRONISATION FINALE -

- -

- "Activez tous les modules pour réussir la synchronisation..." -

- - {/* Instructions */} -
-

📋 INSTRUCTIONS :

-
-

Activez les 4 modules pour réussir la synchronisation finale

-

• Cliquez sur chaque module pour l'activer (il s'illuminera)

-

• Vous avez 60 secondes pour activer tous les modules

-

• Les deux joueurs doivent coordonner leurs actions

-
-
- - {/* Timer */} - {!solved && ( -
-
- {formatTime(timeLeft)} -
-
TEMPS RESTANT
- {timeLeft === 0 && ( -
-

⏰ TEMPS ÉCOULÉ!

- -
- )} -
- )} - - {/* Progress Indicators */} -
- {modules.map((module) => ( -
- {module.activated ? '✓' : module.id} -
- ))} -
- - {/* Modules Grid */} -
-

- MODULES DE SYNCHRONISATION -

- -
- {modules.map((module) => ( - - ))} -
- - {!solved && timeLeft > 0 && ( -
-

- {modules.filter(m => m.activated).length}/4 modules activés -

-
- )} -
- - {/* Success */} - {solved && ( -
-
- ✨ SYNCHRONISATION PARFAITE ✨ -
-
-

- Les deux mondes ne font plus qu'un. -

-

- Tous les modules sont synchronisés! -

-

- Temps: {formatTime(60 - timeLeft)} -

-

- Code final: SYNC-0000 -

-
-
- - MISSION ACCOMPLIE → - -
-
- )} - -
- ⚡ "L'union fait la force... La synchronisation parfaite." -
-
-
-
- - -
- - - {solved && } - - ); -} diff --git a/resources/js/pages/rooms/room-2.tsx b/resources/js/pages/rooms/room-2.tsx index 050e2ba..841d329 100644 --- a/resources/js/pages/rooms/room-2.tsx +++ b/resources/js/pages/rooms/room-2.tsx @@ -4,12 +4,13 @@ import ChatPanel from '@/components/ChatPanel'; import CompleteRoomButton from '@/components/CompleteRoomButton'; import { Message } from '@/types'; +// Chaque symbole correspond à une lettre pour former le mot "VOLER" const symbols = [ - { id: 1, symbol: '☯', color: '#3B82F6', rotation: 0, pattern: 'circle' }, - { id: 2, symbol: '⚛', color: '#EF4444', rotation: 45, pattern: 'atom' }, - { id: 3, symbol: '✡', color: '#10B981', rotation: 0, pattern: 'star' }, - { id: 4, symbol: '☪', color: '#F59E0B', rotation: 90, pattern: 'crescent' }, - { id: 5, symbol: '☸', color: '#8B5CF6', rotation: 180, pattern: 'wheel' }, + { id: 1, symbol: '▲', color: '#EF4444', rotation: 0, pattern: 'triangle', letter: 'V', description: 'Une forme à trois côtés, pointant vers le ciel, teintée de sang écarlate.' }, + { id: 2, symbol: '👁', color: '#3B82F6', rotation: 0, pattern: 'eye', letter: 'O', description: 'L\'observateur éternel qui ne cligne jamais, couleur de l\'océan profond.' }, + { id: 3, symbol: '🔑', color: '#F59E0B', rotation: 45, pattern: 'key', letter: 'L', description: 'Celle qui ouvre toutes les portes, forgée dans l\'or du soleil, inclinée à mi-chemin.' }, + { id: 4, symbol: '🪶', color: '#8B5CF6', rotation: 90, pattern: 'feather', letter: 'E', description: 'Légère comme l\'air, instrument de l\'écrivain, violette comme l\'améthyste, tournée d\'un quart de tour.' }, + { id: 5, symbol: '🔥', color: '#F97316', rotation: 0, pattern: 'flame', letter: 'R', description: 'Danse ardente couleur de braise, source de chaleur, destructrice et créatrice.' }, ]; export default function Room2({ messages, sessionId, emojiOnly = false }: { messages: Message[]; sessionId: string; emojiOnly?: boolean }) { @@ -18,11 +19,12 @@ export default function Room2({ messages, sessionId, emojiOnly = false }: { mess const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - const correctCode = 'SYMBOLE-5432'; - if (userInput.toUpperCase() === correctCode) { + // Le mot correct est formé par les 5 symboles dans l'ordre : V-O-L-E-R + const correctWord = symbols.map(s => s.letter).join(''); + if (userInput.toUpperCase() === correctWord) { setSolved(true); } else { - alert('Code incorrect ! Décrivez bien tous les symboles.'); + alert('Mot incorrect ! Vérifiez la correspondance symbole-lettre.'); setUserInput(''); } }; @@ -57,52 +59,49 @@ export default function Room2({ messages, sessionId, emojiOnly = false }: { mess

- "Observez les symboles sacrés..." + "Les formes parlent à ceux qui savent observer."

{/* Instructions */}

📋 INSTRUCTIONS :

-

Joueur WEB : Décrivez les symboles au joueur MOBILE

-

• Notez la couleur, l'orientation et le motif

-

Joueur MOBILE : Trouvez la bonne suite dans votre manuel

-

• Les détails sont cruciaux pour trouver le code

+

Agent Wilson (WEB) : Lit les 5 descriptions du parchemin ancien

+

Déchiffrez chaque description pour identifier le symbole

+

• Transmettez ces symboles dans l'ordre à Agent Owen (MOBILE)

+

Agent Owen possède une feuille avec la correspondance Symbole → Lettre

+

• Les 5 lettres forment un mot de 5 lettres à entrer ci-dessous

{emojiOnly && (

⚠️ MODE EMOJI UNIQUEMENT - Communication par emojis seulement !

)}
- {/* Symbols Display */} + {/* Symbols Display - Descriptions */}
-
+

+ PARCHEMIN ANCIEN +

+
{symbols.map((item) => ( -
-
-
- {item.symbol} +
+
+
+ {item.id}. +
+
+

+ {item.description} +

-
-
- SYMBOLE {item.id} -
-
-
Couleur:
-
Rotation: {item.rotation}°
-
Motif: {item.pattern}
))}
+
+ "Décrivez ces symboles à votre partenaire qui détient la clé visuelle..." +
@@ -111,16 +110,20 @@ export default function Room2({ messages, sessionId, emojiOnly = false }: { mess
setUserInput(e.target.value)} - className="w-full px-4 py-3 bg-gray-900 border-2 border-yellow-600 text-yellow-400 text-center text-xl font-bold uppercase focus:outline-none focus:border-yellow-400" - placeholder="SYMBOLE-XXXX" + className="w-full px-4 py-3 bg-gray-900 border-2 border-yellow-600 text-yellow-400 text-center text-3xl font-bold uppercase focus:outline-none focus:border-yellow-400 tracking-widest" + placeholder="_ _ _ _ _" + maxLength={5} />
+

+ Utilisez la correspondance Symbole → Lettre d'Agent Owen +

-

- Code de la salle: SYMBOLE-5432 +

+ Mot trouvé: {symbols.map(s => s.letter).join('')} +

+

+ "Le verbe qui décrit l'action du Collectionneur..." +

+

+ Un compartiment s'ouvre. La porte suivante est déverrouillée.

- - TRANSMETTRE LE CODE → -
)}
- - + {solved && } ); diff --git a/resources/js/pages/rooms/room-6.tsx b/resources/js/pages/rooms/room-6.tsx index 6f70dcd..df6197c 100644 --- a/resources/js/pages/rooms/room-6.tsx +++ b/resources/js/pages/rooms/room-6.tsx @@ -4,92 +4,254 @@ import ChatPanel from '@/components/ChatPanel'; import CompleteRoomButton from '@/components/CompleteRoomButton'; import { Message } from '@/types'; -interface CableConnection { - cable: string; - color: string; - terminal: string | null; -} +// Grille 10x10 avec 6 mots cachés (sans chevauchement) +const grid = [ + ['M', 'A', 'N', 'O', 'I', 'R', 'X', 'P', 'Q', 'Z'], + ['B', 'C', 'D', 'E', 'F', 'G', 'H', 'A', 'I', 'Y'], + ['V', 'O', 'L', 'E', 'U', 'R', 'I', 'R', 'J', 'M'], + ['K', 'L', 'M', 'N', 'O', 'P', 'Q', 'I', 'K', 'U'], + ['R', 'S', 'T', 'U', 'V', 'W', 'X', 'S', 'L', 'S'], + ['Y', 'Z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'E'], + ['J', 'A', 'R', 'D', 'I', 'N', 'H', 'I', 'J', 'E'], + ['K', 'L', 'O', 'U', 'V', 'R', 'E', 'R', 'S', 'T'], + ['U', 'V', 'W', 'X', 'Y', 'Z', 'A', 'B', 'C', 'D'], + ['E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N'] +]; -const cables = [ - { name: 'Rouge', color: 'bg-red-500', textColor: 'text-red-500', correctTerminal: 'B' }, - { name: 'Bleu', color: 'bg-blue-500', textColor: 'text-blue-500', correctTerminal: 'D' }, - { name: 'Jaune', color: 'bg-yellow-400', textColor: 'text-yellow-400', correctTerminal: 'A' }, - { name: 'Vert', color: 'bg-green-500', textColor: 'text-green-500', correctTerminal: 'E' }, - { name: 'Orange', color: 'bg-orange-500', textColor: 'text-orange-500', correctTerminal: 'C' }, +// 6 mots à trouver avec leurs positions et couleur associée +const wordsToFind = [ + { + word: 'MANOIR', + positions: [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [0, 5]], + color: 'Rouge', + colorClass: 'bg-red-500', + letter: 'M', + found: false + }, + { + word: 'VOLEUR', + positions: [[2, 0], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5]], + color: 'Vert', + colorClass: 'bg-green-500', + letter: 'V', + found: false + }, + { + word: 'PARIS', + positions: [[0, 7], [1, 7], [2, 7], [3, 7], [4, 7]], + color: 'Bleu', + colorClass: 'bg-blue-500', + letter: 'P', + found: false + }, + { + word: 'MUSEE', + positions: [[2, 9], [3, 9], [4, 9], [5, 9], [6, 9]], + color: 'Jaune', + colorClass: 'bg-yellow-400', + letter: 'M', + found: false + }, + { + word: 'JARDIN', + positions: [[6, 0], [6, 1], [6, 2], [6, 3], [6, 4], [6, 5]], + color: 'Orange', + colorClass: 'bg-orange-500', + letter: 'J', + found: false + }, + { + word: 'LOUVRE', + positions: [[7, 1], [7, 2], [7, 3], [7, 4], [7, 5], [7, 6]], + color: 'Violet', + colorClass: 'bg-purple-500', + letter: 'L', + found: false + } ]; -const terminals = ['A', 'B', 'C', 'D', 'E']; +// Ordre correct des couleurs (Owen a cette info) +const correctColorOrder = ['Orange', 'Vert', 'Bleu', 'Jaune', 'Rouge', 'Violet']; // Donne J-V-P-M-M-L export default function Room6({ messages, sessionId, emojiOnly = false }: { messages: Message[]; sessionId: string; emojiOnly?: boolean }) { - const [connections, setConnections] = useState( - cables.map(c => ({ cable: c.name, color: c.color, terminal: null })) - ); + // Phase 0: Saisie des 6 mots + const [wordsUnlocked, setWordsUnlocked] = useState(false); + const [inputWords, setInputWords] = useState(['', '', '', '', '', '']); + + // Phase 1: Recherche dans la grille + const [selectedCells, setSelectedCells] = useState([]); + const [foundWords, setFoundWords] = useState(wordsToFind.map(w => ({ ...w }))); + const [allWordsFound, setAllWordsFound] = useState(false); + + // Phase 2: Ordre des couleurs et code final + const [colorOrder, setColorOrder] = useState([]); + const [finalCode, setFinalCode] = useState(''); const [solved, setSolved] = useState(false); - const [selectedCable, setSelectedCable] = useState(null); - const connectCable = (terminal: string) => { - if (selectedCable === null) return; + // Phase 0: Validation des 6 mots saisis + const validateInputWords = () => { + const normalizedInputs = inputWords.map(w => w.trim().toUpperCase()); + const correctWords = wordsToFind.map(w => w.word); - const newConnections = [...connections]; - newConnections[selectedCable].terminal = terminal; - setConnections(newConnections); - setSelectedCable(null); - }; + // Vérifier que chaque mot saisi est dans la liste des mots corrects + const allCorrect = normalizedInputs.every(input => correctWords.includes(input)); + + // Vérifier qu'il n'y a pas de doublons + const uniqueInputs = new Set(normalizedInputs); - const checkSolution = () => { - // Check if all cables are connected - const allConnected = connections.every(c => c.terminal !== null); - if (!allConnected) { - alert('Connectez tous les câbles avant de vérifier !'); + if (normalizedInputs.some(w => w === '')) { + alert('Veuillez saisir les 6 mots !'); return; } - // Check if all connections are correct - let isCorrect = true; - connections.forEach((conn, index) => { - if (conn.terminal !== cables[index].correctTerminal) { - isCorrect = false; + if (uniqueInputs.size !== 6) { + alert('Vous avez saisi des doublons ! Chaque mot doit être différent.'); + return; + } + + if (!allCorrect) { + alert('Au moins un mot est incorrect ! Owen vous a dicté les bons mots.'); + return; + } + + if (allCorrect && uniqueInputs.size === 6) { + setWordsUnlocked(true); + } + }; + + // Phase 1: Recherche dans la grille + const getCellKey = (row: number, col: number) => `${row}-${col}`; + + const isCellSelected = (row: number, col: number) => { + return selectedCells.includes(getCellKey(row, col)); + }; + + const isCellInFoundWord = (row: number, col: number) => { + return foundWords.some(word => + word.found && word.positions.some(([r, c]) => r === row && c === col) + ); + }; + + const handleCellClick = (row: number, col: number) => { + if (allWordsFound) return; + + const cellKey = getCellKey(row, col); + const newSelected = isCellSelected(row, col) + ? selectedCells.filter(key => key !== cellKey) + : [...selectedCells, cellKey]; + + setSelectedCells(newSelected); + }; + + const checkWord = () => { + if (selectedCells.length === 0) return; + + const selectedPositions = selectedCells.map(key => { + const [row, col] = key.split('-').map(Number); + return [row, col]; + }); + + let wordFound = false; + const newFoundWords = foundWords.map(wordObj => { + if (wordObj.found) return wordObj; + + const allPositionsSelected = wordObj.positions.every(([r, c]) => + selectedPositions.some(([sr, sc]) => sr === r && sc === c) + ); + + if (allPositionsSelected && selectedPositions.length === wordObj.positions.length) { + wordFound = true; + return { ...wordObj, found: true }; } + + return wordObj; }); - if (isCorrect) { - setSolved(true); + if (wordFound) { + setFoundWords(newFoundWords); + setSelectedCells([]); + + if (newFoundWords.every(w => w.found)) { + setAllWordsFound(true); + } } else { - alert('Connexions incorrectes ! Vérifiez le schéma.'); + alert('Ce n\'est pas un mot valide ! Réessayez.'); + setSelectedCells([]); } }; - const reset = () => { - setConnections(cables.map(c => ({ cable: c.name, color: c.color, terminal: null }))); - setSelectedCable(null); + const resetSelection = () => { + setSelectedCells([]); + }; + + // Phase 2: Ordre des couleurs + const addColorToOrder = (color: string) => { + if (colorOrder.includes(color)) { + setColorOrder(colorOrder.filter(c => c !== color)); + } else { + setColorOrder([...colorOrder, color]); + } + }; + + const removeColorFromOrder = (index: number) => { + setColorOrder(colorOrder.filter((_, i) => i !== index)); + }; + + const checkFinalCode = () => { + if (colorOrder.length !== correctColorOrder.length) { + alert('Vous devez d\'abord ordonner toutes les couleurs !'); + return; + } + + const isOrderCorrect = colorOrder.every((color, index) => color === correctColorOrder[index]); + + if (!isOrderCorrect) { + alert('L\'ordre des couleurs est incorrect !'); + return; + } + + // Vérifier le code final entré + const correctCode = colorOrder.map(color => + foundWords.find(w => w.color === color)?.letter || '' + ).join(''); + + if (finalCode.toUpperCase() === correctCode) { + setSolved(true); + } else { + alert('Le code entré est incorrect ! Owen doit vous donner les bonnes lettres.'); + setFinalCode(''); + } }; return ( <> - -
- {/* Circuit pattern background */} -
- - - - - - - - - - - + +
+ {/* Floating letters background */} +
+ {Array.from({ length: 20 }, (_, i) => ( +
+ {String.fromCharCode(65 + Math.floor(Math.random() * 26))} +
+ ))}
{/* Header */}
- 🔌 + 🔤 - SALLE 6: LE PUZZLE DE CÂBLES + SALLE 6: LE MOT MÊLÉ DU MANOIR
@@ -103,131 +265,325 @@ export default function Room6({ messages, sessionId, emojiOnly = false }: { mess {/* Main content */}
-
-

- 🔌 LE PUZZLE DE CÂBLES +
+

+ 🔤 LE MOT MÊLÉ DU MANOIR

-

- "Connectez les câbles dans le bon ordre pour rétablir le circuit..." +

+ {!wordsUnlocked + ? "Owen vous dicte 6 mots à trouver..." + : !allWordsFound + ? "Trouvez les mots dans la grille..." + : "Ordonnez les couleurs et entrez le code final !"}

{/* Instructions */}

📋 INSTRUCTIONS :

-

Joueur WEB : Voit le panneau avec les câbles et terminaux

-

Joueur MOBILE : A le schéma indiquant les connexions correctes

-

• Cliquez sur un câble puis sur un terminal pour les connecter

-

• Connexions correctes : Rouge→B, Bleu→D, Jaune→A, Vert→E, Orange→C

+ {!wordsUnlocked ? ( + <> +

Agent Owen (MOBILE) : Possède la liste des 6 mots à trouver

+

Agent Wilson (WEB) : NE VOIT PAS la grille pour l'instant

+

• Owen dicte les 6 mots à Wilson

+

• Wilson saisit les 6 mots dans les champs ci-dessous

+

• Une fois validés, la grille apparaîtra !

+ + ) : !allWordsFound ? ( + <> +

Agent Wilson (WEB) : Voit maintenant la grille et cherche les 6 mots

+

Agent Owen (MOBILE) : Guide Wilson si besoin

+

• Cliquez sur les lettres pour sélectionner un mot

+

• Cliquez sur "VALIDER LE MOT" pour vérifier

+

• Chaque mot trouvé révèle une couleur (sans lettre visible)

+ + ) : ( + <> +

Agent Wilson (WEB) : Voit les 6 couleurs (SANS les lettres)

+

Agent Owen (MOBILE) : Possède :

+

→ La correspondance Couleur → Lettre pour chaque mot

+

→ L'ordre correct des couleurs

+

• Owen guide Wilson pour ordonner les couleurs

+

• Owen dicte les lettres correspondantes à Wilson

+

• Wilson entre le code final formé par ces lettres

+ + )} {emojiOnly && (

⚠️ MODE EMOJI UNIQUEMENT - Communication par emojis seulement !

)}
- {/* Puzzle */} -
-

- PANNEAU DE CONNEXIONS -

- -
- {/* Left side - Cables */} -
-

CÂBLES

- {connections.map((conn, index) => ( -
- -
- {conn.terminal && ( -
- )} -
-
- {conn.terminal || '?'} -
+ {/* PHASE 0: Saisie des 6 mots */} + {!wordsUnlocked && ( +
+

+ SAISISSEZ LES 6 MOTS +

+ +

+ Owen vous dicte les 6 mots présents dans la grille.
+ Saisissez-les ci-dessous pour débloquer la grille. +

+ +
+ {inputWords.map((word, index) => ( +
+ + { + const newWords = [...inputWords]; + newWords[index] = e.target.value.toUpperCase(); + setInputWords(newWords); + }} + className="w-full px-4 py-3 bg-gray-900 border-2 border-blue-600 text-blue-300 text-center text-xl font-bold uppercase focus:outline-none focus:border-blue-400" + placeholder="______" + />
))}
- {/* Right side - Terminals */} -
-

TERMINAUX

- {terminals.map((terminal) => ( +
+ +
+
+ )} + + {/* PHASE 1: Recherche dans la grille */} + {wordsUnlocked && !allWordsFound && ( + <> + {/* Progress */} +
+
+ Mots trouvés : + + {foundWords.filter(w => w.found).length} / {foundWords.length} + +
+
+ +
+

+ GRILLE DE LETTRES +

+ +
+
+ {grid.map((row, rowIndex) => ( +
+ {row.map((letter, colIndex) => ( + + ))} +
+ ))} +
+
+ +
- ))} + +
+ +
+ {foundWords.map((wordObj, index) => ( +
+
+ {wordObj.found ? '✓' : '○'} {wordObj.word} +
+ {wordObj.found && ( +
+ Couleur: {wordObj.color} +
+ )} +
+ ))} +
+
+ + )} + + {/* PHASE 2: Ordre des couleurs et code final */} + {allWordsFound && !solved && ( +
+

+ ÉTAPE 1 : ORDONNEZ LES COULEURS +

+ + {/* Available colors - NO LETTERS SHOWN */} +
+

Couleurs disponibles :

+
+ {foundWords.map((wordObj, index) => ( + + ))} +
+
+ + {/* Current order - NO LETTERS SHOWN */} +
+

Ordre actuel des couleurs :

+
+ {colorOrder.length === 0 ? ( + Owen vous dicte l'ordre des couleurs... + ) : ( + colorOrder.map((color, index) => { + const wordObj = foundWords.find(w => w.color === color); + return ( + + ); + }) + )} +
+
+ + {/* Code input - Phase 2 step 2 */} +
+

+ ÉTAPE 2 : ENTREZ LE CODE FINAL +

+

+ Owen possède la correspondance Couleur → Lettre.
+ Il vous dicte les 6 lettres correspondant à votre ordre de couleurs. +

+
+ + setFinalCode(e.target.value.toUpperCase())} + className="w-full px-4 py-3 bg-gray-900 border-2 border-yellow-600 text-yellow-400 text-center text-3xl font-bold uppercase focus:outline-none focus:border-yellow-400 placeholder-yellow-700 tracking-widest" + placeholder="_ _ _ _ _ _" + maxLength={6} + /> +
-
- {!solved && (
- )} +
+ )} - {solved && ( -
-
- ⚡ CIRCUIT ACTIVÉ! ⚡ -
-
-

Tous les câbles sont correctement connectés!

-

Séquence: B-D-A-E-C

-

Code révélé: CABLES-BDAEC

-
-
- - TRANSMETTRE LE CODE → - + {/* Success message */} + {solved && ( +
+
+ 🎉 CODE DÉCOUVERT ! 🎉 +
+
+

Tous les mots ont été trouvés et le code validé !

+
+ {colorOrder.map((color, index) => { + const wordObj = foundWords.find(w => w.color === color); + return ( +
+
{color}
+
{wordObj?.letter}
+
+ ); + })}
+

+ Code final : {finalCode} +

+

La collaboration parfaite révèle tous les secrets...

- )} -
+
+ )} -
- ⚡ "L'électricité suit toujours le bon chemin..." +
+ 🔍 "Seuls ensemble vous découvrirez la vérité..."
diff --git a/resources/js/pages/rooms/room-7.tsx b/resources/js/pages/rooms/room-7.tsx index f3c971d..f4cc729 100644 --- a/resources/js/pages/rooms/room-7.tsx +++ b/resources/js/pages/rooms/room-7.tsx @@ -1,101 +1,116 @@ import { Head, Link } from '@inertiajs/react'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import ChatPanel from '@/components/ChatPanel'; import CompleteRoomButton from '@/components/CompleteRoomButton'; import { Message } from '@/types'; -// 6x6 grid with rainbow colored letters spelling SOLEIL -const grid = [ - [ - { letter: 'A', color: '' }, - { letter: 'B', color: '' }, - { letter: 'S', color: 'text-red-500' }, // Rouge - S - { letter: 'D', color: '' }, - { letter: 'E', color: '' }, - { letter: 'F', color: '' } - ], - [ - { letter: 'G', color: '' }, - { letter: 'H', color: '' }, - { letter: 'I', color: '' }, - { letter: 'O', color: 'text-orange-500' }, // Orange - O - { letter: 'K', color: '' }, - { letter: 'M', color: '' } - ], - [ - { letter: 'N', color: '' }, - { letter: 'P', color: '' }, - { letter: 'Q', color: '' }, - { letter: 'R', color: '' }, - { letter: 'L', color: 'text-yellow-400' }, // Jaune - L - { letter: 'T', color: '' } - ], - [ - { letter: 'U', color: '' }, - { letter: 'E', color: 'text-green-500' }, // Vert - E - { letter: 'W', color: '' }, - { letter: 'X', color: '' }, - { letter: 'Y', color: '' }, - { letter: 'Z', color: '' } - ], - [ - { letter: 'A', color: '' }, - { letter: 'B', color: '' }, - { letter: 'I', color: 'text-blue-500' }, // Bleu - I - { letter: 'D', color: '' }, - { letter: 'E', color: '' }, - { letter: 'F', color: '' } - ], - [ - { letter: 'G', color: '' }, - { letter: 'H', color: '' }, - { letter: 'J', color: '' }, - { letter: 'L', color: 'text-purple-500' }, // Violet - L - { letter: 'M', color: '' }, - { letter: 'N', color: '' } - ] +const colors = [ + { name: 'red', bg: 'bg-red-600', active: 'bg-red-400', label: 'ROUGE' }, + { name: 'blue', bg: 'bg-blue-600', active: 'bg-blue-400', label: 'BLEU' }, + { name: 'green', bg: 'bg-green-600', active: 'bg-green-400', label: 'VERT' }, + { name: 'yellow', bg: 'bg-yellow-500', active: 'bg-yellow-300', label: 'JAUNE' }, ]; -const rainbowOrder = ['Rouge', 'Orange', 'Jaune', 'Vert', 'Bleu', 'Violet']; - export default function Room7({ messages, sessionId, emojiOnly = false }: { messages: Message[]; sessionId: string; emojiOnly?: boolean }) { + const [sequence, setSequence] = useState([]); + const [playerSequence, setPlayerSequence] = useState([]); + const [isPlaying, setIsPlaying] = useState(false); + const [activeButton, setActiveButton] = useState(null); + const [gameStarted, setGameStarted] = useState(false); const [solved, setSolved] = useState(false); - const [userInput, setUserInput] = useState(''); + const [failed, setFailed] = useState(false); + const [owenCodeRevealed, setOwenCodeRevealed] = useState(false); + const [wilsonCodeInput, setWilsonCodeInput] = useState(''); + + // Séquence unique de 8 signaux + const SEQUENCE_LENGTH = 8; + const CORRECT_CODE = 'SIMON-1978'; + + const generateSequence = (length: number) => { + const newSequence = []; + for (let i = 0; i < length; i++) { + newSequence.push(Math.floor(Math.random() * 4)); + } + return newSequence; + }; + + const playSequence = async (seq: number[]) => { + setIsPlaying(true); + setPlayerSequence([]); + + await new Promise(resolve => setTimeout(resolve, 1000)); + + for (const color of seq) { + setActiveButton(color); + await new Promise(resolve => setTimeout(resolve, 800)); + setActiveButton(null); + await new Promise(resolve => setTimeout(resolve, 400)); + } + + setIsPlaying(false); + }; + + const startGame = () => { + const newSequence = generateSequence(SEQUENCE_LENGTH); + setSequence(newSequence); + setGameStarted(true); + setFailed(false); + setPlayerSequence([]); + playSequence(newSequence); + }; + + const handleButtonClick = (index: number) => { + if (isPlaying || solved || !gameStarted) return; + + const newPlayerSequence = [...playerSequence, index]; + setPlayerSequence(newPlayerSequence); - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (userInput.toUpperCase() === 'SOLEIL') { + // Flash the button + setActiveButton(index); + setTimeout(() => setActiveButton(null), 300); + + // Check if player's move is correct + if (newPlayerSequence[newPlayerSequence.length - 1] !== sequence[newPlayerSequence.length - 1]) { + setFailed(true); + setGameStarted(false); + setPlayerSequence([]); + setTimeout(() => setFailed(false), 2000); + return; + } + + // Check if sequence is complete + if (newPlayerSequence.length === sequence.length) { + // Owen a réussi ! Révéler le code + setOwenCodeRevealed(true); + } + }; + + const validateWilsonCode = () => { + if (wilsonCodeInput.toUpperCase() === CORRECT_CODE) { setSolved(true); } else { - alert('Mot incorrect ! Suivez l\'ordre de l\'arc-en-ciel.'); - setUserInput(''); + alert('Code incorrect ! Owen doit vous dicter le bon code.'); + setWilsonCodeInput(''); } }; return ( <> - +
- {/* Rainbow gradient background */} -
-
-
- - {/* Floating rainbow symbols */} -
- {Array.from({ length: 12 }, (_, i) => ( + {/* Pulsing rings background */} +
+ {Array.from({ length: 5 }, (_, i) => (
- 🌈 -
+ /> ))}
@@ -103,9 +118,9 @@ export default function Room7({ messages, sessionId, emojiOnly = false }: { mess
- 🌈 + 🎮 - SALLE 7: MOT MÊLÉ ARC-EN-CIEL + SALLE 7: LE PROTOCOLE SIMON
@@ -118,148 +133,164 @@ export default function Room7({ messages, sessionId, emojiOnly = false }: { mess {/* Main content */}
-
-
-

- 🌈 MOT MÊLÉ ARC-EN-CIEL +
+
+

+ 🎮 LE PROTOCOLE SIMON

-

- "Les couleurs révèlent le mot caché..." +

+ "Mémorisez et répétez la séquence exacte..."

{/* Instructions */}

📋 INSTRUCTIONS :

-

Joueur WEB : Décrit les lettres colorées et leurs positions

-

Joueur MOBILE : Assemble les lettres dans l'ordre de l'arc-en-ciel

-

• Ordre des couleurs : Rouge, Orange, Jaune, Vert, Bleu, Violet

-

• Les lettres colorées forment un mot

+

Agent Wilson (WEB) : Lance le protocole et regarde la séquence de 8 couleurs

+

• Wilson dicte la séquence à Owen : "Rouge, Bleu, Vert..."

+

Agent Owen (MOBILE) : Clique sur les 4 boutons de couleur dans le bon ordre

+

• Quand Owen réussit les 8 signaux, un code lui est révélé

+

• Owen dicte le code à Wilson qui le saisit pour valider

{emojiOnly && (

⚠️ MODE EMOJI UNIQUEMENT - Communication par emojis seulement !

)}
- {/* Puzzle */} -
-

- GRILLE DE LETTRES -

+ {/* Zone Wilson (WEB) - Lancement de la séquence */} +
+
+

+ 💻 ZONE WILSON (WEB) - PROTOCOLE SIMON +

+

+ {!gameStarted && "Lancez le protocole pour voir la séquence"} + {isPlaying && "👁️ Regardez et dictez la séquence à Owen..."} + {gameStarted && !isPlaying && !failed && "Séquence diffusée ! Owen est en train de reproduire..."} + {failed && "Owen a fait une erreur ! Relancez le protocole"} +

+
- {/* Letter Grid */} -
-
- {grid.map((row, rowIndex) => ( -
- {row.map((cell, colIndex) => ( -
- {cell.letter} -
- ))} -
- ))} -
+
+ {colors.map((color, index) => ( +
+ + {color.label} + +
+ ))}
- {/* Rainbow color reference */} -
-

Ordre de l'arc-en-ciel :

-
- 1.Rouge - 2.Orange - 3.Jaune - 4.Vert - 5.Bleu - 6.Violet + {failed && ( +
+ ❌ Owen a fait une erreur !
+ )} + +
+ +
+
+ + {/* Zone Wilson (WEB) - Saisie du code final */} +
+
+

+ 💻 ZONE WILSON (WEB) - CODE FINAL +

+

+ {!solved && "Une fois qu'Owen a réussi, il vous dictera un code à saisir ici"} + {solved && "✓ Code validé !"} +

- {/* Input form */} {!solved && ( - -
- - setUserInput(e.target.value)} - className="w-full px-4 py-3 bg-gray-900 border-2 border-yellow-600 text-yellow-400 text-center text-xl font-bold uppercase focus:outline-none focus:border-yellow-400 placeholder-yellow-700" - placeholder="MOT" - maxLength={6} - /> +
+ + setWilsonCodeInput(e.target.value.toUpperCase())} + className="w-full px-4 py-4 bg-gray-900 border-2 border-purple-600 text-purple-400 text-center text-2xl font-bold uppercase focus:outline-none focus:border-purple-400 placeholder-purple-700 tracking-widest mb-4" + placeholder="ENTREZ LE CODE" + /> +
+
- - +
)} {solved && ( -
-
- 🌈 PARFAIT! 🌈 +
+
+ ✅ MISSION RÉUSSIE !
-

Mot trouvé: SOLEIL

-

- Lettres: S - O - L - E - I - L -

-

Code révélé: RAINBOW-7777

+

Owen a réussi le protocole Simon !

+

Wilson a validé le code avec succès !

+

Code validé :

+

{CORRECT_CODE}

- TRANSMETTRE LE CODE → + RETOUR AU DASHBOARD →
)}
-
- 🌈 "Après la pluie vient le beau temps..." +
+ 🧠 "La communication parfaite révèle tous les secrets..."
diff --git a/resources/js/pages/rooms/room-8.tsx b/resources/js/pages/rooms/room-8.tsx index d80b60a..da1275d 100644 --- a/resources/js/pages/rooms/room-8.tsx +++ b/resources/js/pages/rooms/room-8.tsx @@ -1,135 +1,103 @@ import { Head, Link } from '@inertiajs/react'; -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import ChatPanel from '@/components/ChatPanel'; import CompleteRoomButton from '@/components/CompleteRoomButton'; import { Message } from '@/types'; -const colors = [ - { name: 'red', bg: 'bg-red-600', active: 'bg-red-400', label: 'ROUGE' }, - { name: 'blue', bg: 'bg-blue-600', active: 'bg-blue-400', label: 'BLEU' }, - { name: 'green', bg: 'bg-green-600', active: 'bg-green-400', label: 'VERT' }, - { name: 'yellow', bg: 'bg-yellow-500', active: 'bg-yellow-300', label: 'JAUNE' }, +const objects = ['♚', '♛', '♜', '♝', '♞', '♟']; + +// Source board (référence - plateau rempli qu'Owen voit) +const sourceBoard = [ + ['♚', null, '♛', '♜'], + [null, '♝', null, '♞'], + ['♟', null, '♚', null], + ['♛', '♜', null, '♝'] ]; export default function Room8({ messages, sessionId, emojiOnly = false }: { messages: Message[]; sessionId: string; emojiOnly?: boolean }) { - const [sequence, setSequence] = useState([]); - const [playerSequence, setPlayerSequence] = useState([]); - const [isPlaying, setIsPlaying] = useState(false); - const [activeButton, setActiveButton] = useState(null); - const [gameStarted, setGameStarted] = useState(false); - const [level, setLevel] = useState(1); + // Le plateau CIBLE est celui qu'on reconstitue (Wilson) + const [targetBoard, setTargetBoard] = useState<(string | null)[][]>([ + [null, null, null, null], + [null, null, null, null], + [null, null, null, null], + [null, null, null, null] + ]); + const [selectedObject, setSelectedObject] = useState(null); const [solved, setSolved] = useState(false); - const [failed, setFailed] = useState(false); - - // Sequence lengths: Level 1 = 3, Level 2 = 5, Level 3 = 7 - const getSequenceLength = (lvl: number) => { - if (lvl === 1) return 3; - if (lvl === 2) return 5; - return 7; - }; - const generateSequence = (length: number) => { - const newSequence = []; - for (let i = 0; i < length; i++) { - newSequence.push(Math.floor(Math.random() * 4)); - } - return newSequence; - }; - - const playSequence = async (seq: number[]) => { - setIsPlaying(true); - setPlayerSequence([]); - - await new Promise(resolve => setTimeout(resolve, 500)); - - for (const color of seq) { - setActiveButton(color); - await new Promise(resolve => setTimeout(resolve, 600)); - setActiveButton(null); - await new Promise(resolve => setTimeout(resolve, 300)); - } + const placeObject = (row: number, col: number) => { + if (!selectedObject || solved) return; - setIsPlaying(false); + const newBoard = targetBoard.map(r => [...r]); + newBoard[row][col] = selectedObject; + setTargetBoard(newBoard); + setSelectedObject(null); }; - const startGame = () => { - const newSequence = generateSequence(getSequenceLength(1)); - setSequence(newSequence); - setGameStarted(true); - setLevel(1); - setFailed(false); - setPlayerSequence([]); - playSequence(newSequence); + const clearCell = (row: number, col: number) => { + if (solved) return; + const newBoard = targetBoard.map(r => [...r]); + newBoard[row][col] = null; + setTargetBoard(newBoard); }; - const handleButtonClick = (index: number) => { - if (isPlaying || solved || !gameStarted) return; - - const newPlayerSequence = [...playerSequence, index]; - setPlayerSequence(newPlayerSequence); - - // Flash the button - setActiveButton(index); - setTimeout(() => setActiveButton(null), 300); - - // Check if player's move is correct - if (newPlayerSequence[newPlayerSequence.length - 1] !== sequence[newPlayerSequence.length - 1]) { - setFailed(true); - setGameStarted(false); - setPlayerSequence([]); - setTimeout(() => setFailed(false), 2000); - return; + const checkSolution = () => { + // Compare targetBoard with sourceBoard + let isCorrect = true; + for (let i = 0; i < 4; i++) { + for (let j = 0; j < 4; j++) { + if (targetBoard[i][j] !== sourceBoard[i][j]) { + isCorrect = false; + break; + } + } + if (!isCorrect) break; } - // Check if sequence is complete - if (newPlayerSequence.length === sequence.length) { - if (level >= 3) { - // Game won! - setSolved(true); - } else { - // Next level - setTimeout(() => { - const nextLevel = level + 1; - setLevel(nextLevel); - const newSequence = generateSequence(getSequenceLength(nextLevel)); - setSequence(newSequence); - playSequence(newSequence); - }, 1000); - } + if (isCorrect) { + setSolved(true); + } else { + alert('Placement incorrect ! Vérifiez les positions.'); } }; + const reset = () => { + setTargetBoard([ + [null, null, null, null], + [null, null, null, null], + [null, null, null, null], + [null, null, null, null] + ]); + setSelectedObject(null); + }; + return ( <> - -
- {/* Pulsing rings background */} -
- {Array.from({ length: 5 }, (_, i) => ( -
- ))} + +
+ {/* Board game pattern background */} +
+
+ {Array.from({ length: 64 }, (_, i) => ( +
+ ))} +
{/* Header */}
- 🎮 - - SALLE 8: LE PROTOCOLE SIMON + 🎲 + + SALLE 8: LE PLATEAU MYSTÈRE
- + {'<'} RETOUR
@@ -138,24 +106,25 @@ export default function Room8({ messages, sessionId, emojiOnly = false }: { mess {/* Main content */}
-
-
-

- 🎮 LE PROTOCOLE SIMON +
+
+

+ 🎲 LE PLATEAU MYSTÈRE

-

- "Mémorisez et répétez la séquence exacte..." +

+ "Placez chaque objet au bon endroit..."

{/* Instructions */} -
+

📋 INSTRUCTIONS :

-

Joueur WEB : Observe la séquence et la décrit

-

Joueur MOBILE : Reproduit la séquence

-

• 3 niveaux : 3 boutons → 5 boutons → 7 boutons

-

• Mémorisez l'ordre et reproduisez-le exactement

+

Agent Owen (MOBILE) : Voit le plateau rempli et décrit les positions

+

Agent Wilson (WEB) : Place les objets selon les indications d'Owen

+

• Cliquez sur un objet de la palette puis sur une case du plateau

+

• Cliquez sur une case remplie pour la vider

+

• Placez tous les objets aux bonnes positions pour réussir

{emojiOnly && (

⚠️ MODE EMOJI UNIQUEMENT - Communication par emojis seulement !

)} @@ -163,71 +132,92 @@ export default function Room8({ messages, sessionId, emojiOnly = false }: { mess
{/* Puzzle */} -
-
-

- NIVEAU {level}/3 -

-

- {gameStarted && !isPlaying && !solved && `Entrez la séquence (${playerSequence.length}/${sequence.length})`} - {isPlaying && "👁️ Regardez et mémorisez la séquence..."} - {!gameStarted && !solved && "Cliquez sur DÉMARRER pour commencer"} - {solved && "✓ Protocole validé!"} -

-
- - {/* Simon game board */} -
- {colors.map((color, index) => ( - - ))} +
+
+ {/* Plateau à remplir */} +
+

+ PLATEAU À REMPLIR +

+
+ {targetBoard.map((row, rowIndex) => ( +
+ {row.map((cell, colIndex) => ( + + ))} +
+ ))} +
+
- {failed && ( -
- ❌ ERREUR! Recommencez... + {/* Object Palette */} +
+

+ PALETTE D'OBJETS +

+
+ {objects.map((obj, index) => ( + + ))}
- )} + {selectedObject && ( +

+ Objet sélectionné: {selectedObject} +

+ )} +
{!solved && ( -
+
+
)} {solved && ( -
-
- ✓ PROTOCOLE RÉUSSI! +
+
+ 🎉 PARFAIT! 🎉
-

Vous avez une mémoire exceptionnelle!

-

Les 3 niveaux sont complétés!

-

Code d'accès: SIMON-1978

+

Le plateau a été reproduit à l'identique!

+

Code révélé: PLATEAU-4444

TRANSMETTRE LE CODE → @@ -236,32 +226,20 @@ export default function Room8({ messages, sessionId, emojiOnly = false }: { mess )}
-
- 🧠 "La mémoire est la clé de tous les mystères..." +
+ 🎯 "Chaque pièce a sa place exacte..."
diff --git a/resources/js/pages/rooms/room-9.tsx b/resources/js/pages/rooms/room-9.tsx index a5d4711..b6f3dbf 100644 --- a/resources/js/pages/rooms/room-9.tsx +++ b/resources/js/pages/rooms/room-9.tsx @@ -1,102 +1,141 @@ import { Head, Link } from '@inertiajs/react'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import ChatPanel from '@/components/ChatPanel'; -import CompleteRoomButton from '@/components/CompleteRoomButton'; import { Message } from '@/types'; +import axios from 'axios'; -const objects = ['♔', '♕', '♖', '♗', '♘', '♙', '🎯', '🎲', '🎪', '⭐']; +// Énigmes pour Owen (MOBILE) - Chiffres 1, 2, 3 +const owenRiddles = [ + { + riddle: "Suite de Fibonacci cryptée:\n1, 1, 2, 3, 5, 8, ?, 21\n\nRéponse = chiffre des unités", + answer: 3 + }, + { + riddle: "Système crypté du voleur:\n\n🔑 + 💎 + 🏆 = 15\n💎 × 2 = 🏆\n🔑 × 🔑 = 💎 + 🏆 + 4\n\n🔑 = ?", + answer: 4 + }, + { + riddle: "Code des salles du manoir:\n\nSomme des numéros: 1+2+3+4+5+6+7+8 = ?\n\nFormule: n×(n+1)÷2 où n=8\n\nRéponse en chiffre unique (dernier chiffre)", + answer: 6 + } +]; -// Source board layout (4x4 with 10 objects placed) -const sourceBoard = [ - ['♔', null, '♕', '♖'], - [null, '♗', null, '♘'], - ['♙', null, '🎯', null], - ['🎲', '🎪', null, '⭐'] +// Énigmes pour Wilson (WEB) - Chiffres 4, 5, 6 +const wilsonRiddles = [ + { + riddle: "Code binaire du voleur:\n\n110 (base 2) = ? (base 10)\n\n1×4 + 1×2 + 0×1 = ?", + answer: 6 + }, + { + riddle: "L'horloge cryptée du manoir:\n\nAngle entre les aiguilles à 2h00 = 60°\n\nFormule: |30×H - 5.5×M|\nH=2, M=0\n\nRésultat ÷ 30 = ?", + answer: 2 + }, + { + riddle: "Le carré magique:\n\n2 7 6\n9 5 1\n4 3 ?\n\nChaque ligne/colonne/diagonale = 15\nTrouvez ?", + answer: 8 + } ]; -export default function Room9({ messages, sessionId, emojiOnly = false }: { messages: Message[]; sessionId: string; emojiOnly?: boolean }) { - const [targetBoard, setTargetBoard] = useState<(string | null)[][]>([ - [null, null, null, null], - [null, null, null, null], - [null, null, null, null], - [null, null, null, null] - ]); - const [selectedObject, setSelectedObject] = useState(null); +const CORRECT_CODE = "346628"; // Owen: 3,4,6 + Wilson: 6,2,8 +const MAX_ATTEMPTS = 5; + +export default function Room9({ messages, sessionId }: { messages: Message[]; sessionId: string }) { + const [codeInput, setCodeInput] = useState(''); + const [attempts, setAttempts] = useState<{ code: string; correct: number }[]>([]); const [solved, setSolved] = useState(false); + const [failed, setFailed] = useState(false); - const placeObject = (row: number, col: number) => { - if (!selectedObject || solved) return; + // Marquer automatiquement la salle comme complétée quand le code est correct + useEffect(() => { + if (solved) { + // Appeler l'API pour marquer la salle 9 comme complétée + axios.post('/api/room-progress/complete', { roomId: 9 }) + .catch(error => { + console.error('Erreur lors de la complétion de la salle:', error); + }); + } + }, [solved]); - const newBoard = targetBoard.map(r => [...r]); - newBoard[row][col] = selectedObject; - setTargetBoard(newBoard); - setSelectedObject(null); - }; + const checkCode = () => { + if (codeInput.length !== 6) { + alert('Le code doit contenir exactement 6 chiffres !'); + return; + } - const clearCell = (row: number, col: number) => { - if (solved) return; - const newBoard = targetBoard.map(r => [...r]); - newBoard[row][col] = null; - setTargetBoard(newBoard); - }; + if (codeInput === CORRECT_CODE) { + setSolved(true); + return; + } - const checkSolution = () => { - // Compare targetBoard with sourceBoard - let isCorrect = true; - for (let i = 0; i < 4; i++) { - for (let j = 0; j < 4; j++) { - if (targetBoard[i][j] !== sourceBoard[i][j]) { - isCorrect = false; - break; - } + // Count correct digits in correct positions + let correctCount = 0; + for (let i = 0; i < 6; i++) { + if (codeInput[i] === CORRECT_CODE[i]) { + correctCount++; } - if (!isCorrect) break; } - if (isCorrect) { - setSolved(true); - } else { - alert('Placement incorrect ! Vérifiez les positions.'); + const newAttempts = [...attempts, { code: codeInput, correct: correctCount }]; + setAttempts(newAttempts); + setCodeInput(''); + + if (newAttempts.length >= MAX_ATTEMPTS) { + setFailed(true); } }; const reset = () => { - setTargetBoard([ - [null, null, null, null], - [null, null, null, null], - [null, null, null, null], - [null, null, null, null] - ]); - setSelectedObject(null); + setCodeInput(''); + setAttempts([]); + setFailed(false); + setSolved(false); }; return ( <> - -
- {/* Board game pattern background */} -
-
- {Array.from({ length: 64 }, (_, i) => ( + +
+ {/* Vault pattern background */} +
+
+ {Array.from({ length: 100 }, (_, i) => (
))}
+ {/* Spinning locks */} +
+ {Array.from({ length: 6 }, (_, i) => ( +
+ 🔒 +
+ ))} +
+ {/* Header */}
- 🎲 - - SALLE 9: LE PLATEAU MYSTÈRE + 🔐 + + SALLE 9: LE COFFRE-FORT DU VOLEUR
- + {'<'} RETOUR
@@ -106,167 +145,251 @@ export default function Room9({ messages, sessionId, emojiOnly = false }: { mess {/* Main content */}
-
-

- 🎲 LE PLATEAU MYSTÈRE +
+

+ 🔐 LE COFFRE-FORT DU VOLEUR

-

- "Reproduisez exactement le plateau rempli..." +

+ "Six énigmes cryptées... Un seul code... La vérité enfin révélée."

{/* Instructions */} -
+

📋 INSTRUCTIONS :

-

Reproduisez exactement le plateau SOURCE sur le plateau CIBLE

-

• Cliquez sur un objet de la palette puis sur une case du plateau cible

-

• Cliquez sur une case remplie pour la vider

-

• Le joueur qui voit le plateau source décrit les positions

- {emojiOnly && ( -

⚠️ MODE EMOJI UNIQUEMENT - Communication par emojis seulement !

- )} +

Agent Owen (MOBILE) : Résout 3 énigmes sur son téléphone → Trouve 3 chiffres (positions 1, 2, 3)

+

Agent Wilson (WEB) : Résout 3 énigmes ci-dessous → Trouve 3 chiffres (positions 4, 5, 6)

+

Communication obligatoire : Chacun dicte ses 3 chiffres à l'autre

+

• Le code final = [Owen 1][Owen 2][Owen 3][Wilson 1][Wilson 2][Wilson 3]

+

• Wilson entre le code complet à 6 chiffres dans le champ ci-dessous

+

• Vous avez {MAX_ATTEMPTS} tentatives maximum

+

• Après chaque tentative, le système indique combien de chiffres sont corrects

- {/* Puzzle */} -
-
- {/* Source Board */} -
-

- PLATEAU SOURCE -

-
- {sourceBoard.map((row, rowIndex) => ( -
- {row.map((cell, colIndex) => ( -
- {cell} -
- ))} -
- ))} -
+ {/* Attempts Counter */} + {!solved && !failed && ( +
+
+ {MAX_ATTEMPTS - attempts.length} / {MAX_ATTEMPTS}
+
TENTATIVES RESTANTES
+
+ )} - {/* Target Board */} -
-

- PLATEAU CIBLE +
+ {/* WILSON Zone - Énigmes et Saisie */} +
+
+

+ 💻 ZONE WILSON (WEB)

-
- {targetBoard.map((row, rowIndex) => ( -
- {row.map((cell, colIndex) => ( - - ))} -
- ))} +

+ Résolvez vos 3 énigmes pour trouver les chiffres 4, 5, 6 +

+
+

+ Votre code : [?][?][?] +

-
- {/* Object Palette */} -
-

- PALETTE D'OBJETS -

-
- {objects.map((obj, index) => ( - +
+ {wilsonRiddles.map((item, index) => ( +
+
+
+ {index + 1} +
+
+
+ {item.riddle} +
+
+
+
))}
- {selectedObject && ( -

- Objet sélectionné: {selectedObject} -

+ +
+

📞 COMMUNICATION NÉCESSAIRE :

+

1. Résolvez vos 3 énigmes ci-dessus

+

2. Dictez vos 3 chiffres à Owen (qui est sur mobile)

+

3. Écoutez les 3 chiffres d'Owen et combinez-les avec les vôtres

+
+ + {!solved && !failed && ( +
+ +

+ Format : [Owen 1][Owen 2][Owen 3][Wilson 1][Wilson 2][Wilson 3] +

+ setCodeInput(e.target.value.replace(/[^0-9]/g, ''))} + className="w-full px-4 py-4 bg-gray-900 border-2 border-green-600 text-green-400 text-center text-3xl font-bold tracking-[1em] focus:outline-none focus:border-green-400 placeholder-green-700 mb-4" + placeholder="______" + /> +
+ +
+
+ )} + + {/* Historique des tentatives */} + {attempts.length > 0 && ( +
+

+ 📊 HISTORIQUE DES TENTATIVES +

+
+ {attempts.map((attempt, index) => ( +
+
+ #{index + 1} + + {attempt.code} + +
+
0 ? 'text-yellow-400' : 'text-red-400'}`}> + {attempt.correct === 6 ? '✓ PARFAIT' : `${attempt.correct}/6 corrects`} +
+
+ ))} +
+
)}
+
- {!solved && ( -
- + ✨ VOIR LA RÉCOMPENSE FINALE → + +
+
+ )} + + {/* Failed */} + {failed && ( +
+
+ ❌ SYSTÈME DE SÉCURITÉ ACTIVÉ +
+
+

+ Trop de tentatives incorrectes ! +

+

+ Le coffre-fort se verrouille définitivement... +

+

+ Vous avez utilisé toutes vos {MAX_ATTEMPTS} tentatives. +

+
+
- )} - - {solved && ( -
-
- 🎉 PARFAIT! 🎉 -
-
-

Le plateau a été reproduit à l'identique!

-

Code révélé: PLATEAU-4444

-
-
- - TRANSMETTRE LE CODE → - -
-
- )} -
+
+ )} -
- 🎯 "Chaque pièce a sa place exacte..." +
+ 🔐 "Seuls les plus perspicaces déverrouilleront les secrets du voleur..."

- - {solved && } + ); } diff --git a/resources/js/pages/victory.tsx b/resources/js/pages/victory.tsx new file mode 100644 index 0000000..640da82 --- /dev/null +++ b/resources/js/pages/victory.tsx @@ -0,0 +1,211 @@ +import { Head, Link } from '@inertiajs/react'; +import { useState, useEffect } from 'react'; + +export default function Victory() { + const [contentVisible, setContentVisible] = useState(false); + + useEffect(() => { + // Afficher le contenu après un court délai + const timer = setTimeout(() => { + setContentVisible(true); + }, 500); + + return () => { + clearTimeout(timer); + }; + }, []); + + return ( + <> + +
+ {/* Particles d'or qui tombent */} +
+ {Array.from({ length: 50 }, (_, i) => ( +
+ ✨ +
+ ))} +
+ + {/* Rayons lumineux */} +
+ {Array.from({ length: 8 }, (_, i) => ( +
+ ))} +
+ +
+ {/* Titre principal */} +
+

+ 🎉 MISSION ACCOMPLIE ! 🎉 +

+

+ Les œuvres volées ont été retrouvées ! +

+

+ Bravo aux agents Wilson et Owen ! +

+
+ + {/* Message principal de victoire */} + {contentVisible && ( +
+ {/* Message de découverte */} +
+
+ ❤️ +
+

+ "Le Cœur de Vénus" +

+

+ Grâce aux coordonnées que vous avez déchiffrées,
+ l'œuvre volée a été retrouvée ! +

+
+

+ 📍 LOCALISATION FINALE +

+

+ Jardin du Luxembourg, Paris +

+

+ 48°52'12" N, 2°29'28" E +

+

+ "Sous la fontaine Médicis, le trésor attendait..." +

+
+
+

+ Toutes les œuvres volées par le mystérieux cambrioleur ont été récupérées.
+ Le musée peut enfin rouvrir ses portes au public ! +

+
+
+ +
+

+ 🏆 RAPPORT DE MISSION +

+
+
+

SALLES COMPLÉTÉES

+

9 / 9

+
+
+

ÉNIGMES RÉSOLUES

+

25+

+
+
+

TRAVAIL D'ÉQUIPE

+

★★★★★

+
+
+ +
+ Grâce à votre collaboration parfaite entre Wilson (WEB) et Owen (MOBILE),
+ vous avez réussi à déjouer les pièges du voleur et à récupérer toutes les œuvres volées ! +
+
+ + {/* Boutons d'action */} +
+ + 📊 VOIR LE DASHBOARD + + + 🏠 RETOUR À L'ACCUEIL + +
+
+ )} +
+ + +
+ + ); +} diff --git a/routes/web.php b/routes/web.php index cd3b5fb..8c81998 100644 --- a/routes/web.php +++ b/routes/web.php @@ -18,6 +18,10 @@ return Inertia::render('game-over'); })->name('game-over'); +Route::get('victory', function () { + return Inertia::render('victory'); +})->name('victory'); + Route::get('/messages', [MessageController::class, 'index'])->name('messages.index'); Route::post('/messages', [MessageController::class, 'store'])->name('messages.store'); @@ -124,14 +128,3 @@ 'sessionId' => $request->session()->getId(), ]); })->name('room.9'); - -Route::get('/room/10', function (Request $request) { - if (! canAccessRoom(10, $request)) { - return redirect()->route('dashboard')->with('error', 'Vous devez déverrouiller cette salle d\'abord'); - } - - return Inertia::render('rooms/room-10', [ - 'messages' => Message::all()->take(50), - 'sessionId' => $request->session()->getId(), - ]); -})->name('room.10');