Enregistrement du score
Enregistrer le score du joueur dans Supabase, afin de pouvoir afficher un classement.

Notions théoriques
Pourquoi faut-il enregistrer le score dans la base de données ?
Enregistrer le score permet de :
- Garder une trace des performances d’un joueur
- Afficher un classement ou des records
- Motiver les joueurs à s’améliorer
- Reprendre un quiz plus tard ou comparer ses résultats
Où enregistrer les scores ?
Il existe 2 façons d’enregistrer les scores dans la base de données.

1. La méthode complète (prévue dans le MCD)
Dans le modèle de données initial (le MCD), nous avions prévu 2 tables distinctes pour gérer les scores :
| Table | Rôle |
|---|---|
classement | Contient un score, un temps, une date — c’est une partie jouée |
classement_joueur | Fait le lien entre un joueur et une partie (clé étrangère) |
Avantages :
- Permet de stocker plusieurs parties par joueur (historique complet)
- Possibilité de faire un classement général avec tous les joueurs
- Structure souple et scalable pour une vraie application multi-joueurs
Inconvénients :
- Il faut gérer plusieurs tables
- Il faut faire des jointures entre les tables
- Il faut gérer les droits d’accès (RLS) pour sécuriser les données
2. La méthode simplifiée (utilisée dans le TP)
Pour ce TP, nous allons simplifier les choses au maximum :
On va enregistrer le meilleur score directement dans la table joueur.
On ajoute simplement 3 colonnes :
| Colonne | Type | Description |
|---|---|---|
meilleur_score | bigint | Le meilleur score obtenu par le joueur |
meilleur_temps | bigint | Le temps (en secondes) associé à ce score |
date_meilleur_score | date | La date à laquelle ce score a été réalisé |
Avantages :
- Une seule table à gérer : plus rapide à mettre en place
- Pas besoin de jointures ni de requêtes complexes
- Pas besoin de règles de sécurité compliquées (RLS)
Limites :
- On ne garde qu’un seul score par joueur (le meilleur)
- On ne peut pas afficher l’historique des parties
Dans le TP, nous allons :
- Ajouter quelques colonnes à la table
joueur - Enregistrer le meilleur score, le temps associé et la date dans la table
joueur - Abandonner les tables
classementetclassement_joueur
Cela vous permettra de réussir rapidement un enregistrement de score fonctionnel, sans vous perdre dans la complexité des bases relationnelles.
Comment mesurer le temps ?
On mesure le temps que met un joueur pour répondre à toutes les questions :
const debut = Date.now(); // au début du quiz
Puis à la fin :
const tempsTotal = Math.floor((Date.now() - debut) / 1000); // en secondes
Comment enregistrer le meilleur score ?
À la fin du quiz, on compare le score actuel avec le meilleur_score déjà enregistré dans la base.
S’il est meilleur, on met à jour la ligne du joueur :
await supabase
.from("joueur")
.update({
meilleur_score: score,
meilleur_temps: tempsTotal,
date_meilleur_score: new Date().toISOString().split("T")[0],
})
.eq("user_id", userId); // on identifie le joueur
Exemple pratique
Voici un exemple de code exécuté à la fin du quiz, pour mettre à jour le meilleur score :
async function enregistrerMeilleurScore() {
const userId = localStorage.getItem("supabase_user_id");
if (!userId || debut === null || questions.length === 0) return;
const tempsTotal = Math.floor((Date.now() - debut) / 1000);
const aujourdHui = new Date().toISOString().split("T")[0];
// Récupérer l'ancien meilleur score
const { data: joueur, error } = await supabase
.from("joueur")
.select("meilleur_score")
.eq("user_id", userId)
.single();
if (error || !joueur) {
console.error("Erreur récupération joueur :", error);
return;
}
const ancienMeilleur = joueur.meilleur_score || 0;
// Mise à jour uniquement si le score est meilleur
if (score > ancienMeilleur) {
await supabase
.from("joueur")
.update({
meilleur_score: score,
meilleur_temps: tempsTotal,
date_meilleur_score: aujourdHui,
})
.eq("user_id", userId);
}
}
Quelques méthodes à connaître
| Méthode JavaScript | Utilité |
|---|---|
Date.now() | Renvoie l’heure actuelle en millisecondes |
Math.floor() | Arrondit à l’entier inférieur |
new Date().toISOString() | Renvoie la date/heure actuelle au format ISO |
.split("T")[0] | Garde uniquement la date sans l’heure |
await supabase.from(...).update(...) | Met à jour une ligne dans la base de données |
.eq("user_id", userId) | Filtre pour mettre à jour le bon joueur |
Test de mémorisation / compréhension
TP pour réfléchir et résoudre des problèmes
Objectif
Modifier votre application Quiz Cyber pour :
- Enregistrer le score dans la base de données après chaque réponse.
- Enregistrer également le temps écoulé
Simplification de la base de données

