You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1118 lines
38 KiB

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quiz Interactif</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
color: white;
text-align: center;
margin-bottom: 30px;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.header-controls {
background: white;
padding: 20px;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
margin-bottom: 30px;
display: flex;
gap: 15px;
align-items: center;
flex-wrap: wrap;
}
.file-input-container {
display: flex;
gap: 15px;
align-items: center;
flex: 1;
}
.scoring-settings {
display: flex;
gap: 10px;
align-items: center;
padding: 10px 15px;
background: #f5f5f5;
border-radius: 8px;
}
.scoring-input {
width: 50px;
padding: 5px 8px;
border: 2px solid #e0e0e0;
border-radius: 5px;
font-size: 14px;
text-align: center;
font-weight: 600;
}
.scoring-label {
font-size: 14px;
color: #666;
font-weight: 500;
}
.file-input-label {
display: inline-block;
padding: 12px 24px;
background: #667eea;
color: white;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
font-size: 16px;
transition: all 0.3s ease;
}
.file-input-label:hover {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
#fileInput {
display: none;
}
.btn-connect {
padding: 12px 24px;
background: #4CAF50;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
font-size: 16px;
transition: all 0.3s ease;
}
.btn-connect:hover {
background: #45a049;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.4);
}
.modules-status {
color: #666;
font-weight: 600;
}
.main-content {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
}
.quiz-card {
background: white;
border-radius: 15px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
margin-bottom: 20px;
}
.question {
font-size: 1.5em;
font-weight: bold;
color: #333;
margin-bottom: 25px;
line-height: 1.4;
}
.question-number {
color: #667eea;
margin-right: 10px;
}
.answers {
display: flex;
flex-direction: column;
gap: 15px;
}
.answer {
padding: 15px 20px;
border: 2px solid #e0e0e0;
border-radius: 10px;
transition: all 0.3s ease;
font-size: 1.1em;
background: #f9f9f9;
display: flex;
justify-content: space-between;
align-items: center;
}
.answer-letter {
background: #667eea;
color: white;
width: 35px;
height: 35px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 15px;
}
.answer-content {
display: flex;
align-items: center;
flex: 1;
}
.answer.correct {
border-color: #4CAF50;
background: #e8f5e9;
}
.answer.correct .answer-letter {
background: #4CAF50;
}
.modules-panel {
display: flex;
flex-direction: column;
gap: 15px;
}
.module-card {
background: white;
border-radius: 10px;
padding: 12px 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.module-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 2px solid #667eea;
}
.module-name {
font-weight: bold;
color: #667eea;
font-size: 0.95em;
}
.disconnect-btn {
background: #f44336;
color: white;
padding: 4px 8px;
font-size: 11px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
}
.disconnect-btn:hover {
background: #da190b;
}
.module-info {
display: flex;
justify-content: space-around;
align-items: center;
gap: 10px;
}
.module-info-item {
text-align: center;
flex: 1;
}
.module-answer-label {
font-size: 0.75em;
color: #666;
margin-bottom: 4px;
}
.module-answer-value {
font-size: 1.3em;
font-weight: bold;
color: #667eea;
}
.module-answer-value.correct {
color: #4CAF50;
}
.module-answer-value.incorrect {
color: #f44336;
}
.module-waiting {
color: #999;
font-style: italic;
}
.module-answered {
color: #667eea;
font-style: normal;
}
.controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 30px;
gap: 15px;
}
button {
padding: 12px 30px;
font-size: 16px;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 600;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.btn-primary:disabled {
background: #cccccc;
cursor: not-allowed;
}
.btn-results {
background: #ff9800;
color: white;
width: 100%;
margin-top: 20px;
}
.btn-results:hover:not(:disabled) {
background: #f57c00;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(255, 152, 0, 0.4);
}
.btn-results:disabled {
background: #cccccc;
cursor: not-allowed;
}
.progress {
text-align: center;
color: #666;
font-weight: 600;
font-size: 1.1em;
}
.score-card {
background: white;
border-radius: 15px;
padding: 40px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
text-align: center;
}
.score-title {
font-size: 2em;
color: #667eea;
margin-bottom: 20px;
}
.score-value {
font-size: 3em;
font-weight: bold;
color: #333;
margin-bottom: 30px;
}
.score-message {
font-size: 1.3em;
color: #666;
margin-bottom: 30px;
}
.hidden {
display: none;
}
@media (max-width: 968px) {
.main-content {
grid-template-columns: 1fr;
}
}
/* Système de notifications */
.notification {
position: fixed;
top: 20px;
right: 20px;
min-width: 300px;
max-width: 500px;
padding: 20px 25px;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
color: white;
font-weight: 500;
font-size: 15px;
z-index: 10000;
animation: slideIn 0.3s ease-out;
display: flex;
align-items: flex-start;
gap: 12px;
line-height: 1.5;
}
.notification.success {
background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
}
.notification.error {
background: linear-gradient(135deg, #f44336 0%, #da190b 100%);
}
.notification.info {
background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);
}
.notification-icon {
font-size: 24px;
flex-shrink: 0;
}
.notification-content {
flex: 1;
white-space: pre-line;
}
.notification-close {
background: rgba(255,255,255,0.2);
border: none;
color: white;
width: 24px;
height: 24px;
border-radius: 50%;
cursor: pointer;
font-size: 16px;
line-height: 1;
padding: 0;
flex-shrink: 0;
transition: background 0.2s;
}
.notification-close:hover {
background: rgba(255,255,255,0.3);
}
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(400px);
opacity: 0;
}
}
.notification.hiding {
animation: slideOut 0.3s ease-in forwards;
}
/* Styles pour indices et exposants */
sub {
font-size: 0.75em;
vertical-align: sub;
}
sup {
font-size: 0.75em;
vertical-align: super;
}
</style>
</head>
<body>
<div class="container">
<h1>📝 Quiz Interactif</h1>
<div class="header-controls">
<div class="file-input-container">
<label for="fileInput" class="file-input-label">
📁 Charger un quiz (.txt)
</label>
<input type="file" id="fileInput" accept=".txt">
</div>
<div class="scoring-settings">
<span class="scoring-label"></span>
<input type="number" id="correctScore" class="scoring-input" value="1" min="-10" max="10">
<span class="scoring-label"></span>
<input type="number" id="incorrectScore" class="scoring-input" value="0" min="-10" max="10">
</div>
<button id="connectBtn" class="btn-connect">🔵 Connecter module</button>
<div class="modules-status" id="modulesStatus">Aucun module</div>
</div>
<div id="quizContainer" class="hidden">
<div class="main-content">
<div>
<div class="quiz-card" id="questionCard">
<div class="question">
<span class="question-number"></span>
<span class="question-text"></span>
</div>
<div class="answers" id="answersContainer"></div>
<button id="showResultsBtn" class="btn-results" disabled>
🎯 Afficher les résultats
</button>
<div class="controls">
<button id="prevBtn" class="btn-primary">← Précédent</button>
<div class="progress" id="progress"></div>
<button id="nextBtn" class="btn-primary">Suivant →</button>
</div>
</div>
</div>
<div class="modules-panel" id="modulesPanel">
<!-- Les modules connectés apparaîtront ici -->
</div>
</div>
</div>
<div id="scoreContainer" class="hidden">
<div class="score-card">
<div class="score-title">🎉 Quiz terminé !</div>
<div id="scoresDetails"></div>
<button class="btn-primary" onclick="location.reload()">Recommencer</button>
</div>
</div>
</div>
<script>
// Configuration BLE
const SERVICE_UUID = '4fafc201-1fb5-459e-8fcc-c5c9c331914b';
const CHARACTERISTIC_UUID = 'beb5483e-36e1-4688-b7f5-ea07361b26a8';
const COMMAND_UUID = 'beb5483e-36e1-4688-b7f5-ea07361b26a9';
let questions = [];
let currentQuestionIndex = 0;
let connectedModules = new Map(); // moduleName -> { device, characteristic, commandCharacteristic, answer, scores }
let resultsShown = false;
const fileInput = document.getElementById('fileInput');
const connectBtn = document.getElementById('connectBtn');
const quizContainer = document.getElementById('quizContainer');
const scoreContainer = document.getElementById('scoreContainer');
const modulesStatus = document.getElementById('modulesStatus');
const modulesPanel = document.getElementById('modulesPanel');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const showResultsBtn = document.getElementById('showResultsBtn');
const correctScoreInput = document.getElementById('correctScore');
const incorrectScoreInput = document.getElementById('incorrectScore');
// Fonction pour convertir les indices et exposants
function formatText(text) {
// Remplacer _(contenu) par <sub>contenu</sub>
text = text.replace(/_\(([^)]+)\)/g, '<sub>$1</sub>');
// Remplacer ^(contenu) par <sup>contenu</sup>
text = text.replace(/\^\(([^)]+)\)/g, '<sup>$1</sup>');
return text;
}
// Fonction pour afficher une notification
function showNotification(message, type = 'info', duration = 5000) {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
const icons = {
success: '✅',
error: '❌',
info: 'ℹ'
};
notification.innerHTML = `
<div class="notification-icon">${icons[type]}</div>
<div class="notification-content">${message}</div>
<button class="notification-close" onclick="this.parentElement.remove()">×</button>
`;
document.body.appendChild(notification);
if (duration > 0) {
setTimeout(() => {
notification.classList.add('hiding');
setTimeout(() => notification.remove(), 300);
}, duration);
}
}
// Vérifier si Web Bluetooth est disponible
if (!navigator.bluetooth) {
showNotification('Web Bluetooth non supporté. Utilisez Chrome, Edge ou Opera.', 'error', 0);
connectBtn.disabled = true;
}
// Fonction pour obtenir le scoring actuel
function getScoringConfig() {
return {
correctAnswer: parseInt(correctScoreInput.value) || 1,
incorrectAnswer: parseInt(incorrectScoreInput.value) || 0
};
}
fileInput.addEventListener('change', handleFileSelect);
connectBtn.addEventListener('click', connectModule);
prevBtn.addEventListener('click', showPreviousQuestion);
nextBtn.addEventListener('click', showNextQuestion);
showResultsBtn.addEventListener('click', showResults);
function updateModulesStatus() {
const count = connectedModules.size;
modulesStatus.textContent = count === 0
? 'Aucun module'
: `${count} module${count > 1 ? 's' : ''}`;
}
async function connectModule() {
try {
console.log('Recherche module...');
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: [SERVICE_UUID] }]
});
console.log('Module trouvé:', device.name);
const server = await device.gatt.connect();
const service = await server.getPrimaryService(SERVICE_UUID);
const characteristic = await service.getCharacteristic(CHARACTERISTIC_UUID);
const commandCharacteristic = await service.getCharacteristic(COMMAND_UUID);
// Lire l'état initial
const initialValue = await characteristic.readValue();
const initialData = new TextDecoder().decode(initialValue);
console.log('État initial:', initialData);
const data = JSON.parse(initialData);
const moduleName = data.module;
// Stocker le module
connectedModules.set(moduleName, {
device: device,
characteristic: characteristic,
commandCharacteristic: commandCharacteristic,
answer: null,
totalScore: 0,
gpios: data.gpios
});
// Créer la carte du module
createModuleCard(moduleName);
updateModulesStatus();
// Activer les notifications
await characteristic.startNotifications();
characteristic.addEventListener('characteristicvaluechanged', (event) => {
const value = new TextDecoder().decode(event.target.value);
console.log('🔔 Notification de', moduleName, ':', value);
try {
const data = JSON.parse(value);
handleModuleData(moduleName, data);
} catch (e) {
console.error('Erreur parsing:', e);
}
});
// Gérer la déconnexion
device.addEventListener('gattserverdisconnected', () => {
console.log(moduleName, 'déconnecté');
connectedModules.delete(moduleName);
document.getElementById(`module-${moduleName}`)?.remove();
updateModulesStatus();
});
} catch (error) {
console.error('Erreur connexion:', error);
showNotification(`Erreur de connexion: ${error.message}`, 'error');
}
}
function createModuleCard(moduleName) {
const card = document.createElement('div');
card.className = 'module-card';
card.id = `module-${moduleName}`;
card.innerHTML = `
<div class="module-header">
<div class="module-name">${moduleName}</div>
<button class="disconnect-btn" onclick="disconnectModule('${moduleName}')">
</button>
</div>
<div class="module-info">
<div class="module-info-item">
<div class="module-answer-label">Réponse</div>
<div class="module-answer-value module-waiting" id="answer-${moduleName}">
-
</div>
</div>
<div class="module-info-item">
<div class="module-answer-label">Score</div>
<div class="module-answer-value" id="score-${moduleName}">
0
</div>
</div>
</div>
`;
modulesPanel.appendChild(card);
}
function handleModuleData(moduleName, data) {
const module = connectedModules.get(moduleName);
if (!module) return;
// Mettre à jour les GPIO
module.gpios = data.gpios;
console.log('📊 GPIOs pour', moduleName, ':', data.gpios);
// Détecter la réponse (A=GPIO2, B=GPIO4, C=GPIO5, D=GPIO6)
// Réponse détectée si GPIO est à l'état BAS (1)
let answer = null;
data.gpios.forEach(gpio => {
console.log(` GPIO ${gpio.pin} = ${gpio.state} (type: ${typeof gpio.state})`);
if (String(gpio.state) === '1') {
if (gpio.pin === 2) answer = 'A';
else if (gpio.pin === 4) answer = 'B';
else if (gpio.pin === 5) answer = 'C';
else if (gpio.pin === 6) answer = 'D';
}
});
console.log('➡ Réponse détectée:', answer);
if (answer && !resultsShown) {
// Permet de modifier la réponse tant que les résultats ne sont pas affichés
if (module.answer !== answer) {
module.answer = answer;
updateModuleAnswer(moduleName, answer);
checkAllAnswersReceived();
}
}
}
function updateModuleAnswer(moduleName, answer) {
const answerDiv = document.getElementById(`answer-${moduleName}`);
if (answerDiv) {
answerDiv.textContent = '✓ Répondu';
answerDiv.classList.remove('module-waiting');
answerDiv.classList.add('module-answered');
}
}
function checkAllAnswersReceived() {
let allAnswered = true;
for (let [name, module] of connectedModules.entries()) {
if (!module.answer) {
allAnswered = false;
break;
}
}
showResultsBtn.disabled = !allAnswered || connectedModules.size === 0;
}
function showResults() {
if (resultsShown) return;
resultsShown = true;
const question = questions[currentQuestionIndex];
const correctAnswerIndex = question.correctAnswer;
const correctLetter = ['A', 'B', 'C', 'D'][correctAnswerIndex];
const scoringConfig = getScoringConfig();
// Afficher la bonne réponse
const answerDivs = document.querySelectorAll('.answer');
answerDivs[correctAnswerIndex].classList.add('correct');
// Vérifier les réponses de chaque module
for (let [name, module] of connectedModules.entries()) {
const answerDiv = document.getElementById(`answer-${name}`);
const scoreDiv = document.getElementById(`score-${name}`);
if (answerDiv) {
// Afficher la réponse du module
answerDiv.textContent = module.answer || '-';
answerDiv.classList.remove('module-answered');
if (module.answer === correctLetter) {
answerDiv.classList.add('correct');
module.totalScore += scoringConfig.correctAnswer;
} else {
answerDiv.classList.add('incorrect');
module.totalScore += scoringConfig.incorrectAnswer;
}
// Mettre à jour l'affichage du score
if (scoreDiv) {
scoreDiv.textContent = module.totalScore;
}
}
}
showResultsBtn.disabled = true;
// Envoyer la commande RESET immédiatement à tous les modules
// (LED garde sa couleur, GPIO désactivés)
resetAllModules();
}
async function resetAllModules() {
console.log('Envoi de RESET à tous les modules...');
for (let [name, module] of connectedModules.entries()) {
try {
if (module.commandCharacteristic) {
const encoder = new TextEncoder();
await module.commandCharacteristic.writeValue(encoder.encode('RESET'));
console.log(`RESET envoyé à ${name}`);
}
} catch (error) {
console.error(`Erreur envoi RESET à ${name}:`, error);
}
}
}
async function sendCommandToAllModules(command) {
console.log(`Envoi de ${command} à tous les modules...`);
for (let [name, module] of connectedModules.entries()) {
try {
if (module.commandCharacteristic) {
const encoder = new TextEncoder();
await module.commandCharacteristic.writeValue(encoder.encode(command));
console.log(`${command} envoyé à ${name}`);
}
} catch (error) {
console.error(`Erreur envoi ${command} à ${name}:`, error);
}
}
}
async function sendScoresCommand() {
console.log('Envoi de SCORES à tous les modules...');
for (let [name, module] of connectedModules.entries()) {
try {
if (module.commandCharacteristic) {
const encoder = new TextEncoder();
await module.commandCharacteristic.writeValue(encoder.encode('SCORES'));
console.log(`SCORES envoyé à ${name}`);
}
} catch (error) {
console.error(`Erreur envoi SCORES à ${name}:`, error);
}
}
}
function handleFileSelect(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const content = e.target.result;
try {
parseQuizFile(content);
if (questions.length > 0) {
showNotification(`Quiz chargé avec succès!\n\n${questions.length} question(s) trouvée(s).`, 'success', 3000);
startQuiz();
}
} catch (error) {
console.error('Erreur lors du chargement du quiz:', error);
questions = [];
// L'erreur a déjà été affichée dans parseQuizFile
}
};
reader.readAsText(file);
}
}
function parseQuizFile(content) {
questions = [];
const lines = content.trim().split('\n');
let currentQuestion = null;
let lineNumber = 0;
const errors = [];
for (let line of lines) {
lineNumber++;
const originalLine = line;
line = line.trim();
if (!line) continue;
if (line.startsWith('* ')) {
// Valider la question précédente avant d'en commencer une nouvelle
if (currentQuestion) {
const questionErrors = validateQuestion(currentQuestion, lineNumber - 1);
if (questionErrors.length > 0) {
errors.push(...questionErrors);
} else {
questions.push(currentQuestion);
}
}
const questionText = line.substring(2).trim();
if (!questionText) {
errors.push(`Ligne ${lineNumber}: Question vide après '*'`);
}
currentQuestion = {
question: questionText,
answers: [],
correctAnswer: -1,
lineNumber: lineNumber
};
} else if (line.startsWith('+ ')) {
if (!currentQuestion) {
errors.push(`Ligne ${lineNumber}: Réponse correcte '+' trouvée sans question précédente`);
continue;
}
const answerText = line.substring(2).trim();
if (!answerText) {
errors.push(`Ligne ${lineNumber}: Réponse correcte vide après '+'`);
}
if (currentQuestion.correctAnswer !== -1) {
errors.push(`Ligne ${lineNumber}: Question à la ligne ${currentQuestion.lineNumber} a déjà une réponse correcte (ligne avec '+')`);
}
currentQuestion.correctAnswer = currentQuestion.answers.length;
currentQuestion.answers.push(answerText);
} else if (line.startsWith('- ')) {
if (!currentQuestion) {
errors.push(`Ligne ${lineNumber}: Réponse '-' trouvée sans question précédente`);
continue;
}
const answerText = line.substring(2).trim();
if (!answerText) {
errors.push(`Ligne ${lineNumber}: Réponse vide après '-'`);
}
currentQuestion.answers.push(answerText);
} else {
// Ligne avec un format non reconnu
errors.push(`Ligne ${lineNumber}: Format non reconnu. Utilisez '* ' pour une question, '+ ' pour la bonne réponse, '- ' pour les mauvaises réponses`);
}
}
// Valider la dernière question
if (currentQuestion) {
const questionErrors = validateQuestion(currentQuestion, lineNumber);
if (questionErrors.length > 0) {
errors.push(...questionErrors);
} else {
questions.push(currentQuestion);
}
}
// Vérifier s'il y a des erreurs
if (errors.length > 0) {
const errorMessage = `Erreurs détectées dans le fichier de quiz:\n\n${errors.join('\n')}`;
showNotification(errorMessage, 'error', 10000);
throw new Error('Format de quiz invalide');
}
// Vérifier qu'il y a au moins une question
if (questions.length === 0) {
showNotification('Erreur: Aucune question valide trouvée dans le fichier.\n\nFormat attendu:\n* Question ?\n+ Bonne réponse\n- Mauvaise réponse\n- Mauvaise réponse', 'error', 8000);
throw new Error('Aucune question trouvée');
}
}
function validateQuestion(question, lineNumber) {
const errors = [];
// Vérifier qu'il y a au moins 2 réponses
if (question.answers.length < 2) {
errors.push(`Ligne ${question.lineNumber}: Question "${question.question}" n'a que ${question.answers.length} réponse(s). Minimum requis: 2`);
}
// Vérifier qu'il y a maximum 4 réponses (A, B, C, D)
if (question.answers.length > 4) {
errors.push(`Ligne ${question.lineNumber}: Question "${question.question}" a ${question.answers.length} réponses. Maximum autorisé: 4 (A, B, C, D)`);
}
// Vérifier qu'il y a une réponse correcte
if (question.correctAnswer === -1) {
errors.push(`Ligne ${question.lineNumber}: Question "${question.question}" n'a pas de réponse correcte (utilisez '+' devant la bonne réponse)`);
}
return errors;
}
function startQuiz() {
quizContainer.classList.remove('hidden');
currentQuestionIndex = 0;
// Réinitialiser les scores
for (let [name, module] of connectedModules.entries()) {
module.totalScore = 0;
}
showQuestion();
}
function showQuestion() {
if (currentQuestionIndex >= questions.length) {
showFinalScore();
return;
}
resultsShown = false;
const question = questions[currentQuestionIndex];
// Afficher la question
document.querySelector('.question-number').textContent =
`Question ${currentQuestionIndex + 1}/${questions.length}`;
document.querySelector('.question-text').innerHTML = formatText(question.question);
// Afficher les réponses
const answersContainer = document.getElementById('answersContainer');
answersContainer.innerHTML = '';
const letters = ['A', 'B', 'C', 'D'];
question.answers.forEach((answer, index) => {
const answerDiv = document.createElement('div');
answerDiv.className = 'answer';
answerDiv.innerHTML = `
<div class="answer-content">
<div class="answer-letter">${letters[index]}</div>
<div>${formatText(answer)}</div>
</div>
`;
answersContainer.appendChild(answerDiv);
});
// Réinitialiser les réponses des modules
for (let [name, module] of connectedModules.entries()) {
module.answer = null;
const answerDiv = document.getElementById(`answer-${name}`);
if (answerDiv) {
answerDiv.textContent = '-';
answerDiv.className = 'module-answer-value module-waiting';
}
}
// Envoyer la commande START à tous les modules
sendCommandToAllModules('START');
// Mettre à jour les contrôles
document.getElementById('progress').textContent =
`${currentQuestionIndex + 1} / ${questions.length}`;
prevBtn.disabled = currentQuestionIndex === 0;
nextBtn.textContent = currentQuestionIndex === questions.length - 1 ? 'Voir les scores' : 'Suivant →';
showResultsBtn.disabled = true;
}
function showPreviousQuestion() {
if (currentQuestionIndex > 0) {
currentQuestionIndex--;
showQuestion();
}
}
function showNextQuestion() {
if (currentQuestionIndex < questions.length - 1) {
currentQuestionIndex++;
showQuestion();
} else {
showFinalScore();
}
}
function showFinalScore() {
quizContainer.classList.add('hidden');
scoreContainer.classList.remove('hidden');
const scoresDetails = document.getElementById('scoresDetails');
let html = '';
for (let [name, module] of connectedModules.entries()) {
const percentage = Math.round((module.totalScore / questions.length) * 100);
html += `
<div style="margin: 20px 0; padding: 20px; background: #f5f5f5; border-radius: 10px;">
<div style="font-size: 1.5em; font-weight: bold; color: #667eea; margin-bottom: 10px;">
${name}
</div>
<div style="font-size: 2em; font-weight: bold; color: #333;">
${module.totalScore} / ${questions.length} (${percentage}%)
</div>
</div>
`;
}
if (html === '') {
html = '<p style="color: #999;">Aucun module connecté</p>';
}
scoresDetails.innerHTML = html;
// Envoyer la commande SCORES à tous les modules (blanc fixe, GPIO désactivés)
sendScoresCommand();
}
window.disconnectModule = function(moduleName) {
const module = connectedModules.get(moduleName);
if (module && module.device.gatt.connected) {
module.device.gatt.disconnect();
}
};
</script>
</body>
</html>