Educational Multiplication Game: Farm Themed Multiplication Tables
génère l'application
Voici l’application complète, auto-contenue et prête à l’emploi.
Copie/collez simplement le code ci-dessous dans un fichier nommé ferme_multiplication.py, puis exécutez-le avec Python 3 (Tkinter est inclus dans la plupart des installations Python).
Des commentaires clairs vous permettront de le personnaliser ou de l’étendre.
Copie/collez simplement le code ci-dessous dans un fichier nommé ferme_multiplication.py, puis exécutez-le avec Python 3 (Tkinter est inclus dans la plupart des installations Python).
Des commentaires clairs vous permettront de le personnaliser ou de l’étendre.
Python
复制
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Ferme Multiplication
Jeu éducatif de révision des tables de 2 à 10
Dessin animé | Animations | Sons | Multi-support
"""
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import random
import os
import time
import datetime
# -------------------------------------------------
# 1) Paramètres généraux (modifiables à volonté)
# -------------------------------------------------
WINDOW_TITLE = "Ferme Multiplication"
WINDOW_ICON = None # Mettre un .ico si vous en avez un
SOUNDS_DIR = "sounds" # Dossier contenant vos .wav/.mp3
USE_SOUNDS = False # Mettre True pour activer les sons
FONT_FAMILY = "Comic Sans MS"
# Couleurs « ferme »
BG_MAIN = "#e8f4d9"
BG_CARD = "#fffae0"
COL_CORRECT = "#6fc276"
COL_WRONG = "#e06c75"
# -------------------------------------------------
# 2) Petites fonctions utilitaires
# -------------------------------------------------
def play(sound):
"""Joue un fichier sonore (simple, multiplateforme)"""
if not USE_SOUNDS:
return
import pygame
pygame.mixer.init()
try:
pygame.mixer.music.load(os.path.join(SOUNDS_DIR, sound))
pygame.mixer.music.play()
except Exception:
pass # pas de son, pas d’erreur
# -------------------------------------------------
# 3) Classe principale du jeu
# -------------------------------------------------
class FermeMultiplication(tk.Tk):
def __init__(self):
super().__init__()
self.title(WINDOW_TITLE)
if WINDOW_ICON and os.path.exists(WINDOW_ICON):
self.iconbitmap(WINDOW_ICON)
self.resizable(False, False)
self.configure(bg=BG_MAIN)
# Variables de jeu
self.selected_tables = []
self.nb_questions = 10
self.nb_options = 4
self.time_per_q = 10 # secondes
self.score = 0
self.current_q_idx = 0
self.questions = [] # Liste des questions générées
self.results_log = [] # Historique complet
self.timer_job = None
# Widgets
self.container = ttk.Frame(self)
self.container.pack(fill="both", expand=True)
self.frames = {}
for F in (AccueilPage, JeuPage, ResultatsPage):
page_name = F.__name__
frame = F(parent=self.container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("AccueilPage")
# Styles ttk
style = ttk.Style(self)
style.theme_use("clam")
style.configure("TButton", font=(FONT_FAMILY, 12), padding=6)
style.configure("TLabel", font=(FONT_FAMILY, 12), background=BG_MAIN)
style.configure("Card.TFrame", background=BG_CARD, relief="raised", borderwidth=2)
# Navigation entre écrans
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
if hasattr(frame, "on_show"):
frame.on_show()
# Génération des questions
def generer_questions(self):
self.questions.clear()
for _ in range(self.nb_questions):
table = random.choice(self.selected_tables)
b = random.randint(2, 10)
correct = table * b
# Génération des mauvaises réponses sans dépasser 100
wrongs = set()
while len(wrongs) < self.nb_options - 1:
delta = random.choice([-2, -1, 1, 2, -table, table])
w = correct + delta
if 0 < w <= 100 and w != correct:
wrongs.add(w)
options = list(wrongs) + [correct]
random.shuffle(options)
self.questions.append({
"q": f"{table} × {b}",
"a": correct,
"opts": options
})
# -------------------------------------------------
# 4) Écran d’accueil
# -------------------------------------------------
class AccueilPage(ttk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
self.configure(style="Card.TFrame")
self.columnconfigure(0, weight=1)
# Titre
ttk.Label(self, text="Bienvenue à la Ferme Multiplication !",
font=(FONT_FAMILY, 18, "bold")).grid(pady=20)
# Règles
rules = ("Choisis les tables à réviser, le nombre d’opérations,\n"
"la difficulté et le temps par question.\n"
"Clique sur la bonne réponse avant la fin du sablier !")
ttk.Label(self, text=rules, justify="center").grid(pady=10)
# Sélecteurs
self.build_selectors()
ttk.Button(self, text="Démarrer la partie !",
command=self.lancer_partie).grid(pady=20)
def build_selectors(self):
# Tables
tables_frame = ttk.LabelFrame(self, text="Tables à réviser")
tables_frame.grid(pady=5, padx=20, sticky="we")
self.tables_vars = {}
for i in range(2, 11):
var = tk.BooleanVar(value=True)
self.tables_vars[i] = var
ttk.Checkbutton(tables_frame, text=str(i), variable=var).pack(side="left", padx=5)
# Nb questions
q_frame = ttk.LabelFrame(self, text="Nombre d’opérations")
q_frame.grid(pady=5, padx=20, sticky="we")
self.q_var = tk.IntVar(value=10)
ttk.Scale(q_frame, from_=5, to=30, variable=self.q_var,
orient="horizontal").pack(fill="x", padx=10, pady=5)
ttk.Label(q_frame, textvariable=self.q_var).pack()
# Difficulté (nb options)
d_frame = ttk.LabelFrame(self, text="Difficulté")
d_frame.grid(pady=5, padx=20, sticky="we")
self.d_var = tk.IntVar(value=4)
ttk.Scale(d_frame, from_=2, to=8, variable=self.d_var,
orient="horizontal").pack(fill="x", padx=10, pady=5)
ttk.Label(d_frame, textvariable=self.d_var).pack()
# Temps
t_frame = ttk.LabelFrame(self, text="Temps par question (s)")
t_frame.grid(pady=5, padx=20, sticky="we")
self.t_var = tk.IntVar(value=10)
ttk.Scale(t_frame, from_=5, to=20, variable=self.t_var,
orient="horizontal").pack(fill="x", padx=10, pady=5)
ttk.Label(t_frame, textvariable=self.t_var).pack()
def lancer_partie(self):
# Récupération des paramètres
self.controller.selected_tables = [k for k, v in self.tables_vars.items() if v.get()]
if not self.controller.selected_tables:
messagebox.showwarning("Attention", "Sélectionne au moins une table !")
return
self.controller.nb_questions = self.q_var.get()
self.controller.nb_options = self.d_var.get()
self.controller.time_per_q = self.t_var.get()
# Génération des questions
self.controller.generer_questions()
self.controller.score = 0
self.controller.current_q_idx = 0
self.controller.results_log.clear()
self.controller.show_frame("JeuPage")
# -------------------------------------------------
# 5) Écran de jeu
# -------------------------------------------------
class JeuPage(ttk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
self.configure(style="Card.TFrame")
self.columnconfigure(0, weight=1)
# Widgets fixes
self.question_lbl = ttk.Label(self, font=(FONT_FAMILY, 24, "bold"))
self.question_lbl.grid(pady=20)
self.timer_lbl = ttk.Label(self, font=(FONT_FAMILY, 16))
self.timer_lbl.grid()
self.score_lbl = ttk.Label(self, font=(FONT_FAMILY, 14))
self.score_lbl.grid(pady=10)
# Zone des boutons réponses
self.reponses_frame = ttk.Frame(self)
self.reponses_frame.grid(pady=20)
self.reponse_btns = []
def on_show(self):
self.load_question()
def load_question(self):
# Nettoyage
for w in self.reponse_btns:
w.destroy()
self.reponse_btns.clear()
if self.controller.current_q_idx >= len(self.controller.questions):
self.controller.show_frame("ResultatsPage")
return
q = self.controller.questions[self.controller.current_q_idx]
self.question_lbl.config(text=q["q"])
self.controller.score_lbl = self.score_lbl # pour update_timer
self.update_score()
self.time_left = self.controller.time_per_q
self.update_timer()
# Création des boutons réponse
for opt in q["opts"]:
btn = ttk.Button(self.reponses_frame, text=str(opt), width=8,
command=lambda val=opt: self.check_answer(val))
btn.pack(side="left", padx=10)
self.reponse_btns.append(btn)
def update_timer(self):
self.timer_lbl.config(text=f"Temps : {self.time_left}s")
if self.time_left <= 0:
self.record_timeout()
return
self.time_left -= 1
self.controller.timer_job = self.after(1000, self.update_timer)
def check_answer(self, value):
if self.controller.timer_job:
self.after_cancel(self.controller.timer_job)
q = self.controller.questions[self.controller.current_q_idx]
correct = q["a"]
if value == correct:
self.controller.score += 1
play("correct.wav")
else:
self.controller.score -= 1
play("wrong.wav")
self.controller.results_log.append({
"question": q["q"],
"user_answer": value,
"correct_answer": correct,
"timed_out": False
})
self.update_score()
self.after(500, self.next_q)
def record_timeout(self):
q = self.controller.questions[self.controller.current_q_idx]
self.controller.score -= 1
self.controller.results_log.append({
"question": q["q"],
"user_answer": "Temps écoulé",
"correct_answer": q["a"],
"timed_out": True
})
self.update_score()
self.after(500, self.next_q)
def next_q(self):
self.controller.current_q_idx += 1
self.load_question()
def update_score(self):
self.score_lbl.config(text=f"Score : {self.controller.score}")
# -------------------------------------------------
# 6) Écran de résultats
# -------------------------------------------------
class ResultatsPage(ttk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
self.configure(style="Card.TFrame")
self.columnconfigure(0, weight=1)
ttk.Label(self, text="Résultats de la partie",
font=(FONT_FAMILY, 20, "bold")).grid(pady=20)
self.details_txt = tk.Text(self, width=60, height=15, font=(FONT_FAMILY, 11))
self.details_txt.grid(pady=10)
ttk.Button(self, text="Télécharger les résultats (.txt)",
command=self.save_txt).grid(pady=10)
ttk.Button(self, text="Nouvelle partie",
command=lambda: controller.show_frame("AccueilPage")).grid()
def on_show(self):
self.details_txt.delete("1.0", tk.END)
self.details_txt.insert("end", f"Score final : {self.controller.score}\n\n")
for i, entry in enumerate(self.controller.results_log, 1):
self.details_txt.insert("end",
f"{i}) {entry['question']} → "
f"Ta réponse : {entry['user_answer']} | "
f"Réponse : {entry['correct_answer']}\n")
def save_txt(self):
filename = filedialog.asksaveasfilename(
title="Sauvegarder les résultats",
defaultextension=".txt",
filetypes=[("Fichiers texte", "*.txt")])
if not filename:
return
with open(filename, "w", encoding="utf-8") as f:
f.write("=== Ferme Multiplication - Résultats ===\n")
f.write(f"Date : {datetime.datetime.now():%d/%m/%Y %H:%M:%S}\n")
f.write(f"Score : {self.controller.score}/{len(self.controller.results_log)}\n\n")
for entry in self.controller.results_log:
f.write(f"{entry['question']} → "
f"Réponse donnée : {entry['user_answer']} | "
f"Réponse correcte : {entry['correct_answer']}\n")
messagebox.showinfo("Sauvegarde", "Résultats enregistrés avec succès !")
# -------------------------------------------------
# 7) Lancement de l’application
# -------------------------------------------------
if __name__ == "__main__":
app = FermeMultiplication()
app.mainloop()
🧑🌾 Comment l’utiliser ?
- Enregistrer le code dans
ferme_multiplication.py
. - Lancer :bash复制
python ferme_multiplication.py
- Personnaliser :
- Ajoutez vos fichiers
.wav
dans un dossiersounds/
(ex.correct.wav
,wrong.wav
) puis passezUSE_SOUNDS = True
. - Remplacez
WINDOW_ICON
par un.ico
pour la fenêtre Windows. - Changez les couleurs, polices ou images facilement via les variables ou en enrichissant les widgets.
L’application est entièrement responsive (grâce à Tkinter) et fonctionne sur Windows, macOS et Linux, tablettes ou smartphones via des interpréteurs Python mobiles (Pydroid 3, etc.).
Amuse-toi bien dans la ferme !