# ################################## # Pipopipette # Version 2 # nsi.gecif.net # Juin 2026 # ################################## import turtle # --- CONFIGURATION ET VARIABLES GLOBALES --- # Demande la taille de la grille à l'utilisateur (entre 5 et 12 points) NB_DOTS = turtle.numinput("Configuration", "Taille de la grille (de 5 à 12 points) :", default=5, minval=5, maxval=12) # Si l'utilisateur clique sur "Annuler", on met une valeur par défaut if NB_DOTS is None: NB_DOTS = 5 else: NB_DOTS = int(NB_DOTS) ESPACE = 60 # Distance en pixels entre deux points ORIGINE_X = -((NB_DOTS - 1) * ESPACE) / 2 ORIGINE_Y = ((NB_DOTS - 1) * ESPACE) / 2 joueur_actuel = "Rouge" score = {"Rouge": 0, "Bleu": 0} # Structures de données pour suivre l'état du jeu lignes_tracees = set() # Contient des tuples ((x1,y1), (x2,y2)) pour les lignes actives carres_valides = set() # Contient les coordonnées du coin supérieur gauche des carrés fermés # --- FONCTIONS REQUISITES --- def coord_vers_pixel(cx, cy): """Convertit des coordonnées de grille (0 à NB_DOTS-1) en pixels à l'écran.""" px = ORIGINE_X + (cx * ESPACE) py = ORIGINE_Y - (cy * ESPACE) return px, py def dessiner_grille(): """Dessine tous les points de la grille initiale.""" turtle.tracer(0) # Désactive l'animation pour un tracé instantané stylo.idlemode = True for r in range(NB_DOTS): for c in range(NB_DOTS): px, py = coord_vers_pixel(c, r) stylo.penup() stylo.goto(px, py) stylo.pendown() stylo.dot(10, "#333333") turtle.update() stylo.idlemode = False def mettre_a_jour_titre(): """Met à jour les informations de score et de tour dans la barre de titre.""" titre = "Pipopipette ({0}x{0}) | Tour : {1} | Score -> Rouge : {2} - Bleu : {3}" turtle.title(titre.format(NB_DOTS, joueur_actuel, score["Rouge"], score["Bleu"])) def colorier_carre(cx, cy, couleur): """Rempli l'intérieur d'un carré fermé de 1x1 avec la couleur du joueur.""" px, py = coord_vers_pixel(cx, cy) stylo.penup() stylo.goto(px + 4, py - 4) stylo.color(couleur) stylo.begin_fill() for _ in range(4): stylo.forward(ESPACE - 8) stylo.right(90) stylo.end_fill() def verifier_carrés_fermes(p1, p2, couleur): """Vérifie si la ligne tracée ferme un ou deux carrés adjacents.""" global score carre_cree = False x1, y1 = p1 x2, y2 = p2 # Liste des carrés potentiels à inspecter selon l'orientation de la ligne carrés_a_tester = [] if y1 == y2: # Ligne horizontale cx = min(x1, x2) carrés_a_tester.append((cx, y1 - 1)) # Carré au-dessus carrés_a_tester.append((cx, y1)) # Carré au-dessous else: # Ligne verticale cy = min(y1, y2) carrés_a_tester.append((x1 - 1, cy)) # Carré à gauche carrés_a_tester.append((x1, cy)) # Carré à droite for (cx, cy) in carrés_a_tester: # On s'assure que le carré est dans les limites de la grille if 0 <= cx < NB_DOTS - 1 and 0 <= cy < NB_DOTS - 1: if (cx, cy) not in carres_valides: # Un carré possède 4 segments. On vérifie s'ils appartiennent tous à 'lignes_tracees' l_haut = ((cx, cy), (cx + 1, cy)) l_bas = ((cx, cy + 1), (cx + 1, cy + 1)) l_gauche = ((cx, cy), (cx, cy + 1)) l_droite = ((cx + 1, cy), (cx + 1, cy + 1)) if (l_haut in lignes_tracees and l_bas in lignes_tracees and l_gauche in lignes_tracees and l_droite in lignes_tracees): carres_valides.add((cx, cy)) colorier_carre(cx, cy, "#ffb3b3" if couleur == "Rouge" else "#b3d1ff") score[couleur] += 1 carre_cree = True return carre_cree def au_clic(px, py): """Gère l'analyse géométrique lors d'un clic de souris à l'écran.""" global joueur_actuel # Évite les clics accidentels si la partie est terminée total_carres_possibles = (NB_DOTS - 1) * (NB_DOTS - 1) if len(carres_valides) == total_carres_possibles: return # 1. Déterminer quel est le segment (la ligne) le plus proche du clic distance_min = 15 # Seuil de tolérance en pixels pour valider la précision du clic ligne_choisie = None for r in range(NB_DOTS): for c in range(NB_DOTS): # Tester le voisin de droite (ligne horizontale) if c < NB_DOTS - 1: x1, y1 = coord_vers_pixel(c, r) x2, y2 = coord_vers_pixel(c + 1, r) # Calcul de la distance du clic par rapport au milieu de ce segment milieu_x, milieu_y = (x1 + x2) / 2, (y1 + y2) / 2 dist = ((px - milieu_x)**2 + (py - milieu_y)**2)**0.5 if dist < distance_min: distance_min = dist ligne_choisie = ((c, r), (c + 1, r)) # Tester le voisin du bas (ligne verticale) if r < NB_DOTS - 1: x1, y1 = coord_vers_pixel(c, r) x2, y2 = coord_vers_pixel(c, r + 1) milieu_x, milieu_y = (x1 + x2) / 2, (y1 + y2) / 2 dist = ((px - milieu_x)**2 + (py - milieu_y)**2)**0.5 if dist < distance_min: distance_min = dist ligne_choisie = ((c, r), (c, r + 1)) # 2. Si une ligne valide et non-tracée a été cliquée if ligne_choisie and (ligne_choisie not in lignes_tracees): lignes_tracees.add(ligne_choisie) # Tracé vectoriel de la ligne sélectionnée p1_px, p1_py = coord_vers_pixel(ligne_choisie[0][0], ligne_choisie[0][1]) p2_px, p2_py = coord_vers_pixel(ligne_choisie[1][0], ligne_choisie[1][1]) turtle.tracer(0) stylo.penup() stylo.goto(p1_px, p1_py) stylo.pendown() stylo.pensize(5) stylo.color("red" if joueur_actuel == "Rouge" else "blue") stylo.goto(p2_px, p2_py) turtle.update() # 3. Vérification des carrés un_carre_a_ferme = verifier_carrés_fermes(ligne_choisie[0], ligne_choisie[1], joueur_actuel) # 4. Changement de joueur ou attribution du tour bonus if not un_carre_a_ferme: joueur_actuel = "Bleu" if joueur_actuel == "Rouge" else "Rouge" mettre_a_jour_titre() # 5. Déclaration de fin de partie if len(carres_valides) == total_carres_possibles: if score["Rouge"] == score["Bleu"]: res = "Égalité parfaite !" else: gagnant = "Rouge" if score["Rouge"] > score["Bleu"] else "Bleu" res = "Le joueur {0} a gagné !".format(gagnant) turtle.title("PARTIE TERMINÉE : " + res) # --- INITIALISATION DE L'INTERFACE TURTLE --- turtle.setup(width=800, height=800) stylo = turtle.Turtle() stylo.hideturtle() stylo.speed(0) # Tracé initial dessiner_grille() mettre_a_jour_titre() # Association de l'événement clic de souris de l'écran Turtle à notre fonction turtle.onscreenclick(au_clic) turtle.listen() turtle.mainloop()