Forum Programmation.python Un démineur pour apprendre...

Posté par  (site web personnel) . Licence CC By‑SA.
6
5
sept.
2022

Bonjour,

Voici donc un démineur en Python, en mode textuel et graphique Tk.

En mode textuel, il faut prendre la main en interactif après avoir exécuté demineur.py

>>> explorer((5,5))
False
>>> print_cases()
__________________________1X1___
________111_______111_____1X1___
______113X2_______1X1_____111___
______1XXX2_______111___________
11____11211_____________________
X1______________________________
11___________________111________
_____________________1X1________
_____________111_____111________
_____________1X1________________
_____________111________________
________________________________
________________________________
111_____________111_____________
XX1_____________1X1_____________
XX1_____________1X1_____________
Sur les 10 bombes, il vous en reste 10 à trouver.

demineur-g.py permet d’avoir une interface graphique. Le nombre de bombes se trouve indiqué dans la barre de titre.

Titre de l'image

Je l’ai fait dans le cadre du cours que je commence à donner à mes élèves pour apprendre Python.

Moi-même j’apprends le langage depuis peu. Il y a donc certainement plein d’améliorations à faire sur ce code. Je compte sur vos commentaires pour m’aider à améliorer celui-ci.

Je pense devoir faire une version sans récursivité car avec de trop grands terrains, Python atteint une limite… À moins qu’il ne soit possible (dans le code-même ?) de paramétrer cette limite ?

Bien entendu, d’un point de vue didactique, cela doit rester simple, pas trop long, etc.

Merci d’avance:)

