Aller au contenu principal

Enregistrement du score

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

EnregistrerScore

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.

ou_enregistrer_scores.png


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 :

TableRôle
classementContient un score, un temps, une date — c’est une partie jouée
classement_joueurFait 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 :

ColonneTypeDescription
meilleur_scorebigintLe meilleur score obtenu par le joueur
meilleur_tempsbigintLe temps (en secondes) associé à ce score
date_meilleur_scoredateLa 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
remarque

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 classement et classement_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 JavaScriptUtilité
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


Pourquoi enregistrer le score dans la base de données ?


Quelle méthode permet de mesurer le temps écoulé ?


Quel type de données est utilisé pour le score ?


À quel moment faut-il enregistrer le score ?


Que fait `.split('T')[0]` sur une date ISO ?



TP pour réfléchir et résoudre des problèmes

Objectif

Modifier votre application Quiz Cyber pour :

  1. Enregistrer le score dans la base de données après chaque réponse.
  2. Enregistrer également le temps écoulé

Simplification de la base de données

simplifier_la_BD.png

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.

ColonneTypeDescription
scorebigintLe nombre de bonnes réponses
tempsbigintLe temps total mis en secondes
astuce

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 classement et classement_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;

10_ajout_colonnes.png

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;

12_supprimer_tables

14_supprimer_tables


2. Mettre à jour app/page.tsx

Mettre à jour app/page.tsx pour 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

16_useState.png

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();
}, []);

18_setDebut.png


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");
}
}
}

20_enregistrerScore.png


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]);

22_QuizTermine.png

4. Améliorer l’écran de fin

Remplacer le code suivant :

23_ecranFinSimple.png

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>
);
}

24_ecranFin.png

Voir le code complet d'une solution possible