mirror of
https://github.com/NousResearch/atropos.git
synced 2026-04-19 12:57:58 +00:00
Linting done
This commit is contained in:
parent
a58562447f
commit
65108d12b2
264 changed files with 606 additions and 142874 deletions
345
environments/community/selcube/rubiks_cube_logic.py
Normal file
345
environments/community/selcube/rubiks_cube_logic.py
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Rubik's Cube logic extracted from the environment for independent testing
|
||||
"""
|
||||
|
||||
# Define the face colors for visualization
|
||||
UP_COLOR = 'W' # White
|
||||
DOWN_COLOR = 'Y' # Yellow
|
||||
RIGHT_COLOR = 'R' # Red
|
||||
LEFT_COLOR = 'O' # Orange
|
||||
FRONT_COLOR = 'G' # Green
|
||||
BACK_COLOR = 'B' # Blue
|
||||
|
||||
class Cube:
|
||||
"""
|
||||
A Rubik's cube implementation with accurate move handling.
|
||||
"""
|
||||
def __init__(self):
|
||||
# Initialize a solved cube
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
"""Reset the cube to solved state"""
|
||||
# Initialize the cube as a 3D array [face][row][col]
|
||||
# Faces: 0=UP, 1=DOWN, 2=LEFT, 3=RIGHT, 4=FRONT, 5=BACK
|
||||
self.cube = [
|
||||
[[UP_COLOR for _ in range(3)] for _ in range(3)], # UP
|
||||
[[DOWN_COLOR for _ in range(3)] for _ in range(3)], # DOWN
|
||||
[[LEFT_COLOR for _ in range(3)] for _ in range(3)], # LEFT
|
||||
[[RIGHT_COLOR for _ in range(3)] for _ in range(3)], # RIGHT
|
||||
[[FRONT_COLOR for _ in range(3)] for _ in range(3)], # FRONT
|
||||
[[BACK_COLOR for _ in range(3)] for _ in range(3)] # BACK
|
||||
]
|
||||
|
||||
def is_solved(self) -> bool:
|
||||
"""Check if the cube is solved"""
|
||||
for face in self.cube:
|
||||
center_color = face[1][1] # Center color never changes
|
||||
for row in face:
|
||||
for color in row:
|
||||
if color != center_color:
|
||||
return False
|
||||
return True
|
||||
|
||||
def count_solved_cubies(self) -> float:
|
||||
"""
|
||||
Count the number of stickers in their correct position
|
||||
Returns a normalized score between 0 and 1
|
||||
"""
|
||||
# Create a solved reference cube
|
||||
reference = Cube()
|
||||
|
||||
# Count matching stickers
|
||||
total_stickers = 6 * 9 # 6 faces, 9 stickers per face
|
||||
match_count = 0
|
||||
|
||||
for face_idx in range(6):
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
if self.cube[face_idx][i][j] == reference.cube[face_idx][i][j]:
|
||||
match_count += 1
|
||||
|
||||
return match_count / total_stickers
|
||||
|
||||
def rotate(self, move: str):
|
||||
"""
|
||||
Perform a move on the cube using standard notation
|
||||
U, D, L, R, F, B are clockwise rotations of respective faces
|
||||
U', D', L', R', F', B' are counterclockwise rotations
|
||||
U2, D2, L2, R2, F2, B2 are double (180°) rotations
|
||||
"""
|
||||
# Map move notation to face index and rotation count
|
||||
face_map = {
|
||||
'U': 0, 'D': 1, 'L': 2, 'R': 3, 'F': 4, 'B': 5
|
||||
}
|
||||
|
||||
# Parse the move
|
||||
if len(move) == 0:
|
||||
raise ValueError("Empty move")
|
||||
|
||||
face = move[0]
|
||||
if face not in face_map:
|
||||
raise ValueError(f"Invalid face: {face}")
|
||||
|
||||
face_idx = face_map[face]
|
||||
|
||||
# Handle rotation direction
|
||||
if len(move) == 1:
|
||||
# Clockwise rotation
|
||||
count = 1
|
||||
elif len(move) == 2:
|
||||
if move[1] == "'":
|
||||
# Counterclockwise rotation
|
||||
count = 3
|
||||
elif move[1] == "2":
|
||||
# Double rotation
|
||||
count = 2
|
||||
else:
|
||||
raise ValueError(f"Invalid move modifier: {move[1]}")
|
||||
else:
|
||||
raise ValueError(f"Invalid move format: {move}")
|
||||
|
||||
# Apply the rotation 'count' times
|
||||
for _ in range(count):
|
||||
self._rotate_face_clockwise(face_idx)
|
||||
self._rotate_adjacent_faces(face_idx)
|
||||
|
||||
def _rotate_face_clockwise(self, face_idx: int):
|
||||
"""Rotate a face clockwise"""
|
||||
face = self.cube[face_idx]
|
||||
new_face = [[None for _ in range(3)] for _ in range(3)]
|
||||
|
||||
# Copy with 90-degree clockwise rotation
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
new_face[j][2-i] = face[i][j]
|
||||
|
||||
self.cube[face_idx] = new_face
|
||||
|
||||
def _rotate_adjacent_faces(self, face_idx: int):
|
||||
"""Rotate the appropriate edges on adjacent faces"""
|
||||
if face_idx == 0: # UP face
|
||||
# Rotate the top edges of FRONT, RIGHT, BACK, LEFT
|
||||
temp = self.cube[4][0][:] # Save FRONT top edge
|
||||
self.cube[4][0] = self.cube[2][0][:] # FRONT <- LEFT
|
||||
self.cube[2][0] = self.cube[5][0][:] # LEFT <- BACK
|
||||
self.cube[5][0] = self.cube[3][0][:] # BACK <- RIGHT
|
||||
self.cube[3][0] = temp # RIGHT <- FRONT
|
||||
|
||||
elif face_idx == 1: # DOWN face
|
||||
# Rotate the bottom edges of FRONT, LEFT, BACK, RIGHT
|
||||
temp = self.cube[4][2][:] # Save FRONT bottom edge
|
||||
self.cube[4][2] = self.cube[3][2][:] # FRONT <- RIGHT
|
||||
self.cube[3][2] = self.cube[5][2][:] # RIGHT <- BACK
|
||||
self.cube[5][2] = self.cube[2][2][:] # BACK <- LEFT
|
||||
self.cube[2][2] = temp # LEFT <- FRONT
|
||||
|
||||
elif face_idx == 2: # LEFT face
|
||||
# Rotate the left edges of UP, FRONT, DOWN, BACK
|
||||
# Need to extract and set columns, not rows
|
||||
temp = [self.cube[0][i][0] for i in range(3)] # Save UP left column
|
||||
|
||||
# UP left <- BACK right (reversed)
|
||||
for i in range(3):
|
||||
self.cube[0][i][0] = self.cube[5][2-i][2]
|
||||
|
||||
# BACK right <- DOWN left (reversed)
|
||||
for i in range(3):
|
||||
self.cube[5][i][2] = self.cube[1][2-i][0]
|
||||
|
||||
# DOWN left <- FRONT left
|
||||
for i in range(3):
|
||||
self.cube[1][i][0] = self.cube[4][i][0]
|
||||
|
||||
# FRONT left <- UP left
|
||||
for i in range(3):
|
||||
self.cube[4][i][0] = temp[i]
|
||||
|
||||
elif face_idx == 3: # RIGHT face
|
||||
# Rotate the right edges of UP, BACK, DOWN, FRONT
|
||||
temp = [self.cube[0][i][2] for i in range(3)] # Save UP right column
|
||||
|
||||
# UP right <- FRONT right
|
||||
for i in range(3):
|
||||
self.cube[0][i][2] = self.cube[4][i][2]
|
||||
|
||||
# FRONT right <- DOWN right
|
||||
for i in range(3):
|
||||
self.cube[4][i][2] = self.cube[1][i][2]
|
||||
|
||||
# DOWN right <- BACK left (reversed)
|
||||
for i in range(3):
|
||||
self.cube[1][i][2] = self.cube[5][2-i][0]
|
||||
|
||||
# BACK left <- UP right (reversed)
|
||||
for i in range(3):
|
||||
self.cube[5][i][0] = temp[2-i]
|
||||
|
||||
elif face_idx == 4: # FRONT face
|
||||
# Rotate the edges of UP bottom, RIGHT left, DOWN top, LEFT right
|
||||
# UP bottom row
|
||||
temp = self.cube[0][2][:]
|
||||
|
||||
# UP bottom <- LEFT right (rotated)
|
||||
for i in range(3):
|
||||
self.cube[0][2][i] = self.cube[2][2-i][2]
|
||||
|
||||
# LEFT right <- DOWN top (rotated)
|
||||
for i in range(3):
|
||||
self.cube[2][i][2] = self.cube[1][0][i]
|
||||
|
||||
# DOWN top <- RIGHT left (rotated)
|
||||
for i in range(3):
|
||||
self.cube[1][0][i] = self.cube[3][2-i][0]
|
||||
|
||||
# RIGHT left <- UP bottom (rotated)
|
||||
for i in range(3):
|
||||
self.cube[3][i][0] = temp[i]
|
||||
|
||||
elif face_idx == 5: # BACK face
|
||||
# Rotate the edges of UP top, LEFT left, DOWN bottom, RIGHT right
|
||||
# UP top row
|
||||
temp = self.cube[0][0][:]
|
||||
|
||||
# UP top <- RIGHT right (rotated)
|
||||
for i in range(3):
|
||||
self.cube[0][0][i] = self.cube[3][2-i][2]
|
||||
|
||||
# RIGHT right <- DOWN bottom (rotated)
|
||||
for i in range(3):
|
||||
self.cube[3][i][2] = self.cube[1][2][i]
|
||||
|
||||
# DOWN bottom <- LEFT left (rotated)
|
||||
for i in range(3):
|
||||
self.cube[1][2][i] = self.cube[2][2-i][0]
|
||||
|
||||
# LEFT left <- UP top (rotated)
|
||||
for i in range(3):
|
||||
self.cube[2][i][0] = temp[i]
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Convert cube to string representation"""
|
||||
face_names = ['U', 'D', 'L', 'R', 'F', 'B']
|
||||
result = []
|
||||
|
||||
for i, face in enumerate(self.cube):
|
||||
result.append(f"{face_names[i]}: {' '.join(face[0])}")
|
||||
result.append(f" {' '.join(face[1])}")
|
||||
result.append(f" {' '.join(face[2])}")
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
def test_basic_moves():
|
||||
"""Test basic moves and their inverses"""
|
||||
print("=== TESTING BASIC MOVES ===")
|
||||
|
||||
# Test each basic move and its inverse
|
||||
for move, inverse in [
|
||||
("R", "R'"), ("L", "L'"), ("U", "U'"),
|
||||
("D", "D'"), ("F", "F'"), ("B", "B'")
|
||||
]:
|
||||
cube = Cube()
|
||||
cube.rotate(move)
|
||||
cube.rotate(inverse)
|
||||
solved = cube.is_solved()
|
||||
|
||||
print(f"Move {move} followed by {inverse}: {'PASS' if solved else 'FAIL'}")
|
||||
|
||||
if not solved:
|
||||
print("Final cube state:")
|
||||
print(str(cube))
|
||||
|
||||
def test_double_moves():
|
||||
"""Test double (180°) moves"""
|
||||
print("\n=== TESTING DOUBLE MOVES ===")
|
||||
|
||||
# Test each double move applied twice
|
||||
for move in ["U2", "D2", "L2", "R2", "F2", "B2"]:
|
||||
cube = Cube()
|
||||
cube.rotate(move)
|
||||
cube.rotate(move)
|
||||
solved = cube.is_solved()
|
||||
|
||||
print(f"Double move {move} applied twice: {'PASS' if solved else 'FAIL'}")
|
||||
|
||||
if not solved:
|
||||
print("Final cube state:")
|
||||
print(str(cube))
|
||||
|
||||
def test_complex_algorithms():
|
||||
"""Test more complex algorithms"""
|
||||
print("\n=== TESTING COMPLEX ALGORITHMS ===")
|
||||
|
||||
# Test algorithms
|
||||
algorithms = [
|
||||
{
|
||||
"name": "Sexy Move (R U R' U') × 6",
|
||||
"moves": ["R", "U", "R'", "U'"] * 6,
|
||||
"should_solve": True
|
||||
},
|
||||
{
|
||||
"name": "Scramble + Inverse",
|
||||
"moves": ["R", "U", "F'", "L", "D2"] + ["D2", "L'", "F", "U'", "R'"],
|
||||
"should_solve": True
|
||||
},
|
||||
{
|
||||
"name": "Sune Algorithm (R U R' U R U2 R')",
|
||||
"moves": ["R", "U", "R'", "U", "R", "U2", "R'"],
|
||||
"should_solve": False
|
||||
}
|
||||
]
|
||||
|
||||
for algo in algorithms:
|
||||
cube = Cube()
|
||||
print(f"\nTesting: {algo['name']}")
|
||||
|
||||
# Apply moves
|
||||
for move in algo["moves"]:
|
||||
cube.rotate(move)
|
||||
|
||||
# Check result
|
||||
is_solved = cube.is_solved()
|
||||
expected = algo["should_solve"]
|
||||
|
||||
if is_solved == expected:
|
||||
print(f"Result: PASS (Expected {'solved' if expected else 'not solved'}, Got {'solved' if is_solved else 'not solved'})")
|
||||
else:
|
||||
print(f"Result: FAIL (Expected {'solved' if expected else 'not solved'}, Got {'solved' if is_solved else 'not solved'})")
|
||||
print("Final cube state:")
|
||||
print(str(cube))
|
||||
|
||||
# Show progress percentage if not solved
|
||||
if not is_solved:
|
||||
progress = cube.count_solved_cubies()
|
||||
print(f"Progress toward solution: {progress:.2f}")
|
||||
|
||||
def test_scramble_and_count():
|
||||
"""Test scrambling and counting progress"""
|
||||
print("\n=== TESTING SCRAMBLING AND PROGRESS TRACKING ===")
|
||||
|
||||
# Create a cube and apply random-like scramble
|
||||
cube = Cube()
|
||||
print("Solved cube:")
|
||||
print(str(cube))
|
||||
print(f"Is solved: {cube.is_solved()}")
|
||||
print(f"Progress: {cube.count_solved_cubies():.2f}")
|
||||
|
||||
# Apply a sequence of moves to scramble
|
||||
scramble = ["R", "U", "F", "D", "L", "B'", "R'", "U2"]
|
||||
|
||||
print(f"\nApplying scramble: {' '.join(scramble)}")
|
||||
for move in scramble:
|
||||
cube.rotate(move)
|
||||
|
||||
print("Scrambled cube:")
|
||||
print(str(cube))
|
||||
print(f"Is solved: {cube.is_solved()}")
|
||||
print(f"Progress: {cube.count_solved_cubies():.2f}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_basic_moves()
|
||||
test_double_moves()
|
||||
test_complex_algorithms()
|
||||
test_scramble_and_count()
|
||||
Loading…
Add table
Add a link
Reference in a new issue