
Rubik's Cube Algorithm Development Framework
A modular Python framework for developing, testing, and benchmarking Rubik's Cube solving algorithms with automatic algorithm discovery, standard cube notation, state tracking, debugging utilities, and a plugin based architecture.
Table of Contents
RCube: Building a Framework for Rubik's Cube Solving Algorithms
Writing a Rubik's Cube solving algorithm and building the infrastructure to test one are two completely different problems, and most people end up spending more time on the second one than the first. Before you can even think about implementing something like CFOP or Roux, you need a working cube data structure, move validation, a way to check if the cube is actually solved, and some way to see what your algorithm is doing while it runs. RCube exists to remove all of that from the equation, so the only thing left to write is the actual solving logic.
This post covers how the framework is structured, why it makes the specific tradeoffs it does, and what changed in version 2.0.0 to make it genuinely usable for real algorithm implementations rather than just row and column manipulation.
The Core Idea: Separate the Framework from the Workspace
The project structure draws a hard line between two folders that are never supposed to mix:
rcube/ Framework code, not meant to be modified
algorithms/ Your workspace, one folder per algorithmThis separation exists for a practical reason. If someone is testing three different approaches to solving the cube, maybe a basic layer by layer method, a CFOP implementation, and something experimental, none of those three should be able to break each other, and none of them should need to touch cube internals to work correctly. Keeping the framework folder off limits means every algorithm interacts with the cube through the same stable interface, regardless of how different the underlying solving strategy is.
The workspace side is intentionally minimal. Create a folder under algorithms/, add a file implementing a solve(cube) method, and the framework finds it automatically. There is no manual registration step, no configuration file listing available algorithms, and no boilerplate beyond the one method that actually matters. main.py discovers and runs everything in algorithms/ on its own, including generating the __init__.py files needed to make each folder a proper Python package.
Why Automatic Move Recording Matters More Than It Sounds
Every operation performed on the cube gets tracked automatically. This sounds like a minor convenience until you actually try to debug a solving algorithm that produces the wrong result after forty moves. Without automatic tracking, finding the exact move that broke the cube's state means adding print statements manually and rerunning the algorithm repeatedly. With it, the full move history is already there the moment something goes wrong.
This is also what makes utilities like reverseSequence() possible without extra work from whoever is writing an algorithm. Since every move is recorded in a consistent format, reversing a sequence of moves like ["R", "U", "F2"] into ["F2", "U'", "R'"] is just a transformation over already tracked data, not something each algorithm needs to implement separately.
Standard Notation as a First Class Citizen
Version 2.0.0 added support for standard cube notation, meaning moves like R, U', and F2 execute directly rather than requiring calls to lower level row and column flip methods. This matters beyond convenience. Every published cube solving method, Thistlethwaite's algorithm, CFOP, Roux, all of them are documented using standard notation, since that is the shared language the speedcubing community actually communicates in.
Before this addition, translating a documented algorithm into working code meant manually converting each notated move into the framework's row and column flip equivalents, which is exactly the kind of error prone busywork the framework is supposed to eliminate. Supporting notation directly, including sequence execution from either a list or a plain string like "R U R' U'", means an algorithm implementation can read almost identically to the notation it was copied from.
Piece Type Detection and Orientation Tracking
The bigger addition in this version is the ability to query pieces by type, edges, corners, and centers, and to check their orientation. This is the difference between a framework that supports beginner layer by layer solving and one that can actually support the algorithms serious cubers use.
Advanced methods like CFOP and Roux do not think about the cube purely in terms of face colors. They reason about edge orientation and corner permutation parity, since entire steps in those methods exist specifically to fix orientation before dealing with permutation, or vice versa. Without getEdgeOrientation(), getCornerOrientation(), and permutation parity tracking, implementing something like OLL or PLL from CFOP inside this framework would mean reimplementing that entire layer of cube state analysis from scratch, inside every single algorithm that needed it. Putting it in the framework instead means that logic gets written once and every algorithm built on top of RCube gets it for free.
Designing for Debuggability, Not Just Execution
A framework meant for iterating on algorithms needs to make failure obvious, not just successful runs pretty. RCube leans into this with a live updating execution table, colored status indicators, and detailed exception messages when something goes wrong, rather than a flat pass or fail printed at the end.
The reasoning here is straightforward. Algorithm development is mostly iteration, writing a version, watching it fail on some specific cube state, adjusting, and rerunning. If the only feedback is "solved" or "not solved," most of the actual debugging work has to happen outside the framework, in manually inserted print statements. Surfacing execution status live, along with utilities like clone() for testing against a saved cube state and isSolved() for quick validation, keeps that iteration loop inside the framework instead of pushed onto whoever is writing the algorithm.
Querying the Cube Without Reaching Into Internals
The filter() method lets you query pieces by face, row, column, or any combination of those, without needing to know how the cube is represented internally. This is a small design choice with a real payoff. An algorithm written against cube.filter(face=0, row=0, column=0) keeps working even if the internal data structure representing the cube changes in a future framework version, since the algorithm never touched that representation directly in the first place.
That is the same reasoning behind keeping rcube/ off limits to modification. The framework's internal representation is allowed to evolve because nothing outside of it depends on the specifics of how a cube is stored, only on the query and move interfaces it exposes.
Why Branch Protection and Review Matter Here
The contribution process requires branching from dev and going through review before anything merges. For a framework specifically meant to be shared infrastructure across many different algorithm implementations, this matters more than it would for a typical side project. A change to core move logic or piece detection does not just affect one algorithm, it affects every algorithm anyone has built on top of the framework so far. Requiring review before changes land in a shared foundation is a direct consequence of that framework acting as a foundation, not just as one more feature among many.
Getting Started
git clone https://github.com/sadeeshasathsara/RCube
cd RCube
python -m venv .venv
source .venv/bin/activate # or .venv\Scripts\activate on Windows
pip install -e .
python main.pyFrom there, the fastest way to understand the framework is to read one of the example algorithms in algorithms/, then create a new folder with a single solve(cube) method and watch it get picked up automatically the next time main.py runs.


