Catégorie : Projets

Projets

Un Sokoban pour la NumWorks

Le Sokoban est un jeu de puzzle inventé au Japon en 1982. Le principe est simple : vous êtes un gardien d’entrepôt et vous devez amener les caisses aux bons emplacement. Pour cela, vous pouvez uniquement pousser les caisses, et une seule à la fois.*

Une partie des informations de cette page sont extraites de la page Wikipédia Sokoban.
Si vous souhaitez creuser le sujet, n’hésitez pas à la consulter.

L’objectif du jeu est de résoudre les niveaux en un nombre minimum de déplacements. Par ailleurs, il existe des milliers de niveaux, pour la majorité créés par la communauté. En fonction de la taille et de la complexité de la grille, ainsi que de l’exigence de l’utilisateur pour réduire son nombre de coups, tous les niveaux de difficultés sont imaginables. Ces éléments donnent une grande durabilité au jeu.

En raison de son apparition rapide dans le monde du jeu vidéo, de sa simplicité apparente et de son intérêt toujours renouvelé, le jeu a connu un grand succès et a été développé sous de nombreuses versions et plateformes. A mon tour de le porter sur la calculatrice NumWorks !

Fonctionnalités

  • Aide (règles, touches, signification des couleurs) affichée au lancement et accessible à tout moment.
  • Ajustement de l’affichage à la taille de la grille.
  • Possibilité d’annuler les mouvements précédents.
  • Déplacement rapide.
  • Détection de la victoire.
  • Ajout et choix de niveaux facile (voir la section correspondante).

Lancer le jeu

Le moteur de jeu et le contenu (les niveaux) sont stockés dans des scripts séparés :

Pour jouer, il faut télécharger ces scripts et exécuter sokoban.py. Il vous est demandé de préciser la cartouche (liste de niveaux) à utiliser ainsi que le niveau auquel vous souhaitez commencer. Il n’est pas nécessaire de remplir ces champs, le jeu commencera par défaut au niveau 1 de la cartouche soko.

Ajouter des niveaux

Si vous venez à bout des 50 niveaux inclus dans la cartouche de base, vous pouvez en télécharger une autre sur le Workshop. Ils sont nommés soko1soko2, … Pour les utiliser, il suffit de rentrer le nom de la cartouche au lancement du jeu.

Une présentation détaillée de ce projet à été publiée sur tiplanet.org

Projets

Un Tetris en python pour la NumWorks

Tout le monde sait qu’une calculatrice programmable sert à jouer à des jeux. Or quand on parle de jeux, un des premiers à nous venir en tête est Tetris, alors pourquoi pas un Tetris pour NumWorks ?

Ainsi, après un démineur, un snake, on vous propose cette fois ci le jeu mythique de la Gameboy adapté en flat design et codé en python pour la NumWorks.

Donne moi 32 ko, je te ferai un Tetris !

Jusqu’à la version 13.2 d’Epsilon, l’OS officiel de la NumWorks, faire un jeu en python sur la couche graphique de la NumWorks était impossible et les scripts commençaient à planter dès qu’ils pesaient 3-4 ko.

Avec un tas python heap de 32 ko, on peut enfin commencer à s’amuser et monter des projets dont le code source pèse près de 8ko.

Le récit de la création d’un jeu en image

Tout commença le 19 avril 2020 dans une nuit confinée.

🧐 Bon on vient de finir nos trois projets en python, on fait quoi maintenant ?
🤓 Oh on peut acheter des contrefaçon de Gameboy sur Aliexpress pour 10€ ! Et il y a 400 jeux dedans.
🧐 La nuit porte conseil, on en parle demain, enfin dans quelques heures !

Le lendemain :

🧐 Bon on fait un Tetris. Je viens de lire l’article sur Wikipédia, c’est passionnant. Par contre par un mot à Arthur, il va nous piquer notre idée sinon. Et cette fois, on ne stocke pas coordonnées des cases occupées dans des matrices, et on va utiliser la fonction get_pixel() de la NumWorks pour déterminer si une case est occupée ou non.

et c’est ainsi qu’un premier Tétrominos apparaît à l’écran.

🤓 J’ai peut être fini la collision
🧐 Hein ?! Mais j’ai pas commencé moi…
🧐 Bon je partage l’écran. Qu’est ce qu’on a dit déjà ? 120 pixels pour le menu, 120 pixels pour le score et 42 pixels pour le jeu en lui même c’est bien ça ?


🧐 Bon je viens de finir la maquette d’une ébauche de menu, reste à le coder !
🤓 et moi je viens de finir les collisions …
🧐 Pour les couleurs ça sera délicat et subtil. On va s’écarter du code couleur original.

🧐 Et du coup on va faire un rappel dans le menu
🤓 On fixe les points d’ancrage dans les pièces, et ainsi elles peuvent tourner

Rendu final souhaité (et obtenu)

🤓 Astuce : Si tu joues sur Omega, alors l’interface du jeu n’est plus orange mais rouge, elle s’adapte au fork de Epsilon.

🧐 D’ailleurs faudra que je relance l’équipe de développement de Omega, ils m’ont promis un tas python de 120 ko et j’ai l’impression que ce projet n’avance pas !
🧐 Bon ils sont encore sur répondeur, ils ont du bloquer mon numéro !
🤓 On fait une pause, j’ai envie de jouer un peu :

🧐 Bon je vais spoiler Critor de tiplanet.org mais toujours aucune info à Arthur !
🤓 Score codé, avec des fonctions polynôme et les points marqués dépendent du niveau du jeu …
🧐 Niveau max : 99, Score max : 999 999
🤓 Vitesse du jeux codé, avec une fonction logarithme népérien car un polynôme n’était pas adapté …

🧐 Bon maintenant s’agit de faire un gros score, histoire d’être certain que Critor ne nous batte pas !

Commencé un dimanche, fini le mercredi suivant, c’était presque trop facile !

🧐 Je reviens, je vais conseiller à Critor de commencer level 42, ça plantera son score !

Meilleurs scores

🧐 Il a commencé sa partie directement au niveau 42, du coup son score est à peine acceptable !🤓 En plus, il a triché ! Il a utilisé une vision prédictive triple et la grille d’aide !

Bien évidemment, il a nié avoir triché 🙄

Je ne les ai même pas utilisés, c’était pour mettre à l’écran le maximum d’éléments visuels, car j’étais parti pour l’utiliser également comme photo d’illustration d’article

Xavier Andréani, alias Critor du tiplanet.org

Mais nous ne sommes pas dupes non plus !

Réagir ou commenter ce jeu

N’hésitez à réagir à cet article sur le forum tiplanet.org associé :
Tetris en Python pour NumWorks 13.2.0+

Vous pouvez également réagir à l’annonce de ce Tetris posté sur twitter :

Projets

Un menu en python pour les jeux sur la…

Un jeu commence souvent par un écran de paramétrage. Celui-ci doit être ergonomique, intuitif, et permettre à l’utilisateur final de personnaliser son expérience ludique. Cette article présente l’un des tout premier menu adaptable conçu en python pour la calculatrice NumWorks, ainsi que 3 exemples d’utrilisation.

Le contexte

Avec la sortie de la version 13.2.0 d’Epsilon, l’OS qui anime la calculatrice NumWorks, les capacités de la calculatrice à exécuter des scripts python a été doublée.

Le tas python « heap » est ainsi passé de 16ko à un peu plus de 31ko.
Et on peut donc désormais se lancer dans des projets un peu plus ambitieux.

Après avoir développé chacun un démineur en python pour la NumWorks, nous avons décidé de mettre au point un système de menu commun, paramétrable, utilisable dans ces deux démineurs et dans le snake que Arthur Jacquin a développé.

Ce menu a été codé pour permettre un maximum de souplesse, donc n’hésitez pas à l’utiliser comme tel ou à l’adapter.

D’un écran statique à un menu dynamique

Le « menu » originel de l’un des deux démineurs n’est en fait que du texte, affiché quelques secondes, uniquement lors du premier lancement du jeu. On ne peut pas interagir, et il va falloir intuiter le fonctionner des différentes touches.

Sur une application de calcul mental, nous avions conçu un vrai menu en mode texte :

Avec un système de navigation interne au menu primitif mais fonctionnel.

L’idée était donc de créer un menu ergonomique, sur la couche graphique de la NumWorks.

Les quelques croquis conceptuels ci-dessous illustrent parfaitement le cheminement fait d’essais/erreurs pour concevoir un menu simple, intuitif, ergonomique. La démarche est évidemment ponctuée de tests de positionnement.

Croquis

Test

Vertical ou horizontal ? Hybride ? Finalement, c’est l’affichage vertical qui est retenu, avec une coloration de l’objet actif pour une navigation claire, simple, en flat design.

Fonctionnalités

  • S’accorde avec la couleur de l’OS (Epsilon est orange, Omega rouge),
  • Permet le paramétrage de 1 à 5 paramètres, comportant autant d’options que souhaité,
  • Permet une option par défaut pour les paramètres,
  • Design épuré, minimaliste,
  • Possibilité d’afficher des informations sur plusieurs lignes.

Brancher le menu sur votre programme

Il suffit ensuite d’appeler la fonction menu() avec votre application. Elle retourne une liste des paramètres validés.

Il est souhaitable de faire cet appel proprement, et de prévoir le cas ou le menu n’est pas disponible, ainsi si le menu n’est pas présent sur la calculatrice, alors le jeu fonctionnera quand même avec des paramètres par défaut.

Par exemple :

try:
    mines, co, cr = menu("MINESWEEPER",
         "Lancer la partie",
         ["Mines", 22, 32, 42, 12],
         ["Commandes", " Nav: Flèches", " Déminer: OK", "Drapeau: DEL", "Rejouer: OK"],
         ["Crédits", "Arthur J.", "Vincent R."])[0]
except :
    mines = 20

Pour les utilisateurs avancés de ce menu, il est également possible de modifier les paramètres d’affichage avec une version souple (les versions du menu seront déclinées en fin d’article), permettant ainsi d’afficher plus de 5 paramètres, des chaînes de caractères plus longues, ou d’ajuster selon vos goûts.

Un branchement plus détaillé est présenté à la fin de cet article.

Paramètres de la fonction()

Le deuxième paramètre est le nom de l’action correspondant à la validation des paramètres, ici « Lancer la partie ».

Les paramètres suivants sont stockés dans des listes, il peut y en avoir jusqu’à 4 ou 5 et le dernier gère l’affichage sur deux lignes de manière optionnelle. Chaque paramètre comporte, dans l’ordre :

  • 1 nom (chaîne de caractère)
  • 1 ou plusieurs options

Affichage d’information

Les paramètres, comme leur nom l’indique, sont destinés à paramétrer l’expérience qui suivra ce menu. Cependant, vous pouvez également les utiliser pour afficher des informations, telles que les crédits ou les commandes. C’est notamment pour cela qu’est prévu l’affichage sur plusieurs lignes (qui est optionnel).

