Skip to content

Commit 54491c6

Browse files
authored
Improve API docs (#35)
* API doc improvements * Updated README and type hints
1 parent 1e14ad8 commit 54491c6

11 files changed

Lines changed: 91 additions & 50 deletions

File tree

.github/workflows/publish-package.yml

Whitespace-only changes.

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ print("State:", cube.get())
5353
print("State (Kociemba):", cube.get_kociemba_facelet_colors())
5454
```
5555

56-
## Examples
56+
## Documentation and Examples
5757

58-
See [examples](https://github.com/trincaog/magiccube/tree/main/examples) folder.
58+
- [Code Samples](https://github.com/trincaog/magiccube/tree/main/examples)
59+
- [API Documentation](https://trincaog.github.io/magiccube/magiccube/cube.html)
5960

6061
## Supported Moves and Notation
6162

magiccube/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
"""MagicCube: A fast implementation of the Rubik Cube based in Python 3.x."""
22

3+
__all__ = ["cube", "cube_base", "cube_piece",
4+
"cube_move", "solver"]
5+
36
from .cube import Cube
47
from .cube_base import Color, CubeException, Face, PieceType
58
from .cube_piece import CubePiece
69
from .cube_move import CubeMove, CubeMoveType
710
from .cube_print import CubePrintStr, Terminal
11+
from .solver.basic.basic_solver import BasicSolver

magiccube/cube.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Rubik Cube implementation"""
2-
from typing import Dict, List, Tuple, Union
2+
from typing import Dict, List, Optional, Tuple, Union
33
import random
44
import numpy as np
55
from magiccube.cube_base import Color, CubeException, Face
@@ -12,14 +12,16 @@ class Cube:
1212
"""Rubik Cube implementation"""
1313

1414
__slots__ = ("size", "_store_history", "_cube_face_indexes", "_cube_piece_indexes",
15-
"_cube_piece_indexes_inv", "cube", "_history")
15+
"_cube_piece_indexes_inv", "_cube", "_history")
1616

17-
def __init__(self, size: int = 3, state=None, hist=True):
17+
def __init__(self, size: int = 3, state: Optional[str] = None, hist: Optional[bool] = True):
1818

1919
if size <= 1:
2020
raise CubeException("Cube size must be >= 2")
2121

2222
self.size = size
23+
"""Cube size"""
24+
2325
self._store_history = hist
2426

2527
# record the indexes of every cube face
@@ -68,7 +70,7 @@ def reset(self):
6870
for y in range(self.size)]
6971
for z in range(self.size)
7072
]
71-
self.cube = np.array(initial_cube, dtype=np.object_)
73+
self._cube = np.array(initial_cube, dtype=np.object_)
7274
self._history = []
7375

7476
def set(self, image: str):
@@ -127,7 +129,7 @@ def set(self, image: str):
127129
_z = self.size-1
128130
self.get_piece((_x, _y, _z)).set_piece_color(2, color)
129131

130-
def get(self, face_order=None):
132+
def get(self, face_order: Optional[List[Face]] = None):
131133
"""
132134
Get the cube state as a string with the colors of every cube face in the following order: UP, LEFT, FRONT, RIGHT, BACK, DOWN.
133135
@@ -142,15 +144,15 @@ def get(self, face_order=None):
142144
res += self.get_face_flat(face)
143145
return "".join([x.name for x in res])
144146

145-
def scramble(self, num_steps: int = 50, wide=None) -> List[CubeMove]:
147+
def scramble(self, num_steps: int = 50, wide: Optional[bool] = None) -> List[CubeMove]:
146148
"""Scramble the cube with random moves.
147149
By default scramble only uses wide moves to cubes with size >=4."""
148150

149151
movements = self.generate_random_moves(num_steps=num_steps, wide=wide)
150152
self.rotate(movements)
151153
return movements
152154

153-
def generate_random_moves(self, num_steps: int = 50, wide=None) -> List[CubeMove]:
155+
def generate_random_moves(self, num_steps: int = 50, wide: Optional[bool] = None) -> List[CubeMove]:
154156
"""Generate a list of random moves (but don't apply them).
155157
By default scramble only uses wide moves to cubes with size >=4."""
156158

@@ -187,7 +189,7 @@ def get_face(self, face: Face) -> List[List[Color]]:
187189
face_indexes = self._cube_face_indexes[face.value]
188190
res = []
189191
for line in face_indexes:
190-
line_color = [self.cube[index].get_piece_color(
192+
line_color = [self._cube[index].get_piece_color(
191193
face.get_axis()) for index in line]
192194
res.append(line_color)
193195
return res
@@ -205,15 +207,15 @@ def get_all_faces(self) -> Dict[Face, List[List[Color]]]:
205207

206208
def get_piece(self, coordinates: Coordinates) -> CubePiece:
207209
"""Get the CubePiece at a given coordinate"""
208-
return self.cube[coordinates]
210+
return self._cube[coordinates]
209211

210212
def get_all_pieces(self) -> Dict[Coordinates, CubePiece]:
211213
"""Return a dictionary of coordinates:CubePiece"""
212-
res = [self.cube[x] for x in self._cube_piece_indexes]
214+
res = [self._cube[x] for x in self._cube_piece_indexes]
213215

214216
res = {
215217
(xi, yi, zi): piece
216-
for xi, x in enumerate(self.cube)
218+
for xi, x in enumerate(self._cube)
217219
for yi, y in enumerate(x)
218220
for zi, piece in enumerate(y)
219221
if xi == 0 or xi == self.size-1
@@ -278,10 +280,10 @@ def _rotate_once(self, move: CubeMove) -> None:
278280
slice(None) if i != axis else slices for i in range(3))
279281
rotation_axes = tuple(i for i in range(3) if i != axis)
280282

281-
plane = self.cube[rotation_plane]
283+
plane = self._cube[rotation_plane]
282284
rotated_plane = np.rot90(plane, direction, axes=rotation_axes)
283-
self.cube[rotation_plane] = rotated_plane
284-
for piece in self.cube[rotation_plane].flatten():
285+
self._cube[rotation_plane] = rotated_plane
286+
for piece in self._cube[rotation_plane].flatten():
285287
if piece is not None:
286288
piece.rotate_piece(axis)
287289

@@ -365,7 +367,7 @@ def undo(self, num_moves: int = 1) -> None:
365367
self._history.pop()
366368

367369
def __repr__(self):
368-
return str(self.cube)
370+
return str(self._cube)
369371

370372
def __str__(self):
371373
printer = CubePrintStr(self)

magiccube/cube_move.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77

88
class CubeMoveType(Enum):
9+
"""Cube Move Type"""
10+
911
L = "L"
1012
R = "R"
1113
D = "D"
@@ -61,6 +63,7 @@ def get_axis(self):
6163
str(self.value)) # pragma: no cover
6264

6365
def is_cube_rotation(self):
66+
"""Return True if the movement type is a whole cube rotation on any of the X,Y,Z axis"""
6467
return self in (CubeMoveType.X, CubeMoveType.Y, CubeMoveType.Z)
6568

6669

@@ -71,16 +74,25 @@ class CubeMove():
7174

7275
__slots__ = ('type', 'is_reversed', 'wide', 'layer', 'count')
7376

74-
regex_pattern = re.compile(
77+
_regex_pattern = re.compile(
7578
"^(?:([0-9]*)(([LRDUBF])([w]?)|([XYZMES]))([']?)([0-9]?))$")
7679

7780
# pylint: disable=too-many-positional-arguments
7881
def __init__(self, move_type: CubeMoveType, is_reversed: bool = False, wide: bool = False, layer: int = 1, count: int = 1):
7982
self.type = move_type
83+
"""CubeMoveType"""
84+
8085
self.is_reversed = is_reversed
86+
"""True if the move is reversed (counter clock wise)"""
87+
8188
self.wide = wide
89+
"""True if the move is wide (2+ layers)"""
90+
8291
self.layer = layer
92+
"""Layer of the move (1-N)"""
93+
8394
self.count = count
95+
"""Number of repetitions of the move"""
8496

8597
@staticmethod
8698
def _create_move(result, special_move):
@@ -120,7 +132,7 @@ def create(move_str: str):
120132
"""Create a CubeMove from string representation"""
121133
# pylint: disable=too-many-return-statements
122134

123-
result = CubeMove.regex_pattern.match(move_str)
135+
result = CubeMove._regex_pattern.match(move_str)
124136
if result is None:
125137
raise CubeException("invalid movement " + str(move_str))
126138
result = result.groups()

magiccube/cube_print.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414

1515

1616
class Terminal(Enum):
17+
"""Type of terminal for displaying the cube"""
1718
default = 0
19+
"""default terminal - no colors"""
1820
x256 = 1
21+
"""xterm-256color - colors supported"""
1922

2023

2124
class CubePrintStr:
@@ -30,7 +33,7 @@ class CubePrintStr:
3033
}
3134

3235
def __init__(self, cube, terminal: Union[Terminal, None] = None):
33-
self.cube = cube
36+
self._cube = cube
3437
if terminal is not None:
3538
self.term = terminal
3639
else:
@@ -64,7 +67,7 @@ def _print_top_down_face(self, cube, face):
6467

6568
def print_cube(self):
6669
"Print the cube to stdout"
67-
cube = self.cube
70+
cube = self._cube
6871

6972
# flatten middle layer
7073
print_order_mid = zip(cube.get_face(Face.L), cube.get_face(Face.F),

magiccube/solver/basic/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__all__ = ["basic_solver"]

magiccube/solver/basic/basic_solver.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List, Tuple
1+
from typing import List, Optional, Tuple
22
from magiccube.cube import Cube, CubeException
33
from magiccube.optimizer.move_optimizer import MoveOptimizer
44
from magiccube.solver.basic.solver_base import SolverException, SolverStage
@@ -7,7 +7,7 @@
77
stage_order_top_corners, stage_turn_top_corners
88

99

10-
stages = {
10+
_stages = {
1111
"stage_recenter_down": (("W",), stage_recenter_down),
1212
"stage_recenter_front": (("G",), stage_recenter_front),
1313

@@ -47,55 +47,60 @@
4747

4848

4949
class BasicSolver:
50+
"""Cube Solver using the beginner's method"""
5051

51-
def __init__(self, cube: Cube, init_stages=None):
52+
def __init__(self, cube: Cube):
5253
if cube.size != 3:
5354
raise SolverException("Solver only works with 3x3x3 cube")
54-
self.cube = cube
55-
self.stages: List[SolverStage] = []
56-
self.default_debug = False
57-
self.max_iterations_per_stage = 12
55+
self._cube = cube
56+
self._stages: List[SolverStage] = []
57+
self._default_debug = False
58+
self._max_iterations_per_stage = 12
5859

60+
self._set_stages()
61+
62+
def _set_stages(self, init_stages: Optional[List[str]] = None):
63+
"""Method used for testing: Set the stages to be used for solving the cube."""
5964
if init_stages is None:
60-
for name, stage in stages.items():
61-
self.add(
62-
name=name, target_colors=stage[0], pattern_condition_actions=stage[1], debug=self.default_debug)
65+
for name, stage in _stages.items():
66+
self._add(
67+
name=name, target_colors=stage[0], pattern_condition_actions=stage[1], debug=self._default_debug)
6368
else:
6469
for init_stage in init_stages:
65-
self.add(name=init_stage, target_colors=stages[init_stage][0],
66-
pattern_condition_actions=stages[init_stage][1], debug=self.default_debug)
70+
self._add(name=init_stage, target_colors=_stages[init_stage][0],
71+
pattern_condition_actions=_stages[init_stage][1], debug=self._default_debug)
6772

6873
def _solve_pattern_stage(self, stage: SolverStage) -> List[str]:
6974
"""Solve one stage of the cube"""
7075

7176
full_actions = []
7277
iteration = 0
7378

74-
while iteration < self.max_iterations_per_stage:
79+
while iteration < self._max_iterations_per_stage:
7580
iteration += 1
76-
target_pieces = [self.cube.find_piece(
81+
target_pieces = [self._cube.find_piece(
7782
target_color) for target_color in stage.target_colors]
7883

7984
if stage.debug: # pragma:no cover
8085
print("solve_stage start:", stage.name,
8186
stage.target_colors, target_pieces)
82-
print(self.cube)
87+
print(self._cube)
8388

8489
actions, is_continue = stage.get_moves(target_pieces)
8590

86-
self.cube.rotate(actions)
91+
self._cube.rotate(actions)
8792
full_actions += actions
8893

8994
if stage.debug: # pragma:no cover
9095
print("solve_stage end:", stage.name,
9196
target_pieces, actions, is_continue)
92-
print(self.cube)
97+
print(self._cube)
9398

9499
if not is_continue:
95100
# stage is complete
96101
break
97102

98-
if iteration >= self.max_iterations_per_stage:
103+
if iteration >= self._max_iterations_per_stage:
99104
raise SolverException(f"stage iteration limit exceeded: {stage}")
100105

101106
return full_actions
@@ -104,7 +109,7 @@ def solve(self, optimize=True):
104109
"""Solve the cube by running all the registered pattern stages"""
105110
try:
106111
full_actions = []
107-
for stage in self.stages:
112+
for stage in self._stages:
108113
if stage.debug: # pragma:no cover
109114
print("starting stage", stage)
110115
actions = self._solve_pattern_stage(stage)
@@ -117,8 +122,8 @@ def solve(self, optimize=True):
117122
except CubeException as e:
118123
raise SolverException("unable to solve cube", e) from e
119124

120-
def add(self, name, target_colors: Tuple[str, ...], pattern_condition_actions: Tuple[ConditionAction, ...], debug=False):
125+
def _add(self, name, target_colors: Tuple[str, ...], pattern_condition_actions: Tuple[ConditionAction, ...], debug=False):
121126
"""Add a stage to the solver."""
122-
self.stages.append(SolverStage(
127+
self._stages.append(SolverStage(
123128
target_colors, pattern_condition_actions, name=name, debug=debug))
124129
return self

magiccube/solver/basic/solver_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
class SolverException(Exception):
9-
pass
9+
"""Exception raised when the solver fails to find a solution"""
1010

1111

1212
@dataclass

test/test_cube.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,9 @@ def test_get_cube():
424424

425425
def test_inconsistent_cube():
426426
c = Cube(3)
427-
c.cube[0, 0, 0] = CubePiece(colors=[None, None, None])
427+
428+
# pylint: disable=protected-access
429+
c._cube[0, 0, 0] = CubePiece(colors=[None, None, None])
428430
with pytest.raises(CubeException):
429431
c.check_consistency()
430432

0 commit comments

Comments
 (0)