I'm trying to make a 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

# 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, no matter how hard I try, the level shows, but when I try clicking on a node, it just doesn’t do anything.

Hello!
I’m not too familiar with your setup (is it the native p5 library in python?) and don’t have it set up here, but here are two things I notice:

  1. You have called noLoop() in setup() so the draw() will only be called once.

The mouse-pressed logic you have in draw() will only be run once, right at the start, not throughout the game. So when you click, nothing is listening.

When you aren’t using a draw loop, it’s common to add a mouse_pressed function outside of draw, which will be called any time the mouse is pressed.

If I guess correctly the library you’re using, mouse_pressed is documented here.

  1. Your code refers to mouseX and mouseY, which are correct names for the global variables in p5 in javascript but in your environment those should be mouse_x and mouse_y. As documented here. I could be wrong about that - again it depends on your environment.

I hope those help to get you started!

Also, I think the correct category for this post is p5py

…because this current category (“p5.js”) is for p5 with javascript, not python.

It’s not a big deal, I think! But if you don’t get much help here, you can try there instead, where there may be more python programmers!

(post deleted by author)

Thanks for the help, but it’s said that it uses p5.js

Is it using PyScript + p5js by chance?

I don’t know at all, but I’m using python on a site named basthon