L’affichage multiligne n’est utilisable uniquement sur le dernier paramètre, et réduit évidemment le nombre de paramètres possibles. Pour l’activer, il suffit de séparer le contenu de la première ligne et celui de la deuxième par un dollar ($), par exemple :

  • « Auteur$Vincent Robert »
  • « Site web$nsi.xyz/snake »
  • « Navigation$Flèches »

Le dollar ($) est un caractère ACSII standard mais il faudra le saisir depuis le workshop, il n’est pas disponible de le saisir directement sur la calculatrice.

Epsilon ou Omega ?

L’essai d’une fonction exclusive à Omega suffit, et permet un accord visuel de l’objet actif avec la barre de titre de l’écran :

def omega(): # Vérificateur d'OS
    try: get_keys()
    except: return False
    else: return True

Détection des touches

Les différents projets, et notamment les jeux, nécessitent très souvent un système de détection des touches pressées. La fonction wait() est destinée à cet usage, et s’importe en même temps que le menu. Pratique !

def wait(buttons = range(53)): # Attends qu'une des touches précisées soit pressée
    while True:
        for i in buttons:
            if keydown(i):
                while keydown(i): True # Fonction anti-rebond
                return i

La fonction, par défaut, attendra qu’une des 46 touches soit pressée. Cependant, pour plus d’efficacité et pour n’avoir à traiter uniquement les signaux qui vous intéressent, vous pouvez passer en paramètre la liste des touches à interroger.

La table des codes clavier de la NumWorks est accessible sur cet article de Ti-Planet.

Table des codes clavier

Tester le menu

Le menu est disponible ici :

MenuVersion AVersion B
VariationsLe titre est en noir, quelques variations dans le code mais le rendu final est identiqueLe titre est en bleue, quelques variations dans le code mais le rendu final est identique
Téléchargermenu.pymenu.py
Démonstrationmenu_demo.pymenu_demo.py
Développementmenu_devn.a.

Pour le voir en action directement dans le workshop, vous pouvez utiliser une version non branchée mais autonome, ce sont les scripts menu_demo.py.

Une version de développement est également disponible, si vous souhaitez changer ou tester d’autres réglages : menu_dev.py

À la parution de cet article, quelques scripts utilisent ce menu, ce qui peut vous donner une idée plus concrète des usages possibles du menu :

Liendemine.pysnake.pycalcul.py
Taille8.42 ko8.42 ko8.42 ko
Auteur principalRobert VincentArthur JacquinFedyna Kevin
Menu

Ces applications existent en deux versions :

app.py est l’application qui intègre une copie du code du menu, app_se.py désigne une édition spéciale, allégée de menu qui est appelé depuis un autre script. Ainsi, le code du menu est mutualisé, ce code occupe donc moins de place car l’espace de stockage des scripts sur la calculatrice est limité.

Les applications app_se.py peuvent se lancer à priori sans le menu, mais dans ce cas là, il n’est pas possible de paramétrer l’application. On a encore quelques bug à résoudre, un try import semble ne pas fonctionner … On travaille sur ce sujet. Les applications sans _se sont par contre fonctionnelles.

Si l’équipe de Numworks nous lit, on aimerait bien

  • profiter un peu des 8 Mo de la ROM de la N0110, et donc avoir plus de 32ko pour stocker nos scripts
  • Que les touches les touches manquantes soient mappés sur le workshop, c’est à dire qu’il faut mapper toutes les touches en fait et non pas seulement 7…

Brancher proprement le menu sur une application

Un branchement propre est un branchement qui se comporte ainsi :

  • Si au lancement du script, la fonction menu() n’est pas détectée alors le jeux ou l’application se lance avec les paramètres par défaut, c’est un fonctionnement dégradé mais l’application marche. (On a encore quelques bug à résoudre)
  • Si au lancement du script, la fonction menu() est détectée, le menu se lance.

Exemple de branchement qui intègre le mode de fonctionnement dégradé :

def start():
    global mines, triche, graphisme
    try:
        mines, triche, graphisme, com, cre = menu("Démineur", "Lancer la partie",
                                                  ["Mines", 19, 22, 25, 28, 31, 33, 36, 39, 42, 7, 10, 13, 16],
                                                  ["Mode Triche", "non", "oui"],
                                                  ["Graphisme", "sobre", "classique"],
                                                  ["Commandes", "Nav: Flèches", "Déminer: OK", "Drapeau: Retour",
                                                   "Rejouer: Maison", "Triche: shift"],
                                                  ["Crédits", "Site web$nsi.xyz/demine", "Auteur$Vincent Robert",
                                                   "Contributeur$Bisam tiplanet.org",
                                                   "Contributeur$Critor tiplanet.org", "Contributeur$Arthur Jacquin"])
        triche = 0 * (triche == "non") + 1 * (triche == "oui")
        graphisme = 0 * (graphisme == "sobre") + 1 * (graphisme == "classique")
    except:
        mines, triche, graphisme = 19, 0, 0
    jeux()

Pour que tout ceci marche, il faut que la calculatrice importe le script menu.py et le script qui utilise le menu lors de la bascule vers la console d’éxécution. Ce fonctionnement ne peut malheureusement pas être simulé sur le workshop, mais sur calculatrice cela fonctionne.

Depuis le gestionnaire de script python, il suffit d’activer les options :
« Importation auto dans la console » des deux scripts.

Vous pouvez réagir à cet article sur le topic tiplanet.org de cette news :
Snake, calcul mental, et interface lancement jeux Python

Projets

Un Snake codé en python pour la NumWorks

Le snake est un jeu très ancien, apparu en 1976 dans Blockade. Le principe est simple : le joueur dirige un serpent qui grandit, et doit éviter le plus longtemps possible de se mordre la queue ou les bordures. Il existe cependant de très nombreuses variantes. Nous vous proposons ici un snake codé en python pour la NumWorks.

Un classique des portables Nokia sous Symbian OS

Sa simplicité apparente et son caractère addictif l’ont mené à être porté sur de très nombreuses plateformes, provoquant un succès planétaire. Nokia y a grandement participé en l’intégrant sur ses téléphones dès 1998, y compris l’iconique 3310.

Cet article présente donc une énième version de ce jeu, cette fois pour la calculatrice NumWorks.

Evolution et défis

La première version ne propose pas d’option et stocke le corps du serpent dans une liste constituée des coordonnées, en divisant l’écran en une grille de 32 par 20 carreaux :

snake = [[3, 3], [4, 3], [5, 3], [6, 3]] # Corps du serpent, de la queue à la tête

Fonctionnel, très pratique pour le faire avancer, par les méthodes pop()et append(), et pour savoir comment dessiner les bordures. Cependant un problème apparaît rapidement : l’occupation de la mémoire. En effet, les listes, et tout particulièrement les listes de listes, sont très coûteuses.

La première évolution a donc consisté à changer le système de stockage du serpent : désormais, seules les coordonnées de la queue et la tête sont enregistrées, diminuant considérablement la place occupée en mémoire, et ce quelle que soit la longueur du serpent.

snake = [3, 3, 6, 3] # Coordonnées de la queue et de la tête

Cela demande en revanche de modifier l’ensemble des procédures graphiques, et d’utiliser une méthode de détection de couleur de pixel, kandinsky.get_pixel(). Les fonctions get_pixel() et set_pixel()n’étant pas bijectives, il a fallu s’assurer de la bijectivité des couleurs utilisées. (Ceci fera l’objet prochainement d’un article dédié)

Pour éviter des conversions fréquentes entre les coordonnées de la grille et celles des pixels, l’ensemble des coordonnées est ensuite modifié pour ne présenter uniquement des coordonnées en pixels.

snake = [30, 52, 60, 52] # Coordonnées de la queue et de la tête

La gestion de la direction a également suscité réflexion, car il faut pouvoir saisir à tout moment les instructions de changement de direction, et il faut également éviter au joueur de mourir lors des demi-tours. Après quelques améliorations, on arrive à ce code :

# Gestion du temps et de la direction
direction = di
while monotonic() < time + speed: # Attente du prochain rafraîchissement du serpent
    for k in range(4): # Changement de direction
        if keydown(k) and direction+k != 3: di = k
    if keydown(6): start() # Retour au menu
time = monotonic()

A la demande de Robert Vincent, j’adapte le code pour pouvoir implémenter un mode où la téléportation d’un bord à l’autre de l’écran est possible, par un jeu de modulos. Le script perd en optimisation, mais gagne en polyvalence.

Les étapes suivantes sont moins lourdes de conséquences :

  • ajout de différents niveaux de difficulté, modifiant la vitesse du serpent et l’intensité de son agrandissement à chaque pomme mangée. 
  • ajout du mode « Dingue », où le serpent grandi et accélère tout seul, progressivement, au lieu de manger des pommes. 

Les dernières étapes ont été de brancher le jeu sur un menu polyvalent graphique en python codé en parallèle, et de finaliser les options d’accessibilité : 

  • choix définitif des touches utilisées
  • amélioration du dessin de la pomme 
  • ajustement du système de points

L’ajout d’autres modes n’est pas exclu.

Images

MenuInterface de jeu
TéléportationPartie perdue

Réagir à cet article

Vous pouvez réagir à cet article sur le topic tiplanet.org de cette news : 
Snake, calcul mental, et interface lancement jeux Python

Projets

Un démineur en python sur la NumWorks

Le démineur, jeu de réflexion dont le but est de localiser des mines cachées dans une grille représentant un champ de mines virtuel, avec pour seule indication le nombre de mines dans les zones adjacentes est désormais porté en python sur ta NumWorks !

Souvenir de jeunesse

J’ai découvert la programmation sur une désormais antique Casio 9960GT, propulsé par son CPU 8 bits à 4,3 MHz. Lycéen et autodidacte, j’ai alors entrepris de créer un démineur sur ma calculatrice et cela fut pour moi une découverte de différents concepts en algorithmique.

Le processeur de cette génération de calculatrice était tellement lent et l’accès à la mémoire encore plus lent que chaque test logique prenait un temps considérable. Le démineur codé et parfaitement fonctionnel se montrait ainsi d’une lenteur remarquable au premier lancement lors de l’affichage de la couche graphique, il fallait près de 42 secondes pour initialiser la couche graphique.

Quelques caractéristiques de ce démineur :

  • 5 niveau de difficulté
  • un plateau de jeu de 14 cases par 7, soit 98 cases en tout.
  • L’initialisation de la couche graphique prenait à elle seule plus de 30 secondes
  • La navigation était fluide, mais la fonction découverte pouvait prendre une bonne dizaine de seconde.

Sur une NumWorks, en Python

20 ans plus tard, l’idée est donc de coder un nouveau démineur, en python, et de le faire tourner sur la calculatrice à la mode du moment : une NumWorks, propulsé par un CPU ARMv7 à 100 / 200 mhz, donc probablement plus de 50 fois plus rapide que celui de mon antique Casio.

Mes élèves de seconde étant tous équipés de cette calculatrice, ils pourront s’amuser en silence quand les cours sont trop ennuyeux, et cela montrera aux futurs élèves de la spécialité NSI les possibilités de la calculatrice et la souplesse du langage python.

