I'm trying to use the p5.js library to create a game inspired be The Witness

from p5 import *


# DÉFINITION DES NIVEAUX

LEVELS = [
    {
        "grid": (4, 4),          # taille de la grille : 4 colonnes x 4 lignes
        "start": (0, 0),         # nœud de départ
        "end": (4, 4),           # nœud d'arrivée
        "blocked": [(1,1),(2,1)],# cases interdites
        "symbols": [(0,2,'circle'), (3,1,'circle')] # symboles à visiter avant l'arrivée
    },
    {
        "grid": (5, 5),
        "start": (0, 4),
        "end": (4, 0),
        "blocked": [(2,2),(1,3)],
        "symbols": [(0,2,'circle'), (2,4,'circle')]
    },
    {
        "grid": (6, 6),
        "start": (0, 0),
        "end": (5, 5),
        "blocked": [(2,2),(3,3),(1,4)],
        "symbols": [(1,1,'circle'), (4,4,'circle')]
    }
]


# CONFIGURATION VISUELLE

CELL = 80       # taille d'une cellule
MARGIN = 40     # marge autour de la grille pour le dessin
SNAP_RADIUS = CELL / 1.5  # rayon pour “attraper” le nœud avec le clic

# VARIABLES DU JEU

current_level = 0   # niveau actuel
path = []           # liste des nœuds visités pour le chemin
lines_drawn = set() # lignes déjà tracées (pour ne pas repasser dessus)
game_finished = False

# FONCTIONS UTILITAIRES


def node_position(node):
    """
    Convertit un nœud (col, row) en coordonnées pixels pour l'affichage
    """
    x, y = node
    return MARGIN + x * CELL, MARGIN + y * CELL

def distance(x1, y1, x2, y2):
    """
    Calcul de la distance euclidienne entre deux points
    """
    return ((x1-x2)**2 + (y1-y2)**2)**0.5

def snap_to_node(mx, my, grid):
    """
    Retourne le nœud le plus proche du clic
    """
    closest = None
    min_dist = SNAP_RADIUS
    cols, rows = grid
    for x in range(cols+1):
        for y in range(rows+1):
            nx, ny = node_position((x, y))
            d = distance(mx, my, nx, ny)
            if d <= min_dist:
                closest = (x, y)
                min_dist = d
    return closest

def is_adjacent(a, b):
    """
    Vérifie si deux nœuds sont adjacents horizontalement ou verticalement
    """
    return abs(a[0]-b[0]) + abs(a[1]-b[1]) == 1

def node_blocked(node):
    """
    Vérifie si un nœud est dans la liste des blocs interdits
    """
    x, y = node
    return (x, y) in LEVELS[current_level].get('blocked', [])

def line_used(a, b):
    """
    Vérifie si une ligne entre deux nœuds a déjà été tracée
    """
    return ((a,b) in lines_drawn) or ((b,a) in lines_drawn)


# CONFIGURATION DE LA FENÊTRE

def setup():
    """
    Initialisation du canvas en fonction de la taille de la grille
    """
    cols, rows = LEVELS[current_level]["grid"]
    createCanvas(2*MARGIN + cols*CELL, 2*MARGIN + rows*CELL)
    noLoop()  # dessin manuel uniquement (rafraîchissement avec redraw())



def draw_grid():
    """
    Dessine la grille principale
    """
    cols, rows = LEVELS[current_level]["grid"]
    stroke(80)
    for x in range(cols+1):
        line(MARGIN+x*CELL, MARGIN, MARGIN+x*CELL, MARGIN+rows*CELL)
    for y in range(rows+1):
        line(MARGIN, MARGIN+y*CELL, MARGIN+cols*CELL, MARGIN+y*CELL)

def draw_blocks():
    """
    Dessine les cases bloquées
    """
    fill(0)
    noStroke()
    for bx, by in LEVELS[current_level].get('blocked', []):
        rect(MARGIN+bx*CELL, MARGIN+by*CELL, CELL, CELL)

