I'm creating a small game inspired by the puzzles of the witness =

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


# 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)




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:
        #print(current_level)
        
        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[0],node[1])
            lines_drawn = set()
            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
                    return
                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))
                return
            
run()

The problem is that when I try clicking, it doesn’t do anything, and I don’t know why.

    cols, rows = LEVELS[current_level]["grid"]
    createCanvas(2*MARGIN + cols*CELL, 2*MARGIN + rows*CELL).mousePressed(redraw)