L’objectif est de sortir ce démineur pour la sortie officielle de la version 13 de Epsilon, le logiciel sous licence CC BY-NC-SA de la calculatrice NumWorks. La version 13 de Epsilon introduit beaucoup d’améliorations et de nouveautés à l’application Python, celle qui nous intéresse particulièrement et le getkey, la capacité de la calculatrice dans un script python à détecter si une touche est pressée.

Le site tiplanet.org ayant déjà bien documenté cette fonctionnalité, on dispose donc déjà des codes associés aux touches :

Créer une interface graphique

Pour faire un démineur, on a besoin d’un tableau de n lignes et p colonnes dans lequel on va afficher une mine, un drapeau ou un chiffre.

Il faut donc s’adapter à la résolution de l’écran, et la principale contrainte à prendre en compte est la taille d’affichage des chiffres.

Puis on les met en couleur

et enfin on code les mines et le drapeau.

#codage des couleurs
co=color
f,h,n = 255,127,0
c=[co(f,f,f), co(45,125,210), co(151,204,4), co(238,185,2), co(244,93,1), co(215,65,167), co(n,n,n), co(n,n,n), co(n,n,n),
   co(h,h,h),co(192,192,192),co(96,96,96),co(253,236,185)]
def terrain(): #Affiche la grille
  fill_rect(8,21,300,200,c[9])
  for y in range(21,243,20):
    for x in range(8,309):
      set_pixel(x,y,c[10])
  for x in range(8,320,20):
    for y in range(21,222):
      set_pixel(x,y,c[10])
def cl(x,y):
  fill_rect(9+20*x,22+20*y,19,19,c[0])
 
def drapeau(x,y):
  fill_rect(17+20*x,26+20*y,3,9,c[12])
  fill_rect(17+20*x,36+20*y,3,3,c[12])
 
def mine(x,y): # Ce code ne sera pas retenu pour la version finale.
  cl(x,y);
  fill_rect(12+20*x,36+20*y,13,4,c[9])
  fill_rect(14+20*x,34+20*y,9,2,c[11])
  fill_rect(17+20*x,32+20*y,3,2,c[5])

Une matrice pour stocker les mines

Le plus simple est de stocker les mines dans une matrice 10×15.
Pour cela on défini une matrice en python et on la rempli de 0.

m = [[0 for y in range(10)] for x in range(15)]

puis un script génère des bombes, codées 999 dans la matrice.

def gvt(): #fonction renommée depuis ...
  b = 20
  while b>0:
    x, y = randint(0,14),randint(0,9)
    if m[x][y]!=999:
      m[x][y]=999
      b-=1
      for p in range(max(0,x-1),min(15,x+2)):
        for q in range(max(0,y-1),min(10,y+2)):
          if m[p][q]!=999:m[p][q]+=100  

Le script ci-dessus a deux fonctions, il place les 20 mines puis « calcule » pour chaque case le nombre de bombes dans les 8 cases autour.

Ainsi, la matrice contient à ce stade uniquement des entiers :
soit des 0 (0 bombes dans les 8 cases environnantes), soit des multiples de 100, soit des 999.

Pourquoi 100 et pas 1 pour indiquer qu’il y a une bombe ?

Car on va utiliser cette matrice pour y stocker une information importante, à savoir si la case a été découverte ou pas.

m[i][j]La case (i,j) contientDans les 8 cases autour de la case (i,j) il y acase découverte
9991 mineon ne sait pasnon sinon (…)
3420 mine3 minesoui
3000 mine3 minesnon
420 mine0 mineoui
10 mine0 mineen cours de découverte
00 mine0 minenon

Calcul du nombre de mines de chaque case

En parcourant l’article Wikipédia sur le démineur lors de la rédaction de ce compte rendu, j’y ai découvert que j’ai construit intuitivement l’un des deux algorithmes classiques pour le calcul des nombres des différentes cases du démineur.

Sur mon antique Casio 9960GT, j’utilisais l’algorithme par case :

explorer tableau par case
si case est vide
nombre := compter_mines(case.voisinage)
case.valeur = nombre<

et sur la NumWorks, j’ai construit une version légèrement adapté de l’algorithme par mine :

// Placement de la mine
explorer mine.voisinage par case
si case est vide
incrémenter case.valeur

Dans une première version en python, j’utilisais la partie décimale pour y stocker le 42 ou le 0.01, ainsi cela donnait 1,42 si il y a une mine dans le voisinage et que la case est déjà découverte, mais ceci posa un problème, le test :

if 1.42-round(1.42)==42

ne marchait pas, car python ne dispose pas d’un moteur de calcul exact sur les réels.
Le résultat du calcul 1.42-1 en python est 0.419999999999999

Par ailleurs, stocker en réel et non un entier est susceptible de consommer plus de mémoire, donc le stockage de l’information sous la forme d’entier est préférable pour les tests conditionnels.

Une fonction pour les découvrir toutes !

A partir de la matrice, on peut exécuter un script qui révèle les positions et vérifie que tout fonctionne correctement :

def verif():
  for x in range(15):
    for y in range(10):
      if m[x][y]==666:
        mine(x,y)
      elif m[x][y]>0:
        chiffre(x,y)

La fonction chiffre elle a pour objectif d’afficher le nombre de bombes autour :

def chiffre(x,y):
  cl(x,y)
  nb[1]+=(m[x][y]%100!=42)
  i=m[x][y]-m[x][y]%100
  m[x][y]=i+42
  if i>=100:v=int(i/100);draw_string(str(v),13+20*(x),23+20*(y),c[v],c[0])
  deminage()

pas à pas de la fonction chiffre :

  • cl(x,y) éclairci la case
  • nb[1] s’incrémente de 1 si la case n’a pas déjà été découverte, c’est le compteur de la victoire
  • Si la valeur m[x][y] contenait Z00 elle sera désormais de Z42, Z étant un entier entre 0 et 8.
  • On affiche la valeur de Z, avec une couleur adaptée.
  • On exécute le script deminage(), qui calcule le score et vérifie si l’on a gagné.

Le plus difficile à coder est la fonction de découverte de proche en proche, si on dévoile une case ne contenant aucune mine, et pas de mine non plus dans les 8 cases alentours, il faut faire un « script récursif » de traitement et de découverte de ces cases.

La fonction decouvre() est la suivante :

def decouvre(x,y):
  i=1
  while i>0:
    chiffre(x,y)
    for p in range(max(0,x-1),min(15,x+2)):
      for q in range(max(0,y-1),min(10,y+2)):
        if m[p][q]>=100:chiffre(p,q)
        elif m[p][q]==0:m[p][q]+=1
    i=0
    for p in range(15):
      for q in range(10):
        if m[p][q]%100==1:i=1;x=p;y=q;p=14;q=9

Elle est appelée par la fonction marche() :

def marche(x,y):
  if m[x][y]>=999:explose()
  elif m[x][y]>=100:chiffre(x,y)
  else:decouvre(x,y)

Ainsi la fonction découverte n’est appelée que si on « marche » sur une case sans mine, et que les 8 cases aux alentours ne contiennent également aucune mine.

Elle teste les 8 cases du voisinage, affiche le chiffre de celles qui doivent afficher un chiffre. Si les cases contiennent un 0, il devient 1 et devra être traité par le script ultérieurement.

Ce fonctionnement n’est pas optimal en temps de calcul, mais il ne crée aucun nouvel objet en mémoire. Il n’est pas vraiment non plus récursif au sens python pour éviter là encore de consommer trop de mémoire.

Une précédente version ajoutait les coordonnées des cases à traiter dans une liste, mais cette liste grossissait et pouvait saturer la mémoire.

Piloter un drone sur un terrain miné

Il ne reste plus qu’à coder la navigation dans l’écran, c’est à dire l’interaction entre l’affichage et le clavier de la calculatrice.

a,r,t,l,k,s=set_pixel,fill_rect,draw_string,range,keydown,sleep
p=[[6,5],[7,5]]
 
def survol():
  x, y = p[1][0], p[1][1]
  v = m[x][y]
  gps(x,y)
  # t(str(int(v/100)),20,2)
  x, y = p[0][0], p[0][1]
  v = m[x][y]
  if p[1][0]!=x or p[1][1]!=y:
    if (v-42)%100==0:gps(x,y,0)
    else:gps(x,y,9)
  del p[0]
 
def gps(x,y,i=6):
  r(9+20*x,22+20*y,19,2,c[i])
  r(26+20*x,22+20*y,2,19,c[i])
  r(9+20*x,22+20*y,2,19,c[i])
  r(9+20*x,39+20*y,19,2,c[i])
 
def drone():
  while not k(6):
    if k(0):p.append([max(p[0][0]-1,0),p[0][1]])
    if k(3):p.append([min(p[0][0]+1,14),p[0][1]])
    if k(1):p.append([p[0][0],max(p[0][1]-1,0)])
    if k(2):p.append([p[0][0],min(p[0][1]+1,9)])
    if k(4):marche(p[0][0],p[0][1])
    if k(17) or k(16):drapeau(p[0][0],p[0][1])
    if k(52):nb[2]=0;nb[0]=22;minage(nb[0]);s(1)
    if k(45):nb[0]=min(nb[0]+5,90);minage(nb[0]);s(1)
    if k(46):nb[0]=max(nb[0]-5,10);minage(nb[0]);s(1)
    if len(p)>1:survol();s(0.120)

La fonction survol() déplace le curseur, en rétablissant l’affichage de la case précédemment survolée à l’aide de la fonction gps().

la fonction drone() attend que l’utilisateur appuie sur une touche du clavier.

ToucheCode toucheAction
Flèche haut1se déplacer
Flèche bas2se déplacer
Flèche gauche0se déplacer
Flèche droite3se déplacer
OK4marcher sur la case
Clear ou Tolbox17Afficher un drapeau
Maison6quitter la boucle
+45ajouter 3 mines et recommencer la partie
46enlever 3 mines et recommencer la partie
EXE52recommencer la partie avec les paramètres par défaut

Le nombre de mine par défaut est fixé à 19 pour un plateau de 150 cases, soit un ratio de 12.7%.

Le démineur de Microsoft sur Windows 10, appelé Minesweeper propose des ratios de 11,1%, 15%, et 20% respectivement pour les modes faciles, moyen et difficile.

Le nombre de mine qu’il est possible de placer est un nombre dans la liste :
13, 16, 19, 22, 25, …, 40 mais une petite astuce mathématiques permet de choisir n’importe quel entier entre 11 et 42 pour désigner le nombre de mine.

Oui n’importe quel nombre entier entre 11 et 42 alors que l’interface et l’utilisation des touches [+] et [-] ne permet que d’ajouter ou d’enlever 3 mines…

Epsilon version 13.2

