Saisie texte en temps limité

Résolu
Praseodyme Messages postés 4 Date d'inscription jeudi 29 août 2024 Statut Membre Dernière intervention 30 août 2024 - Modifié le 30 août 2024 à 14:59
mamiemando Messages postés 33443 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 19 décembre 2024 - 2 sept. 2024 à 12:37

Bonjour,

J'ai un bug avec mon code. Ayant commencé à coder il y a moins d'une semaine je me suis lancé le défi de coder des minis jeux, puis de les enchaîner. Mais voilà, dans le jeu du juste prix, quand le timer arrive à la fin, la fonction ne retourne pas False et ne propose pas de relancer une partie. Pouvez-vous me décoincer ?

Au passage, si vous voyez d'autres bugs, n'hésitez pas à me les signaler. Voici le code, n'hésitez pas à le copier coller dans votre terminal ! 

import random
import time
from random import randint
import threading

stop_event = threading.Event()


def shi_fu_mi(point_bot=0, point_joueur=0):
    possibilite_bot = ["pierre", "feuille", "ciseaux"]
    while point_joueur < 3 and point_bot < 3:
        valeur_aleatoire = random.choice(possibilite_bot)
        choix_joueur = input("Choisissez entre pierre feuille et ciseaux : ").lower()
        while choix_joueur not in possibilite_bot:
            print("Vous devez choisir entre pierre feuille et ciseaux")
            choix_joueur = input("Choisissez entre pierre feuille et ciseaux : ").lower()
        print(f"vous avez choisi {choix_joueur}\n")
        print(f"l'ordinateur lui a choisi {valeur_aleatoire}\n")
        if choix_joueur == valeur_aleatoire:
            print("Dommage égalité ")
        elif (
            (choix_joueur == "pierre" and valeur_aleatoire == "ciseaux") or
            (choix_joueur == "feuille" and valeur_aleatoire == "pierre") or
            (choix_joueur == "ciseaux" and valeur_aleatoire == "feuille")
        ):
            point_joueur += 1
            print("Vous avez gagné cette manche")
        else:
            point_bot += 1
            print("Vous avez perdu cette manche")
        print(f"Votre score {point_joueur}, Ordinateur {point_bot}\n ")
        if point_joueur == 3:
            print("Bravo vous avez gagné")
            return True
        elif point_bot == 3:
            print("L'ordinateur à gagné")
            return False


def chrono():
    x = 0
    for i in range(6):
        if stop_event.is_set():
            return
        time.sleep(10)
        x += 10
        print("IL VOUS RESTE ", 60 - x, "secondes")
    print("Temps écoulé !")
    stop_event.set()


def jeu_juste_prix():
    stop_event.clear()
    chrono_thread = threading.Thread(target=chrono)
    chrono_thread.start()
    just_prize = randint(1, 2000)
    player = 0
    while not stop_event.is_set():
        try:
            player = int(input("Je pense que le nombre est "))
            if player == just_prize:
                print("bravo vous avez trouvé le juste prix")
                stop_event.set()
                chrono_thread.join()
                return True
            elif player < 1 or player > 2000:
                print("Vous devez rentrez un nombre entre 1 et 2000")
            elif player > just_prize:
                print("C'est moins")
            elif player < just_prize:
                print("C'est plus")
        except ValueError:
            print("Vous devez rentrez un nombre")
    chrono_thread.join()
    return False


MOTS_ALEATOIRES = [
    "tartiflette", "necrophile", "molecule", "architecte", "vachette",
    "python", "gamberger", "mouiller", "tigre", "mouche", "trouble",
    "montagne", "usine", "oppulence", "laconique", "discuter", "cravate",
    "obtemperer", "programme", "moustache", "extase", "suffit", "gourer",
    "nuises", "rectal", "lymphe"
]


