# -*- coding: utf-8 -*-
"""Rubix :class:`Cube` class data-structure Module
Module Description
==================
Collection of methods that define the main Rubix :class:`Cube` class data
structure and how it is interacted with by other modules.
Note:
Using `Western Color Scheme
<https://ruwix.com/the-rubiks-cube/japanese-western-color-schemes/>`_ as
default Rubix Cube coloring scheme.
Module Contents
===============
* :class:`Rubix Cube <Cube>` class that is capable of being parameterized
with a custom set of 6 unique colors (`Default Color Scheme
<https://www.schemecolor.com/rubik-cube-colors.php>`_) and can invoke
the following moves.
.. figure:: ./../../misc/cube_moves.png
:name: cube_moves
:align: center
:scale: 75%
6 cube face rotations both clock-wise and
counter-clockwise (inverse) are considered to be the standard
move-set. (`How to Solve
<https://www.rubiks.com/en-us/blog/how-to-solve-the-rubiks-cube-stage-1>`_
)
.. todo::
* Need to finish implementing the ``get_num_solved_rings``.
.. figure:: ./../../misc/flattened_cube.png
:name: flattened_cube
:align: center
:scale: 45%
At this view of the cube, each face is a 3x3 array indexed with
[0,0] in the top-left and [2,2] in the bottom right. (`Flattened
<https://rantonse.no/en/blog/2016-05-12>`_)
.. moduleauthor:: David Grethlein <djg329@drexel.edu>
"""
import os
import sys
import json
import copy
import warnings
from typing import List, Tuple, Dict
import numpy as np
from matplotlib.colors import is_color_like
[docs]class Cube(object):
"""Data structure for representing a 3x3x3 rubix-cube.
Attributes:
__colors (Dict[str,str]): Dictionary of HEX colors that define the
rendering of the :class:`Cube`'s tile coloring.
__faces (Dict[str,np.ndarray]): Dictionary of
:class:`numpy arrays <numpy.ndarray>` that define the rendering of
the :class:`Cube`'s tile configuration.
"""
#==========================================================================
# CLASS CONSTRUCTOR
#==========================================================================
[docs] def __init__(self,
colors : Dict[str,int] = None,
faces : Dict[str,np.array] = None):
""":class:`Cube` class constructor.
Args:
colors (Dict[str,str], optional): Dictionary of color HEX strings.
Default value is ``None`` which will create a cube with default
colors :attr:`DEFAULT_FACE_COLORS`.
.. code-block::
:name: init_colors_keys
:linenos:
:caption: Required ``colors`` dictionary keys.
colors = {'UP_COLOR' : ...,
'DOWN_COLOR' : ...,
'FRONT_COLOR' : ...,
'BACK_COLOR' : ...,
'LEFT_COLOR' : ...,
'RIGHT_COLOR' : ...
}
All colors passed as values must return ``True`` when examined
by :func:`matplotlib.colors.is_color_like`.
faces (Dict[str,np.array], optional): Dictionary of face names to
3x3 arrays of the the tile face values. Default value is
``None`` which will create a solved cube with default colors.
.. code-block::
:name: init_faces_keys
:linenos:
:caption: Required ``faces`` dictionary keys.
faces = {'UP_FACE' : ...,
'DOWN_FACE' : ...,
'FRONT_FACE' : ...,
'BACK_FACE' : ...,
'LEFT_FACE' : ...,
'RIGHT_FACE' : ...
}
All faces passed as value must be 3x3
:class:`numpy arrays <numpy.ndarray>` with each element
returning ``True`` when examined by
:func:`matplotlib.colors.is_color_like`.
"""
# Sets private attributes via properties
self.colors = Cube.DEFAULT_FACE_COLORS
self.faces = Cube.DEFAULT_FACES
if isinstance(colors, dict):
self.colors = colors
if isinstance(faces, dict):
self.faces = faces
#==========================================================================
# FILE I/O METHOD(s)
#==========================================================================
[docs] def to_json_safe_dict(self) -> Dict:
"""Constructs a JSON-safe dictionary for saving :class:`Cube` state to
JSON file.
Returns:
Dict: **json_dict** - JSON-safe dictionary with faces attribute dict
values converted from :class:`numpy.ndarray` to :class:`list`.
"""
if self.is_well_formed():
json_dict = {"colors" : self.colors,
"faces" : dict(zip(self.faces.keys(),\
[face.tolist() for face in self.faces.values()]))}
return json_dict
#==========================================================================
# OVERLOADED OPERATOR(s)
#==========================================================================
[docs] def __eq__(self, other) -> bool:
"""Tests if the :class:`Rubix Cube <rubix_cube.cube.Cube>` faces are
exactly identical between the two objects.
Args:
other (TYPE): Description
Returns:
bool: Description
"""
if self.is_well_formed()\
and isinstance(other , self.__class__)\
and other.is_well_formed():
for face , other_face in zip(self.faces , other.faces):
if not np.array_equal(self.faces[face],other.faces[other_face]):
return False
return True
else:
return False
[docs] def __ne__(self, other) -> bool:
"""Tests if the :class:`Rubix Cube <rubix_cube.cube.Cube>` faces are
``NOT`` exactly identical between the two objects.
Args:
other (TYPE): Description
Returns:
bool: Description
"""
return not (self == other)
[docs] def __mod__(self, other) -> bool:
"""Tests if the :class:`Rubix Cube <rubix_cube.cube.Cube>` faces are
exactly identical between the two objects after re-orientation.
Essentially are the cubes identical after rotation?
Args:
other (TYPE): Description
Returns:
bool:
"""
return self.is_equivalent_to(other)
[docs] def is_equivalent_to(self, other) -> bool:
"""
Args:
other (TYPE): Description
Returns:
bool:
"""
if self.is_well_formed()\
and isinstance(other , self.__class__)\
and other.is_well_formed():
other_test_seqs = [[Cube.rotate_roll,
Cube.rotate_roll,
Cube.rotate_roll],
[Cube.rotate_yaw,
Cube.rotate_roll,
Cube.rotate_roll,
Cube.rotate_roll],
[Cube.rotate_yaw,
Cube.rotate_yaw,
Cube.rotate_roll,
Cube.rotate_roll,
Cube.rotate_roll],
[Cube.rotate_yaw_inverse,
Cube.rotate_roll,
Cube.rotate_roll,
Cube.rotate_roll],
[Cube.rotate_pitch,
Cube.rotate_roll,
Cube.rotate_roll,
Cube.rotate_roll],
[Cube.rotate_pitch_inverse,
Cube.rotate_roll,
Cube.rotate_roll,
Cube.rotate_roll]]
# Tests if exact match
if self.__eq__(other):
return True
# Tests the 24 sequences of re-orientations for exact matches
for seq in other_test_seqs:
# Makes a local copy to manipulate
l_other = copy.deepcopy(other)
# Performs each move
for mv in seq:
mv(l_other)
# Tests for exact match
if self.__eq__(l_other):
return True
return False
#==========================================================================
# PROPERTY INTERFACE(s)
#==========================================================================
@property
def colors(self):
"""Can only be set to be a dictionary with 6 unique color string values
that all return ``True`` when examined by
:func:`matplotlib.colors.is_color_like`.
.. code-block::
:name: colors_keys
:linenos:
:caption: Required ``colors`` dictionary keys.
colors = {'UP_COLOR' : ...,
'DOWN_COLOR' : ...,
'FRONT_COLOR' : ...,
'BACK_COLOR' : ...,
'LEFT_COLOR' : ...,
'RIGHT_COLOR' : ...
}
"""
return self.__colors
@colors.setter
def colors(self, colors : Dict[str,str]):
required_keys = ['UP_COLOR',
'DOWN_COLOR',
'FRONT_COLOR',
'BACK_COLOR',
'LEFT_COLOR',
'RIGHT_COLOR']
if isinstance(colors, dict)\
and all([key in colors for key in required_keys]):
set_colors = np.array([colors[key] for key in required_keys])
if all([is_color_like(color) for color in set_colors]):
self.__colors = dict(zip(required_keys , set_colors))
@property
def faces(self) -> Dict[str,np.ndarray]:
"""Can only be set to be a dictionary of 6 strings mapped to the faces
of a Rubix Cube. Each value must be a 3x3
:class:`numpy array <numpy.ndarray>` of values all of which are valid
colors that can be found within the :attr:`colors` attribute.
.. code-block::
:name: faces_keys
:linenos:
:caption: Required ``faces`` dictionary keys.
faces = {'UP_FACE' : ...,
'DOWN_FACE' : ...,
'FRONT_FACE' : ...,
'BACK_FACE' : ...,
'LEFT_FACE' : ...,
'RIGHT_FACE' : ...
}
"""
return self.__faces
@faces.setter
def faces(self, faces : Dict[str,np.ndarray]):
required_keys = ['UP_FACE',
'DOWN_FACE',
'FRONT_FACE',
'BACK_FACE',
'LEFT_FACE',
'RIGHT_FACE']
if isinstance(faces, dict)\
and all([key in faces for key in required_keys]):
set_faces = np.array([faces[key] for key in required_keys])
if all([self.is_valid_face(face) for face in set_faces]):
self.__faces = dict(zip(required_keys , set_faces))
#==========================================================================
# QUALITY ASSURANCE METHOD(s)
#==========================================================================
[docs] def is_valid_face(self, face : np.ndarray) -> bool:
"""Checks if the provided array could be a valid face on the
currently initialized :class:`Cube`.
Args:
face (np.ndarray): Array to be tested for being valid in the
context of the current :class:`Cube`.
Returns:
``True`` if faces is 3 x 3 array of valid colors as defined by
current instance's :attr:`colors` attribute, ``False`` otherwise.
"""
if isinstance(face, np.ndarray)\
and face.shape == (3,3)\
and all([all([val in self.colors.values()
for val in row])
for row in face]):
return True
else:
return False
#==========================================================================
# SOLUTION CHECKING METHOD(s)
#==========================================================================
[docs] def is_solved_face(self, face: np.ndarray) -> bool:
"""Checks if the provided array could be a valid face on the
currently initialized :class:`Cube`.
Args:
face (np.ndarray): Array to be tested for being solved in the
context of the current :class:`Cube`.
Returns:
``True`` if faces is solved 3 x 3 array of valid colors as defined
by current instance's :attr:`colors` attribute, ``False``
otherwise.
.. code-block::
:name: is_solved_face
:linenos:
:caption: A solved ``face`` returns ``True`` when examined by
:func:`is_valid_face` and only contains 1 unique value.
return len(np.unique(face) == 1)
"""
if not self.is_valid_face(face):
return False
else:
return len(np.unique(face)) == 1
[docs] def get_num_solved_faces(self) -> int:
"""Counts the number of solved faces by examining each one using
:func:`is_solved_face` if the currently initialized :class:`Cube`
:func:`is_well_formed`, 0 otherwise.
Returns:
int: **num_faces_solved** - The number of solved faces on the
currently initialized :class:`Cube`.
"""
num_faces_solved = 0
if self.is_well_formed():
for face in self.faces.values():
if self.is_solved_face(face):
num_faces_solved += 1
return num_faces_solved
[docs] def is_solved(self) -> bool:
"""Calls :func:`get_num_solved_faces` to check if all faces of the
:class:`Cube` are solved.
Returns:
bool: Value representing if all faces
are solved completely.
.. code-block::
:name: is_solved
:linenos:
:caption: Checks to see if the number of solved faces is 6.
return (self.get_num_solved_faces() == 6)
"""
return (self.get_num_solved_faces() == 6)
[docs] def get_num_matching_adjacent_tiles_face(self, face : np.ndarray) -> int:
"""Counts the number of tiles on the current face that are adjacent
and have the same color values.
Args:
face (np.ndarray): Array to be tested for being solved in the
context of the current :class:`Cube`.
Returns:
int: **num_match_adj_tiles** - The number of tiles on the given
``face`` that are adjacent (same row XOR same column) and have
the same color valus.
"""
# Ensures dealing with valid face
if self.is_valid_face(face):
num_match_adj_tiles = 0
# Iterates over each tile in the cube
for r_idx , row in enumerate(face):
for c_idx , col in enumerate(row):
neighbors = list()
if r_idx > 0:
neighbors.append(face[r_idx - 1, c_idx]) # Tile above
if r_idx < 2:
neighbors.append(face[r_idx + 1, c_idx]) # Tile below
if c_idx > 0:
neighbors.append(face[r_idx, c_idx - 1]) # Tile left
if c_idx < 2:
neighbors.append(face[r_idx, c_idx + 1]) # Tile right
if any([face[r_idx, c_idx] == c for c in neighbors]):
num_match_adj_tiles += 1
return num_match_adj_tiles
[docs] def get_num_matching_adjacent_tiles(self) -> int:
"""Counts the number of tiles on the :class:`Cube` that are adjacent
and have the same color values. Uses successive calls to
:func:`get_num_matching_adjacent_tiles_face` for every face.
Returns:
int: **num_match_adj_tiles** - The number of tiles on the given
:class:`Cube` that are adjacent (same row XOR same column) and have
the same color valus.
"""
num_match_adj_tiles = 0
if self.is_well_formed():
for face in self.faces.values():
if self.is_valid_face(face):
num_match_adj_tiles += self.get_num_matching_adjacent_tiles_face(face)
return num_match_adj_tiles
#==========================================================================
# MOVE METHOD(s)
#==========================================================================
[docs] def move_up(self):
"""Up Move
"""
if self.is_well_formed():
self.faces['UP_FACE'] = np.rot90(self.faces['UP_FACE'],
axes=(1,0))
temp = self.faces['BACK_FACE'][0,:].copy()
self.faces['BACK_FACE'][0,:] = self.faces['LEFT_FACE'][0,:]
self.faces['LEFT_FACE'][0,:] = self.faces['FRONT_FACE'][0,:]
self.faces['FRONT_FACE'][0,:] = self.faces['RIGHT_FACE'][0,:]
self.faces['RIGHT_FACE'][0,:] = temp
[docs] def move_up_inverse(self):
"""Up Inverse Move
"""
if self.is_well_formed():
self.faces['UP_FACE'] = np.rot90(self.faces['UP_FACE'])
temp = self.faces['BACK_FACE'][0,:].copy()
self.faces['BACK_FACE'][0,:] = self.faces['RIGHT_FACE'][0,:]
self.faces['RIGHT_FACE'][0,:] = self.faces['FRONT_FACE'][0,:]
self.faces['FRONT_FACE'][0,:] = self.faces['LEFT_FACE'][0,:]
self.faces['LEFT_FACE'][0,:] = temp
[docs] def move_down(self):
"""Down Move
"""
if self.is_well_formed():
self.faces['DOWN_FACE'] = np.rot90(self.faces['DOWN_FACE'])
temp = self.faces['FRONT_FACE'][2,:].copy()
self.faces['FRONT_FACE'][2,:] = self.faces['LEFT_FACE'][2,:]
self.faces['LEFT_FACE'][2,:] = self.faces['BACK_FACE'][2,:]
self.faces['BACK_FACE'][2,:] = self.faces['RIGHT_FACE'][2,:]
self.faces['RIGHT_FACE'][2,:] = temp
[docs] def move_down_inverse(self):
"""Down Inverse Move
"""
if self.is_well_formed():
self.faces['DOWN_FACE'] = np.rot90(self.faces['DOWN_FACE'],
axes=(1,0))
temp = self.faces['FRONT_FACE'][2,:].copy()
self.faces['FRONT_FACE'][2,:] = self.faces['RIGHT_FACE'][2,:]
self.faces['RIGHT_FACE'][2,:] = self.faces['BACK_FACE'][2,:]
self.faces['BACK_FACE'][2,:] = self.faces['LEFT_FACE'][2,:]
self.faces['LEFT_FACE'][2,:] = temp
[docs] def move_front(self):
"""Front Move
"""
if self.is_well_formed():
self.faces['FRONT_FACE'] = np.rot90(self.faces['FRONT_FACE'],
axes=(1,0))
temp = self.faces['UP_FACE'][2,:].copy()
self.faces['UP_FACE'][2,:] = np.flip(self.faces['LEFT_FACE'][:,2])
self.faces['LEFT_FACE'][:,2] = self.faces['DOWN_FACE'][0,:]
self.faces['DOWN_FACE'][0,:] = np.flip(self.faces['RIGHT_FACE'][:,0])
self.faces['RIGHT_FACE'][:,0] = temp
[docs] def move_front_inverse(self):
"""Front Inverse Move
"""
if self.is_well_formed():
self.faces['FRONT_FACE'] = np.rot90(self.faces['FRONT_FACE'])
temp = self.faces['UP_FACE'][2,:].copy()
self.faces['UP_FACE'][2,:] = self.faces['RIGHT_FACE'][:,0]
self.faces['RIGHT_FACE'][:,0] = np.flip(self.faces['DOWN_FACE'][0,:])
self.faces['DOWN_FACE'][0,:] = self.faces['LEFT_FACE'][:,2]
self.faces['LEFT_FACE'][:,2] = np.flip(temp)
[docs] def move_back(self):
"""Back Move
"""
if self.is_well_formed():
self.faces['BACK_FACE'] = np.rot90(self.faces['BACK_FACE'],
axes=(1,0))
temp = self.faces['DOWN_FACE'][2,:].copy()
self.faces['DOWN_FACE'][2,:] = self.faces['LEFT_FACE'][:,0]
self.faces['LEFT_FACE'][:,0] = np.flip(self.faces['UP_FACE'][0,:])
self.faces['UP_FACE'][0,:] = self.faces['RIGHT_FACE'][:,2]
self.faces['RIGHT_FACE'][:,2] = np.flip(temp)
[docs] def move_back_inverse(self):
"""Back Inverse Move
"""
if self.is_well_formed():
self.faces['BACK_FACE'] = np.rot90(self.faces['BACK_FACE'])
temp = self.faces['DOWN_FACE'][2,:].copy()
self.faces['DOWN_FACE'][2,:] = np.flip(self.faces['RIGHT_FACE'][:,2])
self.faces['RIGHT_FACE'][:,2] = self.faces['UP_FACE'][0,:]
self.faces['UP_FACE'][0,:] = np.flip(self.faces['LEFT_FACE'][:,0])
self.faces['LEFT_FACE'][:,0] = temp
[docs] def move_left(self):
"""Left Move
"""
if self.is_well_formed():
self.faces['LEFT_FACE'] = np.rot90(self.faces['LEFT_FACE'],
axes=(1,0))
temp = self.faces['DOWN_FACE'][:,0].copy()
self.faces['DOWN_FACE'][:,0] = self.faces['FRONT_FACE'][:,0]
self.faces['FRONT_FACE'][:,0] = self.faces['UP_FACE'][:,0]
self.faces['UP_FACE'][:,0] = np.flip(self.faces['BACK_FACE'][:,2])
self.faces['BACK_FACE'][:,2] = np.flip(temp)
[docs] def move_left_inverse(self):
"""Left Inverse Move
"""
if self.is_well_formed():
self.faces['LEFT_FACE'] = np.rot90(self.faces['LEFT_FACE'])
temp = self.faces['DOWN_FACE'][:,0].copy()
self.faces['DOWN_FACE'][:,0] = np.flip(self.faces['BACK_FACE'][:,2])
self.faces['BACK_FACE'][:,2] = np.flip(self.faces['UP_FACE'][:,0])
self.faces['UP_FACE'][:,0] = self.faces['FRONT_FACE'][:,0]
self.faces['FRONT_FACE'][:,0] = temp
[docs] def move_right(self):
"""Right Move
"""
if self.is_well_formed():
self.faces['RIGHT_FACE'] = np.rot90(self.faces['RIGHT_FACE'],
axes=(1,0))
temp = self.faces['UP_FACE'][:,2].copy()
self.faces['UP_FACE'][:,2] = self.faces['FRONT_FACE'][:,2]
self.faces['FRONT_FACE'][:,2] = self.faces['DOWN_FACE'][:,2]
self.faces['DOWN_FACE'][:,2] = np.flip(self.faces['BACK_FACE'][:,0])
self.faces['BACK_FACE'][:,0] = np.flip(temp)
[docs] def move_right_inverse(self):
"""Right Inverse Move
"""
if self.is_well_formed():
self.faces['RIGHT_FACE'] = np.rot90(self.faces['RIGHT_FACE'])
temp = self.faces['UP_FACE'][:,2].copy()
self.faces['UP_FACE'][:,2] = np.flip(self.faces['BACK_FACE'][:,0])
self.faces['BACK_FACE'][:,0] = np.flip(self.faces['DOWN_FACE'][:,2])
self.faces['DOWN_FACE'][:,2] = self.faces['FRONT_FACE'][:,2]
self.faces['FRONT_FACE'][:,2] = temp
[docs] def move_middle(self):
"""Middle Slice Move
"""
if self.is_well_formed():
temp = self.faces['DOWN_FACE'][:,1].copy()
self.faces['DOWN_FACE'][:,1] = self.faces['FRONT_FACE'][:,1]
self.faces['FRONT_FACE'][:,1] = self.faces['UP_FACE'][:,1]
self.faces['UP_FACE'][:,1] = np.flip(self.faces['BACK_FACE'][:,1])
self.faces['BACK_FACE'][:,1] = np.flip(temp)
[docs] def move_middle_inverse(self):
"""Middle Slice Inverse Move
"""
if self.is_well_formed():
temp = self.faces['DOWN_FACE'][:,1].copy()
self.faces['DOWN_FACE'][:,1] = np.flip(self.faces['BACK_FACE'][:,1])
self.faces['BACK_FACE'][:,1] = np.flip(self.faces['UP_FACE'][:,1])
self.faces['UP_FACE'][:,1] = self.faces['FRONT_FACE'][:,1]
self.faces['FRONT_FACE'][:,1] = temp
[docs] def move_equator(self):
"""Equator Slice Move
"""
if self.is_well_formed():
temp = self.faces['FRONT_FACE'][1,:].copy()
self.faces['FRONT_FACE'][1,:] = self.faces['LEFT_FACE'][1,:]
self.faces['LEFT_FACE'][1,:] = self.faces['BACK_FACE'][1,:]
self.faces['BACK_FACE'][1,:] = self.faces['RIGHT_FACE'][1,:]
self.faces['RIGHT_FACE'][1,:] = temp
[docs] def move_equator_inverse(self):
"""Equator Slice Inverse Move
"""
if self.is_well_formed():
temp = self.faces['FRONT_FACE'][1,:].copy()
self.faces['FRONT_FACE'][1,:] = self.faces['RIGHT_FACE'][1,:]
self.faces['RIGHT_FACE'][1,:] = self.faces['BACK_FACE'][1,:]
self.faces['BACK_FACE'][1,:] = self.faces['LEFT_FACE'][1,:]
self.faces['LEFT_FACE'][1,:] = temp
[docs] def move_standing(self):
"""Standing Slice Move
"""
if self.is_well_formed():
temp = self.faces['UP_FACE'][1,:].copy()
self.faces['UP_FACE'][1,:] = np.flip(self.faces['LEFT_FACE'][:,1])
self.faces['LEFT_FACE'][:,1] = self.faces['DOWN_FACE'][1,:]
self.faces['DOWN_FACE'][1,:] = np.flip(self.faces['RIGHT_FACE'][:,1])
self.faces['RIGHT_FACE'][:,1] = temp
[docs] def move_standing_inverse(self):
"""Standing Slice Inverse Move
"""
if self.is_well_formed():
temp = self.faces['UP_FACE'][1,:].copy()
self.faces['UP_FACE'][1,:] = self.faces['RIGHT_FACE'][:,1]
self.faces['RIGHT_FACE'][:,1] = np.flip(self.faces['DOWN_FACE'][1,:])
self.faces['DOWN_FACE'][1,:] = self.faces['LEFT_FACE'][:,1]
self.faces['LEFT_FACE'][:,1] = np.flip(temp)
[docs] def rotate_pitch(self):
"""Pitch Rotation
"""
if self.is_well_formed():
self.move_left_inverse()
self.move_middle_inverse()
self.move_right()
[docs] def rotate_pitch_inverse(self):
"""Pitch Inverse Rotation
"""
if self.is_well_formed():
self.move_left()
self.move_middle()
self.move_right_inverse()
[docs] def rotate_roll(self):
"""Roll Rotation
"""
if self.is_well_formed():
self.move_front()
self.move_standing()
self.move_back_inverse()
[docs] def rotate_roll_inverse(self):
"""Roll Inverse Rotation
"""
if self.is_well_formed():
self.move_front_inverse()
self.move_standing_inverse()
self.move_back()
[docs] def rotate_yaw(self):
"""Yaw Rotation
"""
if self.is_well_formed():
self.move_up()
self.move_equator_inverse()
self.move_down_inverse()
[docs] def rotate_yaw_inverse(self):
"""Yaw Inverse Rotation
"""
if self.is_well_formed():
self.move_up_inverse()
self.move_equator()
self.move_down()
#==========================================================================
# CONSTANTS FOR DEFAULT CUBE-COLORS
#==========================================================================
DEFAULT_UP_COLOR = '#ffffff' # White
DEFAULT_DOWN_COLOR = '#ffd500' # Cyber Yellow
DEFAULT_FRONT_COLOR = '#009b48' # Green (Pigment)
DEFAULT_BACK_COLOR = '#0045ad' # Cobalt Blue
DEFAULT_LEFT_COLOR = '#ff5900' # Orange (Pantone)
DEFAULT_RIGHT_COLOR = '#b90000' # UE Red
DEFAULT_FACE_COLORS = {
'UP_COLOR' : DEFAULT_UP_COLOR,
'DOWN_COLOR' : DEFAULT_DOWN_COLOR,
'FRONT_COLOR' : DEFAULT_FRONT_COLOR,
'BACK_COLOR' : DEFAULT_BACK_COLOR,
'LEFT_COLOR' : DEFAULT_LEFT_COLOR,
'RIGHT_COLOR' : DEFAULT_RIGHT_COLOR
}
DEFAULT_UP_FACE = np.full((3,3), DEFAULT_UP_COLOR)
DEFAULT_DOWN_FACE = np.full((3,3), DEFAULT_DOWN_COLOR)
DEFAULT_FRONT_FACE = np.full((3,3), DEFAULT_FRONT_COLOR)
DEFAULT_BACK_FACE = np.full((3,3), DEFAULT_BACK_COLOR)
DEFAULT_LEFT_FACE = np.full((3,3), DEFAULT_LEFT_COLOR)
DEFAULT_RIGHT_FACE = np.full((3,3), DEFAULT_RIGHT_COLOR)
DEFAULT_FACES = {
'UP_FACE' : DEFAULT_UP_FACE,
'DOWN_FACE' : DEFAULT_DOWN_FACE,
'FRONT_FACE' : DEFAULT_FRONT_FACE,
'BACK_FACE' : DEFAULT_BACK_FACE,
'LEFT_FACE' : DEFAULT_LEFT_FACE,
'RIGHT_FACE' : DEFAULT_RIGHT_FACE
}