Bei RandomSolve ging es mir darum, einmal ganz bewusst einen eigenen Weg zu gehen. Statt nach bestehenden Lösungen zu suchen oder mir anzuschauen, wie andere das Problem angehen, wollte ich herausfinden, wie man das Ganze selbst denken und umsetzen kann – von der Würfelgeometrie bis zur Programmstruktur. Keine fertigen (Cube)-Bibliotheken, keine Tutorials – einfach nur die Frage: Wie könnte ein Würfelprogramm funktionieren, das sich Schritt für Schritt einer Lösung nähert?
Der Ansatz: Zufällig generierte Zugfolgen, kombiniert mit einer einfachen Bewertungsstrategie – verbessert sich der Würfelzustand (mehr korrekt platzierte Steine), wird die neue Position übernommen. Wenn nicht, geht’s weiter mit neuen Versuchen. Durch Hashing vermeidet das Skript doppelte Zustände, über mehrere CPU-Kerne werden Kandidaten parallel ausprobiert. Parameter, Startzustand und Move-Mappings kommen aus externen Dateien, was den Ablauf flexibel und nachvollziehbar macht.
Natürlich habe ich beim Coden selbst hin und wieder Unterstützung genutzt – insbesondere bei technischen Details oder bei strukturellen Fragen. Aber die Grundidee und der Algorithmus sind in einem eigenen Denkprozess entstanden, ganz ohne Blick nach außen.
RandomSolve ist kein High-End-Solver, sondern ein kleines Experiment in Eigenregie – mit dem Ziel, ein komplexes Problem auf eigene Weise anzugehen und dabei ein tieferes Verständnis für die Struktur und Dynamik des Cubes zu gewinnen.
Dieses Python-Skript dient der automatisierten Suche nach Zugfolgen zur Verbesserung einer gegebenen Rubik’s-Cube-Konfiguration – konkret: zur schrittweisen Optimierung korrekt platzierter Steine auf verschiedenen Schwierigkeitsstufen (Level E1, E2, E3). Es handelt sich nicht um einen klassischen Cube-Solver, sondern um ein exploratives Optimierungstool mit lernfähigem Heuristik-System.
Das Skript lädt zu Beginn Konfigurationsparameter aus einer CSV-Datei, wählt zufällig ein Parameter-Set und verarbeitet dann in mehreren „Runs“ die Ausgangsstellung eines Würfels aus einer zweiten CSV-Datei. Dabei werden parallelisierte Suchvorgänge durchgeführt, um über zufällige, gezielt gesteuerte Move-Sequenzen Zustände mit einer höheren Anzahl korrekt platzierter Steine zu finden.
Die wichtigsten Bestandteile im Überblick:
Die Datei parameter_csv-Parameter.csv liefert für jeden Run ein zufälliges Set von Parametern (z. B. maximale Iterationen, Zielanzahl korrekt platzierter Steine, erlaubte Zuglängen etc.). Die Ausgangsposition wird aus csv_export-StartPos.csv geladen.
Die Bewegungstransformationen (Mapping von Positionen bei Zügen) kommen aus mappings.json.
Es gibt drei Levels von Zielstücken (E1, E2, E3), die jeweils aus definierten Stein-Positionen und erwarteten Farbzuordnungen bestehen. Ziel ist es, möglichst viele dieser Stücke korrekt zu positionieren.
Das Skript generiert zufällige, aber durch inverse Züge und Variabilitätsparameter gesteuerte Move-Sequenzen. Parallelisierte Worker suchen unabhängig voneinander nach Verbesserungen (mehr korrekt platzierte Steine). Verbesserungen werden anhand von Hashes des Zustands erkannt und nur dann geloggt, wenn sie neu sind.
Bereits „gesehene“ Zustände werden über kanonische Hashes erkannt (Rotationen berücksichtigt). Verbesserungen werden in Improvements.csv gespeichert, inklusive Hash, Move-Sequenz, Zuwachs an korrekten Positionen etc. Ergebnisse jedes Runs landen zusätzlich in Results.csv sowie in einer Run-spezifischen Logdatei.
- Multi-Processing via concurrent.futures.ProcessPoolExecutor
- Hash-basierte Erkennung bereits gelernter Zustände
- Mehrstufiges Zielsystem (Level E1 bis E3) mit iterativer Verfeinerung
- Dynamische Anpassung der Zuglänge bei Stagnation (wenn keine Verbesserung erkannt wurde)
- Lesbare Visualisierung des Cube-Zustands im ASCII-Format
Dieses Skript war für mich ein kleiner persönlicher Meilenstein – so eine Art Hello Rubik-Moment. Es war spannend zu erleben, wie sich aus ein paar Überlegungen auf dem Papier ein funktionierender Code entwickeln lässt. Und das ganz ohne von Anfang an deterministische Algorithmen vorzugeben. Stattdessen habe ich versucht, das Problem über die Positionen der Steine zu greifen und in Python einen Weg zu finden, mit dem sich Züge systematisch anwenden und bewerten lassen.
Im Zentrum stand die Idee, nicht blind durchzuprobieren, sondern ein Gefühl für die Struktur der Lösung zu entwickeln – wie man durch geeignete Kombinationen von Zügen dem Zielzustand näherkommen kann. Dabei habe ich keinen fixen Lösungsweg implementiert, sondern eher einen Ansatz verfolgt, bei dem sich im Laufe der Zeit ein Algorithmus herauskristallisiert hat.
Das Skript lief über viele Stunden. Irgendwann hatte ich das Gefühl, dass ich der Lösung langsam näherkomme. Ich habe dann begonnen, Wahrscheinlichkeiten abzuschätzen – wie oft ein bestimmter Zustand auftritt, wie weit einzelne Konfigurationen noch vom Ziel entfernt sind, und ob sich daraus etwas ableiten lässt.
Und dann war plötzlich eine vollständige Lösung dabei. Kurz darauf noch eine. An dem Punkt war für mich klar: Das Skript erfüllt seinen Zweck. Mehr musste es erstmal gar nicht leisten.
99 Züge in meiner Notation – nach HTM wären das schlanke 83. Noch kein God’s Number, aber wir nähern uns dem heiligen Gral.
- Skript_01: RandomSolve.py
- Parameterdatei: parameter_csv-Parameter.csv
- Mappingdatei der Züge: mappings.json
- Importdatei mit Startposition: csv_export-StartPos.csv
- Skript zur Erstellung Startposition: CreateStartPosition.py
- Testskript zur Verifikation der Lösung: TestMoves.py