(page web dédiée sur mon site)

  • # Commentaires en vrac

    Posté par  . Évalué à 6.

    Bonjour

    J'ai remarqué que la forme int(x/v) est souvent utilisée alors que x est déjà un entier, dans ce cas il vaut mieux utiliser la division entière : x//v

    Il y a plusieurs constructions de conditions qui ne sont pas facile à lire, par exemple dans la fonction draw_case de demineur-g:

    if connu :
        if bombe :
            txt = "*"
        else :
            if nbalentours < 1 :
                txt = " "
            else :
                txt = str( nbalentours )[0]
    else :
        if drapeau < 1 :
            txt = "X"
        else :
            if drapeau < 2 :
                txt = "P"
            else :
                txt = "?"
    

    on peut ici utiliser des elif plutôt que des if imbriqués et des == à la place des < pour améliorer la lisibilité

    if connu and bombe:
        txt = "*"
    elif connu:
        txt = " " if nbalentours==0 else str(nbalentours)
    elif drapeau == 1:
        txt = "P"
    elif drapeau == 2:
        txt = "?"
    else:
        txt = "X"
    #la dernière partie pourrait juste être (drapeau ne peux valoir que 0,1 ou 2)
    #else:
    #   txt = "XP?"[drapeau]
    

    La fonction explorer renvoie False si ont sort de la grille ou si on sort normalement de la fonction, elle renvoie True si on est sur une bombe, mais il me semble que la valeur renvoyée n'est jamais utilisée, de plus le test pour savoir si la case est sur la grille peut être simplifié en utilisant le dictionnaire de la grille if case not in cases:

    La syntaxe cases[(ligne, colonne)] être remplacée par cases[ligne, colonne], c'est le signe , qui défini le tuple pas les parenthèses (sauf pour le tuple vide ())

  • # Quelques remarques du coup

    Posté par  (site web personnel) . Évalué à 7.

    Je pense qu'avec une classe Case et une liste de liste d'instances de cette classe on aurait un code un peu plus joli. Autant l'unpacking (ex: a, b, c = foobar ) est une fonctionnalité cool de Python, autant là ça ne me semble pas très pertinent ; en effet vous passez votre temps à unpacker des tuples (il faut bien se rappeler de la sémantique de chaque élément du tuple), à avoir des variables inutiles (tous les éléments du tuple ne sont pas utiles à chaque fois), et à reconstruire des tuples (vu qu'ils sont immuables).

    Avec des instances, ce code :

    def tout_connaitre() :
        for ligne in range( hauteur ) :
            for colonne in range( largeur ) :
                (bombe, connu, nbalentours, drapeau) = cases[(ligne, colonne)]
                connu = True
                drapeau = 0
                cases[(ligne, colonne)] = (bombe, connu, nbalentours, drapeau)

    deviendrait par exemple :

    def tout_connaitre():
        for ligne_cases in cases:
            for case in ligne_cases:
                case.connu = True
                case.drapeau = 0

    Hop une f-string pour la route:
    print( "Sur les",nbombes, "bombes, il vous en reste",nbombes-nbdrapeaux, "à trouver.")
    peut s'écrire plus simplement:
    print(f"Sur les {nbombes} bombes, il vous en reste {nbombes - nbdrapeaux} à trouver.")

    Bonne nuit !

  • # Tkinter canvas

    Posté par  (site web personnel) . Évalué à 6.

    Les canvas tkinter c'est bien. Mangez en !
    Franchement tkinter est un peu vieillissant niveau graphique mais c'est ultra puissant.
    Le canvas ne fais pas exception.

    cv.create_text(...) (ainsi que tous les create_foo) retourne l'id de l'objet créé.
    Si tu utilises des classes comme proposé dans le commentaire précédent, tu peux stocker l'id et modifier le texte (avec cv.itemconfigure(id, ...)) plutôt que supprimer tous les éléments et recréer les lignes et textes.

    Tu peux associer des tags à des items dans un canvas. Ça te permet ensuite de retrouver tes petits et de les modifier d'un seul coup.
    Par exemple, si tu tags correctement tes éléments, tu peux les modifier très facilement :

    # On crée la grille
    for ligne in range(De.hauteur):
        for colonne in range(De.largeur):
            cv.create_text(X, Y, text=..., tags=("case", f"l:{ligne}", f"c:{colonne}"))
    
    # On change le texte d'une case x,y:
    cv.configureitem(f"l:{x} && c:{y}", text=...)
    # Oui, on peut faire des requêtes "complexes" sur les tags
    
    # Il existe deux tags magiques : "all" pour tous les éléments et "current" pour .... l'item sous le curseur de la souris. Du coup, tu peux trouver l'élément à modifier avec :
    cv.find_withtag("current")

    Et puis tu peux binder des événements directement aux tags plutôt qu'au canvas:

    cv.tag_bind("case", "<Button-1>", on_click)
    def on_click(event):
        item = cv.find_withtag("current")
        ....

    Par contre il n'est pas possible d'associer une valeur arbitraire à un item.
    Du coup, difficile de retrouver les coordonnées d'un item à partir de l'item lui même.
    Soit en utilisant un dict qui map l'id de l'item à ses coordonnée (ou l'objet python qui va bien)
    Soit en scannant les tags de l'item:

    def get_line(tagOrId):
        for tag in cv.gettags(tagOrId):
            if tag.startswith("l:"):
                return int(tag[2:])

    Bien sûr, du coup, ta fonction resize doit modifier les items existant au lieu de les recréer.

    LA doc qui va bien c'est l'officielle de tlc/tk : https://www.tcl.tk/man/tcl8.5/TkCmd/contents.html
    C'est un peu brut puisque c'est en tcl mais le mapping tcl=>python est assez simple, en se basant sur tkdocs (https://tkdocs.com/tutorial/canvas.html) qui affiche des exemples dans plusieurs langages on retrouver facilement ses petits.

    Et bien sûr, ce mode de fonctionnement se retrouve dans tout tk, des widgets de base qui peuvent être tagués et avec possibilité de rebinder tout les widgets au widget text qui permet de grande configuration du texte.
    Le tout en rajoutant la possibilité de mettre n'importe quel widget dans un canvas ou du text.
    C'est parfois un peu bas niveau ancien, il faut parfois écrire un peu (beaucoup) plus de code que pour d'autre toolkit pour des trucs un peu complexe, mais tkinter est le seul toolkit graphique qui soit si configurable (à l'exception peut-être des kivy et QtQML que je n'ai pas testé, mais on est dans un autre domaine de toolkit)
    Je me languis d'une mise à jour majeur de tk ou d'un nouveau toolkit un peu plus moderne mais qui reprenne les même principe de fonctionnement que tk.

    Matthieu Gautier|irc:starmad

    • [^] # Re: Tkinter canvas

      Posté par  (site web personnel) . Évalué à 3.

      J'ai pas mal galéré avec un ch'tit projet en UI, et puis j'ai découvert PySimpleGUI. C'est une surcouche à tkinter et c'est tell'ment plus mieux simple.

      (en plus, y'a plein plein d'exemples inclus)

      Proverbe Alien : Sauvez la terre ? Mangez des humains !

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.