Le programme présenté met en œuvre un jeu de réflexion élégant : Connect Circuit, un puzzle où le joueur doit faire pivoter des tuiles pour former un circuit électrique fermé.
Chaque tuile représente un morceau de câble, et l’objectif est de relier toutes les pièces à une source d’énergie centrale.
Ce jeu repose sur une combinaison de génération procédurale, décodage binaire, propagation logique, et interface graphique dynamique.
Configuration générale et variables globales
Le jeu s’affiche dans une zone de :
- 360 px de large
- une zone d’en-tête de 40 px
- une zone de pied de 44 px
La grille varie selon le niveau :
- Petit : 4×4
- Moyen : 6×6
- Grand : 8×8
Chaque tuile est codée par un nombre représentant ses connexions :
- 1 = Nord
- 2 = Est
- 4 = Sud
- 8 = Ouest
Ce codage binaire permet de combiner les directions :
par exemple, une tuile en T vers le nord-est-sud vaut 1 + 2 + 4 = 7.
Le programme gère plusieurs tableaux :
- map : structure du circuit
- visite : utilisé pour la génération
- alimente : indique quelles tuiles reçoivent le courant
- historique et hist_sens : pour annuler les coups
- pile : pour la génération en profondeur
Lecture des bits : décoder les tuiles
Quatre fonctions permettent de lire les directions d’une tuile :
- bit_nord(v)
- bit_est(v)
- bit_sud(v)
- bit_ouest(v)
Elles extraient les bits correspondants.
Ce système rend le code très compact : une tuile peut être manipulée comme un simple entier.
Rotation des tuiles
Deux fonctions gèrent les rotations :
- tourner_droite(v)
- tourner_gauche(v)
Elles réorganisent les bits pour simuler une rotation de 90°.
Par exemple, tourner à droite fait passer :
- Nord ? Est
- Est ? Sud
- Sud ? Ouest
- Ouest ? Nord
Ces transformations sont réalisées en recomposant les bits dans un nouvel ordre.
Vérification de la victoire : un circuit parfaitement fermé
La fonction verifier_victoire() inspecte chaque tuile :
- si une tuile a un fil vers le nord, la tuile au-dessus doit avoir un fil vers le sud
- si elle a un fil vers l’est, la tuile à droite doit avoir un fil vers l’ouest
- etc.
Si une connexion débouche dans le vide ou sur une tuile incompatible, la victoire est impossible.
Cette vérification garantit que le circuit est totalement fermé, sans extrémité libre.
Dessin de la scène : interface et feedback visuel
La procédure dessiner_scene() construit l’affichage :
Fond et titre
Fond sombre bleu nuit
Titre rouge ou vert selon l’état (jeu / victoire)
Barre de progression
Elle indique la proportion de tuiles alimentées par le courant.
Grille de tuiles
Chaque tuile est dessinée avec :
- un fond plus clair si elle est alimentée
- des segments représentant les fils
une forme centrale selon le nombre de branches :
- source d’énergie
- extrémité
- jonction
Les couleurs changent selon l’alimentation, donnant un feedback immédiat au joueur.
Barre de boutons
Quatre boutons :
- Nouveau
- Annuler
- Sens de rotation
- Taille de la grille
Génération du circuit : un labyrinthe électrique
Le circuit est généré par une fouille en profondeur (DFS) :
- La source est placée au centre.
- On explore les voisins non visités.
- Chaque déplacement crée une connexion entre les tuiles.
- On continue jusqu’à avoir visité toute la grille.
Le résultat est un circuit parfaitement connecté.
Brouillage
Ensuite, toutes les tuiles sont rotées aléatoirement.
Si par hasard le circuit reste correct, on recommence.
Cela garantit que le joueur doit réellement résoudre le puzzle.
Boucle de jeu : interaction et propagation du courant
À chaque clic :
- la tuile est tournée dans le sens choisi
- l’historique est mis à jour
- la propagation du courant est recalculée
La propagation fonctionne comme un flood-fill :
- on part de la source
- on avance dans les directions compatibles
- chaque tuile alimentée est ajoutée à une pile
- on continue jusqu’à ne plus pouvoir avancer
Cela permet d’afficher en temps réel quelles tuiles sont correctement connectées.
Victoire : circuit complet
Lorsque verifier_victoire() retourne vrai :
- toutes les tuiles sont marquées comme alimentées
- la barre de progression se remplit
- le titre devient vert
- le joueur peut cliquer pour rejouer ou changer de taille
Conclusion
Ce programme est un excellent exemple de jeu de réflexion procédural, combinant :
- codage binaire intelligent
- génération de labyrinthe
- propagation logique
- interface graphique dynamique
- gestion des interactions utilisateur
Le résultat est un puzzle fluide, lisible, et agréable à jouer, où chaque rotation de tuile rapproche le joueur de la satisfaction d’un circuit parfaitement fermé.
Programme :
@ Configuration et Variables Globales
LARGEUR est un nombre vaut 360
HAUT_ENTETE est un nombre vaut 40
HAUT_PIED est un nombre vaut 44
niveau est un nombre vaut 1 // 0 = Petit (4x4), 1 = Moyen (6x6), 2 = Grand (8x8)
COLS est un nombre vaut 6
LIGNES est un nombre vaut 6
pxtuile est un nombre vaut 60 // 360 / COLS : 90, 60 ou 45 selon le niveau
map est un tableau // valeur de chaque tuile : 1=Nord, 2=Est, 4=Sud, 8=Ouest
visite est un tableau // utilisé par la génération
alimente est un tableau // 1 si la tuile est reliée à la source
historique est un tableau // indices des tuiles tournées (pour Annuler)
hist_sens est un tableau // sens utilisé pour chaque coup
pile est un tableau
gagne est un booléen vaut faux
rejouer est un booléen vaut faux
continuer est un booléen vaut vrai
sens est un nombre vaut 1 // 1 = horaire, 0 = anti-horaire
source est un nombre vaut 0 // tuile "génératrice" du courant
nb_alim est un nombre vaut 0 // nombre de tuiles alimentées
maToile est une toile
dimension(maToile, LARGEUR, HAUT_ENTETE + LARGEUR + HAUT_PIED)
@ Fonctions de lecture des bits
// Évite de répéter partout les mêmes calculs de décodage
fonction bit_nord(v)
retourne v mod 2
fin fonction
fonction bit_est(v)
retourne arrondi_inferieur(v / 2) mod 2
fin fonction
fonction bit_sud(v)
retourne arrondi_inferieur(v / 4) mod 2
fin fonction
fonction bit_ouest(v)
retourne arrondi_inferieur(v / 8) mod 2
fin fonction
@ Fonctions Logiques
// Rotation de 90 degrés vers la droite : Nord -> Est -> Sud -> Ouest -> Nord
fonction tourner_droite(v)
retourne bit_ouest(v) + bit_nord(v) * 2 + bit_est(v) * 4 + bit_sud(v) * 8
fin fonction
// Rotation de 90 degrés vers la gauche : Nord -> Ouest -> Sud -> Est -> Nord
fonction tourner_gauche(v)
retourne bit_est(v) + bit_sud(v) * 2 + bit_ouest(v) * 4 + bit_nord(v) * 8
fin fonction
// Vérifie que le circuit est fermé : aucune extrémité dans le vide
fonction verifier_victoire()
pour y de 0 à LIGNES - 1
pour x de 0 à COLS - 1
idx est un nombre
idx vaut y * COLS + x
val est un nombre
val vaut map[idx]
// Connexion Nord
si bit_nord(val) = 1 alors
si y = 0 alors
retourne faux
fin si
si bit_sud(map[idx - COLS]) = 0 alors
retourne faux
fin si
fin si
// Connexion Est
si bit_est(val) = 1 alors
si x = COLS - 1 alors
retourne faux
fin si
si bit_ouest(map[idx + 1]) = 0 alors
retourne faux
fin si
fin si
// Connexion Sud
si bit_sud(val) = 1 alors
si y = LIGNES - 1 alors
retourne faux
fin si
si bit_nord(map[idx + COLS]) = 0 alors
retourne faux
fin si
fin si
// Connexion Ouest
si bit_ouest(val) = 1 alors
si x = 0 alors
retourne faux
fin si
si bit_est(map[idx - 1]) = 0 alors
retourne faux
fin si
fin si
fin pour
fin pour
retourne vrai
fin fonction
@ Interface et Dessin
procédure dessiner_scene()
// On efface la toile précédente avant de redessiner,
// sinon les images s'empilent à chaque tour de boucle
effacer(maToile)
remplir(maToile, #0f0f1e)
couleur_on est un texte
couleur_on vaut #00d4ff
couleur_off est un texte
couleur_off vaut #35476b
si gagne = vrai alors
couleur_on vaut #4ade80
couleur_off vaut #4ade80
fin si
// Titre
si gagne = vrai alors
label(maToile, 8, 17, "CIRCUIT COMPLET ! Cliquez pour rejouer", #4ade80, 12)
sinon
label(maToile, 8, 17, "Connect Circuit - pivotez les tuiles", #e94560, 12)
fin si
// Barre de progression : proportion de tuiles alimentées
rectangle_arrondi(maToile, 8, 24, LARGEUR - 16, 8, 4, #1b1b2f)
larg est un nombre
larg vaut (LARGEUR - 16) * nb_alim / (COLS * LIGNES)
si larg > 0 alors
rectangle_arrondi(maToile, 8, 24, larg, 8, 4, couleur_on)
fin si
// Grille
pour y de 0 à LIGNES - 1
pour x de 0 à COLS - 1
idx est un nombre
idx vaut y * COLS + x
val est un nombre
val vaut map[idx]
px est un nombre
px vaut x * pxtuile
py est un nombre
py vaut y * pxtuile + HAUT_ENTETE
// Fond de la tuile : plus clair si elle reçoit le courant
fond est un texte
fond vaut #1b1b2f
si alimente[idx] = 1 alors
fond vaut #232b4d
fin si
rectangle_arrondi(maToile, px + 2, py + 2, pxtuile - 4, pxtuile - 4, 5, fond)
// Couleur du fil : vif si alimenté, terne sinon
cfil est un texte
cfil vaut couleur_off
si alimente[idx] = 1 alors
cfil vaut couleur_on
fin si
cx est un nombre
cx vaut px + pxtuile / 2
cy est un nombre
cy vaut py + pxtuile / 2
ep est un nombre
ep vaut arrondi_inferieur(pxtuile / 9)
si bit_nord(val) = 1 alors
ligne(maToile, cx, cy, cx, py + 2, cfil, ep)
fin si
si bit_est(val) = 1 alors
ligne(maToile, cx, cy, px + pxtuile - 2, cy, cfil, ep)
fin si
si bit_sud(val) = 1 alors
ligne(maToile, cx, cy, cx, py + pxtuile - 2, cfil, ep)
fin si
si bit_ouest(val) = 1 alors
ligne(maToile, cx, cy, px + 2, cy, cfil, ep)
fin si
// Nombre de branches de la tuile
deg est un nombre
deg vaut bit_nord(val) + bit_est(val) + bit_sud(val) + bit_ouest(val)
si idx = source alors
// La source d'énergie
rectangle_arrondi(maToile, cx - pxtuile / 6, cy - pxtuile / 6, pxtuile / 3, pxtuile / 3, 5, #ffd166)
cercle(maToile, cx, cy, ep, #0f0f1e)
sinon si deg = 1 alors
// Extrémité : dessinée comme une petite ampoule carrée
rectangle_arrondi(maToile, cx - pxtuile / 8, cy - pxtuile / 8, pxtuile / 4, pxtuile / 4, 4, cfil)
cercle(maToile, cx, cy, ep / 2, #0f0f1e)
sinon si deg > 0 alors
cercle(maToile, cx, cy, ep + 2, cfil)
cercle(maToile, cx, cy, ep / 2, #0f0f1e)
fin si
fin pour
fin pour
// Barre de boutons
by est un nombre
by vaut HAUT_ENTETE + LARGEUR + 5
pour b de 0 à 3
bx est un nombre
bx vaut 4 + b * 88
rectangle_arrondi(maToile, bx, by, 84, 32, 6, #1b1b2f)
fin pour
label(maToile, 26, by + 21, "Nouveau", #e6e6e6, 11)
label(maToile, 115, by + 21, "Annuler", #e6e6e6, 11)
si sens = 1 alors
label(maToile, 200, by + 21, "Sens : D", #e6e6e6, 11)
sinon
label(maToile, 200, by + 21, "Sens : G", #e6e6e6, 11)
fin si
si niveau = 0 alors
label(maToile, 297, by + 21, "Petit", #e6e6e6, 11)
sinon si niveau = 1 alors
label(maToile, 293, by + 21, "Moyen", #e6e6e6, 11)
sinon
label(maToile, 293, by + 21, "Grand", #e6e6e6, 11)
fin si
affiche maToile
fin procédure
@ Programme Principal
tant que continuer = vrai
@ Préparation d'une partie
pxtuile vaut LARGEUR / COLS
// Vide les tableaux de la partie précédente
tant que longueur(map) > 0
map supprime (longueur(map) - 1)
fin tant que
tant que longueur(visite) > 0
visite supprime (longueur(visite) - 1)
fin tant que
tant que longueur(alimente) > 0
alimente supprime (longueur(alimente) - 1)
fin tant que
tant que longueur(historique) > 0
historique supprime (longueur(historique) - 1)
fin tant que
tant que longueur(hist_sens) > 0
hist_sens supprime (longueur(hist_sens) - 1)
fin tant que
tant que longueur(pile) > 0
pile supprime (longueur(pile) - 1)
fin tant que
pour i de 0 à LIGNES * COLS - 1
map ajoute 0
visite ajoute 0
alimente ajoute 0
fin pour
// La source est placée au centre de la grille
source vaut arrondi_inferieur(LIGNES / 2) * COLS + arrondi_inferieur(COLS / 2)
@ Génération du circuit (fouille en profondeur)
pile ajoute source
visite[source] vaut 1
tant que longueur(pile) > 0
courant est un nombre
courant vaut pile[longueur(pile) - 1]
cx_gen est un nombre
cx_gen vaut courant mod COLS
cy_gen est un nombre
cy_gen vaut arrondi_inferieur(courant / COLS)
voisins est un tableau
si cy_gen > 0 alors
si visite[courant - COLS] = 0 alors
voisins ajoute 0
fin si
fin si
si cx_gen < COLS - 1 alors
si visite[courant + 1] = 0 alors
voisins ajoute 1
fin si
fin si
si cy_gen < LIGNES - 1 alors
si visite[courant + COLS] = 0 alors
voisins ajoute 2
fin si
fin si
si cx_gen > 0 alors
si visite[courant - 1] = 0 alors
voisins ajoute 3
fin si
fin si
si longueur(voisins) = 0 alors
pile supprime (longueur(pile) - 1)
sinon
r est un nombre
r vaut hasard(longueur(voisins) - 1)
choix est un nombre
choix vaut voisins[r]
n_idx est un nombre
si choix = 0 alors
n_idx vaut courant - COLS
map[courant] vaut map[courant] + 1
map[n_idx] vaut map[n_idx] + 4
sinon si choix = 1 alors
n_idx vaut courant + 1
map[courant] vaut map[courant] + 2
map[n_idx] vaut map[n_idx] + 8
sinon si choix = 2 alors
n_idx vaut courant + COLS
map[courant] vaut map[courant] + 4
map[n_idx] vaut map[n_idx] + 1
sinon
n_idx vaut courant - 1
map[courant] vaut map[courant] + 8
map[n_idx] vaut map[n_idx] + 2
fin si
visite[n_idx] vaut 1
pile ajoute n_idx
fin si
fin tant que
@ Brouillage
// On rebrouille tant que le circuit tombe par hasard sur la solution
gagne vaut vrai
tant que gagne = vrai
pour i de 0 à longueur(map) - 1
rots est un nombre
rots vaut hasard(3)
pour r de 1 à rots
map[i] vaut tourner_droite(map[i])
fin pour
fin pour
gagne vaut verifier_victoire()
fin tant que
rejouer vaut faux
@ Boucle de jeu
tant que gagne = faux et rejouer = faux
// --- Propagation du courant depuis la source ---
pour i de 0 à longueur(alimente) - 1
alimente[i] vaut 0
fin pour
alimente[source] vaut 1
nb_alim vaut 1
pile_p est un tableau
pile_p ajoute source
tant que longueur(pile_p) > 0
c est un nombre
c vaut pile_p[longueur(pile_p) - 1]
pile_p supprime (longueur(pile_p) - 1)
cxp est un nombre
cxp vaut c mod COLS
cyp est un nombre
cyp vaut arrondi_inferieur(c / COLS)
si cyp > 0 alors
si bit_nord(map[c]) = 1 alors
vN est un nombre
vN vaut c - COLS
si alimente[vN] = 0 alors
si bit_sud(map[vN]) = 1 alors
alimente[vN] vaut 1
nb_alim vaut nb_alim + 1
pile_p ajoute vN
fin si
fin si
fin si
fin si
si cxp < COLS - 1 alors
si bit_est(map[c]) = 1 alors
vE est un nombre
vE vaut c + 1
si alimente[vE] = 0 alors
si bit_ouest(map[vE]) = 1 alors
alimente[vE] vaut 1
nb_alim vaut nb_alim + 1
pile_p ajoute vE
fin si
fin si
fin si
fin si
si cyp < LIGNES - 1 alors
si bit_sud(map[c]) = 1 alors
vS est un nombre
vS vaut c + COLS
si alimente[vS] = 0 alors
si bit_nord(map[vS]) = 1 alors
alimente[vS] vaut 1
nb_alim vaut nb_alim + 1
pile_p ajoute vS
fin si
fin si
fin si
fin si
si cxp > 0 alors
si bit_ouest(map[c]) = 1 alors
vO est un nombre
vO vaut c - 1
si alimente[vO] = 0 alors
si bit_est(map[vO]) = 1 alors
alimente[vO] vaut 1
nb_alim vaut nb_alim + 1
pile_p ajoute vO
fin si
fin si
fin si
fin si
fin tant que
appelle dessiner_scene()
clic_x est un nombre
clic_y est un nombre
cliquer maToile dans clic_x, clic_y
// --- Clic dans la grille ---
si clic_y >= HAUT_ENTETE alors
si clic_y < HAUT_ENTETE + LARGEUR alors
cx_clic est un nombre
cx_clic vaut arrondi_inferieur(clic_x / pxtuile)
cy_clic est un nombre
cy_clic vaut arrondi_inferieur((clic_y - HAUT_ENTETE) / pxtuile)
si cx_clic >= 0 et cx_clic < COLS et cy_clic >= 0 et cy_clic < LIGNES alors
idx_clic est un nombre
idx_clic vaut cy_clic * COLS + cx_clic
si sens = 1 alors
map[idx_clic] vaut tourner_droite(map[idx_clic])
sinon
map[idx_clic] vaut tourner_gauche(map[idx_clic])
fin si
historique ajoute idx_clic
hist_sens ajoute sens
fin si
fin si
fin si
// --- Clic sur la barre de boutons ---
si clic_y >= HAUT_ENTETE + LARGEUR alors
// Nouveau
si clic_x >= 4 et clic_x < 88 alors
rejouer vaut vrai
fin si
// Annuler le dernier coup
si clic_x >= 92 et clic_x < 176 alors
si longueur(historique) > 0 alors
tuile_annulee est un nombre
tuile_annulee vaut historique[longueur(historique) - 1]
sens_annule est un nombre
sens_annule vaut hist_sens[longueur(hist_sens) - 1]
historique supprime (longueur(historique) - 1)
hist_sens supprime (longueur(hist_sens) - 1)
si sens_annule = 1 alors
map[tuile_annulee] vaut tourner_gauche(map[tuile_annulee])
sinon
map[tuile_annulee] vaut tourner_droite(map[tuile_annulee])
fin si
fin si
fin si
// Sens de rotation
si clic_x >= 180 et clic_x < 264 alors
si sens = 1 alors
sens vaut 0
sinon
sens vaut 1
fin si
fin si
// Taille de la grille
si clic_x >= 268 et clic_x < 352 alors
niveau vaut (niveau + 1) mod 3
si niveau = 0 alors
COLS vaut 4
sinon si niveau = 1 alors
COLS vaut 6
sinon
COLS vaut 8
fin si
LIGNES vaut COLS
rejouer vaut vrai
fin si
fin si
si rejouer = faux alors
gagne vaut verifier_victoire()
fin si
fin tant que
@ Écran de victoire
si gagne = vrai alors
// Le circuit est fermé : tout est alimenté
pour i de 0 à longueur(alimente) - 1
alimente[i] vaut 1
fin pour
nb_alim vaut COLS * LIGNES
appelle dessiner_scene()
clic_fin_x est un nombre
clic_fin_y est un nombre
cliquer maToile dans clic_fin_x, clic_fin_y
// On peut changer de taille directement depuis l'écran de victoire
si clic_fin_y >= HAUT_ENTETE + LARGEUR alors
si clic_fin_x >= 268 et clic_fin_x < 352 alors
niveau vaut (niveau + 1) mod 3
si niveau = 0 alors
COLS vaut 4
sinon si niveau = 1 alors
COLS vaut 6
sinon
COLS vaut 8
fin si
LIGNES vaut COLS
fin si
fin si
fin si
fin tant que