đïž Darkwaar27 : Pigment â Un casse-tĂȘte avec des dĂ©s basĂ© sur la rotation et l'algĂšbre des couleurs
le 11 octobre 2026
Lire : https://darkwoodcom.itch.io/darkwaar27
Un dé, une rÚgle
Pigment est un casse-tĂȘte dĂ©terministe se jouant sur un plateau de 5Ă5. Un dĂ© occupe une case du plateau. Une commande le dĂ©place vers une case voisine orthogonale et le fait pivoter de quatre-vingt-dix degrĂ©s afin quâune nouvelle face soit tournĂ©e vers le bas. Cette face tournĂ©e vers le bas colore la case de destination. Rien dâautre ne bouge.
Le joueur ne choisit jamais de couleur dans un menu. La couleur qui apparaĂźt est celle de la face sur laquelle le dĂ© sâest arrĂȘtĂ© aprĂšs avoir Ă©tĂ© pivotĂ© par le chemin tracĂ©. Les quatre directions sâapparentent Ă un calcul dâitinĂ©raire ; elles redĂ©finissent Ă©galement les faces qui seront disponibles par la suite. Câest ce couplage qui constitue le jeu.
Cinq niveaux créés par lâauteur, une limite de coups par plateau, des fonctions « annuler » et « recommencer ». Aucun Ă©lĂ©ment alĂ©atoire et aucun autre acteur.
ModĂ©lisation dâun dĂ© Ă six faces
Le dĂ© est constituĂ© de six entiers associĂ©s Ă chaque face : D, U, N, S, E, W. Les opposĂ©s sont fixes : nulânoir, rougeâbleu, jauneâblanc. Chaque niveau commence avec la mĂȘme orientation :
| Direction | Face |
|---|---|
| Bas | Null |
| Haut | Noir |
| Nord | Rouge |
| Sud | Bleu |
| Est | Jaune |
| Ouest | Blanc |
Une rotation est une permutation de ces six emplacements. Lâest et lâouest laissent le nord et le sud inchangĂ©s ; le nord et le sud laissent lâest et lâouest inchangĂ©s. Au dĂ©part, la premiĂšre face « Est » est jaune, la premiĂšre face « Sud » est bleue, la premiĂšre face « Nord » est rouge et la premiĂšre face « Ouest » est blanche. Les lancers suivants dĂ©placent ces faces.
static func roll(die: Dictionary, action: String) -> Dictionary:
var d := int(die["D"])
var u := int(die["U"])
var n := int(die["N"])
var s := int(die["S"])
var e := int(die["E"])
var w := int(die["W"])
match action:
"E":
return {"D": e, "U": w, "N": n, "S": s, "E": u, "W": d}
"W" :
return {"D" : w, "U" : e, "N" : n, "S" : s, "E" : d, "W" : u}
"N" :
return {"D" : n, "U" : s, "N" : u, "S" : d, "E" : e, "W" : w}
"S" :
return {"D" : s, "U" : n, "N" : d, "S" : u, "E" : e, "W" : w}
_ :
return {"D" : d, "U" : u, "N" : n, "S" : s, "E" : e, "W" : w}
Il sâagit dâune permutation dâĂ©tats, et non dâune simulation physique. Il nây a ni vitesse angulaire, ni rĂ©action aux collisions, ni maillage de surface. Le cube Ă lâĂ©cran est un dessin reprĂ©sentant ces six entiers une fois que les rĂšgles ont acceptĂ© la pointe. Un contrĂŽle dâinvariance garantit que les opposĂ©s restent cohĂ©rents : si D est rouge, U doit toujours ĂȘtre bleu.
AlgĂšbre des couleurs
Les taches forment un petit alphabet : vide, rouge, jaune, bleu, blanc, noir, orange, violet, vert. La face dâatterrissage Ă©crit la cellule de destination via une fonction dĂ©terministe de (tache_existante, face_dâatterrissage) :
static func mix(existing: int, face: int) -> int:
if face == EMPTY:
return existing
if face == WHITE:
return EMPTY
if face == BLACK:
return BLACK
if existing == EMPTY:
return face
if existing == face:
return existing
var low := mini(existing, face)
var high := maxi(existing, face)
if low == RED and high == YELLOW:
return ORANGE
if low == RED and high == BLUE:
renvoie VIOLET
si low == JAUNE et high == BLEU :
renvoie VERT
renvoie NOIR
Les couleurs primaires peignent les cases vides et se mĂ©langent pour former des couleurs secondaires. L'application deux fois de la mĂȘme couleur primaire n'a aucun effet. Le blanc efface tout, y compris une case scellĂ©e. Le noir scelle. Une couleur primaire appliquĂ©e sur une couleur secondaire, ou sur une case scellĂ©e, la scelle. La valeur null laisse la case inchangĂ©e â une rotation libre qui consomme tout de mĂȘme un coup.
Le mĂ©lange lâemporte sur la réécriture, car la case se souvient de la premiĂšre couleur primaire. Appliquer la seconde moitiĂ© dâune paire implique de dĂ©penser dâautres cases lors des tours qui ont mis cette face vers le bas. Le plateau est Ă la fois une toile et une boĂźte de vitesses.
Une action copie lâĂ©tat, refuse les directions illĂ©gales, effectue un lancer, mĂ©lange, puis classe la phase :
static func apply(state: Dictionary, action: String) -> Dictionary:
var current := snapshot(state)
if str(current["phase"]) != "play" or not DIRS.has(action):
return {"state": current, "events": {"accepted": false, "action": action}}
var from: Vector2i = current["pos"]
var to: Vector2i = from + DIRS[action]
if not _floor(current, to):
return {"state": current, "events": {"accepted": false, "action": action, "from": from}}
var die : Dictionary = roll(current["die"], action)
var stains : Array = current["stains"]
var at := _index(to)
var before := int(stains[at])
var after := mix(before, int(die["D"]))
stains[at] = after
current["die"] = die
current["stains"] = stains
current["pos"] = to
current["spent"] = int(current["spent"]) + 1
# la phase devient « gagnée » ou « perdue » en fonction des marques, de la limite et des conseils légaux
Les conseils hors plateau sont refusĂ©s sans dĂ©penser de coup. Une marque scellĂ©e ne constitue pas un blocage dĂ©finitif immĂ©diat : le blanc peut toujours la supprimer Ă lâintĂ©rieur de la limite, et la fonction « undo » rĂ©tablit lâorientation et la marque prĂ©cĂ©dentes.
Position Ă Orientation Ă Pigment
La recherche de chemin vĂ©rifie si une case est accessible. Le pigment vĂ©rifie si une case est accessible avec une face spĂ©cifique tournĂ©e vers le bas, aprĂšs un historique de taches spĂ©cifique. LâĂ©tat complet est le produit de :
- Position sur le 5Ă5
- de lâorientation du dĂ© (six faces, contrainte par les opposĂ©s)
- de vingt-cinq valeurs de coloration
- des coups utilisés par rapport à la limite de niveau
Deux chemins menant Ă la mĂȘme case divergent lorsque le dĂ© arrive avec une face diffĂ©rente, ou lorsquâun indice antĂ©rieur a laissĂ© le mauvais pigment sur une marque.
Le niveau 2 (Retour) concrĂ©tise ce produit. DĂ©part Ă (0, 0). La seule marque est (1, 0), verte. Limite 5. Le vert nâest pas prĂ©sent sur le dĂ© ; le jaune et le bleu doivent se rencontrer sur cette case.
| Ătape | Pointe | Terrain | Face vers le bas | Effet sur (1, 0) |
|---|---|---|---|---|
| 1 | Est | (1, 0) |
Jaune | Vide â Jaune |
| 2 | Sud | (1, 1) |
Bleu | la case non marquée devient bleue |
| 3 | Ouest | (0, 1) |
Nul | Pas de couleur |
| 4 | Nord | (0, 0) |
Jaune | La case de départ devient jaune |
| 5 | Est | (1, 0) |
Bleu | Jaune + Bleu â Vert |
Les Ă©tapes 2 Ă 4 servent principalement Ă faire pivoter le tableau. La case bleue sous la marque est une preuve fournie par le joueur, pas un objectif. Une autre ligne de longueur 5 rend Ă©galement (1, 0) verte : est, sud, est, nord, ouest. Le niveau 2 accepte les deux. Le niveau 3 ajoute une deuxiĂšme marque â la case de dĂ©part doit ĂȘtre jaune â et la deuxiĂšme ligne Ă©choue. MĂȘme case verte, mais les cases restantes ne sont pas les bonnes. La liste des marques constitue lâintĂ©gralitĂ© de la condition de victoire ; les cases non marquĂ©es peuvent prendre nâimporte quelle couleur Ă la fin.
Cinq niveaux formant une échelle de contraintes
Chaque plateau ajoute une contrainte au mĂȘme mĂ©canisme :
- PremiĂšre face â placer du jaune sur
(1, 0)en un seul coup. Pas de mĂ©lange. - Retour â mĂ©langer le vert en repassant par certaines cases. Limite de 5.
- Ce que vous laissez â du vert sur la marque et du jaune au dĂ©part. MĂȘmes cinq coups que pour le niveau 2 ; la ligne verte alternative ne respecte pas la marque supplĂ©mentaire.
- Lâautre paire â de lâorange en
(1, 1). Le rouge et le jaune doivent se rencontrer. Limite : 6. Peindre la marque une seule fois puis sâarrĂȘter ne permet pas dâobtenir de lâorange. - Blanc â
(2, 0)commence scellĂ©. Les marques exigent du jaune sur(1, 0)et que la case scellĂ©e soit vide. Le blanc doit ĂȘtre pivotĂ© sur la case scellĂ©e. Ăviter la case scellĂ©e ne permet pas de gagner.
Les limites de coups correspondent aux longueurs les plus courtes des solutions connues ; un dĂ©tour doit donc remplacer des Ă©tapes plutĂŽt que de les complĂ©ter. La victoire est vĂ©rifiĂ©e aprĂšs chaque conseil acceptĂ© : la tache de chaque marque correspond Ă sa cible. La dĂ©faite survient lorsque la limite est Ă©puisĂ©e avec une marque erronĂ©e, ou lorsquâil ne reste plus de conseil valide avec une marque erronĂ©e.
Les cas de relecture testent les tables : les opposĂ©s restent cohĂ©rents, les chemins créés atteignent la phase attendue, les marques se maintiennent en cas de victoire, les refus de coin ne consomment pas de coups, et le niveau 5 commence scellĂ©. Il sâagit dâune rĂ©gression par rapport aux lignes connues, et non dâune preuve exhaustive dâunicitĂ©.
La couleur violette figure dans la table de mĂ©lange et nâest pas utilisĂ©e par ces cinq plateaux. La rĂšgle est conservĂ©e afin que lâalphabet ne change pas si un plateau ultĂ©rieur en a besoin.
RĂšgles sĂ©parĂ©es de lâaffichage
LâexĂ©cution se divise clairement :
| ĂlĂ©ment | ResponsabilitĂ© |
|---|---|
rules.gd |
Orientation, mélange, données de niveau, victoire/défaite, vérifications de relecture |
main.gd |
EntrĂ©e, _draw, interpolation, interface utilisateur, pile dâhistorique |
main.tscn |
Un seul Control |
rules.gd est un RefCounted contenant des fonctions statiques. Il nâeffectue jamais de dessin. main.gd contient le dictionnaire actuel, enregistre un instantanĂ© en profondeur avant lâacceptation dâune nouvelle piĂšce, puis lance une interpolation. La vue nâinvente jamais la face suivante.
Les transitions sont des fonctions dĂ©terministes de lâĂ©tat et de lâaction ; la vue est une projection. Le programme nâest pas purement fonctionnel : les dictionnaires et les tableaux sont modifiĂ©s aprĂšs une copie dâinstantanĂ©, et la fonction « Annuler » utilise une pile dâhistorique explicite plutĂŽt que des donnĂ©es persistantes. Cette sĂ©paration suffit Ă garantir la testabilitĂ© de la table de permutation sans avoir recours Ă un arbre de scĂšne composĂ© de nĆuds de faces.
Pas dâECS, pas de corps physiques, pas de shaders, pas de gĂ©nĂ©rateur procĂ©dural. La fonction « Annuler » retire un Ă©lĂ©ment de lâhistorique et exĂ©cute une interpolation inverse plus courte. La fonction « RedĂ©marrer » reconstruit start_level(index) et efface la pile. Les deux fonctionnent aprĂšs une perte de donnĂ©es, ce qui permet de rĂ©cupĂ©rer une marque scellĂ©e.
Rendu de lâĂ©tat
Le plateau est constituĂ© dâune couche de pigment uniforme sur un fond sombre. Les marques sont composĂ©es dâun anneau et dâune lettre (Y, G, O, âŠ) ; lâobjectif nâest donc pas uniquement indiquĂ© par la teinte. Les cases scellĂ©es utilisent un remplissage sombre et un contour fin.
Le dĂ© est un cube pseudo-3D : six quadrilatĂšres, un biais de camĂ©ra, un algorithme de peinture en fonction de la profondeur, des glyphes sur la face supĂ©rieure. Lors dâun dĂ©placement vers le haut, les positions sont interpolĂ©es, le cube sâĂ©lĂšve selon un arc sinusoĂŻdal et une Basis pivote de k · Ï/2 autour de lâaxe de dĂ©placement. La tache de destination effectue une interpolation entre la couleur prĂ©cĂ©dente et la nouvelle avec le mĂȘme poids. Un mĂ©lange correspond Ă un changement de couleur. Le blanc clignote sur le papier, puis se pose au sol.
Sous le plateau, un compas de nuanciers montre la face infĂ©rieure et les quatre cĂŽtĂ©s â le dĂ© posĂ© Ă plat. Lâorientation est la ressource ; le dessin doit rendre la face visible lisible, sinon le mĂ©lange semble arbitraire.
Les commandes sont les flĂšches, les touches WASD, les tapotements sur les cellules voisines et un pavĂ© tactile Ă lâĂ©cran. Pendant quâune interpolation sâexĂ©cute, les pointes sont ignorĂ©es afin que lâimage ne prenne pas de retard par rapport aux rĂšgles.
Ce quâimplique le modĂšle
Position Ă orientation Ă taches : voilĂ un vaste espace dâĂ©tats issu dâun petit ensemble de rĂšgles. Les conseils nuls consomment un tour pour pivoter sans peindre. Les tableaux dĂ©terministes restent difficiles Ă parcourir Ă lâĆil nu : la ligne verte en cinq Ă©tapes est Ă©vidente une fois quâon lâa vue, mais opaque avant. Lâanimation qui nomme une rĂšgle â arc de pointe, changement de tache, boussole â fait partie de lâinterface, ce nâest pas une dĂ©coration.
La campagne est finie : cinq plateaux, le violet nâest pas utilisĂ©, pas de score. Lâobjet intĂ©ressant reste le dĂ© : piĂšce et peinture, avec lâorientation comme mĂ©moire. Tout le reste nâest quâun tableau et une interpolation.