La sortie de la version 13.2.0 d’Espilon, le logiciel qui anime la NumWorks permet de faire tourner sans problème ce démineur. Si dans la version 13.0.0 et 13.1.0 le démineur ne fonctionnait pas à cause d’une affectation insuffisante de mémoire pour l’exécution des scripts python, la version 13.2.0 résout ce problème qui n’est plus qu’un mauvais souvenir. Ainsi, la NumWorks est l’une des calculatrice la plus rapide lors de l’exécution des scripts Python, et que pour un prix d’achat de 79€ elle n’a clairement aucune concurrence dans cette gamme de prix.

Omega, un firmware alternatif pour votre NumWorks

Le logiciel de la NumWorks est libre et ouvert, ce qui est très appréciable et on peut même dire admirable dans l’univers très fermé des calculatrices.

Ainsi, tout le monde peut accéder au code source, le lire, le modifier et le redistribuer en respectant les conditions imposées par la licence CC BY-NC-SA.

Une équipe de passionné a donc décidé de développer un firmware alternatif, et il l’ont appelé Omega. Ils intègrent différentes amélioration, et poussent les developpeur du firmware stock à s’améliorer sans cesse.

Pour tester ce démineur, tu dois :

  • Soit mettre à jour ta calculatrice en version 13.2.0 au minimum.
  • Soit installer le firmware Omega.
InstallerLien hypertexte
1. Installer Le firmware Omegaen quelques clics
1. Ou mettre à jour Epsilonen quelques clics
2. Installer le démineurworshop

Le démineur en version 1.20

Une nouvelle version de ce démineur est sortie le 18 avril 2020, elle intègre un menu graphique python développé en interne pour cette occasion.

Le script du démineur pèse désormais 8.42ko, et le démineur peut désormais être paramétré par un menu graphique, interactif et intuitif.

De nouvelles options ont été ajoutées :

  1. Le choix du graphisme : Flat vs Classique
  2. Un mode triche à tester avec la touche MAJ de l’ordinateur ou Shift sur la calculatrice.

Quelques dernières remarques

Si tu souhaites réagir à cet article, tu es invité à le faire sur ce forum :
[tiplanet.org] Un démineur en python pour la NumWorks, tu y trouvera des discussions pointues sur l’utilisation de la mémoire lors de l’éxécution d’un script Python.

Entre le script initial et le script proposé en ligne à cet instant, de nombreuses réécritures portions du code ont été réécrites, mais les explications de cet article restent valables.

Projets

Identifier les composants de son PC

Tutoriel permettant de se renseigner a propos des composant de son PC tel que sa carte graphique, son processeur, ses Giga de RAM etc.

Identifier les composants de son PC

Méthode 1 & 2 disponible seulement pour Windows Retour ligne automatique
Méthode 3 disponible pour Tous

Méthode 1

Grâce à cette première méthode, il sera possible de se renseigner sur seulement quelques composants de votre pc :

  • 1
    appuyez sur le bouton Windows en bas à gauche de votre écran :
  • 2 descendez jusqu’à trouver l’icône “paramètre” :
  • 3 Un nouvel onglet apparaît, cliquez sur l’icône nommée « système » :
  • 4 Cliquez sur la dernière catégorie « Information Système » :
  • 5 Cliquez une nouvelle fois sur « information système » mais cette fois ci a droite de votre écran :
  • 6 Pour finir un onglet apparaît avec des information sur votre processeur, mémoire RAM, type du système et sur votre édition de Windows .

Cette technique est rapide d’utilisation mais ne permet pas de connaitre la totalités des composant de votre PC.

Bonus : pour aller plus vite il suffit d’appuyer sur les deux touches « Windows » et « pause » simultanément :

Méthode 2

  • 1 Pour commencer faites un clic-droit sur le bouton démarrer :
  • 2 cliquez sur « Gestionnaire de périphériques » :
  • 3 une nouvelle fenêtre apparaît :
  • 4 Déroulez les catégories en cliquant sur la flèche :

Cette technique est tout aussi rapide que la précédente et cela permet de connaitre la totalités des composant mais beaucoup de catégories ne sont pas des composants, on s’y perd vite.

Méthode 3 :

Cette technique nécessite une utilisation d’un logiciel gratuit : Speccy :

  • 1 Commencez par télécharger le logiciel, écrivez Speccy dans la barre de recherche de votre moteur de recherche :
  • 2 Cliquer sur le premier lien et suivez les instructions ci-dessus :
  • 3 Lorsque Speccy est télécharger, une fenêtre apparaît et vous indique quels sont les composants de votre PC :

Cette troisième et dernière technique est la plus compréhensible mais nécessite internet et une confiance en ce logiciel. Deplus ce logiciel détaille clairement les composants.

Projets

Automatiser des actions sur iOS

L’application Raccourcis est une application développée par Apple pour simplifier des tâches que des utilisateurs peuvent effectuer chaque jours ou bien régulièrement.

Cette application fonctionne très simplement, fonctionnement semblable au logiciel « Scratch », logiciel au programme du collège pour aborder la programmation de manière récréative.

Nous allons voir ensemble comment utiliser l’application par le biais d’un tutoriel pour créer un raccourcis bien utile quand la batterie est à sec, et par la suite créer vos propres raccourcis.

Nous allons voir ensemble comment fonctionne l’application en créant un raccourcis bien utile quand la batterie de son appareil est à sec, pour pouvoir par la suite créer vos propres raccourcis.

L’interface

Une fois l’application téléchargée et ouverte, la page d’accueil est divisé en 3 partie : « Mes raccourcis » qui regroupe l’ensemble des raccourcis créés ou téléchargés, « Automatisation » qui sont des raccourcis qui s’effectuent seules, ce qui est utile par exemple pour effectuer des routines quotidienne (annoncer la météo de la journée et les RDV quand le réveil est désactivé), et enfin « Galerie » qui regroupe des raccourcis créés par Apple est mis à disposition pour tous.

Cette interface est celle sur iPad mais le principe est le même pour iPhone

A gauche se situe la bibliothèque des « actions » réalisables, elles sont classées en fonctions de leurs types (script, notification, texte…). A droite se situe l’éditeur de script (vide pour l’instant).

Créer un raccourcis

Maintenant que nous avons fait un rapide tour de l’interface, nous pouvons commencer à créer notre premier raccourcis. 
Une fois l’idée trouvée (pour nous ça sera un économiseur de batterie), nous pouvons donc donner un nom à notre raccourcis en appuyant sur « « . Et comme nous pouvons le personnaliser, nous allons également changer l’icon et la couleur de fond, vous comprendrez pourquoi à la fin de ce tutoriel.

Maintenant que nous lui avons donné un nom il faut créer le script. Les morceaux script fonctionnent comme des « LEGO » à assembler.

Pour notre économiseur de batterie, nous voulons que quand nous le lançons, il y ai un signal sonore et un menu de confirmation. Il suffit de chercher les deux éléments dans la bibliothèque et de les assembler :

Pour le choix « Oui » nous allons mettre plusieurs actions qui modifient les réglages de l’appareil afin de conserver de la batterie un maximum de temps : désactiver le bluetooth, le Wi-Fi, les notifications, redéfinir la luminosité et activer le mode « économie d’énergie » (non disponible sur iPad).

Pour le choix « Non » nous allons simplement informer l’utilisateur qu’il va quitter le raccourcis avec une alerte sous la forme d’un « pop-up ».

Nous avons maintenant terminé le menu. A noter que le nombre de choix dans le menu est modifiable mais dans le cas présent nous avions besoin seulement de deux choix.
Nous souhaitons ensuite informé l’utilisateur du pourcentage de batterie restante.
Nous allons profiter de la recherche des fonctions dans la bibliothèque pour voir l’intérêt de faire des recherches pertinentes en cherchant non pas l’action en elle-même mais plutôt l’outil auquel elle va faire appelle.

Il suffit maintenant de configurer la fonction et d’y « emboîter » une alerte qui donne le pourcentage de batterie restante :

Nous ajoutons simplement une alerte sonore qui informe l’utilisateur que le script est terminé.

L’intérêt de personnaliser l’icon est qu’il est possible de la faire apparaître sur l’écran d’accueil de la même manière qu’une application téléchargeable sur l’App Store. Pour cela il suffit d’appuyer sur « Sur l’écran d’accueil« 

L’intérêt

L’application raccourcis est utile pour tous les jours. De plus, non seulement elle « simplifie la vie » , mais les raccourcis créés peuvent aussi être modifiés aux goûts de l’utilisateur comme bon lui semble.
Le raccourcis peut être lancer depuis une icon d’app, l’application raccourcis elle même mais aussi avec l’assistant vocal Siri.Retour ligne automatique
Voici quelques exemples de scripts qui peuvent être réalisés et qui peuvent peut-être donner de l’inspiration :

Ainsi qu’un exemple d’automatisation :

Le + et le –

Le + : de nombreux raccourcis sont disponibles dans la Galerie mais bien plus sont téléchargeables sur internet. Vous pouvez également les partager avec vos proches grâce à AirDrops entre plusieurs appareils Apple.

Le – : l’application Raccourcis est uniquement disponible sur iOS.

Banque d’idée

Vous ne verrez peut être pas tout de suite l’intérêt de perdre du temps à créer un script qui exécute une action que l’on peut faire seul même de temps en temps mais voilà une liste de raccourcis qui peuvent être créer :

  • télécharger une vidéo YouTube dans la pellicule photo
  • partager le mot de passe de la Wi-Fi sans le connaitre (mais en étant connecté)
  • compter la quantité de protéine mangée en une journée
  • calculer une promotion en % pendant les soldes
  • minuteur pour la musique

Mais bien sur je vous laisse chercher comment les créer…

Projets

2 méthodes pour observer les performances de son pc

Ce tutoriel permet a l’utilisateur d’observer de 2 manières différentes les performances de son ordinateur (Windows 10).

Pourquoi analyser les performances de son système est important ?

Ces informations vous permettent, par exemple, de connaître votre charge de travail et ses effets sur les ressources de votre système. Vous vous rendrez peut-être compte que la lenteur de votre ordinateur provient, par exemple, de la saturation de votre mémoire RAM et de l’utilisation de votre disque dur en renfort.

Pour observer les performances de votre pc en temps réel ce tutoriel, vous propose 2 options : Une première méthode plus simple et moins avancée qui ne nécessite pas d’installation tierce, et une deuxième méthode plus avancée utilisant un logiciel très complet.

1. Méthode simple mais moins complète.

Pour cette méthode aucune installation n’est requise il vous suffit d’ouvrir le « gestionnaire de tâches » de Windows, pour cela 2 manipulation sont possible :

Faire un clic droit sur le menu démarré puis sélectionnez « gestionnaire de tâches » comme cela :

Ou bien appuyer simultanément sur les touches CTRL+ALT+SUPPR de votre clavier :

Puis cliquer en suite sur « gestionnaire des tâches » :

Une fois dans le Gestionnaire des tâches il ne vous reste plus qu’a cliquer sur « plus de détails » en bas à gauche :

Et enfin cliquer sur l’onglet « performance » en haut dans la barres des tâches :

2. Méthode plus complexe mais plus complète.

