Skip to main content

A Python implementation of Kociemba's two-phase algorithm for solving the Rubik's Cube

Project description

rubik-solver-py

A pure-Python implementation of Kociemba's two-phase algorithm for solving the Rubik's Cube optimally (≤22 moves). Port of the TypeScript rubik-solver library.

Installation

pip install rubik-solver-py

Quick Start

from rubik_solver import Cube, init_solver, solve, scramble

# init_solver() pre-computes move and pruning tables (~30-60 s, one-time cost).
# Call it once at application startup before solving anything.
init_solver()

# Solve from a move sequence
cube = Cube().move("R U R' U' R' F R2 U' R' U' R U R' F'")
solution = solve(cube)
print(solution)  # e.g. "F R U R' U' F'"

# Generate a random scramble
print(scramble())

API Reference

init_solver()

Pre-computes all move and pruning tables required by the solver.

Must be called once before solve() or scramble().

Safe to call multiple times — subsequent calls are no-ops.


solve(cube, max_depth=22) → str | None

Solves the given Cube instance using Kociemba's two-phase algorithm.

Parameter Type Default Description
cube Cube The cube to solve
max_depth int 22 Maximum move count; returns Noneif exceeded

Returns a move string like "R U R' U'", or None if no solution was found.


scramble() → str

Returns a random scramble sequence as a move string.


Cube

The main cube class.

Method / Property Description
Cube() Creates a solved cube
.move(alg) Applies an algorithm string in place; returns self
.clone() Returns a deep copy
.is_solved() Returns Trueif the cube is solved
.as_string() Returns the 54-char facelet string
.randomize() Randomizes the cube in place; returns self
.verify() Returns Trueif valid, or an error string
Cube.from_string(s) Parses a 54-char facelet string into a Cube
Cube.random() Returns a new randomized Cube
Cube.inverse(alg) Inverts an algorithm string

Supported Move Notation

Faces: U R F D L B

Slice moves: E M S

Rotations: x y z

Wide moves: u r f d l b

Modifiers: ' (inverse), 2 (double)


Usage Examples

Build and verify a cube manually

from rubik_solver import Cube

# Solved cube
cube = Cube()
print(cube.is_solved())    # True

# Apply moves
cube.move("R U R' U'")
print(cube.is_solved())    # False

# Serialize to 54-char facelet string
print(cube.as_string())
# "UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB"

# Parse from a facelet string
cube2 = Cube.from_string("UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB")

# Invert an algorithm
print(Cube.inverse("R U R' U'"))  # "U R U' R'"

Solve a random cube

from rubik_solver import Cube, init_solver, solve

init_solver()
cube = Cube.random()
solution = solve(cube)
print(f"Solution ({len(solution.split())} moves): {solution}")

Validate a cube from facelet string

from rubik_solver import Cube

cube = Cube.from_string("UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB")
result = cube.verify()
if result is True:
    print("Cube is valid!")
else:
    print(f"Invalid: {result}")

Architecture

rubik_solver/
├── __init__.py        ← Public API exports
├── types.py           ← Enums (Center, Corner, Edge) and CubeState dataclass
├── constants.py       ← Table sizes, facelet mappings, base move data
├── math_utils.py      ← cnk, factorial, rotate_left, rotate_right
├── cube.py            ← Cube class (core logic, coordinates, serialization)
├── tables/
│   └── tables.py      ← Move table and pruning table generation
└── solver/
    ├── search_state.py ← SearchState used in phase 1 & 2 search
    └── solver.py       ← init_solver(), solve(), scramble(), two-phase search

Performance

Scenario Time
init_solver()— first ever call ~8 s (builds NumPy tables, writes cache)
init_solver()— subsequent calls ~15 ms (loads from ~/.cache/rubik_solver/)
solve()per cube ~0.1 – 1 s
scramble() ~0.5 – 2 s

Tables are cached automatically as .npy files. You can override the cache directory:

export RUBIK_SOLVER_CACHE_DIR=/path/to/your/cache

To force a rebuild:

from rubik_solver import clear_cache, init_solver
clear_cache()
init_solver(verbose=True)  # prints progress

Why not 10 ms like JavaScript?

V8 JIT-compiles the table construction loops. Python uses NumPy for vectorised BFS (fast) but the coordinate-computation inner loop still runs in CPython. The disk cache makes the difference negligible in practice — after the first run startup is ~15 ms regardless.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rubik_solver_py-0.1.1.tar.gz (15.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rubik_solver_py-0.1.1-py3-none-any.whl (17.5 kB view details)

Uploaded Python 3

File details

Details for the file rubik_solver_py-0.1.1.tar.gz.

File metadata

  • Download URL: rubik_solver_py-0.1.1.tar.gz
  • Upload date:
  • Size: 15.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for rubik_solver_py-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c604a3b7455095aa3bb5a6f76162f28f2c626713794fa0f43b544f02b6acebe7
MD5 a5ec4e243732fbe42a7cf83f57df74e0
BLAKE2b-256 f06848deb6786e8c92e927f7108716c163bc63582a5ca3e8a57b413517b2ba01

See more details on using hashes here.

File details

Details for the file rubik_solver_py-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for rubik_solver_py-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 461ac3c3fdf2aca63f70c3b00c36ec155f3c414d2a6cab5c0ae7b0d2cdecb1cc
MD5 7dadf2b2dac4be35566439b32d0679c1
BLAKE2b-256 c445107563e3a2d2dee6405e059a7568b76dfde92ecbb64a7fb14f16f7b0b943

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page