def draw_symbols():
    """
    Dessine les symboles (ex: cercles) que le joueur doit visiter
    """
    symbols = LEVELS[current_level].get('symbols', [])
    strokeWeight(8)
    for x, y, kind in symbols:
        nx, ny = node_position((x, y))
        if kind=='circle':
            stroke(0,0,255)
            noFill()
            ellipse(nx, ny, 20, 20)
    strokeWeight(1)

def draw_points():
    """
    Dessine les points de départ (vert) et d'arrivée (rouge)
    """
    sx, sy = LEVELS[current_level]["start"]
    ex, ey = LEVELS[current_level]["end"]
    strokeWeight(10)
    stroke(0,255,0)
    px, py = node_position((sx, sy))
    point(px, py)
    stroke(255,0,0)
    px, py = node_position((ex, ey))
    point(px, py)
    strokeWeight(1)

def draw_path():
    """
    Dessine le chemin jaune tracé par le joueur
    """
    stroke(255,255,0)
    strokeWeight(6)
    for i in range(len(path)-1):
        a = path[i]
        b = path[i+1]
        x1, y1 = node_position(a)
        x2, y2 = node_position(b)
        line(x1, y1, x2, y2)
    strokeWeight(1)


# INTERACTION : CLICS

# DESSIN

def draw():
    """
    Dessine la grille, les blocs, les symboles, les points de départ/arrivée et le chemin
    """
    background(30)
    global path, lines_drawn, current_level, game_finished
    if game_finished:
        fill(255)
        textSize(32)
        textAlign(CENTER, CENTER)
        text("JEU TERMINÉ", width/2, height/2)
        return

    draw_grid()
    draw_blocks()
    draw_symbols()
    draw_path()
    draw_points()
    
    if mouseIsPressed:
         """
    Chaque clic fait progresser le chemin :
    - si clic sur départ et chemin vide : commence le chemin
    - si clic sur un nœud adjacent valide : ajoute au chemin
    - si clic sur l'arrivée : vérifie symboles et termine le niveau
    """

    if game_finished: 
        return

    # détecte le nœud le plus proche du clic
    node = snap_to_node(mouseX, mouseY, LEVELS[current_level]["grid"])
    if node is None: 
        return

    # si début du chemin
    if not path and node == LEVELS[current_level]["start"]:
        path.append(node)
        lines_drawn = set()
        redraw()
        return

    # si on a déjà commencé le chemin
    elif path:
        last = path[-1]

        # clic sur l'arrivée
        if node == LEVELS[current_level]["end"]:
            # vérifier que tous les symboles ont été visités
            symbols = LEVELS[current_level].get('symbols', [])
            visited = set(path)
            all_symbols = all((x,y) in visited for x,y,_ in symbols)
            if not all_symbols:
                print("Vous devez passer sur tous les symboles !")
                return
            # niveau terminé : passer au niveau suivant
            current_level += 1
            path = []
            lines_drawn = set()
            if current_level >= len(LEVELS):
                game_finished = True
            redraw()
            return

        # clic sur nœud adjacent valide
        elif is_adjacent(last, node) and not node_blocked(node) and not line_used(last, node):
            path.append(node)
            lines_drawn.add((last, node))
            redraw()
run()

it doesn’t work, and I don’t know why… can anyone help me ?

The script does not appear to have the function run() defined anywhere. run() is not a built-in keyword that automatically runs your script. You have to define it as part of your script.

Did you mean: draw()

It works when I use it, the game is shown, but the interaction with the mouse doesn’t work

run might be imported here

This is why from X import * should be avoided.

I just meant that the problem is when I use the if mouseIsPressed() but I don’t understand what’s wrong

@JamesParrott
Ahh, I see. It is a function from the p5 library.

