新建会话
Ctrl
K
Kimi+
历史会话

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.
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 ?

  1. Enregistrer le code dans ferme_multiplication.py.
  2. Lancer :
    bash
    复制
    python ferme_multiplication.py
  3. Personnaliser :
    • Ajoutez vos fichiers .wav dans un dossier sounds/ (ex. correct.wav, wrong.wav) puis passez USE_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 !
Kimi AI 生成