Pour cette méthode un logiciel est requis mais vous aurez bien plus d’information sur votre système.
En premier lieu il vous faut télécharger le logiciel CPU-Z

(Pour cela cliquer sur le lien hypertexte (CPU-Z), une fois sur le site cliquez sur le rectangle « DOAWNLOAD NOW ! » et suivez les instructions.)

Une fois le logiciel installé sur votre pc, vous pouvez l’exécuter et observer les performances de votre système en naviguant dans les différents onglets.

Attention ! pour lancer ce programme il vous faut l’exécuter en tant qu’administrateur soit « Accepter que ce logiciel apporte des modification à votre appareil » sinon le logiciel ne s’exécutera pas.

Voilà ! vous pouvez dès à présent analyser les performances de votre pc en toute simplicité… 👌

Projets

Solveur de Sudoku en Python

C’était il y a quelques semaines, je me baladais sur le Play Store en quête de divertissement quand j’ai trouvé la perle rare : une application de sudoku. Je m’y suis donc mis (je n’y avais jamais joué auparavant) et je me suis découvert une passion pour ce jeu mathématique.

Or, comme dit le proverbe :

Il faut être intelligemment paresseux. 

J’ai donc réalisé un solveur de sudoku en Python, avec une interface graphique utilisateur (GUI).

Présentation du projet :

L’objectif :

Un script capable de résoudre n’importe quel sudoku, des plus simples aux plus complexes.

Cahier des charges :

  • Le programme doit résoudre n’importe quel sudoku.
  • Bien optimisé pour ne pas être trop gourmand en RAM et en mémoire.
  • Il doit présenter une interface graphique intuitive et plaisante :
    • Une grille bien dessinée et interactive.
    • La case survolée par la souris doit changer de couleur.
    • Il suffit d’appuyer sur une touche du pavé numérique pour entrer le chiffre et sur Effacer pour effacer.
    • Un bouton avec Entrée comme raccourcis pour résoudre le sudoku.

Réflexions :

La principale question est la suivante : Comment résout-on un Sudoku ?
Après recherches personnelles puis, sur Internet, j’ai obtenu une liste de méthodes de recherches :

  • Par inclusion : Si une case admet une seule possibilité de nombre, alors ce nombre est solution.
  • Par exclusion : Si un secteur admet une seule case possible pour un nombre, alors ce nombre est solution. Est appelé secteur, une ligne, une colonne ou un carré.
  • Par paires exclusives : Si un couple de nombre (n,m) est possible sur deux cases d’un secteur, alors la probabilité qu’ils soient sur d’autres cases du secteur est de 0.
  • Par triplets exclusifs :
    • Triplet parfait : Comme pour les paires exclusives mais avec un triplet (n,m,k).
    • Triplet tronqué : Le triplet est présent sur deux cases et présente deux nombres du triplet sur une troisième.
    • Triplet doublement tronqué : Le triplet est présent sur une case et présente deux nombres du triplet sur deux autres.
  • Choix multiple (ou essais-erreur) : On teste une des possibilité d’une case présentant le moins de possibilité si et seulement si la grille est figée dans un état.

Programmation du moteur de résolution :

Pour bien programmer, il est important d’avoir les idées claires.

Aussi, j’ai commencé par m’occuper de toutes les méthodes de recherche en omettant l’essais-erreur.
Car il faut être capable de résoudre les possibilité d’une grille avant de savoir si elle est figée…

Pour tout le script, grille est le tableau qui stocke la grille et grilleFinie celui qui stocke « l’état » des cases, c’est-à-dire, si leur valeur est solution de la case (True) ou non (False).

J’ai organisé mon travail en 3 parties :

Moteur principalRecherche pour une caseVérification
La fonction chercher()La fonction solutions()La fonction verifier()

La fonction verifier(l=0,c=0)

Commençons par la plus simple : cette fonction accepte deux paramètres qui représentent la ligne et la colonne dans la grille.

Si l == 0 et c == 0 (quand on écrit verifier()), alors la fonction vérifie toutes les cases de la grille.
Pour tout vérifier, on suit la logique suivante pour chaque cases :

La case contient une listeLa case contient un entier
Si la liste contient 1 élément différent de 0 : VraiSi entier différent de 0 : Vrai

Si on donne des valeurs différentes à l et c, on vérifie juste la case en appliquant la logique précédente en voulant que la case contienne une liste.

Le code :

def verifier(l=0,c=0):
    global grille,grilleFinie
    if not l and not c:
        for i in range(9):
            for j in range(9):
                if type(grille[i][j]) is list:
                    if len(grille[i][j]) == 1 and grille[i][j][0]:
                        grilleFinie[i][j] = True
                        grille[i][j] = grille[i][j][0]
                else:
                    if grille[i][j] != 0:
                        grilleFinie[i][j] = True
    else:
        if type(grille[l][c]) is list:
            if len(grille[l][c]) == 1 and grille[l][c][0]:
                grilleFinie[l][c] = True
                grille[l][c] = grille[l][c][0]

La fonction solutions(l,c,ites)

C’est la fonction la plus longue du script (un beau bébé de 131 lignes), cette fonction applique un certain nombre d’opérations sur une case et ses 3 secteurs.

Cette fonction prend en compte trois paramètres obligatoires :

  • l pour la ligne de la case
  • c pour la colonne de la case
  • ites pour le nombre d’itérations dans la couche de récursion (voir fonction chercher())

Dans un premier temps, il faut savoir dans quel carré se situe la case, et pour ça, on a besoin de deux lignes :

lc = l in [0,1,2] and [0,1,2] or l in [3,4,5] and [3,4,5] or l in [6,7,8] and [6,7,8]
cc = c in [0,1,2] and [0,1,2] or c in [3,4,5] and [3,4,5] or c in [6,7,8] and [6,7,8]

On pourrait décomposer chacune de ces lignes en 6 lignes pour mieux comprendre :

lc = l in [0,1,2] and [0,1,2] or l in [3,4,5] and [3,4,5] or l in [6,7,8] and [6,7,8]
# Est strictement equivalent à :
if l in [0,1,2]:
    lc = [0,1,2]
elif l in [3,4,5]:
    lc = [3,4,5]
elif l in [6,7,8]:
    lc = [6,7,8]
# Avec : l in [0,1,2] ssi 0 <= l <= 2

Intéressons nous maintenant aux méthodes de recherche :
Elles ne s’activerons que si la case de grilleFinie correspondante contient False.

Par inclusion :

Si nous sommes sur la première itération de la couche, on affecte à la case une liste contenant tout les nombre de 1 à 9, on supprimera au fur et à mesure les issues impossibles.

Ensuite, pour chaque case (i,j) avec (i,j) != (l,c) de chaque secteur :

  • Si la case contient un entier : On supprime, s’il est dedans, le nombre de la liste des issues possibles.
  • Si la case contient une liste : On la passe.

Ainsi, on supprime toute les valeurs qui ne sont pas solution.

Le code :

if not grilleFinie[l][c]:
    if not ites:
        grille[l][c] = [1,2,3,4,5,6,7,8,9]
    for k in range(9):
        if grilleFinie[l][k] and grille[l][k] in grille[l][c] and k != c:
            grille[l][c].remove(grille[l][k])
        if grilleFinie[k][c] and grille[k][c] in grille[l][c] and k != l:
            grille[l][c].remove(grille[k][c])
    for i in lc:
        for j in cc:
            if grilleFinie[i][j] and grille[i][j] in grille[l][c] and (i,j) != (l,c):
                grille[l][c].remove(grille[i][j])
    verifier(l,c)

Par exclusion :

Il faut que cette méthode se déclenche après une itération car toute les listes d’issues probables doivent être définies.

Cette méthode est en deux parties :

Pour chaque nombre contenus dans la liste des possible, on :

  • Compte le nombre d’apparition du nombre dans chaque secteur
  • Si le nombre n’apparaît pas, il est solution car sa probabilité d’être solution est 1

Une fois qu’on a trouvé la solution, il est important de supprimer la possibilité dans les autres secteurs (si un seul secteur permet de déduire la solution, il faut « débloquer » les autres)

Ainsi, on code :

if not grilleFinie[l][c] and ites:
    for nb in grille[l][c]:
        compteur = [0,0,0]
        for k in range(9):
            if not grilleFinie[l][k] and k != c:
                for n in grille[l][k]:
                    compteur[0] += int(n == nb)
            if not grilleFinie[k][c] and k != l:
                for n in grille[k][c]:
                    compteur[1] += int(n == nb)
        for i in lc:
            for j in cc:
                if not grilleFinie[i][j] and (i,j) != (l,c):
                    for n in grille[i][j]:
                        compteur[2] += int(n == nb)
        if compteur[0] == 0 or compteur[1] == 0 or compteur[2] == 0:
            grilleFinie[l][c] = True
            grille[l][c] = nb   
            for k in range(9):
                if not grilleFinie[l][k] and grille[l][c] in grille[l][k] and k != c:
                    grille[l][k].remove(grille[l][c])
                if not grilleFinie[k][c] and grille[l][c] in grille[k][c] and k != l:
                    grille[k][c].remove(grille[l][c])
            for i in lc:
                for j in cc:
                    if not grilleFinie[i][j] and grille[l][c] in grille[i][j] and (i,j) != (l,c):
                        grille[i][j].remove(grille[l][c])
            break

Attardons-nous sur deux points importants :

compteur[x] += int(n == nb)

Cette ligne permet de coder la condition et le compteur sur la même ligne. En effet, si la condition retourne True, alors le compteur gagne 1, sinon, il gagne 0, ce qui est exactement ce qu’on veut.

break

A la fin du code, le break est nécessaire car il permet de ne pas faire planter le programme. En effet, la boucle parente (c’est-à-dire, celle la « plus proche ») du break est :

Une fois qu’on a trouvé la solution, il ne faut plus chercher, donc arrêter la boucle avec un break.

Par paires exclusives :

Le « principe » est le même : la méthode ne se déclenche qu’après une itération et on observe les 3 secteurs de la case.

Il faut que la case contienne une liste de 2 de longueur.

Pour les paires, c’est assez simple, il s’agit de vérifier si la même paire est présente sur une autre case, et si oui, éliminer les composantes de la paire dans les autres cases du secteur concerné.

Ainsi, on code :

    if not grilleFinie[l][c] and ites and len(grille[l][c]) == 2:
        existe = [False,False,False]
        for k in range(9):
            if not grilleFinie[l][k] and grille[l][k] == grille[l][c] and k != c:
                existe[0] = True
            if not grilleFinie[k][c] and grille[k][c] == grille[l][c] and k != l:
                existe[1] = True
        for i in lc:
            for j in cc:
                if not grilleFinie[i][j] and grille[i][j] == grille[l][c] and (i,j) != (l,c):
                    existe[2] = True
        if existe[0]:
            for k in range(9):
                if not grilleFinie[l][k] and grille[l][k] != grille[l][c]:
                    for n in grille[l][k]:
                        if n in grille[l][c]:
                            grille[l][k].remove(n)
        if existe[1]:
            for k in range(9):
                if not grilleFinie[k][c] and grille[k][c] != grille[l][c]:
                    for n in grille[k][c]:
                        if n in grille[l][c]:
                            grille[k][c].remove(n)
        if existe[2]:
            for i in lc:
                for j in cc:
                    if not grilleFinie[i][j] and grille[i][j] != grille[l][c]:
                        for n in grille[i][j]:
                            if n in grille[l][c]:
                                grille[i][j].remove(n)