@Fluffy1o, do you understand what @JamesParrott is referring to? Generally it is bad practice to import a library with an asterisk ( “*” ) because then its namespace objects may either interfere with existing namespace objects or may not be clear as to what specific objects are being imported. Try instead importing the library with an alias or as is since its name is already pretty condensed (but without an asterisk). Then any imported object from this library can be prefixed with its name (i.e., p5.run()). This eliminates misunderstandings as well as the potential for name collisions.

Regarding the if mouseIsPressed: conditional statement:

You’ll notice that it only includes one multi-line comment but no actual Python instructions to execute should the conditional statement be met. Thus, as is, nothing happens when the condition is met. You have to add code here to perform what specifically it is you want it to execute should the conditional statement be satisfied.

Is this clear?

from p5 import run,createCanvas,noLoop,background,stroke,line,fill,noStroke,rect,strokeWeight,noFill,ellipse,point,mouseIsPressed


# DÉFINITION DES NIVEAUX

LEVELS = [
    {
        "grid": (4, 4),          # taille de la grille : 4 colonnes x 4 lignes
        "start": (0, 0),         # nœud de départ
        "end": (4, 4),           # nœud d'arrivée
        "blocked": [(1,1),(2,1)],# cases interdites
        "symbols": [(0,2,'circle'), (3,1,'circle')] # symboles à visiter avant l'arrivée
    },
    {
        "grid": (5, 5),
        "start": (0, 4),
        "end": (4, 0),
        "blocked": [(2,2),(1,3)],
        "symbols": [(0,2,'circle'), (2,4,'circle')]
    },
    {
        "grid": (6, 6),
        "start": (0, 0),
        "end": (5, 5),
        "blocked": [(2,2),(3,3),(1,4)],
        "symbols": [(1,1,'circle'), (4,4,'circle')]
    }
]


# CONFIGURATION VISUELLE

CELL = 80       # taille d'une cellule
MARGIN = 40     # marge autour de la grille pour le dessin
SNAP_RADIUS = CELL / 1.5  # rayon pour “attraper” le nœud avec le clic

# VARIABLES DU JEU

current_level = 0   # niveau actuel
path = []           # liste des nœuds visités pour le chemin
lines_drawn = set() # lignes déjà tracées (pour ne pas repasser dessus)
game_finished = False


# FONCTIONS UTILITAIRES


def node_position(node):
    """
    Convertit un nœud (col, row) en coordonnées pixels pour l'affichage
    """
    x, y = node
    return MARGIN + x * CELL, MARGIN + y * CELL

def distance(x1, y1, x2, y2):
    """
    Calcul de la distance euclidienne entre deux points
    """
    return ((x1-x2)**2 + (y1-y2)**2)**0.5

def snap_to_node(mx, my, grid):
    """
    Retourne le nœud le plus proche du clic
    """
    closest = None
    min_dist = SNAP_RADIUS
    cols, rows = grid
    for x in range(cols+1):
        for y in range(rows+1):
            nx, ny = node_position((x, y))
            d = distance(mx, my, nx, ny)
            if d <= min_dist:
                closest = (x, y)
                min_dist = d
    return closest

def is_adjacent(a, b):
    """
    Vérifie si deux nœuds sont adjacents horizontalement ou verticalement
    """
    return abs(a[0]-b[0]) + abs(a[1]-b[1]) == 1

def node_blocked(node):
    """
    Vérifie si un nœud est dans la liste des blocs interdits
    """
    x, y = node
    return (x, y) in LEVELS[current_level].get('blocked', [])

def line_used(a, b):
    """
    Vérifie si une ligne entre deux nœuds a déjà été tracée
    """
    return ((a,b) in lines_drawn) or ((b,a) in lines_drawn)


# CONFIGURATION DE LA FENÊTRE

def setup():
    """
    Initialisation du canvas en fonction de la taille de la grille
    """
    cols, rows = LEVELS[current_level]["grid"]
    createCanvas(2*MARGIN + cols*CELL, 2*MARGIN + rows*CELL)
    noLoop()  # dessin manuel uniquement (rafraîchissement avec redraw())