def pendu():
    solution = random.choice(MOTS_ALEATOIRES)
    solutions_lettres = list(solution)
    corps_plein = [" ", "_", "|", "|", "|", "|", "_", "|", "O", "|", "/", "\\", "/", "\\"]
    corps_vide = [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
    doublons = set()
    fin = set()
    counts = 0
    for i in range(30):
        for lettres in solutions_lettres:
            if lettres in fin:
                print(lettres, end=" ")
            else:
                print("_", end=" ")
        print()
        print(f" {corps_vide[6]}{corps_vide[6]}{corps_vide[6]}{corps_vide[6]}{corps_vide[6]}{corps_vide[6]}{corps_vide[6]} ")
        print(f" {corps_vide[5]}    {corps_vide[7]}")
        print(f" {corps_vide[4]}    {corps_vide[8]}   ")
        print(f" {corps_vide[3]}   {corps_vide[10]}{corps_vide[9]}{corps_vide[11]} ")
        print(f"{corps_vide[1]}{corps_vide[2]}{corps_vide[1]}  {corps_vide[12]}{corps_vide[13]} ")
        attemps = input("Je pense qu'il y a un ")
        if len(attemps) != 1:
            print("Vous devez rentrer une seule lettre")
            continue
        elif not attemps.isalpha():
            print("Vous devez rentrer une lettre")
            continue
        elif not attemps.islower():
            print("La lettre doit être minuscule")
            continue
        if attemps in doublons:
            print("Vous avez déjà rentré cette lettre, voici la liste des doublons: " f"{doublons}")
            continue
        doublons.add(attemps)
        if attemps in solutions_lettres:
            print("Bravo", f"{attemps}", "est dans le mot")
            fin.add(attemps)
        else:
            print("Dommage", f"{attemps}", "n'est pas dans le mot ")
            counts += 1
            corps_vide[counts] = corps_plein[counts]
        if all(lettre in fin for lettre in solutions_lettres):
            print("Bravo vous avez trouvé, le mot était ", f"{solution}")
            return True
        print("Il vous reste", f"{13-counts}", " coups ")
        if counts >= 13:
            print("Dommage vous avez perdu")
            return False


erreur = 0


def jeu_fin():
    global erreur
    while True:
        print("Rappel vous êtes à ", erreur, " tentatives")
        if shi_fu_mi():
            print("Le prochain jeu va se lancer dans 10 secondes")
            time.sleep(10)
            if jeu_juste_prix():
                print("Le prochain jeu va se lancer dans 10 secondes")
                time.sleep(10)
                if pendu():
                    print("Bravo vous avez gagné aux 3 jeux")
                    erreur = 0
                    replay = input("Voulez vous rejouer o/n: ")
                    if replay == "o":
                        time.sleep(2)
                        jeu_fin()
                    elif replay == "n":
                        return
                else:
                    print("Dommage vous avez perdu au pendu, vous devez recommencer au début")
                    # corps_plein = [" ", "_", "|", "|", "|", "|", "_", "|", "O", "|", "/", "\\", "/", "\\"]
                    # corps_vide = [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
                    erreur += 1
                    replay = input("Voulez vous rejouer o/n: ")
                    if replay == "o":
                        time.sleep(2)
                        jeu_fin()
                    elif replay == "n":
                        return
            else:
                print("Dommage vous avez perdu au juste prix, vous devez recommencer au début  ")
                erreur += 1
                replay = input("Voulez vous rejouer o/n: ")
                if replay == "o":
                    time.sleep(2)
                    jeu_fin()
                elif replay == "n":
                    return
        else:
            print("Dommage vous avez perdu au pierre feuille ciseaux, vous devez recommencer au début")
            erreur += 1
            replay = input("Voulez-vous rejouer o/n: ")
            if replay == "o":
                time.sleep(1)
                jeu_fin()
            elif replay == "n":
                return


print("""Les règles sont simples: 3 mini-jeux vont s"enchaîner, il faut gagner aux 3 jeux d"affilée pour gagner.
Si vous échouez à un seul des 3, vous devez recommencer depuis le début.
Le premier est le pierre-feuille-ciseaux, le premier à 3 points.
Le deuxième est un juste prix avec un temps d"1 minute pour trouver un nombre entre 1 et 2000.
Et enfin, le dernier est un pendu tout ce qu"il y a de plus classique. Bon courage !""")
time.sleep(25)
print("Etes vous prêt?")
time.sleep(2)
print("3")
time.sleep(0.2)
print("2")
time.sleep(0.2)
print("1")
time.sleep(0.2)
print("go")
time.sleep(0.2)
jeu_fin()

Modération : code remis en forme. Merci de suivre ce tutoriel dans les futurs messages.
Windows / Chrome 128.0.0.0

3 réponses

mamiemando Messages postés 33443 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 19 décembre 2024 7 811
Modifié le 30 août 2024 à 15:10

Bonjour

Plusieurs recommandations préalables.

  • Essaye de garder un sujet par discussion. Ici ta question est double puisque tu demandes de l'aide sur le module threading et une revue de code. Essaye d'être aussi spécifique que possible.
    • Comme le souligne PierrotLeFou (voir #2), il faut alors construire un exemple minimal qui met en évidence le problème avant d'exposer ton problème.
  • Pense à mettre le code en forme (cf ton premier message que j'ai remis en forme + le lien en fin de message).

Correction du bug

Explication du bug : quand le chronomètre expire, le fil d'exécution principale est toujours bloqué sur input.

Solution : Tu peux utiliser la technique proposée dans cette discussion, j'ai testé ça fonctionne : 

import selectors
import sys
  
def main():
    sel = selectors.DefaultSelector()
    sel.register(sys.stdin, selectors.EVENT_READ)
    print("Enter passcode: ", end='')
    sys.stdout.flush()
    pairs = sel.select(timeout=5)
    if pairs:
        passcode = sys.stdin.readline().strip()
        print('you entered:', passcode)
    else:
        print('\ntimed out')
  
if __name__ == '__main__':
    main()

Conseils généraux

Je te renvoie aux suggestions faites par yg_be et PierrotLeFou (voir #1 et #2) :

  • Regarde l'état des variables à l'aide de print. Pour des cas plus avancés, le module pdb peut t'aider. Dans des cas encore plus compliqués, tu peux aussi regarder logging. Mais à ce stade, utiliser print suffit.
  • De plus, c'est souvent une bonne idée de prévoir une suite de tests pour vérifier qu'une fonction fait ce qui faut (notamment dans les cas limites). Une bonne idée est d'utiliser pytest.
  • Tu devrais utiliser un outil comme flake8 qui te permettra de respecter les conventions de codage python et voir quelques erreurs de programmation.  En outre, jeu_fin déclare deux variables corps_plein et corps_vide qui ne servent pas. Hormis les lignes trop longues, j'ai corrigé ton code en conséquence dans le message initial.

Revue du code

Je n'ai pas remarqué de bug à part la partie concernant l'appel à isdigit (voir plus loin). Par contre, tu peux améliorer le design et la robustesse de ton code.

Dans pendu

Robustesse :

  • Le nombre d'essais (variable i) devrait varier de 0 à len(corps_plein).
  • Tu ne devrais pas imposer de taper de taper en minuscule ou majuscule, simplement faire la conversion dans ton code.
  • Le test isdigit n'est pas suffisant car il autorise la saisie d'un chiffre, il faudrait plutôt contrôler ainsi :
    from pprint import pformat
    
    def is_letter(c) -> True:
        c = c.lower()
        return (
            len(c) == 1
            and 'a' <= c <= 'z'
        )
    
    def test_is_letter():
        for (c, expected) in {
            "": False,
            "AB": False,
            "B": True,
            "c": True,
            "1": False,
            "%": False
        }.items():
            obtained = is_letter(c)
            print(f"is_letter('{c}') == {obtained}")
            assert obtained == expected, pformat(locals())
    
    test_is_letter()

Design : Concernant le chrono, yu peux améliorer un peu le design et l'appel de cette fonction

import time
from random import randint
from functools import partial
import threading

stop_event = threading.Event()

def chrono(duration: int = 30, step: int = 10):
    for i in range(0, duration):
        if stop_event.is_set():
            return
        time.sleep(step)
        if i % step == 0:
            print(f"\nIl reste {duration - i} secondes")
    print("Temps écoulé !")
    stop_event.set()


def jeu_juste_prix():
    stop_event.clear()
    chrono_thread = threading.Thread(target=partial(chrono, duration=10, step=2))
    chrono_thread.start()
    just_prize = randint(1, 2000)
    player = 0
    while not stop_event.is_set():
        try:
            player = int(input("Je pense que le nombre est "))
            if player == just_prize:
                print("bravo vous avez trouvé le juste prix")
                stop_event.set()
                chrono_thread.join()
                return True 
            elif player < 1 or player > 2000:
                print("Vous devez rentrez un nombre entre 1 et 2000")
            elif player > just_prize:
                print("C'est moins")
            elif player < just_prize:
                print("C'est plus")
        except ValueError:
            print("Vous devez rentrez un nombre")
    chrono_thread.join()
    print("return False")
    return False

jeu_juste_prix()

Dans le programme principal

Modularité : Ton code commence à être un peu long pour un fichier. Tu devrais faire un fichier par jeu (par exemple shi_fu_mi.py, juste_prix.py et pendu.py), et un fichier principal (disons main.py) qui importe chacun des jeux comme suit :

from shi_fu_mi import shi_fu_mi
from juste_prix import jeu_juste_prix
from pendu import pendu

# Reste du code

Design :

  • Il n'est pas très élégant d'enchaîner les jeux comme tu le fais, tu pourrais faire une liste de fonction, où chaque fonction est un jeu, et itérer sur cette liste en faisant quelque chose dans ce genre :
jeux = [shi_fu_mi, jeu_juste_prix, pendu]
progression = 0
while progression < len(jeux):
   jeu = jeux[progression]
   gagne = jeu()
   if gagne:
      progression += 1
   else:
      print(f"Dommage, vous avez perdu au {jeu.__name__}")
      break
if progression == len(jeux):
   print("Bravo")
else:
   print("Dommage !")
  • Les mots aléatoire du pendu devraient être un paramètre de la fonction pendu. Ainsi tu te laisses la possibilité de charger par la suite tes mots depuis un fichier texte tiers (par exemple /usr/share/dict/french sous Linux).
  • Si tu ajoutes des paramètres aux fonctions de jeu, dans l'extrait de code précédent, utilise partial pour que les paramètres soient précisés au moment de construire jeux (ainsi l'appel du jeu courant se fait sans paramètre).
  • Il faut idéalement se passer de variables globales. La seule exception concerne les "constantes" (que j'ai renommées en capitales afin de se conformer aux conventions python).
  • Garde aussi peu de code que possible en dehors d'une fonction. Ton programme principal devrait être déporté dans une fonction main, et la seule chose en dehors des fonctions devrait être les déclarations des constantes et l'appel à main. Ainsi, tu es sûr que chaque fonction s'en tient aux constantes et aux variables reçu en paramètre (pas de risque de lire une variable globale).

Bonne chance

1
Praseodyme Messages postés 4 Date d'inscription jeudi 29 août 2024 Statut Membre Dernière intervention 30 août 2024
30 août 2024 à 21:48

Bonjour,

Merci pour tous ces bons conseils. Je suis désolé pour toutes mes erreurs, je suis novice dans la programmation et c'était la première fois que j'écrivais dans un forum pour de l'aide. Je vais tâcher d'appliquer tous vos bons conseils

0
mamiemando Messages postés 33443 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 19 décembre 2024 7 811 > Praseodyme Messages postés 4 Date d'inscription jeudi 29 août 2024 Statut Membre Dernière intervention 30 août 2024
2 sept. 2024 à 12:37

Aucun problème, on est tous là pour progresser :-) Est-ce que tu as toutes tes réponses sur le problème initial ? Si oui, merci de basculer le sujet en résolu. Tu peux également upvoter la ou les réponses qui t'ont aidées.

0
yg_be Messages postés 23401 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 19 décembre 2024 Ambassadeur 1 557
29 août 2024 à 20:24

bonjour, quelle technique utiles-tu pour débugger, en dehors de demander de l'aide?

Une technique habituelle, et très simple, c'est d'ajouter des print(), afin de comprendre le comportement du code que tu as écrit.

0
Praseodyme Messages postés 4 Date d'inscription jeudi 29 août 2024 Statut Membre Dernière intervention 30 août 2024
30 août 2024 à 21:38

Bonjour,

j'utilise les point d'arrêt avec le débugger de Visual Studio Code. ET quand je n'arrive pas à me débugger je demande à chat gpt de trouver le problème et de m'expliquer ce qui ne marchait pas. Or, ici chat gpt n'arrivais pas à me débugger.

J'ai essayer de mettre un print si la fonction me return true. Or, la fonction ne m'affichait pas le print en question

0
PierrotLeFou
30 août 2024 à 02:16

As-tu seulement testé le threading avec une fonction très simple qui ne fait que ça?

0
Praseodyme Messages postés 4 Date d'inscription jeudi 29 août 2024 Statut Membre Dernière intervention 30 août 2024
30 août 2024 à 21:41

Bonjour, 

je ne suis pas sûr de bien comprendre votre remarque. Le threading marche sur la fonction juste_prix toute seule. Il se lance en parallèle du jeu et se stop quand on trouve la valeur. Il fonctionne bien. Le problème c'est que quand il arrive au bout sans qu'on ai trouvé, la fonction juste_prix ne return pas False et donc engendre le problème dans la fonction jeu_fin. 

0