Ici, pas de nouvelles astuces de programmation, juste beaucoup de boucles et conditions imbriquées.

Par triplet exclusifs :

C’est ici que les choses se corsent…
On note que c’est la partie la plus longue de la fonction, faisant 42 lignes.

En effet, même si on applique le même principe que pour les paires, il faut aussi permettre au programme de détecter quand les triplets ne sont pas complets (cf début de l’article).

Il faut que la case contienne une liste de 3 de longueur.

Partie 1 : La recherche du triplet, quel qu’il soit

    existe = [[],[],[]]
    decompo = [[grille[l][c][0],grille[l][c][1]],[grille[l][c][1],grille[l][c][2]],[grille[l][c][0],grille[l][c][2]]]
    for k in range(9):
        if not grilleFinie[l][k] and k != c and (grille[l][k] == grille[l][c] or grille[l][k] in decompo):
            existe[0].append(grille[l][k])
        if not grilleFinie[k][c] and k != l and (grille[k][c] == grille[l][c] or grille[k][c] in decompo):
            existe[1].append(grille[k][c])
    for i in lc:
        for j in cc:
            if not grilleFinie[i][j] and (i,j) != (l,c) and (grille[i][j] == grille[l][c] or grille[i][j] in decompo):
                existe[2].append(grille[i][j])

Qu’est-ce qu’il se passe ?

Pour commencer, on organise notre pensée :

existedecompo
Liste contenant une liste des triplets/couples correspondant à la conditionListe contenant les couples possibles à partir du triplet de départ
[a,b,c] => [a,b],[b,c],[a,c]

Ensuite, pour chaque case de chaque secteur, on regarde si :

  • La case contient le triplet souhaité
  • La case contient un des couples de decompo

A l’aide de la condition suivante :

