🖍️ Darkwaar27: Pigment – Ein Würfelrätsel, das auf Drehung und Farbalgebra basiert
vom 11. Oktober 2026
Abspielen: https://darkwoodcom.itch.io/darkwaar27
Ein Würfel, eine Regel
„Pigment“ ist ein deterministisches Rätselspiel auf einem 5×5-Spielfeld. Ein Würfel belegt eine Zelle auf dem Spielfeld. Ein Klick verschiebt ihn auf eine orthogonal benachbarte Zelle und dreht ihn um neunzig Grad, sodass eine neue Seite nach unten zeigt. Diese nach unten gerichtete Seite färbt die Zielzelle ein. Sonst bewegt sich nichts.
Der Spieler wählt niemals eine Farbe aus einem Menü aus. Das Pigment, das auf dem Spielfeld landet, entspricht der Seite, auf die der Würfel während der Bewegung gedreht wurde. Die vier Richtungen ähneln der Wegfindung; sie legen zudem fest, welche Seiten später verfügbar sein werden. Diese Kopplung macht das Spiel aus.
Fünf vorgefertigte Level, ein Zuglimit pro Spielfeld, Rückgängigmachen und Neustart. Keine Zufälligkeit und kein zweiter Akteur.
Modellierung eines sechsseitigen Würfels
Der Würfel besteht aus sechs ganzen Zahlen, die den Seiten zugeordnet sind: D, U, N, S, E, W. Die Gegenüberlagen sind festgelegt: Null ↔ Schwarz, Rot ↔ Blau, Gelb ↔ Weiß. Jedes Level beginnt mit derselben Ausrichtung:
| Richtung | Seite |
|---|---|
| Unten | Null |
| Oben | Schwarz |
| Norden | Rot |
| Süden | Blau |
| Osten | Gelb |
| Westen | Weiß |
Eine Drehung ist eine Permutation dieser sechs Positionen. Bei einer Drehung nach Osten oder Westen bleiben Norden und Süden unverändert; bei einer Drehung nach Norden oder Süden bleiben Osten und Westen unverändert. Zu Beginn zeigt die erste Ost-Spitze Gelb, die erste Süd-Spitze Blau, die erste Nord-Spitze Rot und die erste West-Spitze Weiß. Spätere Würfe verschieben diese Seiten.
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}
Dies ist eine Zustandspermutation, keine Physiksimulation. Es gibt keine Winkelgeschwindigkeit, keine Kollisionsreaktion, keine Mesh-Haut. Der Würfel auf dem Bildschirm ist eine Darstellung derselben sechs ganzen Zahlen, nachdem die Regeln den Tipp bereits akzeptiert haben. Eine Invariante-Prüfung sorgt dafür, dass Gegensätze einhalten: Wenn D rot ist, muss U weiterhin blau sein.
Farbalgebra
Flecken bilden ein kleines Alphabet: leer, rot, gelb, blau, weiß, schwarz, orange, violett, grün. Die Landefläche schreibt die Zielzelle über eine deterministische Funktion von (existing_stain, landing_face):
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:
gib PURPLE zurück
wenn low == YELLOW und high == BLUE:
gib GREEN zurück
gib BLACK zurück
Primärfarben füllen leere Zellen aus und vermischen sich zu Sekundärfarben. Die doppelte Verwendung derselben Primärfarbe hat keine Auswirkung. Weiß löscht alles, einschließlich einer versiegelten Zelle. Schwarz versiegelt. Eine Primärfarbe auf einer Sekundärfarbe oder auf einer versiegelten Zelle versiegelt diese. „Null“ lässt die Zelle unverändert – eine freie Drehung, die dennoch einen Zug verbraucht.
Mischen hat Vorrang vor Überschreiben, da sich die Zelle an die erste Primärfarbe erinnert. Das Aufbringen der zweiten Hälfte eines Paares bedeutet, dass in den Zügen, in denen diese Seite nach unten gedreht wird, andere Zellen verbraucht werden. Das Spielfeld ist sowohl Leinwand als auch Getriebe.
Eine Aktion kopiert den Zustand, lehnt unzulässige Richtungen ab, würfelt, mischt und klassifiziert anschließend die 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
# Die Phase wird anhand von Markierungen, Limit und zulässigen Zügen zu „gewonnen“ oder „verloren“
Tipps außerhalb des Spielfelds werden abgelehnt, ohne dass ein Zug verbraucht wird. Eine versiegelte Markierung ist keine sofortige Sperre: Weiß kann sie innerhalb des Limits weiterhin entfernen, und mit „Undo“ werden die vorherige Ausrichtung und der vorherige Fleck wiederhergestellt.
Position × Ausrichtung × Pigment
Die Wegfindung prüft, ob eine Zelle erreichbar ist. Das Pigment prüft, ob eine Zelle mit einer bestimmten nach unten gerichteten Seite nach einer bestimmten Färbungshistorie erreichbar ist. Der vollständige Zustand ist das Produkt aus:
- Position auf dem 5×5-Raster
- Würfelausrichtung (sechs Seiten, durch gegenüberliegende Seiten eingeschränkt)
- 25 Färbungswerte
- Gegen das Level-Limit verbrauchte Züge
Zwei Wege zur selben Zelle divergieren, wenn der Würfel mit unterschiedlicher Ausrichtung ankommt oder wenn ein früherer Tipp das falsche Pigment auf einer Markierung hinterlassen hat.
Level 2 (Rückkehr) konkretisiert das Produkt. Start bei (0, 0). Die einzige Markierung ist (1, 0) grün. Limit 5. Grün ist nicht auf dem Würfel vorhanden; Gelb und Blau müssen sich auf dieser Zelle treffen.
| Schritt | Spitze | Landefläche | Abgewendete Seite | Auswirkung auf (1, 0) |
|---|---|---|---|---|
| 1 | Osten | (1, 0) |
Gelb | Leer → Gelb |
| 2 | Süden | (1, 1) |
Blau | Unmarkierte Zelle wird blau |
| 3 | Westen | (0, 1) |
Null | keine Farbe |
| 4 | Norden | (0, 0) |
Gelb | Startfeld wird gelb |
| 5 | Osten | (1, 0) |
Blau | Gelb + Blau → Grün |
Die Schritte 2–4 dienen hauptsächlich der Drehung. Die blaue Zelle unter der Markierung ist ein vom Spieler erzeugter Hinweis, kein Ziel. Eine weitere Linie der Länge 5 färbt ebenfalls (1, 0) grün: Ost, Süd, Ost, Nord, West. Level 2 akzeptiert beides. Level 3 fügt eine zweite Markierung hinzu – die Startzelle muss gelb sein – und die zweite Reihe scheitert. Gleiches Grün, falsche Reste. Die Markierungsliste ist die gesamte Gewinnbedingung; unmarkierte Zellen können beliebig enden.
Fünf Level als Leiter der Einschränkungen
Jedes Spielfeld fügt dem gleichen Mechanismus eine Einschränkung hinzu:
- Erste Seite – lande gelb auf
(1, 0)in einem Zug. Keine Vermischung. - Rückkehr – mische Grün durch erneuten Besuch. Begrenzung auf 5.
- Was du hinterlässt – Grün auf der Markierung und Gelb am Start. Dieselben fünf Züge wie in Stufe 2; die alternative grüne Linie scheitert an der zusätzlichen Markierung.
- Das andere Paar – Orange bei
(1, 1). Rot und Gelb müssen sich treffen. Limit 6. Einmaliges Einfärben der Markierung und Anhalten darf kein Orange erzeugen. - Weiß –
(2, 0)beginnt versiegelt. Markierungen erfordern Gelb auf(1, 0)und leer auf der Versiegelung. Weiß muss auf die versiegelte Zelle gedreht werden. Das Umgehen der Versiegelung führt nicht zum Sieg.
Die Zugbeschränkungen entsprechen den kürzesten bekannten Lösungslängen, daher muss ein Umweg Schritte ersetzen, anstatt sie aufzufüllen. Der Sieg wird nach jedem akzeptierten Tipp überprüft: Die Färbung jeder Markierung entspricht ihrem Ziel. Eine Niederlage liegt vor, wenn das Limit mit einer falschen Markierung erschöpft ist oder bei einer falschen Markierung keine zulässigen Tipps mehr übrig sind.
Wiederholungsfälle testen die Tabellen: Gegensätze bleiben konsistent, erstellte Pfade erreichen die erwartete Phase, Markierungen bleiben bei Siegen erhalten, Ablehnungen an Ecken verbrauchen keine Züge und Level 5 beginnt versiegelt. Dies ist eine Regression gegen bekannte Spielzüge, kein erschöpfender Eindeutigkeitsbeweis.
Lila befindet sich in der Mischungstabelle und wird von diesen fünf Spielbrettern nicht verwendet. Die Regel bleibt bestehen, damit sich das Alphabet nicht ändert, falls ein späteres Spielbrett es benötigt.
Von der Ansicht getrennte Regeln
Die Laufzeit ist klar aufgeteilt:
| Komponente | Zuständigkeit |
|---|---|
rules.gd |
Ausrichtung, Mix, Level-Daten, Sieg/Niederlage, Wiederholungsprüfungen |
main.gd |
Eingabe, _draw, Tween, UI, Verlaufsstapel |
main.tscn |
Ein Control |
rules.gd ist ein RefCounted mit statischen Funktionen. Es zeichnet niemals. main.gd enthält das aktuelle Wörterbuch, speichert vor einem akzeptierten Tipp einen tiefen Snapshot und startet dann ein Tween. Die Ansicht generiert niemals die nächste Fläche.
Übergänge sind deterministische Funktionen von Zustand und Aktion; die Ansicht ist eine Projektion. Das Programm ist nicht rein funktional: Wörterbücher und Arrays werden nach einer Snapshot-Kopie verändert, und „Undo“ ist ein expliziter Verlaufsstapel statt persistenter Daten. Diese Trennung reicht aus, um die Permutationstabelle ohne einen Szenenbaum aus Flächenknoten testbar zu halten.
Kein ECS, keine Physikkörper, keine Shader, kein prozeduraler Generator. „Undo“ entfernt einen Eintrag aus dem Verlauf und spielt ein kürzeres umgekehrtes Tween ab. „Restart“ baut start_level(index) neu auf und löscht den Stapel. Beides funktioniert auch nach einem Datenverlust, sodass eine versiegelte Markierung wiederherstellbar ist.
Darstellung des Zustands
Das Spielbrett besteht aus flachem Pigment auf einem dunklen Hintergrund. Markierungen bestehen aus einem Ring und einem Buchstaben (Y, G, O, …), sodass das Ziel nicht allein durch den Farbton vermittelt wird. Versiegelte Felder verwenden eine dunkle Füllung und einen dünnen Rand.
Der Würfel ist ein Pseudo-3D-Würfel: sechs Quadrate, eine Kameraverschiebung, ein Malalgorithmus nach Tiefe, Glyphen auf der nach oben gerichteten Fläche. Während eines Tipps werden Positionen interpoliert, der Würfel hebt sich auf einem Sinusbogen an, und eine Basis dreht sich um k · π/2 um die Tip-Achse. Die Zielfarbe überblendet mit gleicher Gewichtung von der vorherigen zur neuen Farbe. Ein Mix entspricht einem Farbwechsel. Weiß blitzt wie Papier auf und setzt sich dann auf den Boden.
Unter dem Spielbrett zeigt ein Kompass aus Farbfeldern die Unterseite und die vier Seiten – den flach liegenden Würfel. Die Ausrichtung ist die Ressource; die Zeichnung muss die Ausrichtung lesbar machen, sonst wirkt die Mischung willkürlich.
Eingaben sind Pfeile, WASD, Tippen auf benachbarte Zellen und ein Bildschirm-Pad. Während ein Tween läuft, werden Tipps ignoriert, damit das Bild nicht hinter den Regeln zurückbleibt.
Was das Modell impliziert
Position × Ausrichtung × Flecken ergeben einen großen Zustandsraum aus einem kleinen Regelsatz. Null-Tipps verbrauchen einen Zug, um sich zu drehen, ohne zu malen. Deterministische Tabellen lassen sich mit dem Auge nur schwer überblicken: Die fünfstufige grüne Linie ist offensichtlich, sobald man sie sieht, und zuvor undurchsichtig. Animationen, die eine Regel benennen – Spitzenbogen, Farbwechsel, Kompass – sind Teil der Benutzeroberfläche, keine Dekoration.
Die Kampagne ist endlich: fünf Spielbretter, Lila ungenutzt, keine Punktzahl. Das interessante Objekt ist nach wie vor der Würfel: Spielstein und Farbe, mit der Ausrichtung als Gedächtnis. Alles andere ist eine Tabelle und ein Tween.