def draw_grid():
    """
    Dessine la grille principale
    """
    cols, rows = LEVELS[current_level]["grid"]
    stroke(80)
    for x in range(cols+1):
        line(MARGIN+x*CELL, MARGIN, MARGIN+x*CELL, MARGIN+rows*CELL)
    for y in range(rows+1):
        line(MARGIN, MARGIN+y*CELL, MARGIN+cols*CELL, MARGIN+y*CELL)

def draw_blocks():
    """
    Dessine les cases bloquées
    """
    fill(0)
    noStroke()
    for bx, by in LEVELS[current_level].get('blocked', []):
        rect(MARGIN+bx*CELL, MARGIN+by*CELL, CELL, CELL)

def draw_symbols():
    """
    Dessine les symboles (ex: cercles) que le joueur doit visiter
    """
    symbols = LEVELS[current_level].get('symbols', [])
    strokeWeight(8)
    for x, y, kind in symbols:
        nx, ny = node_position((x, y))
        if kind=='circle':
            stroke(0,0,255)
            noFill()
            ellipse(nx, ny, 20, 20)
    strokeWeight(1)

def draw_points():
    """
    Dessine les points de départ (vert) et d'arrivée (rouge)
    """
    sx, sy = LEVELS[current_level]["start"]
    ex, ey = LEVELS[current_level]["end"]
    strokeWeight(10)
    stroke(0,255,0)
    px, py = node_position((sx, sy))
    point(px, py)
    stroke(255,0,0)
    px, py = node_position((ex, ey))
    point(px, py)
    strokeWeight(1)

def draw_path():
    """
    Dessine le chemin jaune tracé par le joueur
    """
    stroke(255,255,0)
    strokeWeight(6)
    for i in range(len(path)-1):
        a = path[i]
        b = path[i+1]
        x1, y1 = node_position(a)
        x2, y2 = node_position(b)
        line(x1, y1, x2, y2)
    strokeWeight(1)


# INTERACTION : CLICS
# DESSIN

def draw():
    """
    Dessine la grille, les blocs, les symboles, les points de départ/arrivée et le chemin
    """
    background(30)
    global path, lines_drawn, current_level, game_finished
    if game_finished:
        fill(255)
        textSize(32)
        textAlign(CENTER, CENTER)
        text("JEU TERMINÉ", width/2, height/2)
        return

    draw_grid()
    draw_blocks()
    draw_symbols()
    draw_path()
    draw_points()

    
    
    if mouseIsPressed:

        if game_finished: 
            return

        # détecte le nœud le plus proche du clic
        node = snap_to_node(mouseX, mouseY, LEVELS[current_level]["grid"])
        if node is None: 
            return

        # si début du chemin
        if not path and node == LEVELS[current_level]["start"]:
            path.append(node)
            lines_drawn = set()
            redraw()
            return

        # si on a déjà commencé le chemin
        elif path:
            last = path[-1]

            # clic sur l'arrivée
            if node == LEVELS[current_level]["end"]:
                # vérifier que tous les symboles ont été visités
                symbols = LEVELS[current_level].get('symbols', [])
                visited = set(path)
                all_symbols = all((x,y) in visited for x,y,_ in symbols)
                if not all_symbols:
                    print("Vous devez passer sur tous les symboles !")
                    return
                    # niveau terminé : passer au niveau suivant
                current_level += 1
                path = []
                lines_drawn = set()
                if current_level >= len(LEVELS):
                    game_finished = True
                    redraw()
                return

            # clic sur nœud adjacent valide
            elif is_adjacent(last, node) and not node_blocked(node) and not line_used(last, node):
                path.append(node)
                lines_drawn.add((last, node))
                redraw()
        
run()

Well, I changed the indentation and import problems, but it still doesn’t do anything when I try clicking a node.