La table classement est prévue pour stocker plusieurs scores par joueur, mais afin de simplifier ce TP,
nous allons utiliser la table joueur pour stocker le score et le temps total du joueur.
| Colonne | Type | Description |
|---|---|---|
score | bigint | Le nombre de bonnes réponses |
temps | bigint | Le temps total mis en secondes |
En stockant le score et le temps total dans la table joueur, vous n’aurez pas besoin de créer des RLS complexes pour ce TP et vous pourrez vous concentrer sur l’enregistrement du score.
- On abandonne les tables
classementetclassement_joueur - On stocke le meilleur score, le temps mis et la date dans la table
joueur
1. Migration SQL
Migration SQL pour ajouter les colonnes à la table
joueur
Exécutez ces commandes dans Supabase → SQL Editor :
-- Ajouter les colonnes pour stocker le meilleur score
ALTER TABLE public.joueur
ADD COLUMN IF NOT EXISTS meilleur_score bigint DEFAULT 0,
ADD COLUMN IF NOT EXISTS meilleur_temps bigint, -- en secondes (NULL si jamais terminé)
ADD COLUMN IF NOT EXISTS date_meilleur_score date;

Supprimer les tables inutiles (facultatif)
-- Supprimer les tables inutiles (facultatif)
DROP TABLE IF EXISTS public.classement_joueur CASCADE;
DROP TABLE IF EXISTS public.classement CASCADE;


2. Mettre à jour app/page.tsx
Mettre à jour
app/page.tsxpour enregistrer le score à la fin du quiz
Modifiez votre fichier app/page.tsx comme suit :
Ajouter les états nécessaires
const [debut, setDebut] = useState<number | null>(null);
const [quizTermine, setQuizTermine] = useState(false); // Pour éviter double enregistrement

Dans le useEffect qui charge les questions, initialisez la variable debut pour démarrer le chrono au chargement des questions :
useEffect(() => {
async function fetchQuestion() {
const { data, error } = await supabase
.from("question")
.select(`
id, texte, image_url, image_credit_nom, image_credit_url, explication,
reponses:reponse (id, texte, est_correcte)
`)
.order("id", { ascending: true });
if (error) {
console.error("Erreur Supabase :", error);
} else {
setQuestions(data || []);
setDebut(Date.now()); // Démarrer le chrono ici
}
}
fetchQuestion();
}, []);

2. Créer enregistrerMeilleurScore
Créer la fonction
enregistrerMeilleurScore
La fonction enregistrerMeilleurScore sera appelée à la fin
async function enregistrerMeilleurScore() {
const userId = localStorage.getItem("supabase_user_id");
if (!userId || debut === null || questions.length === 0) return;
const tempsTotal = Math.floor((Date.now() - debut) / 1000);
const scoreFinal = score;
const aujourdHui = new Date().toISOString().split("T")[0];
// Récupérer le joueur et son ancien meilleur score
const { data: joueur, error } = await supabase
.from("joueur")
.select("meilleur_score")
.eq("user_id", userId)
.single();
if (error || !joueur) {
console.error("Erreur récupération joueur :", error);
return;
}
const ancienMeilleur = joueur.meilleur_score || 0;
// Mettre à jour seulement si nouveau record
if (scoreFinal > ancienMeilleur) {
const { error: updateError } = await supabase
.from("joueur")
.update({
meilleur_score: scoreFinal,
meilleur_temps: tempsTotal,
date_meilleur_score: aujourdHui,
})
.eq("user_id", userId);
if (updateError) {
console.error("Erreur mise à jour record :", updateError);
} else {
console.log("Nouveau record !", scoreFinal, "points en", tempsTotal, "s");
}
}
}

3. Appeler enregistrerMeilleurScore
Appeler la fonction
enregistrerMeilleurScoreà la fin du quiz, lorsque toutes les questions ont été répondues.
Détecter la fin du quiz et enregistrer le score
useEffect(() => {
if (questionIndex >= questions.length && questions.length > 0 && !quizTermine) {
setQuizTermine(true);
enregistrerMeilleurScore();
}
}, [questionIndex, questions.length, quizTermine]);

4. Améliorer l’écran de fin
Remplacer le code suivant :

par :
if (!question && questions.length > 0) {
return (
<div className="text-center mt-20 max-w-2xl mx-auto">
<h2 className="text-4xl font-bold mb-8 text-primary">Quiz terminé !</h2>
<Card>
<CardHeader>
<CardTitle>Votre résultat</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-xl">
<p>Score : <span className="font-bold text-green-600">{score}</span> / {questions.length}</p>
<p className="text-muted-foreground">
Temps : {debut ? Math.floor((Date.now() - debut) / 1000) : 0} secondes
</p>
{score === questions.length && (
<p className="text-2xl">Parfait ! 100% de bonnes réponses !</p>
)}
</CardContent>
</Card>
<div className="mt-8">
<p className="text-lg mb-4">
Merci {joueurNom} pour votre participation !
</p>
</div>
</div>
);
}

Voir le code complet d'une solution possible
Vous devez être connecté pour voir le contenu.