Skip to content

Commit

Permalink
Merge pull request #3 from trincaog/devel
Browse files Browse the repository at this point in the history
Added param to specify initial cube state
  • Loading branch information
trincaog authored Jul 21, 2022
2 parents a41fb11 + 4d76d2b commit 2daae0c
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 37 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pip install magiccube
import magiccube

# 3x3x3 Cube
cube = magiccube.Cube(3)
cube = magiccube.Cube(3,"YYYYYYYYYRRRRRRRRRGGGGGGGGGOOOOOOOOOBBBBBBBBBWWWWWWWWW")
print(cube)
```
![Cube](https://trincaopub.s3.amazonaws.com/imgs/magiccube/cube3x3.png)
Expand Down
14 changes: 8 additions & 6 deletions examples/cube3x3.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import magiccube

# Create the cube
cube = magiccube.Cube(3)
# Create the cube in solved state
cube = magiccube.Cube(3,"YYYYYYYYYRRRRRRRRRGGGGGGGGGOOOOOOOOOBBBBBBBBBWWWWWWWWW")

# Print the cube
print(cube)

# Make some cube rotations
cube.rotate("R' L U D' F B' R' L")

# Print the cube
print(cube)

# Create the cube with a fixed state
cube = magiccube.Cube(3, "YYYYYYGGGGGWRRRRRROOOGGWGGWYBBOOOOOORRRYBBYBBWWBWWBWWB")

# Reset to the initial position
cube.reset()

# Scramble the cube
cube.scramble()

# Print the cube
print("Scrambled cube")
print(cube)

# Print the move history
print("History: ", cube.history())

Expand Down
19 changes: 19 additions & 0 deletions examples/cube4x4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import magiccube

# Create the cube
cube = magiccube.Cube(4,state="""
YYYYYYYYYYYYGGGG
GGGWRRRRRRRRRRRR
OOOOGGGWGGGWGGGW
YBBBOOOOOOOOOOOO
RRRRYBBBYBBBYBBB
WWWBWWWBWWWBWWWB
""")

# Print the cube
print(cube)

# Make some cube rotations
cube.rotate("U' R'")


5 changes: 3 additions & 2 deletions examples/solve_cube_3x3.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
cube = Cube(hist=False, size=3)
solver = BasicSolver(cube)

# Solve the cube 100 times
# Solve the cube N times
for _ in range(5):
# Reset & Scramble the cube
cube.reset()
Expand All @@ -23,4 +23,5 @@
# Solve the cube
actions = solver.solve()
print("Solution actions:", actions)
print()

assert cube.is_done()
75 changes: 66 additions & 9 deletions magiccube/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@
from magiccube.cube_move import CubeMove, CubeMoveType
from magiccube.cube_print import CubePrintStr

class CubeException(Exception):
pass

class Cube:
"""Rubik Cube implementation"""

__slots__=("size","_store_history","_cube_face_indexes","_cube_piece_indexes",
"_cube_piece_indexes_inv","cube","_history")

def __init__(self, size: int=3, hist=True):
def __init__(self, size: int=3, state=None, hist=True):

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

self.size = size
self._store_history = hist

# record the indexes of every cube face
self._cube_face_indexes = [
[[(0,y,z) for z in range(self.size)]
for y in reversed(range(self.size))], #L
Expand All @@ -35,6 +40,7 @@ def __init__(self, size: int=3, hist=True):
for y in reversed(range(self.size))], #F
]

# record the indexes of every cube piece
self._cube_piece_indexes = [
(x,y,z)
for z in range(self.size)
Expand All @@ -44,9 +50,12 @@ def __init__(self, size: int=3, hist=True):
]
self._cube_piece_indexes_inv={v:idx for idx,v in enumerate(self._cube_piece_indexes)}

self.reset()
if state is None:
self.reset()
else:
self.set(state)

def _is_outer_position(self,_z:int,_y:int,_x:int)->bool:
def _is_outer_position(self,_x:int,_y:int,_z:int)->bool:
"""Test if the coordinates indicate and outer cube position"""
return _x==0 or _x==self.size-1 \
or _y==0 or _y==self.size-1 \
Expand All @@ -64,6 +73,51 @@ def reset(self):
self.cube = np.array(initial_cube, dtype=np.object_)
self._history = []

def set(self, image:str):
"""Sets the cube state"""
image = image.replace(" ", "")
image = image.replace("\n", "")

if len(image) != 6*self.size*self.size:
raise CubeException("Cube state has an invalid size. Should be: " + str(6*self.size*self.size))

img = [CubeColor.create(x) for x in image]

self.reset()
for i,c in enumerate(img):
face = i // (self.size**2)
remain = i%(self.size**2)
if face ==0: #U
x=remain%self.size
y = self.size-1
z=remain//self.size
self.get_piece((x,y,z)).set_piece_color(1,c)
elif face == 5: #D
x=remain%self.size
y = 0
z=self.size-(remain//self.size)-1
self.get_piece((x,y,z)).set_piece_color(1,c)
elif face == 1: #L
x = 0
y=self.size-(remain//self.size)-1
z=remain%self.size
self.get_piece((x,y,z)).set_piece_color(0,c)
elif face == 3: #R
x = self.size-1
y=self.size-(remain//self.size)-1
z=self.size-(remain%self.size)-1
self.get_piece((x,y,z)).set_piece_color(0,c)
elif face == 4: #B
x=self.size-(remain%self.size)-1
y = self.size-(remain//self.size)-1
z=0
self.get_piece((x,y,z)).set_piece_color(2,c)
elif face == 2: #F
x=remain%self.size
y=self.size-(remain//self.size)-1
z=self.size-1
self.get_piece((x,y,z)).set_piece_color(2,c)

def scramble(self, num_steps:int=50, wide=None) -> List[CubeMove]:
"""Scramble the cube with random moves.
By default scramble only uses wide moves to cubes with size >=4."""
Expand Down Expand Up @@ -102,7 +156,7 @@ def find_piece(self, colors:str) -> Tuple[CubeCoordinates, CubePiece]:
for coord, piece in self.get_all_pieces().items():
if colors == piece.get_piece_colors_str(no_loc=True):
return coord,piece
raise Exception ("piece not found " + colors)
raise CubeException("piece not found " + colors)

def get_face(self, face:CubeFace)->List[List[CubeColor]]:
"""Get face colors in a multi-dim array"""
Expand Down Expand Up @@ -146,7 +200,8 @@ def get_all_pieces(self)->Dict[CubeCoordinates,CubePiece]:
def _move_to_index(self, move:CubeMove):
"""return the indexes affted by a given CubeMove"""

assert move.layer>=1 and move.layer<=self.size,"invalid layer " + str(move.layer)
if not(move.layer>=1 and move.layer<=self.size):
raise CubeException("invalid layer " + str(move.layer))

if move.type in (CubeMoveType.R, CubeMoveType.U, CubeMoveType.F):
if move.wide:
Expand All @@ -159,7 +214,9 @@ def _move_to_index(self, move:CubeMove):
else:
return (move.layer-1,)
elif move.type in (CubeMoveType.M, CubeMoveType.E, CubeMoveType.S):
assert self.size%2==1, "M,E,S moves not allow for even sizes"
if self.size%2 != 1:
raise CubeException("M,E,S moves not allowed for even size cubes")

return (self.size//2,)
else: # move.type in (CubeMoveType.X, CubeMoveType.Y, CubeMoveType.Z):
return tuple(range(self.size))
Expand All @@ -171,7 +228,7 @@ def _get_direction(self,move:CubeMove)->int:
elif move.type in (CubeMoveType.L,CubeMoveType.U,CubeMoveType.B,CubeMoveType.M, CubeMoveType.Y):
direction = 1
else:
raise Exception("invalid move face " + str(move.type))
raise CubeException("invalid move face " + str(move.type))

if move.is_reversed:
direction=direction*-1
Expand Down Expand Up @@ -220,7 +277,7 @@ def check_consistency(self, raise_exception = True):
face = self.get_face(face_name)
if any((x is None for x in face)):
if raise_exception:
raise Exception("cube is not consistent on face "+ str(face_name))
raise CubeException("cube is not consistent on face "+ str(face_name))
return False
return True

Expand Down
17 changes: 14 additions & 3 deletions magiccube/cube_piece.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Cube Piece implementation"""
from typing import List, Optional
import numpy as np
from magiccube.cube_base import PieceColor, CubeColor, CubeCoordinates, PieceType

Expand All @@ -7,11 +8,17 @@ class CubePiece:

__slots__ = ('_colors',)

def __init__(self, cube_size: int, position:CubeCoordinates):
#self.initial_position = position
self._colors = self._build_piece_colors(cube_size, position)
def __init__(self, cube_size: Optional[int]=None, position:Optional[CubeCoordinates]=None,
colors:Optional[List[Optional[CubeColor]]]=None):
if cube_size is not None and position is not None:
self._colors = self._build_piece_colors(cube_size, position)
elif colors is not None and len(colors)==3:
self._colors = np.array(colors)
else:
assert False, "Can't create CubePiece. Either position or color must be specified."

def _build_piece_colors(self, cube_size:int, position:CubeCoordinates) ->np.ndarray:
"""Creates the default piece colors"""
(_z,_y,_x)=position

if _x == 0:
Expand Down Expand Up @@ -62,6 +69,10 @@ def get_piece_colors(self, no_loc=False)->PieceColor:

return tuple(colors)

def set_piece_color(self, axis:int, color:CubeColor):
"""Set the piece colors"""
self._colors[axis]=color

def rotate_piece(self, axis:int) -> None:
"""Rotate the piece colors according to a given movement"""

Expand Down
38 changes: 23 additions & 15 deletions magiccube/solver/basic/basic_solver.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import List, Tuple
from magiccube.cube import Cube
from magiccube.cube import Cube, CubeException
from magiccube.optimizer.move_optimizer import MoveOptimizer
from magiccube.solver.basic.solver_base import SolverStage
from magiccube.solver.basic.solver_stages import *
Expand Down Expand Up @@ -43,11 +43,14 @@
"stage_turn_top_corners": (("YRG","YRB","YBO","YGO"), stage_turn_top_corners),
}

class SolverException(Exception):
pass

class BasicSolver:

def __init__(self, cube:Cube, init_stages=None):
assert cube.size==3, "Solver only works with 3x3x3 cube"
if cube.size!=3:
raise SolverException("Solver only works with 3x3x3 cube")
self.cube = cube
self.stages:List[SolverStage]=[]
self.default_debug =False
Expand Down Expand Up @@ -88,23 +91,28 @@ def _solve_pattern_stage(self, stage:SolverStage)-> List[str]:
# stage is complete
break

assert iteration < max_iter, f"stage iteration limit exceeded: {stage}"
if iteration >= max_iter:
raise SolverException(f"stage iteration limit exceeded: {stage}")

return full_actions

def solve(self, optimize=True):
"""Solve the cube by running all the registered pattern stages"""
full_actions=[]
for stage in self.stages:
if stage.debug:
print("starting stage",stage)
actions = self._solve_pattern_stage(stage)
full_actions += actions

#assert self.cube.is_done() , "cube not done"
if optimize:
full_actions = MoveOptimizer().optimize(full_actions)

return full_actions
try:
full_actions=[]
for stage in self.stages:
if stage.debug:
print("starting stage",stage)
actions = self._solve_pattern_stage(stage)
full_actions += actions

#assert self.cube.is_done() , "cube not done"
if optimize:
full_actions = MoveOptimizer().optimize(full_actions)

return full_actions
except CubeException as e:
raise SolverException("unable to solve cube",e)


def add(self,name, target_colors:Tuple[str,...], pattern_condition_actions:Tuple[ConditionAction, ...], debug=False):
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "magiccube"
version = "0.0.5"
version = "0.0.6"
authors = [
{ name="Gonçalo Trincão Cunha", email="goncalo.cunha@gmail.com" },
]
Expand Down
Loading

0 comments on commit 2daae0c

Please sign in to comment.