(grille[k][c] == grille[l][c] or grille[k][c] in decompo

Partie 2 : La vérification du triplet

    for k in range(3):
        triplet = []
        if len(existe[k]) == 2:
            for i in [0,1]:
                for n in existe[k][i]:
                    if n not in triplet:
                        triplet.append(n)
            if triplet != grille[l][c]:
                existe[k] = False
        else:
            existe[k] = False

Qu’est-ce qu’il se passe ?

Pour chaque secteur :

On vérifie qu’il y ait bien existence de 2 éléments.
On vérifie ensuite que les éléments présents dans les couples/triplets donne bien le triplet de départ (afin d’éviter que les paires ne s’immiscent et créent des erreurs).

Si une de ces condition n’est pas respectée, existe devient False pour le secteur concerné.

Partie 3 : Suppression des possibilités dans les autres cases

C’est comme pour les paires exclusives à la différence près que l’on doit également vérifier que les autres cases ne contiennent pas un des couples de existe.

Code final :

    if not grilleFinie[l][c] and ites and len(grille[l][c]) == 3:
        existe = [[],[],[]]
        decompo = [[grille[l][c][0],grille[l][c][1]],[grille[l][c][1],grille[l][c][2]],[grille[l][c][0],grille[l][c][2]]]
        for k in range(9):
            if not grilleFinie[l][k] and k != c and (grille[l][k] == grille[l][c] or grille[l][k] in decompo):
                existe[0].append(grille[l][k])
            if not grilleFinie[k][c] and k != l and (grille[k][c] == grille[l][c] or grille[k][c] in decompo):
                existe[1].append(grille[k][c])
        for i in lc:
            for j in cc:
                if not grilleFinie[i][j] and (i,j) != (l,c) and (grille[i][j] == grille[l][c] or grille[i][j] in decompo):
                    existe[2].append(grille[i][j])
        for k in range(3):
            triplet = []
            if len(existe[k]) == 2:
                for i in [0,1]:
                    for n in existe[k][i]:
                        if n not in triplet:
                            triplet.append(n)
                if triplet != grille[l][c]:
                    existe[k] = False
            else:
                existe[k] = False
        if existe[0]:
            for k in range(9):
                if not grilleFinie[l][k] and grille[l][k] != grille[l][c] and grille[l][k] not in existe[0]:
                    for n in grille[l][k]:
                        if n in grille[l][c]:
                            grille[l][k].remove(n)
        if existe[1]:
            for k in range(9):
                if not grilleFinie[k][c] and grille[k][c] != grille[l][c] and grille[k][c] not in existe[1]:
                    for n in grille[k][c]:
                        if n in grille[l][c]:
                            grille[k][c].remove(n)
        if existe[2]:
            for i in lc:
                for j in cc:
                    if not grilleFinie[i][j] and grille[i][j] != grille[l][c] and grille[i][j] not in existe[2]:
                        for n in grille[i][j]:
                            if n in grille[l][c]:
                                grille[i][j].remove(n)

La fonction chercher(ee=0,ite=0)

Cette fonction est une fonction récursive, c’est-à-dire, capable de se lancer elle-même pour résoudre des problèmes plus complexes.

La première partie est juste une déclaration de variables suivie d’un affichage console d’un arbre :

    global grilleFinie,grille
    iterations = 0
    grilleTest = [[0]*9 for n in range(9)]
    grilleBack = [[0]*9 for n in range(9)]
    cellBack = [0,0,9,[]]
    if ee:
        print(ite,"iter"+"s"*(ite!=1))
    print(" "*(ee-1)+"|"*(ee!=0)+"_ couche",ee,end=" => ")

On retrouve ici les astuces avec les conditions pour afficher ou non certains caractères.
En python, « x »*5 retourne « xxxxx », ainsi, « x »*0 retourne «  »

Tableau des variables :

grilleTestgrilleBackcellBack
Sauvegarde de la grille avant fonction solutions()Sauvegarde de la grille avant récursionSauvegarde de la case modifiée pour essais-erreur

On aborde ensuite une boucle do…while. Petit problème, elles n’existent pas en python, il est donc nécessaire de la recréer. Et pour ça, on écrit une boucle infinie (un while toujours vrai) avec la condition contenant un break ou un return (qui arrête la fonction et non la boucle).

Voici donc la base :

    while "solution non determinee":
        grilleTest = copie(grille)
        for i in range(9):
            for j in range(9):
                solutions(i,j,iterations)
        if grilleFinie == [[True]*9 for n in range(9)]:
            print(iterations,"iter"+"s"*(iterations!=1))
            affG()
            return True

A cause d’un bug incompréhensible, j’ai du recréer la méthode .copy() des liste, d’où la fonction copie()

Ce système est déjà capable de résoudre jusqu’aux sudokus « difficiles » de certaines applications et revues.

Pourquoi un return True et pas un break ?

Notre récursion fonctionne sur une condition (remplie pour rappel, uniquement si la grille est « figée ») que voici :

    if grilleTest == grille:
            grilleBack = copie(grille)
            for i in range(9):
                for j in range(9):
                    if not grilleFinie[i][j] and cellBack[2] > len(grille[i][j]):
                        cellBack = [i,j,len(grille[i][j]),grille[i][j]]
            for n in cellBack[3]:
                grille[cellBack[0]][cellBack[1]] = n
                if chercher(ee+1,iterations):
                    return True
                else:
                    grilleFinie = [[False]*9 for n in range(9)]
                    grille = copie(grilleBack)
                    verifier()
            return False  
        else:
            iterations += 1

Elle est organisée en 3 parties distinctes :

  • On commence par faire une sauvegarde de la grille.
  • On sélectionne la case avec le moins de possibilité de réponses, on garde ses coordonnées et son contenu.
  • Pour chaque possibilité, on lance la fonction en couche n+1.
    • Si la fonction renvoie True (d’où le return True), on renvoie TrueRetour ligne automatique
      Cela permet de tuer les fonctions enfants d’un coup.
    • Sinon, on remet la grille dans l’état sauvegardé.
  • Si toutes les possibilités sont testés et qu’aucune solution n’est trouvé, on renvoie False.

Seulement, il nous manque une dernière chose : Que se passe t’il si le programme attribue une mauvaise solution pour une case ?

Il se trouve que, quand il se trompe, le programme finit par attribuer des listes vides à des cases (car la résolution rend impossible la possibilité d’un nombre sur la case), il suffit de donc de détecter ces cases avant de vérifier que la grille soit « figée ».

    for i in range(9):
        for j in range(9):
            if grille[i][j] == []:
                return False

Programmation du moteur graphique :

Comme vous pouvez le remarquer, on observe que le texte d’aide nous explique d’appuyer sur des touches, et oui : pour la GUI, il faut faire de la programmation événementielle !

A savoir :

  • J’ai nommé ma fenêtre fenetre et mon canevas (support pour le graphisme) interface.
  • J’ai crée un tableau cases des ID des cases et un tableau textCase associé.
  • Cet attribut pour les cases activefill="#e0e0e0" permet d’avoir les cases grisées au survol de la souris.

La fonction event(touche)

Cette fonction est la fonction liée à la fenêtre par fenetre.bind("<Key>",event) ce qui veut dire que quand on va appuyer sur une touche, les informations vont être envoyées à la fonction (d’où le paramètre touche).

touche est un objet contenant plusieurs variables, on s’intéresse à la variable keysym (le nom en clair de la touche), ainsi : toucheK = touche.keysym

Commençons par le plus simple :

    if toucheK == "Return":
        chercher()
    if toucheK == "Delete":
        nouv()
     
    # La fonction nouv()
    def nouv():
        global grille,grilleFinie,textCase
        grille = [[0]*9 for n in range(9)]
        grilleFinie = [[False]*9 for n in range(9)]
        textCase = [[False]*9 for n in range(9)]
        interface.delete("case")

Si j’appuie sur Entrer, je commence la recherche, sur Suppr, j’efface tout.

Maintenant, le plus intéressant, si je veux ajouter un chiffre.

Voici le code :

    if toucheK in "123456789":
            eff = False
            if interface.type(interface.find_closest(touche.x,touche.y)[0]) == "text" and interface.itemcget(interface.find_closest(touche.x,touche.y)[0],"tags") != "i current":
                interface.delete(interface.find_closest(touche.x,touche.y)[0])
                eff = True
            for i in range(9):
                for j in range(9):
                    if interface.find_closest(touche.x,touche.y)[0] == cases[i][j]:
                        if not eff and not grille[i][j]:
                            grille[i][j] = int(toucheK)
                        if eff:
                            grille[i][j] = int(toucheK)
                            textCase[i][j] = False
                        break
                if interface.find_closest(touche.x,touche.y)[0] == cases[i][j]:
                    break
            affG(i,j,toucheK)

Ce script commence par détecter s’il y a un chiffre sur la case et si oui, il le supprime.

Le script cherche ensuite à quelle case correspond les coordonnées de l’événement. Une fois trouvées, s’il n’a rien effacée et que la case est vide, il la remplit, s’il a effacé un nombre, il change la valeur de la case et précise qu’il n’y a pas de nombre.

Enfin, les deux break permettent de sortir des deux boucles.
Nous verrons après la fonction affG.

Si je veux enlever un chiffre, il suffit d’appuyer sur Effacer pour lancer ce code :

    if toucheK == "BackSpace":
        if interface.type(interface.find_closest(touche.x,touche.y)[0]) == "text" and interface.itemcget(interface.find_closest(touche.x,touche.y)[0],"tags") != "i current":
            interface.delete(interface.find_closest(touche.x,touche.y)[0])
            for i in range(9):
                for j in range(9):
                    if interface.find_closest(touche.x,touche.y)[0] == cases[i][j]:
                        grille[i][j] = 0
                        textCase[i][j] = False
                        break
                if interface.find_closest(touche.x,touche.y)[0] == cases[i][j]:
                    break

Ce code est très similaire à celui de l’ajout d’un nombre.

La fonction affG(x=-1,y=-1,nb=-1)

Quand on observe la fonction, deux choses sont frappantes : les lignes 7 et 11.

    def affG(x=-1,y=-1,nb=-1):
        global textCase,cases
        if (x,y) == (-1,-1):
            for x in range(9):
                for y in range(9):
                    if not textCase[x][y]:
                        interface.create_text((interface.coords(cases[x][y])[0]+interface.coords(cases[x][y])[2])//2,(interface.coords(cases[x][y])[1]+interface.coords(cases[x][y])[3])//2,anchor="center",text=str(grille[x][y]),font=("Helvetica","16","bold"),fill="#0f0fa0",tags="case")
        else:
            if not textCase[x][y]:
                textCase[x][y] = True
                interface.create_text((interface.coords(cases[x][y])[0]+interface.coords(cases[x][y])[2])//2,(interface.coords(cases[x][y])[1]+interface.coords(cases[x][y])[3])//2,anchor="center",text=nb,font=("Helvetica","16","bold"),tags="case")

Ces lignes positionne un texte au milieu de la case correspondante en lui ajoutant un tag (une étiquette) : « case ».
On remarque que quand x = y = -1, les nombres sont ajoutés en #0f0fa0.

Ce tag permet à la fonction nouv() de pouvoir supprimer tout les textes en une ligne.

Conclusion :

Notre programme est doté d’une interface graphique agréable permettant de bien visualiser nos sudoku pour les rentrer de façon intuitive. Notre cahier des charges semble être respecté.

Mais l’est-il vraiment ?

Il nous reste à vérifier s’il est capable de résoudre n’importe quel sudoku. Pour cela, je pars du principe suivant :

Qui peut le plus, peut le moins.

Ainsi, je décide de l’essayer avec Al Escargot, le sudoku le plus difficile du monde.

Pour ordre d’idée, il est classé 100 étoiles (contre 7 max pour ceux de revues) soit 5000 coups pour le résoudre, ce qui équivaut à environ 3 jours pour un habitué.

On entre donc la grille :

Après moins d’une seconde :

On remarque l’arbre en retour discret sur notre IDE :

Notre solveur remplit parfaitement ses fonction !

Code complet :

Voici le code complet, en téléchargeable puis en texte :

    # Solveur de Sudoku
    # par Fedyna K.
     
    from tkinter import Tk,Canvas
     
    # GUI
     
    fenetre = Tk()
    fenetre.title("Solveur de Sudoku - NSI")
    fenetre.resizable(False,False)
     
    interface = Canvas(fenetre,width=400,height=500,bg="white")
    interface.pack()
    interface.create_text(5,400,anchor="nw",text="Pour ajouter un chiffre, survolez la case correspondante et appuyez sur\nle pavé numérique.",tags="i")
    interface.create_text(5,435,anchor="nw",text="Pour supprimer un chiffre, survolez la case et appuyez sur 'Effacer'.",tags="i")
    interface.create_text(5,455,anchor="nw",text="Pour résoudre la grille, appuyez sur 'Entrer'.",tags="i")
    interface.create_text(5,480,anchor="nw",text="Pour tout effacer, appuyez sur 'Suppr'.",tags="i")
     
    interface.create_rectangle(12,7,15,379,fill="black")
    interface.create_rectangle(381,7,384,379,fill="black")
    interface.create_rectangle(12,7,384,10,fill="black")
    interface.create_rectangle(12,376,384,379,fill="black")
    cases = [[0]*9 for n in range(9)]
    textCase = [[False]*9 for n in range(9)]
    ic = 0
    ie = 0
    for i in range(15,369,40):
        jc = 0
        je = 0
        for j in range(10,364,40):
            cases[jc][ic] = interface.create_rectangle(i+ie,j+je,i+ie+40,j+je+40,fill="white",activefill="#e0e0e0")
            jc += 1
            if jc in [3,6]:
                interface.create_rectangle(12,j+43+je,384,j+40+je,fill="black")
                je += 3
        ic += 1
        if ic in [3,6]:
            interface.create_rectangle(i+43+ie,7,i+ie+40,379,fill="black")
            ie += 3
     
    def event(touche):
        global grille,textCase,cases
        toucheK = touche.keysym
        if toucheK in "123456789":
            eff = False
            if interface.type(interface.find_closest(touche.x,touche.y)[0]) == "text" and interface.itemcget(interface.find_closest(touche.x,touche.y)[0],"tags") != "i current":
                interface.delete(interface.find_closest(touche.x,touche.y)[0])
                eff = True
            for i in range(9):
                for j in range(9):
                    if interface.find_closest(touche.x,touche.y)[0] == cases[i][j]:
                        if not eff and not grille[i][j]:
                            grille[i][j] = int(toucheK)
                        if eff:
                            grille[i][j] = int(toucheK)
                            textCase[i][j] = False
                        break
                if interface.find_closest(touche.x,touche.y)[0] == cases[i][j]:
                    break
            affG(i,j,toucheK)
        if toucheK == "Return":
            chercher()
        if toucheK == "Delete":
            nouv()
        if toucheK == "BackSpace":
            if interface.type(interface.find_closest(touche.x,touche.y)[0]) == "text" and interface.itemcget(interface.find_closest(touche.x,touche.y)[0],"tags") != "i current":
                interface.delete(interface.find_closest(touche.x,touche.y)[0])
                for i in range(9):
                    for j in range(9):
                        if interface.find_closest(touche.x,touche.y)[0] == cases[i][j]:
                            grille[i][j] = 0
                            textCase[i][j] = False
                            break
                    if interface.find_closest(touche.x,touche.y)[0] == cases[i][j]:
                        break
     
    def affG(x=-1,y=-1,nb=-1):
        global textCase,cases
        if (x,y) == (-1,-1):
            for x in range(9):
                for y in range(9):
                    if not textCase[x][y]:
                        interface.create_text((interface.coords(cases[x][y])[0]+interface.coords(cases[x][y])[2])//2,(interface.coords(cases[x][y])[1]+interface.coords(cases[x][y])[3])//2,anchor="center",text=str(grille[x][y]),font=("Helvetica","16","bold"),fill="#0f0fa0",tags="case")
        else:
            if not textCase[x][y]:
                textCase[x][y] = True
                interface.create_text((interface.coords(cases[x][y])[0]+interface.coords(cases[x][y])[2])//2,(interface.coords(cases[x][y])[1]+interface.coords(cases[x][y])[3])//2,anchor="center",text=nb,font=("Helvetica","16","bold"),tags="case")
       
    fenetre.bind("<Key>",event)
     
    # Moteur de resolution
     
    def nouv():
        global grille,grilleFinie,textCase
        grille = [[0]*9 for n in range(9)]
        grilleFinie = [[False]*9 for n in range(9)]
        textCase = [[False]*9 for n in range(9)]
        interface.delete("case")
     
    def solutions(l,c,ites):
        global grille,grilleFinie
        verifier()
       
        lc = l in [0,1,2] and [0,1,2] or l in [3,4,5] and [3,4,5] or l in [6,7,8] and [6,7,8]
        cc = c in [0,1,2] and [0,1,2] or c in [3,4,5] and [3,4,5] or c in [6,7,8] and [6,7,8]
       
        # Methode de recherche : Inclusion
       
        if not grilleFinie[l][c]:
            if not ites:
                grille[l][c] = [1,2,3,4,5,6,7,8,9]
            for k in range(9):
                if grilleFinie[l][k] and grille[l][k] in grille[l][c] and k != c:
                    grille[l][c].remove(grille[l][k])
                if grilleFinie[k][c] and grille[k][c] in grille[l][c] and k != l:
                    grille[l][c].remove(grille[k][c])
            for i in lc:
                for j in cc:
                    if grilleFinie[i][j] and grille[i][j] in grille[l][c] and (i,j) != (l,c):
                        grille[l][c].remove(grille[i][j])
            verifier(l,c)
           
        # Methode de recherche : Exclusion (ap 1 iteration)
       
        if not grilleFinie[l][c] and ites:
            for nb in grille[l][c]:
                compteur = [0,0,0]
                for k in range(9):
                    if not grilleFinie[l][k] and k != c:
                        for n in grille[l][k]:
                            compteur[0] += int(n == nb)
                    if not grilleFinie[k][c] and k != l:
                        for n in grille[k][c]:
                            compteur[1] += int(n == nb)
                for i in lc:
                    for j in cc:
                        if not grilleFinie[i][j] and (i,j) != (l,c):
                            for n in grille[i][j]:
                                compteur[2] += int(n == nb)
                if compteur[0] == 0 or compteur[1] == 0 or compteur[2] == 0:
                    grilleFinie[l][c] = True
                    grille[l][c] = nb  
                    for k in range(9):
                        if not grilleFinie[l][k] and grille[l][c] in grille[l][k] and k != c:
                            grille[l][k].remove(grille[l][c])
                        if not grilleFinie[k][c] and grille[l][c] in grille[k][c] and k != l:
                            grille[k][c].remove(grille[l][c])
                    for i in lc:
                        for j in cc:
                            if not grilleFinie[i][j] and grille[l][c] in grille[i][j] and (i,j) != (l,c):
                                grille[i][j].remove(grille[l][c])
                    break
                   
        # Methode de recherche : Paires exclusives
       
        if not grilleFinie[l][c] and ites and len(grille[l][c]) == 2:
            existe = [False,False,False]
            for k in range(9):
                if not grilleFinie[l][k] and grille[l][k] == grille[l][c] and k != c:
                    existe[0] = True
                if not grilleFinie[k][c] and grille[k][c] == grille[l][c] and k != l:
                    existe[1] = True
            for i in lc:
                for j in cc:
                    if not grilleFinie[i][j] and grille[i][j] == grille[l][c] and (i,j) != (l,c):
                        existe[2] = True
            if existe[0]:
                for k in range(9):
                    if not grilleFinie[l][k] and grille[l][k] != grille[l][c]:
                        for n in grille[l][k]:
                            if n in grille[l][c]:
                                grille[l][k].remove(n)
            if existe[1]:
                for k in range(9):
                    if not grilleFinie[k][c] and grille[k][c] != grille[l][c]:
                        for n in grille[k][c]:
                            if n in grille[l][c]:
                                grille[k][c].remove(n)
            if existe[2]:
                for i in lc:
                    for j in cc:
                        if not grilleFinie[i][j] and grille[i][j] != grille[l][c]:
                            for n in grille[i][j]:
                                if n in grille[l][c]:
                                    grille[i][j].remove(n)
               
        # Methode de recherche : Triplets exculsifs (hors triplet induit)
               
        if not grilleFinie[l][c] and ites and len(grille[l][c]) == 3:
            existe = [[],[],[]]
            decompo = [[grille[l][c][0],grille[l][c][1]],[grille[l][c][1],grille[l][c][2]],[grille[l][c][0],grille[l][c][2]]]
            for k in range(9):
                if not grilleFinie[l][k] and k != c and (grille[l][k] == grille[l][c] or grille[l][k] in decompo):
                    existe[0].append(grille[l][k])
                if not grilleFinie[k][c] and k != l and (grille[k][c] == grille[l][c] or grille[k][c] in decompo):
                    existe[1].append(grille[k][c])
            for i in lc:
                for j in cc:
                    if not grilleFinie[i][j] and (i,j) != (l,c) and (grille[i][j] == grille[l][c] or grille[i][j] in decompo):
                        existe[2].append(grille[i][j])
            for k in range(3):
                triplet = []
                if len(existe[k]) == 2:
                    for i in [0,1]:
                        for n in existe[k][i]:
                            if n not in triplet:
                                triplet.append(n)
                    if triplet != grille[l][c]:
                        existe[k] = False
                else:
                    existe[k] = False
            if existe[0]:
                for k in range(9):
                    if not grilleFinie[l][k] and grille[l][k] != grille[l][c] and grille[l][k] not in existe[0]:
                        for n in grille[l][k]:
                            if n in grille[l][c]:
                                grille[l][k].remove(n)
            if existe[1]:
                for k in range(9):
                    if not grilleFinie[k][c] and grille[k][c] != grille[l][c] and grille[k][c] not in existe[1]:
                        for n in grille[k][c]:
                            if n in grille[l][c]:
                                grille[k][c].remove(n)
            if existe[2]:
                for i in lc:
                    for j in cc:
                        if not grilleFinie[i][j] and grille[i][j] != grille[l][c] and grille[i][j] not in existe[2]:
                            for n in grille[i][j]:
                                if n in grille[l][c]:
                                    grille[i][j].remove(n)
               
           
    def verifier(l=0,c=0):
        global grille,grilleFinie
        if not l and not c:
            for i in range(9):
                for j in range(9):
                    if type(grille[i][j]) is list:
                        if len(grille[i][j]) == 1 and grille[i][j][0]:
                            grilleFinie[i][j] = True
                            grille[i][j] = grille[i][j][0]
                    else:
                        if grille[i][j] != 0:
                            grilleFinie[i][j] = True
        else:
            if type(grille[l][c]) is list:
                if len(grille[l][c]) == 1 and grille[l][c][0]:
                    grilleFinie[l][c] = True
                    grille[l][c] = grille[l][c][0]
                   
    def chercher(ee=0,ite=0):
        global grilleFinie,grille
        iterations = 0
        grilleTest = [[0]*9 for n in range(9)]
        grilleBack = [[0]*9 for n in range(9)]
        cellBack = [0,0,9,[]]
        if ee:
            print(ite,"iter"+"s"*(ite!=1))
        print(" "*(ee-1)+"|"*(ee!=0)+"_ couche",ee,end=" => ")
        while "solution non determinee":
            grilleTest = copie(grille)
            for i in range(9):
                for j in range(9):
                    solutions(i,j,iterations)
            for i in range(9):
                for j in range(9):
                    if grille[i][j] == []:
                        return False
            if grilleFinie == [[True]*9 for n in range(9)]:
                print(iterations,"iter"+"s"*(iterations!=1))
                affG()
                return True
            if grilleTest == grille:
                grilleBack = copie(grille)
                for i in range(9):
                    for j in range(9):
                        if not grilleFinie[i][j] and cellBack[2] > len(grille[i][j]):
                            cellBack = [i,j,len(grille[i][j]),grille[i][j]]
                for n in cellBack[3]:
                    grille[cellBack[0]][cellBack[1]] = n
                    if chercher(ee+1,iterations):
                        return True
                    else:
                        grilleFinie = [[False]*9 for n in range(9)]
                        grille = copie(grilleBack)
                        verifier()
                return False  
            else:
                iterations += 1
       
    def copie(l):
        re = [[0]*9 for n in range(9)]
        for i in range(9):
            for j in range(9):
                re[i][j] = l[i][j]
        return re
     
    nouv()
    fenetre.mainloop()
Projets

Algorithme sur la forme canonique d’un trinôme du second…

Cet algorithme sert à calculer la forme canonique, les extrémums et le tableau de variation d’un trinôme du second degré.

•Le menu (défini par « menu ») : cette fonction donne accès au menu des 3 autres fonctions.

def menu():
    print("Que voulez-vous chercher : ")
    print("1. Forme canonique")
    print("2. Extremum")
    print("3. Tableau de variation")
    print("9. Menu")
    print("0. Fin")
    choix = int(input("Veuillez rentrer votre choix avec 1, 2, 3, 9 ou : "))
    if choix==0:
        print("Fin")
    elif choix==1:
        canonique()
    elif choix==2:
        extremum()
    elif choix==3:
        variation()
    elif choix==9:
        menu()

•Le calcul de la forme canonique (défini par « canonique ») : cette fonction calcul à l’aide de vos connaissances ou non, la forme canonique d’un trinôme du second degré.

def canonique():
    print("Vous avez fait le choix 1 qui est le calcul de la forme canonique d'un trinome")
    print(a,"x²+",b,"x+",c)
    print("Tout d'abord vous devez connaître les valeurs de alpha, beta et delta ")
    alpha = input("Connaisez vous la formule de alpha ? Si oui veuillez la rentrer : ")
    if alpha == "-b/2a":
        print("C'est bien")
    else: print("Ce n'est pas ça, la formule de alpha est -b/2a ")
    print("On calcule donc alpha = -b/2a = ",alphaa)
    print("Ensuite, il faut connaître la valeur de delta afin de pouvoir calculer beta ")
    delta = input("Connaisez vous la formule de beta ? Si oui veuillez la rentrer : ")
    if delta == "b²-4ac":
        print("C'est bien")
    else: print("Ce n'est pas ça, la formule de delta est b²-4ac ")
    print("On calcule donc delta = b²-4ac = ",deltaa)
    print("Maintenant, on peut calculer beta à l'aide de delta ")
    beta = input("Connaisez vous la formule de beta ? Si oui veuillez la rentrer : ")
    if beta == "-delta/(4a)":
        print("C'est bien")
    else: print("Ce n'est pas ça, la formule de beta est -delta/(4a) ")
    print("On calcule donc beta = -delta/(4a) = ",betaa)
    print("La forme canonique d'une fonction polynome de degré 2 s'écrit sous la forme : a(x-alpha)²+beta")
    print("On a alpha, on a beta, on peut donc mettre cette fonction sous forme canonique")
    if alphaa&lt;0 and betaa&lt;0:
        print("forme canonique : ",a,"(x+",-alphaa,")²-",-betaa)
    if alphaa&lt;0 and betaa>0:
        print("forme canonique : ",a,"(x+",-alphaa,")²+",betaa)
    if alphaa>0 and betaa>0:
        print("forme canonique : ",a,"(x-",alphaa,")²+",betaa)
    if alphaa>0 and betaa &lt;0:
        print("forme canonique : ",a,"(x-",alphaa,")²-",-betaa)
    if b&lt;0 and c&lt;0:
        print("On a maintenant la forme canonique du trinome f(x)=",a,"x²",b,"x",c,)
    if b&lt;0 and c>0:
        print("On a maintenant la forme canonique du trinome f(x)=",a,"x²",b,"x+",c,)
    if b>0 and c>0:
        print("On a maintenant la forme canonique du trinome f(x)=",a,"x²+",b,"x+",c,)
    if b>0 and c&lt;0:
        print("On a maintenant la forme canonique du trinome f(x)=",a,"x²+",b,"x",c,)
        input()

•La détermination et le calcul des extremums (défini par « extremum ») : cette fonction détermine et calcul l’extremum d’un trinôme en fonction de alpha, de beta et du signe de a.

def extremum():
    print("Vous avez fait le choix 2 qui est la détermination de l'extremum de la forme canonique d'un trinome")
    print("La courbe d'un trinome est toujours une parabole, le sens de cette parabole est déduite en fonction du signe de a")
    print("Si a>0, la parabole sera tourner vers le bas et l'extremum sera donc un minimum")
    print("En revanche, si a&lt;0, la parabole sera tourner vers le haut et l'extremum sera donc un maximum")
    if a>0:
        print("Ici, a est positif donc la parabole sera tourner vers le bas et admettra un minimum")
        print("Ce minimum est déduit grâce à alpha et beta où alpha = x et beta = y")
        print("Le minimum de cette fonction est donc admit en x = ",alphaa,"et y = ",betaa)
    if a&lt;0:
        print("Ici, a est négatif donc la parabole sera tourne vers le haut et admettra un maximum")
        print("Ce maximum est déduit grâce à alpha et beta où alpha = x et beta = y")
        print("Le maximum de cette fonction est donc admit en x = ",alphaa,"et y = ",betaa)
        input()

•La détermination du tableau de variation (défini par « variation ») : cette fonction détermine la variation de la courbe en fonction du signe de a.

def variation():
    print("Vous avez fait le choix 3 qui est la construction du tableau de variation de la forme canonique d'un trinome")
    print("Le tableau de variation va donc commencer à -l'infini, admettra la valeur de ",alphaa," pour x, la valeur de ",betaa,"\npour f(x), pour ensuite finir vers +l'infini")
    if a>0:
        print("a>0 donc la fonction est décroissante puis croissante")
    if a&lt;0:
        print("a&lt;0 donc la fonction est croissante puis décroissante")
        input()

Pour la fonction « canonique », l’utilisateur peut rentrer, à l’aide de ses connaissances, les formules de alpha, beta et delta afin de les mémoriser, si il ne les connait, l’algorithme les affiche pour lui et calcul ensuite directement la forme canonique.

Pour la fonction « extremum », l’utilisateur n’a besoin d’aucune connaissance, l’algorithme fera automatiquement les calculs nécessaires tout expliquant le déroulement, de même pour la fonction « variation ».

Lors de l’exécution de l’algorithme, le menu s’affiche et l’utilisateur peut choisir plusieurs options afin de répondre à son propre critère de demande.

Vous pouvez télécharger le code complet ici