from random import randint


LONGUEUR_CODE = 4
MIN_CODE = 1000
MAX_CODE = 9999

MESSAGES = {
    "welcome": "Bienvenue sur Mastermind !",
    "goal": f"Devinez le code secret à {LONGUEUR_CODE} chiffres choisi par la machine.",
    "ready": "Êtes-vous prêt ? (oui/non)",
    "difficulty": "\nChoisissez votre niveau de difficulté :\n"
                  "1 - Facile (15 essais)\n"
                  "2 - Moyen  (10 essais)\n"
                  "3 - Difficile (5 essais)",
    "prompt_guess": f"Entrez un nombre entre {MIN_CODE} et {MAX_CODE} (ou 'q' pour abandonner) : ",
    "invalid_guess": f"Erreur : entrez exactement {LONGUEUR_CODE} chiffres (ex. 1234).",
    "bye": "D'accord, à bientôt !",
    "aborted": "Partie abandonnée. À une prochaine fois !",
}


def pluriel(n: int, singulier: str, pluriel_: str | None = None) -> str:
    """Retourne la forme correcte selon n (0/1/pluriel)."""
    if n == 1:
        return singulier
    return pluriel_ if pluriel_ is not None else singulier + "s"

def demander_oui_non(invite: str) -> bool:
    """Pose une question oui/non et renvoie True si oui."""
    reponse = input(f"{invite}\n> ").strip().lower()
    return reponse in {"oui", "o", "yes", "y"}

def choisir_difficulte() -> int:
    """Demande la difficulté et renvoie le nombre d'essais."""
    print(MESSAGES["difficulty"])
    choix = input("Votre choix (1/2/3) : ").strip()
    if choix == "1":
        return 15
    if choix == "2":
        return 10
    if choix == "3":
        return 5
    print("Choix invalide. Le mode moyen (10 essais) est sélectionné par défaut.\n")
    return 10

def generer_code_secret() -> str:
    """Génère un code secret à 4 chiffres sans zéro en tête (1000-9999)."""
    return str(randint(MIN_CODE, MAX_CODE))

def saisir_essai() -> str | None:
    """
    Demande une proposition. Renvoie:
      - la chaîne à 4 chiffres si valide,
      - None si l'utilisateur abandonne (tape 'q').
    """
    valeur = input(MESSAGES["prompt_guess"]).strip().lower()
    if valeur in {"q", "quit", "exit"}:
        return None
    if valeur.isdigit() and len(valeur) == LONGUEUR_CODE:
        return valeur
    print(MESSAGES["invalid_guess"], "\n")
    return ""  # signal pour redemander

def compter_rouges(code_joueur: str, code_machine: str) -> int:
    """Compte les chiffres bien placés (rouges)."""
    return sum(1 for i in range(LONGUEUR_CODE) if code_joueur[i] == code_machine[i])

def compter_blancs(code_joueur: str, code_machine: str) -> int:
    """
    Compte les chiffres corrects mais mal placés (blancs).
    Méthode : on “consomme” les correspondances dans une copie de la liste machine,
    puis on retire les rouges à la fin pour garder uniquement les mal placés.
    """
    liste_machine = list(code_machine)
    blancs_total = 0
    for ch in code_joueur:
        if ch in liste_machine:
            blancs_total += 1
            liste_machine.remove(ch)
    rouges = compter_rouges(code_joueur, code_machine)
    return blancs_total - rouges

def afficher_feedback(rouges: int, blancs: int, essais_restants: int) -> None:
    """Affiche le feedback utilisateur et le nombre d'essais restants."""
    print(
        f"→ {rouges} {pluriel(rouges, 'chiffre bien placé')} "
        f"et {blancs} {pluriel(blancs, 'chiffre correct')} mais mal placé.\n"
        f"Il vous reste {essais_restants} {pluriel(essais_restants, 'essai')}.\n"
    )

def afficher_victoire(code: str, essais_utilises: int) -> None:
    """Affichage final en cas de victoire (pas de tête ASCII moche, promis !)."""
    print("Bravo ! Vous avez trouvé le code secret !")
    print(f"Code : {code} — en {essais_utilises} {pluriel(essais_utilises, 'essai')}.\n")
    print("        .''.")
    print("     .'':.  .''.     *   *    *")
    print("   .':  :''  :  '.        *")
    print("  :  :  :     :   :   *         *")
    print("  :  :__:_  __:   :        *")
    print("  '._____''_____.'    *")
    print("        🏆  Mastermind Winner 🏆")
    print()

def afficher_defaite(code: str) -> None:
    """Affichage final en cas de défaite."""
    print(f"Dommage... Le code secret était {code}.\nEssayez encore, vous y êtes presque ! 💪")

# -------------------- Boucle de jeu --------------------
def jouer() -> None:
    print("*" * 50)
    print(MESSAGES["welcome"])
    print(MESSAGES["goal"])
    print()

    if not demander_oui_non(MESSAGES["ready"]):
        print(MESSAGES["bye"])
        return

    essais = choisir_difficulte()
    code_secret = generer_code_secret()
    essais_restants = essais
    gagne = False

    while essais_restants > 0:
        proposition = saisir_essai()
        if proposition is None:
            print(MESSAGES["aborted"])
            return
        if proposition == "":  # entrée invalide → on redemande sans consommer d'essai
            continue

        rouges = compter_rouges(proposition, code_secret)
        blancs = compter_blancs(proposition, code_secret)

        if proposition == code_secret:
            gagne = True
            essais_utilises = essais - essais_restants + 1
            afficher_victoire(code_secret, essais_utilises)
            break

        essais_restants -= 1
        afficher_feedback(rouges, blancs, essais_restants)

    if not gagne:
        afficher_defaite(code_secret)

if __name__ == "__main__":
    jouer()

