CodeFights: Pyraminx puzzle

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
6
down vote

favorite
1












Intro



You've mastered the Rubik's Cube and got bored solving it, so now you are looking for a new challenge. One puzzle similar to the Rubik's Cube caught your attention. It's called a Pyraminx puzzle, and is a triangular pyramid-shaped puzzle. The parts are arranged in a pyramidal pattern on each side, while the layers can be rotated with respect to each vertex, and the individual tips can be rotated as well. There are 4 faces on the Pyraminx. The puzzle should be held so that one face faces you and one face faces down, as in the image below. The four corners are then labeled U (for up), R (for right), L (for left), and B (for back). The front face thus contains the U, R, and L corners.



Let's write down all possible moves for vertex U in the following notation:



  • U - 120° counterclockwise turn of topmost tip (assuming that we're looking at the pyraminx from the top, and vertex U is the topmost);

  • U' - clockwise turn for the same tip;

  • u - 120° counterclockwise turn of two upper layer;

  • u' - clockwise turn for the same layers.

enter image description here



For other vertices the moves can be described similarly



enter image description here



The first puzzle you bought wasn't assembled, so you get your professional pyraminx solver friend to assemble it. He does, and you wrote down all his moves notated as described above. Now the puzzle's faces have colors faceColors[0] (front face), faceColors[1] (bottom face), faceColors[2] (left face), faceColors[3] (right face). You want to know the initial state of the puzzle to repeat your friend's moves and see how he solved it.



Example



For face_colors = ['R', 'G', 'Y', 'O'] and moves = ["B", "b'", "u'", "R"], the output should be



pyraminxPuzzle(faceColors, moves) = [['Y', 'Y', 'Y', 'Y', 'R', 'R', 'R', 'R', 'G'],
['G', 'R', 'O', 'O', 'O', 'G', 'G', 'G', 'G'],
['Y', 'O', 'Y', 'G', 'O', 'O', 'G', 'G', 'Y'],
['R', 'O', 'O', 'R', 'O', 'Y', 'Y', 'R', 'R']]


Visual representation



enter image description hereenter image description here



Edit



As requested bij @Jan Kuijken a map for each face to the corresponding index



"""
L[2] : [ 4 | U[0] : [ 0, | R[3] : [ 8,
6, 5, 1 | 1, 2, 3, | 3, 7, 6,
8, 7, 3, 2, 0 ] | 4, 5, 6, 7, 8 ] | 0, 2, 1, 5, 4 ]
-----------------------------------------------------------------------------
B[1] : [ 8, 7, 6, 5, 4,
3, 2, 1,
0 ]
"""


Tests



import unittest

class TestPyraminx(unittest.TestCase):
def test1(self):
exp_1 = [["Y","Y","Y","Y","R","R","R","R","G"],
["G","R","O","O","O","G","G","G","G"],
["Y","O","Y","G","O","O","G","G","Y"],
["R","O","O","R","O","Y","Y","R","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["B", "b'", "u'", "R"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_1)

def test2(self):
exp_2 = [["R","R","R","R","R","R","R","R","R"],
["G","G","G","G","G","G","G","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["O","O","O","O","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_2)

def test3(self):
exp_3 = [["Y","O","R","G","G","G","G","G","G"],
["G","O","G","Y","O","O","Y","Y","Y"],
["R","G","R","R","O","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'", "u", "R", "U'", "L", "R'", "u'", "l'", "L'", "r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_3)

def test4(self):
exp_4 = [["R","R","R","G","R","R","G","G","G"],
["G","O","G","G","O","O","O","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_4)

def test5(self):
exp_5 = [["A","A","A","A","A","A","A","A","A"],
["B","B","B","B","B","B","B","B","B"],
["C","C","C","C","C","C","C","C","C"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["A", "B", "C", "D"]
moves = ["l", "l'", "r'", "r", "u", "U", "u'", "R'", "L", "R", "L'", "B'", "U'", "b", "B", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_5)

def test6(self):
exp_6 = [["E","Y","E","G","Y","Y","R","G","G"],
["Y","E","Y","R","E","E","E","R","R"],
["G","G","G","Y","R","R","E","E","E"],
["R","G","R","R","G","G","Y","Y","Y"]]
colours = ["R", "G", "Y", "E"]
moves = ["b", "l", "r", "u"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_6)

def test7(self):
exp_7 = [["E","R","R","R","L","R","R","R","U"],
["L","U","U","U","E","U","U","U","R"],
["U","L","L","L","R","L","L","L","E"],
["R","E","E","E","U","E","E","E","L"]]
colours = ["R", "U", "L", "E"]
moves = ["U", "B", "R", "L"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_7)

def test8(self):
exp_8 = [["W","W","D","A","W","W","S","A","A"],
["A","D","D","A","D","D","D","A","A"],
["S","S","S","S","S","W","A","A","S"],
["W","W","W","D","D","S","W","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["l", "r'", "U'", "u", "r'", "B", "l'", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_8)

def test9(self):
exp_9 = [["W","S","D","S","W","S","S","W","A"],
["D","W","A","A","D","A","D","W","A"],
["S","A","A","D","S","W","W","S","A"],
["W","D","D","W","S","D","A","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["B'", "R'", "L", "U'", "B", "r'", "l", "B'", "L'", "r'", "L", "U", "u'",
"U", "B'", "r", "L'", "R", "B", "r", "R'", "R", "U'", "U", "L", "r", "L",
"B'", "U", "B", "R", "R'", "R", "u'", "l", "R'", "R", "B", "R'", "U", "u",
"U", "u'", "B'", "r", "L'", "B'", "R'", "B'", "r", "R'", "r", "L", "R'",
"B", "u", "B'", "B", "L", "U", "B", "B", "L", "R", "B", "R", "u'", "R'",
"B", "u", "u'", "L'", "B", "R'", "l'", "U", "U'", "B", "r", "L'", "B", "r'",
"U", "R", "R'", "u'", "r", "R'", "u'", "r'", "L'", "R'", "r'", "U", "u'",
"B'", "U", "L'", "L'", "B"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_9)

def test10(self):
exp_10 = [["W","W","W","W","W","W","W","W","W"],
["A","A","A","A","A","A","A","A","A"],
["S","S","S","S","S","S","S","S","S"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["W", "A", "S", "D"]
moves = ["L", "L", "L", "r'", "r'", "r'", "U", "U'", "U", "U'", "b", "b'", "b'", "b"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_10)

if __name__ == '__main__':
unittest.main()


Code



def pyraminx_puzzle(face_colours, moves):
move_position = "U": [[0, 0], [3, 8], [2, 4]],
"u": [[0, 0], [0, 1], [0, 2], [0, 3], [3, 8], [3, 3], [3, 7], [3, 6], [2, 4], [2, 6], [2, 5], [2, 1]],
"L": [[2, 0], [1, 8], [0, 4]],
"l": [[2, 0], [2, 1], [2, 2], [2, 3], [1, 8], [1, 3], [1, 7], [1, 6], [0, 4], [0, 6], [0, 5], [0, 1]],
"R": [[3, 0], [0, 8], [1, 4]],
"r": [[3, 0], [3, 1], [3, 2], [3, 3], [0, 3], [0, 8], [0, 7], [0, 6], [1, 4], [1, 6], [1, 5], [1, 1]],
"B": [[1, 0], [2, 8], [3, 4]],
"b": [[1, 0], [1, 1], [1, 2], [1, 3], [2, 8], [2, 3], [2, 7], [2, 6], [3, 4], [3, 6], [3, 5], [3, 1]]

puzzle = [[c for _ in range(9)] for c in face_colours]
for move in reversed(moves):
left = "'" not in move
move_colors = move: [puzzle[r][c] for r, c in move_position[move]] for move in move_position
current_color = shift_color(move_colors[move[0]], left)
for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]

return puzzle

def shift_color(current_color, left):
i = len(current_color) // 3
if left:
return current_color[i:] + current_color[0:i]
return current_color[-i:] + current_color[:-i]






share|improve this question





















  • Nice problem, could you also provide a drawing which maps the x index of faceColours[x] to the actual positions on the faces? (for an easier understanding)
    – Jan Kuiken
    Jun 26 at 17:00











  • @JanKuiken I've edited that part in, I hope it is more clear now.
    – Ludisposed
    Jun 27 at 11:24










  • Why is the right face numbered so weirdly?
    – 200_success
    Jul 10 at 20:58










  • You mean import unittest and not import unittests, right?
    – 200_success
    Jul 10 at 21:11










  • @200_succes Thnx, was a typo edited it. The right face is weird indeed, but if you see the R as the top, and go topdown left to right it makes sense.
    – Ludisposed
    Jul 10 at 22:25
















up vote
6
down vote

favorite
1












Intro



You've mastered the Rubik's Cube and got bored solving it, so now you are looking for a new challenge. One puzzle similar to the Rubik's Cube caught your attention. It's called a Pyraminx puzzle, and is a triangular pyramid-shaped puzzle. The parts are arranged in a pyramidal pattern on each side, while the layers can be rotated with respect to each vertex, and the individual tips can be rotated as well. There are 4 faces on the Pyraminx. The puzzle should be held so that one face faces you and one face faces down, as in the image below. The four corners are then labeled U (for up), R (for right), L (for left), and B (for back). The front face thus contains the U, R, and L corners.



Let's write down all possible moves for vertex U in the following notation:



  • U - 120° counterclockwise turn of topmost tip (assuming that we're looking at the pyraminx from the top, and vertex U is the topmost);

  • U' - clockwise turn for the same tip;

  • u - 120° counterclockwise turn of two upper layer;

  • u' - clockwise turn for the same layers.

enter image description here



For other vertices the moves can be described similarly



enter image description here



The first puzzle you bought wasn't assembled, so you get your professional pyraminx solver friend to assemble it. He does, and you wrote down all his moves notated as described above. Now the puzzle's faces have colors faceColors[0] (front face), faceColors[1] (bottom face), faceColors[2] (left face), faceColors[3] (right face). You want to know the initial state of the puzzle to repeat your friend's moves and see how he solved it.



Example



For face_colors = ['R', 'G', 'Y', 'O'] and moves = ["B", "b'", "u'", "R"], the output should be



pyraminxPuzzle(faceColors, moves) = [['Y', 'Y', 'Y', 'Y', 'R', 'R', 'R', 'R', 'G'],
['G', 'R', 'O', 'O', 'O', 'G', 'G', 'G', 'G'],
['Y', 'O', 'Y', 'G', 'O', 'O', 'G', 'G', 'Y'],
['R', 'O', 'O', 'R', 'O', 'Y', 'Y', 'R', 'R']]


Visual representation



enter image description hereenter image description here



Edit



As requested bij @Jan Kuijken a map for each face to the corresponding index



"""
L[2] : [ 4 | U[0] : [ 0, | R[3] : [ 8,
6, 5, 1 | 1, 2, 3, | 3, 7, 6,
8, 7, 3, 2, 0 ] | 4, 5, 6, 7, 8 ] | 0, 2, 1, 5, 4 ]
-----------------------------------------------------------------------------
B[1] : [ 8, 7, 6, 5, 4,
3, 2, 1,
0 ]
"""


Tests



import unittest

class TestPyraminx(unittest.TestCase):
def test1(self):
exp_1 = [["Y","Y","Y","Y","R","R","R","R","G"],
["G","R","O","O","O","G","G","G","G"],
["Y","O","Y","G","O","O","G","G","Y"],
["R","O","O","R","O","Y","Y","R","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["B", "b'", "u'", "R"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_1)

def test2(self):
exp_2 = [["R","R","R","R","R","R","R","R","R"],
["G","G","G","G","G","G","G","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["O","O","O","O","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_2)

def test3(self):
exp_3 = [["Y","O","R","G","G","G","G","G","G"],
["G","O","G","Y","O","O","Y","Y","Y"],
["R","G","R","R","O","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'", "u", "R", "U'", "L", "R'", "u'", "l'", "L'", "r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_3)

def test4(self):
exp_4 = [["R","R","R","G","R","R","G","G","G"],
["G","O","G","G","O","O","O","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_4)

def test5(self):
exp_5 = [["A","A","A","A","A","A","A","A","A"],
["B","B","B","B","B","B","B","B","B"],
["C","C","C","C","C","C","C","C","C"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["A", "B", "C", "D"]
moves = ["l", "l'", "r'", "r", "u", "U", "u'", "R'", "L", "R", "L'", "B'", "U'", "b", "B", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_5)

def test6(self):
exp_6 = [["E","Y","E","G","Y","Y","R","G","G"],
["Y","E","Y","R","E","E","E","R","R"],
["G","G","G","Y","R","R","E","E","E"],
["R","G","R","R","G","G","Y","Y","Y"]]
colours = ["R", "G", "Y", "E"]
moves = ["b", "l", "r", "u"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_6)

def test7(self):
exp_7 = [["E","R","R","R","L","R","R","R","U"],
["L","U","U","U","E","U","U","U","R"],
["U","L","L","L","R","L","L","L","E"],
["R","E","E","E","U","E","E","E","L"]]
colours = ["R", "U", "L", "E"]
moves = ["U", "B", "R", "L"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_7)

def test8(self):
exp_8 = [["W","W","D","A","W","W","S","A","A"],
["A","D","D","A","D","D","D","A","A"],
["S","S","S","S","S","W","A","A","S"],
["W","W","W","D","D","S","W","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["l", "r'", "U'", "u", "r'", "B", "l'", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_8)

def test9(self):
exp_9 = [["W","S","D","S","W","S","S","W","A"],
["D","W","A","A","D","A","D","W","A"],
["S","A","A","D","S","W","W","S","A"],
["W","D","D","W","S","D","A","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["B'", "R'", "L", "U'", "B", "r'", "l", "B'", "L'", "r'", "L", "U", "u'",
"U", "B'", "r", "L'", "R", "B", "r", "R'", "R", "U'", "U", "L", "r", "L",
"B'", "U", "B", "R", "R'", "R", "u'", "l", "R'", "R", "B", "R'", "U", "u",
"U", "u'", "B'", "r", "L'", "B'", "R'", "B'", "r", "R'", "r", "L", "R'",
"B", "u", "B'", "B", "L", "U", "B", "B", "L", "R", "B", "R", "u'", "R'",
"B", "u", "u'", "L'", "B", "R'", "l'", "U", "U'", "B", "r", "L'", "B", "r'",
"U", "R", "R'", "u'", "r", "R'", "u'", "r'", "L'", "R'", "r'", "U", "u'",
"B'", "U", "L'", "L'", "B"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_9)

def test10(self):
exp_10 = [["W","W","W","W","W","W","W","W","W"],
["A","A","A","A","A","A","A","A","A"],
["S","S","S","S","S","S","S","S","S"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["W", "A", "S", "D"]
moves = ["L", "L", "L", "r'", "r'", "r'", "U", "U'", "U", "U'", "b", "b'", "b'", "b"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_10)

if __name__ == '__main__':
unittest.main()


Code



def pyraminx_puzzle(face_colours, moves):
move_position = "U": [[0, 0], [3, 8], [2, 4]],
"u": [[0, 0], [0, 1], [0, 2], [0, 3], [3, 8], [3, 3], [3, 7], [3, 6], [2, 4], [2, 6], [2, 5], [2, 1]],
"L": [[2, 0], [1, 8], [0, 4]],
"l": [[2, 0], [2, 1], [2, 2], [2, 3], [1, 8], [1, 3], [1, 7], [1, 6], [0, 4], [0, 6], [0, 5], [0, 1]],
"R": [[3, 0], [0, 8], [1, 4]],
"r": [[3, 0], [3, 1], [3, 2], [3, 3], [0, 3], [0, 8], [0, 7], [0, 6], [1, 4], [1, 6], [1, 5], [1, 1]],
"B": [[1, 0], [2, 8], [3, 4]],
"b": [[1, 0], [1, 1], [1, 2], [1, 3], [2, 8], [2, 3], [2, 7], [2, 6], [3, 4], [3, 6], [3, 5], [3, 1]]

puzzle = [[c for _ in range(9)] for c in face_colours]
for move in reversed(moves):
left = "'" not in move
move_colors = move: [puzzle[r][c] for r, c in move_position[move]] for move in move_position
current_color = shift_color(move_colors[move[0]], left)
for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]

return puzzle

def shift_color(current_color, left):
i = len(current_color) // 3
if left:
return current_color[i:] + current_color[0:i]
return current_color[-i:] + current_color[:-i]






share|improve this question





















  • Nice problem, could you also provide a drawing which maps the x index of faceColours[x] to the actual positions on the faces? (for an easier understanding)
    – Jan Kuiken
    Jun 26 at 17:00











  • @JanKuiken I've edited that part in, I hope it is more clear now.
    – Ludisposed
    Jun 27 at 11:24










  • Why is the right face numbered so weirdly?
    – 200_success
    Jul 10 at 20:58










  • You mean import unittest and not import unittests, right?
    – 200_success
    Jul 10 at 21:11










  • @200_succes Thnx, was a typo edited it. The right face is weird indeed, but if you see the R as the top, and go topdown left to right it makes sense.
    – Ludisposed
    Jul 10 at 22:25












up vote
6
down vote

favorite
1









up vote
6
down vote

favorite
1






1





Intro



You've mastered the Rubik's Cube and got bored solving it, so now you are looking for a new challenge. One puzzle similar to the Rubik's Cube caught your attention. It's called a Pyraminx puzzle, and is a triangular pyramid-shaped puzzle. The parts are arranged in a pyramidal pattern on each side, while the layers can be rotated with respect to each vertex, and the individual tips can be rotated as well. There are 4 faces on the Pyraminx. The puzzle should be held so that one face faces you and one face faces down, as in the image below. The four corners are then labeled U (for up), R (for right), L (for left), and B (for back). The front face thus contains the U, R, and L corners.



Let's write down all possible moves for vertex U in the following notation:



  • U - 120° counterclockwise turn of topmost tip (assuming that we're looking at the pyraminx from the top, and vertex U is the topmost);

  • U' - clockwise turn for the same tip;

  • u - 120° counterclockwise turn of two upper layer;

  • u' - clockwise turn for the same layers.

enter image description here



For other vertices the moves can be described similarly



enter image description here



The first puzzle you bought wasn't assembled, so you get your professional pyraminx solver friend to assemble it. He does, and you wrote down all his moves notated as described above. Now the puzzle's faces have colors faceColors[0] (front face), faceColors[1] (bottom face), faceColors[2] (left face), faceColors[3] (right face). You want to know the initial state of the puzzle to repeat your friend's moves and see how he solved it.



Example



For face_colors = ['R', 'G', 'Y', 'O'] and moves = ["B", "b'", "u'", "R"], the output should be



pyraminxPuzzle(faceColors, moves) = [['Y', 'Y', 'Y', 'Y', 'R', 'R', 'R', 'R', 'G'],
['G', 'R', 'O', 'O', 'O', 'G', 'G', 'G', 'G'],
['Y', 'O', 'Y', 'G', 'O', 'O', 'G', 'G', 'Y'],
['R', 'O', 'O', 'R', 'O', 'Y', 'Y', 'R', 'R']]


Visual representation



enter image description hereenter image description here



Edit



As requested bij @Jan Kuijken a map for each face to the corresponding index



"""
L[2] : [ 4 | U[0] : [ 0, | R[3] : [ 8,
6, 5, 1 | 1, 2, 3, | 3, 7, 6,
8, 7, 3, 2, 0 ] | 4, 5, 6, 7, 8 ] | 0, 2, 1, 5, 4 ]
-----------------------------------------------------------------------------
B[1] : [ 8, 7, 6, 5, 4,
3, 2, 1,
0 ]
"""


Tests



import unittest

class TestPyraminx(unittest.TestCase):
def test1(self):
exp_1 = [["Y","Y","Y","Y","R","R","R","R","G"],
["G","R","O","O","O","G","G","G","G"],
["Y","O","Y","G","O","O","G","G","Y"],
["R","O","O","R","O","Y","Y","R","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["B", "b'", "u'", "R"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_1)

def test2(self):
exp_2 = [["R","R","R","R","R","R","R","R","R"],
["G","G","G","G","G","G","G","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["O","O","O","O","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_2)

def test3(self):
exp_3 = [["Y","O","R","G","G","G","G","G","G"],
["G","O","G","Y","O","O","Y","Y","Y"],
["R","G","R","R","O","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'", "u", "R", "U'", "L", "R'", "u'", "l'", "L'", "r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_3)

def test4(self):
exp_4 = [["R","R","R","G","R","R","G","G","G"],
["G","O","G","G","O","O","O","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_4)

def test5(self):
exp_5 = [["A","A","A","A","A","A","A","A","A"],
["B","B","B","B","B","B","B","B","B"],
["C","C","C","C","C","C","C","C","C"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["A", "B", "C", "D"]
moves = ["l", "l'", "r'", "r", "u", "U", "u'", "R'", "L", "R", "L'", "B'", "U'", "b", "B", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_5)

def test6(self):
exp_6 = [["E","Y","E","G","Y","Y","R","G","G"],
["Y","E","Y","R","E","E","E","R","R"],
["G","G","G","Y","R","R","E","E","E"],
["R","G","R","R","G","G","Y","Y","Y"]]
colours = ["R", "G", "Y", "E"]
moves = ["b", "l", "r", "u"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_6)

def test7(self):
exp_7 = [["E","R","R","R","L","R","R","R","U"],
["L","U","U","U","E","U","U","U","R"],
["U","L","L","L","R","L","L","L","E"],
["R","E","E","E","U","E","E","E","L"]]
colours = ["R", "U", "L", "E"]
moves = ["U", "B", "R", "L"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_7)

def test8(self):
exp_8 = [["W","W","D","A","W","W","S","A","A"],
["A","D","D","A","D","D","D","A","A"],
["S","S","S","S","S","W","A","A","S"],
["W","W","W","D","D","S","W","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["l", "r'", "U'", "u", "r'", "B", "l'", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_8)

def test9(self):
exp_9 = [["W","S","D","S","W","S","S","W","A"],
["D","W","A","A","D","A","D","W","A"],
["S","A","A","D","S","W","W","S","A"],
["W","D","D","W","S","D","A","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["B'", "R'", "L", "U'", "B", "r'", "l", "B'", "L'", "r'", "L", "U", "u'",
"U", "B'", "r", "L'", "R", "B", "r", "R'", "R", "U'", "U", "L", "r", "L",
"B'", "U", "B", "R", "R'", "R", "u'", "l", "R'", "R", "B", "R'", "U", "u",
"U", "u'", "B'", "r", "L'", "B'", "R'", "B'", "r", "R'", "r", "L", "R'",
"B", "u", "B'", "B", "L", "U", "B", "B", "L", "R", "B", "R", "u'", "R'",
"B", "u", "u'", "L'", "B", "R'", "l'", "U", "U'", "B", "r", "L'", "B", "r'",
"U", "R", "R'", "u'", "r", "R'", "u'", "r'", "L'", "R'", "r'", "U", "u'",
"B'", "U", "L'", "L'", "B"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_9)

def test10(self):
exp_10 = [["W","W","W","W","W","W","W","W","W"],
["A","A","A","A","A","A","A","A","A"],
["S","S","S","S","S","S","S","S","S"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["W", "A", "S", "D"]
moves = ["L", "L", "L", "r'", "r'", "r'", "U", "U'", "U", "U'", "b", "b'", "b'", "b"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_10)

if __name__ == '__main__':
unittest.main()


Code



def pyraminx_puzzle(face_colours, moves):
move_position = "U": [[0, 0], [3, 8], [2, 4]],
"u": [[0, 0], [0, 1], [0, 2], [0, 3], [3, 8], [3, 3], [3, 7], [3, 6], [2, 4], [2, 6], [2, 5], [2, 1]],
"L": [[2, 0], [1, 8], [0, 4]],
"l": [[2, 0], [2, 1], [2, 2], [2, 3], [1, 8], [1, 3], [1, 7], [1, 6], [0, 4], [0, 6], [0, 5], [0, 1]],
"R": [[3, 0], [0, 8], [1, 4]],
"r": [[3, 0], [3, 1], [3, 2], [3, 3], [0, 3], [0, 8], [0, 7], [0, 6], [1, 4], [1, 6], [1, 5], [1, 1]],
"B": [[1, 0], [2, 8], [3, 4]],
"b": [[1, 0], [1, 1], [1, 2], [1, 3], [2, 8], [2, 3], [2, 7], [2, 6], [3, 4], [3, 6], [3, 5], [3, 1]]

puzzle = [[c for _ in range(9)] for c in face_colours]
for move in reversed(moves):
left = "'" not in move
move_colors = move: [puzzle[r][c] for r, c in move_position[move]] for move in move_position
current_color = shift_color(move_colors[move[0]], left)
for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]

return puzzle

def shift_color(current_color, left):
i = len(current_color) // 3
if left:
return current_color[i:] + current_color[0:i]
return current_color[-i:] + current_color[:-i]






share|improve this question













Intro



You've mastered the Rubik's Cube and got bored solving it, so now you are looking for a new challenge. One puzzle similar to the Rubik's Cube caught your attention. It's called a Pyraminx puzzle, and is a triangular pyramid-shaped puzzle. The parts are arranged in a pyramidal pattern on each side, while the layers can be rotated with respect to each vertex, and the individual tips can be rotated as well. There are 4 faces on the Pyraminx. The puzzle should be held so that one face faces you and one face faces down, as in the image below. The four corners are then labeled U (for up), R (for right), L (for left), and B (for back). The front face thus contains the U, R, and L corners.



Let's write down all possible moves for vertex U in the following notation:



  • U - 120° counterclockwise turn of topmost tip (assuming that we're looking at the pyraminx from the top, and vertex U is the topmost);

  • U' - clockwise turn for the same tip;

  • u - 120° counterclockwise turn of two upper layer;

  • u' - clockwise turn for the same layers.

enter image description here



For other vertices the moves can be described similarly



enter image description here



The first puzzle you bought wasn't assembled, so you get your professional pyraminx solver friend to assemble it. He does, and you wrote down all his moves notated as described above. Now the puzzle's faces have colors faceColors[0] (front face), faceColors[1] (bottom face), faceColors[2] (left face), faceColors[3] (right face). You want to know the initial state of the puzzle to repeat your friend's moves and see how he solved it.



Example



For face_colors = ['R', 'G', 'Y', 'O'] and moves = ["B", "b'", "u'", "R"], the output should be



pyraminxPuzzle(faceColors, moves) = [['Y', 'Y', 'Y', 'Y', 'R', 'R', 'R', 'R', 'G'],
['G', 'R', 'O', 'O', 'O', 'G', 'G', 'G', 'G'],
['Y', 'O', 'Y', 'G', 'O', 'O', 'G', 'G', 'Y'],
['R', 'O', 'O', 'R', 'O', 'Y', 'Y', 'R', 'R']]


Visual representation



enter image description hereenter image description here



Edit



As requested bij @Jan Kuijken a map for each face to the corresponding index



"""
L[2] : [ 4 | U[0] : [ 0, | R[3] : [ 8,
6, 5, 1 | 1, 2, 3, | 3, 7, 6,
8, 7, 3, 2, 0 ] | 4, 5, 6, 7, 8 ] | 0, 2, 1, 5, 4 ]
-----------------------------------------------------------------------------
B[1] : [ 8, 7, 6, 5, 4,
3, 2, 1,
0 ]
"""


Tests



import unittest

class TestPyraminx(unittest.TestCase):
def test1(self):
exp_1 = [["Y","Y","Y","Y","R","R","R","R","G"],
["G","R","O","O","O","G","G","G","G"],
["Y","O","Y","G","O","O","G","G","Y"],
["R","O","O","R","O","Y","Y","R","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["B", "b'", "u'", "R"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_1)

def test2(self):
exp_2 = [["R","R","R","R","R","R","R","R","R"],
["G","G","G","G","G","G","G","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["O","O","O","O","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_2)

def test3(self):
exp_3 = [["Y","O","R","G","G","G","G","G","G"],
["G","O","G","Y","O","O","Y","Y","Y"],
["R","G","R","R","O","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","R"]]
colours = ["R", "G", "Y", "O"]
moves = ["l", "l'", "u", "R", "U'", "L", "R'", "u'", "l'", "L'", "r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_3)

def test4(self):
exp_4 = [["R","R","R","G","R","R","G","G","G"],
["G","O","G","G","O","O","O","G","G"],
["Y","Y","Y","Y","Y","Y","Y","Y","Y"],
["R","R","R","R","O","O","O","O","O"]]
colours = ["R", "G", "Y", "O"]
moves = ["r"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_4)

def test5(self):
exp_5 = [["A","A","A","A","A","A","A","A","A"],
["B","B","B","B","B","B","B","B","B"],
["C","C","C","C","C","C","C","C","C"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["A", "B", "C", "D"]
moves = ["l", "l'", "r'", "r", "u", "U", "u'", "R'", "L", "R", "L'", "B'", "U'", "b", "B", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_5)

def test6(self):
exp_6 = [["E","Y","E","G","Y","Y","R","G","G"],
["Y","E","Y","R","E","E","E","R","R"],
["G","G","G","Y","R","R","E","E","E"],
["R","G","R","R","G","G","Y","Y","Y"]]
colours = ["R", "G", "Y", "E"]
moves = ["b", "l", "r", "u"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_6)

def test7(self):
exp_7 = [["E","R","R","R","L","R","R","R","U"],
["L","U","U","U","E","U","U","U","R"],
["U","L","L","L","R","L","L","L","E"],
["R","E","E","E","U","E","E","E","L"]]
colours = ["R", "U", "L", "E"]
moves = ["U", "B", "R", "L"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_7)

def test8(self):
exp_8 = [["W","W","D","A","W","W","S","A","A"],
["A","D","D","A","D","D","D","A","A"],
["S","S","S","S","S","W","A","A","S"],
["W","W","W","D","D","S","W","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["l", "r'", "U'", "u", "r'", "B", "l'", "b'"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_8)

def test9(self):
exp_9 = [["W","S","D","S","W","S","S","W","A"],
["D","W","A","A","D","A","D","W","A"],
["S","A","A","D","S","W","W","S","A"],
["W","D","D","W","S","D","A","S","D"]]
colours = ["W", "A", "S", "D"]
moves = ["B'", "R'", "L", "U'", "B", "r'", "l", "B'", "L'", "r'", "L", "U", "u'",
"U", "B'", "r", "L'", "R", "B", "r", "R'", "R", "U'", "U", "L", "r", "L",
"B'", "U", "B", "R", "R'", "R", "u'", "l", "R'", "R", "B", "R'", "U", "u",
"U", "u'", "B'", "r", "L'", "B'", "R'", "B'", "r", "R'", "r", "L", "R'",
"B", "u", "B'", "B", "L", "U", "B", "B", "L", "R", "B", "R", "u'", "R'",
"B", "u", "u'", "L'", "B", "R'", "l'", "U", "U'", "B", "r", "L'", "B", "r'",
"U", "R", "R'", "u'", "r", "R'", "u'", "r'", "L'", "R'", "r'", "U", "u'",
"B'", "U", "L'", "L'", "B"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_9)

def test10(self):
exp_10 = [["W","W","W","W","W","W","W","W","W"],
["A","A","A","A","A","A","A","A","A"],
["S","S","S","S","S","S","S","S","S"],
["D","D","D","D","D","D","D","D","D"]]
colours = ["W", "A", "S", "D"]
moves = ["L", "L", "L", "r'", "r'", "r'", "U", "U'", "U", "U'", "b", "b'", "b'", "b"]
self.assertEqual(pyraminx_puzzle(colours, moves), exp_10)

if __name__ == '__main__':
unittest.main()


Code



def pyraminx_puzzle(face_colours, moves):
move_position = "U": [[0, 0], [3, 8], [2, 4]],
"u": [[0, 0], [0, 1], [0, 2], [0, 3], [3, 8], [3, 3], [3, 7], [3, 6], [2, 4], [2, 6], [2, 5], [2, 1]],
"L": [[2, 0], [1, 8], [0, 4]],
"l": [[2, 0], [2, 1], [2, 2], [2, 3], [1, 8], [1, 3], [1, 7], [1, 6], [0, 4], [0, 6], [0, 5], [0, 1]],
"R": [[3, 0], [0, 8], [1, 4]],
"r": [[3, 0], [3, 1], [3, 2], [3, 3], [0, 3], [0, 8], [0, 7], [0, 6], [1, 4], [1, 6], [1, 5], [1, 1]],
"B": [[1, 0], [2, 8], [3, 4]],
"b": [[1, 0], [1, 1], [1, 2], [1, 3], [2, 8], [2, 3], [2, 7], [2, 6], [3, 4], [3, 6], [3, 5], [3, 1]]

puzzle = [[c for _ in range(9)] for c in face_colours]
for move in reversed(moves):
left = "'" not in move
move_colors = move: [puzzle[r][c] for r, c in move_position[move]] for move in move_position
current_color = shift_color(move_colors[move[0]], left)
for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]

return puzzle

def shift_color(current_color, left):
i = len(current_color) // 3
if left:
return current_color[i:] + current_color[0:i]
return current_color[-i:] + current_color[:-i]








share|improve this question












share|improve this question




share|improve this question








edited Jul 10 at 22:22
























asked Jun 26 at 13:54









Ludisposed

5,68621656




5,68621656











  • Nice problem, could you also provide a drawing which maps the x index of faceColours[x] to the actual positions on the faces? (for an easier understanding)
    – Jan Kuiken
    Jun 26 at 17:00











  • @JanKuiken I've edited that part in, I hope it is more clear now.
    – Ludisposed
    Jun 27 at 11:24










  • Why is the right face numbered so weirdly?
    – 200_success
    Jul 10 at 20:58










  • You mean import unittest and not import unittests, right?
    – 200_success
    Jul 10 at 21:11










  • @200_succes Thnx, was a typo edited it. The right face is weird indeed, but if you see the R as the top, and go topdown left to right it makes sense.
    – Ludisposed
    Jul 10 at 22:25
















  • Nice problem, could you also provide a drawing which maps the x index of faceColours[x] to the actual positions on the faces? (for an easier understanding)
    – Jan Kuiken
    Jun 26 at 17:00











  • @JanKuiken I've edited that part in, I hope it is more clear now.
    – Ludisposed
    Jun 27 at 11:24










  • Why is the right face numbered so weirdly?
    – 200_success
    Jul 10 at 20:58










  • You mean import unittest and not import unittests, right?
    – 200_success
    Jul 10 at 21:11










  • @200_succes Thnx, was a typo edited it. The right face is weird indeed, but if you see the R as the top, and go topdown left to right it makes sense.
    – Ludisposed
    Jul 10 at 22:25















Nice problem, could you also provide a drawing which maps the x index of faceColours[x] to the actual positions on the faces? (for an easier understanding)
– Jan Kuiken
Jun 26 at 17:00





Nice problem, could you also provide a drawing which maps the x index of faceColours[x] to the actual positions on the faces? (for an easier understanding)
– Jan Kuiken
Jun 26 at 17:00













@JanKuiken I've edited that part in, I hope it is more clear now.
– Ludisposed
Jun 27 at 11:24




@JanKuiken I've edited that part in, I hope it is more clear now.
– Ludisposed
Jun 27 at 11:24












Why is the right face numbered so weirdly?
– 200_success
Jul 10 at 20:58




Why is the right face numbered so weirdly?
– 200_success
Jul 10 at 20:58












You mean import unittest and not import unittests, right?
– 200_success
Jul 10 at 21:11




You mean import unittest and not import unittests, right?
– 200_success
Jul 10 at 21:11












@200_succes Thnx, was a typo edited it. The right face is weird indeed, but if you see the R as the top, and go topdown left to right it makes sense.
– Ludisposed
Jul 10 at 22:25




@200_succes Thnx, was a typo edited it. The right face is weird indeed, but if you see the R as the top, and go topdown left to right it makes sense.
– Ludisposed
Jul 10 at 22:25










1 Answer
1






active

oldest

votes

















up vote
2
down vote



accepted










Trivial observations



You have a parameter named face_colours, and another parameter named current_color, as well as a function named shift_color. Pick one spelling convention and stick with it. American English generally prevails in the programming world, so I recommend that.




In move_position, the values are lists of lists. Since each coordinate is immutable and always has length 2, you should prefer to write them as tuples.





puzzle = [[c for _ in range(9)] for c in face_colours]



would be better written as



puzzle = [[c] * 9 for c in face_colours]




for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]



would be better written as



for (r, c), color in zip(move_position[move[0]], current_color):
puzzle[r][c] = color


Bug



You have an error that causes test6, test8, and test9 to fail. The cause is that in move_position["r"], coordinates [0, 8] and [0, 3] are swapped.



A contributing factor to this bug is the lack of regularity and organization in the lookup table. If the table had been written like this…



move_position = 
"U": [(0, 0), (3, 8), (2, 4)],
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"L": [(2, 0), (1, 8), (0, 4)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"R": [(3, 0), (0, 8), (1, 4)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 3), (0, 8), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"B": [(1, 0), (2, 8), (3, 4)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],



… then two patterns would have been more apparent:



  • Each lowercase move consists of 12 tiles being permuted: 4 tiles on each of 3 faces.

  • Each uppercase move consists of 3 tiles being permuted. Furthermore, the three tiles involved in the uppercase move are a subset of the corresponding lowercase move: namely, the first coordinates of each row of the corresponding lowercase move. The pattern was broken between move_positions["R"] and move_positions["r"].

Therefore, I suggest that the hard-coded data in the lookup table should be minimized. Rather, the code can automatically produce the uppercase moves, given the lowercase moves.



Then, if we are going to programmatically extend the lookup table, we might as well go a step further and programmatically include both clockwise and counterclockwise moves in the lookup table.



Division of labour



It's not obvious what the shift_color function does. I would prefer to see the helpers designed such that pyraminx_puzzle is entirely straightforward:



def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle


Suggested solution



Most of the work goes into building the table of moves. Applying each move, then, becomes a simple matter of performing a lookup and doing some unconditional swaps.



def make_moves(moves):
"""
Extrapolate from a table of moves to build a table that includes
clockwise moves, counterclockwise moves, and corner-only moves.
Each resulting key will be the name of a move (e.g. "U'"), and each
resulting value will be a function that mutates a puzzle.
"""
def make_move(new_positions, old_positions):
def move(puzzle):
old_colors = [puzzle[r][c] for r, c in old_positions]
for (r, c), color in zip(new_positions, old_colors):
puzzle[r][c] = color
return move
for turn, pos in moves.items():
# Clockwise variant
yield turn, make_move(pos[-4:] + pos[:-4], pos)
# Counterclockwise variant
yield turn + "'", make_move(pos[4:] + pos[:4], pos)
# Corner-only clockwise variant
yield turn.upper(), make_move([pos[i] for i in range(-4, 8, 4)], pos[::4])
# Corner-only counterclockwise variant
yield turn.upper() + "'", make_move([pos[i] for i in range(-8, 4, 4)], pos[::4])

MOVES = dict(make_moves(
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 8), (0, 3), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
))

def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle





share|improve this answer





















  • Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way you move, it's much clearer now.
    – Ludisposed
    Jul 11 at 6:33










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f197287%2fcodefights-pyraminx-puzzle%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
2
down vote



accepted










Trivial observations



You have a parameter named face_colours, and another parameter named current_color, as well as a function named shift_color. Pick one spelling convention and stick with it. American English generally prevails in the programming world, so I recommend that.




In move_position, the values are lists of lists. Since each coordinate is immutable and always has length 2, you should prefer to write them as tuples.





puzzle = [[c for _ in range(9)] for c in face_colours]



would be better written as



puzzle = [[c] * 9 for c in face_colours]




for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]



would be better written as



for (r, c), color in zip(move_position[move[0]], current_color):
puzzle[r][c] = color


Bug



You have an error that causes test6, test8, and test9 to fail. The cause is that in move_position["r"], coordinates [0, 8] and [0, 3] are swapped.



A contributing factor to this bug is the lack of regularity and organization in the lookup table. If the table had been written like this…



move_position = 
"U": [(0, 0), (3, 8), (2, 4)],
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"L": [(2, 0), (1, 8), (0, 4)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"R": [(3, 0), (0, 8), (1, 4)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 3), (0, 8), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"B": [(1, 0), (2, 8), (3, 4)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],



… then two patterns would have been more apparent:



  • Each lowercase move consists of 12 tiles being permuted: 4 tiles on each of 3 faces.

  • Each uppercase move consists of 3 tiles being permuted. Furthermore, the three tiles involved in the uppercase move are a subset of the corresponding lowercase move: namely, the first coordinates of each row of the corresponding lowercase move. The pattern was broken between move_positions["R"] and move_positions["r"].

Therefore, I suggest that the hard-coded data in the lookup table should be minimized. Rather, the code can automatically produce the uppercase moves, given the lowercase moves.



Then, if we are going to programmatically extend the lookup table, we might as well go a step further and programmatically include both clockwise and counterclockwise moves in the lookup table.



Division of labour



It's not obvious what the shift_color function does. I would prefer to see the helpers designed such that pyraminx_puzzle is entirely straightforward:



def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle


Suggested solution



Most of the work goes into building the table of moves. Applying each move, then, becomes a simple matter of performing a lookup and doing some unconditional swaps.



def make_moves(moves):
"""
Extrapolate from a table of moves to build a table that includes
clockwise moves, counterclockwise moves, and corner-only moves.
Each resulting key will be the name of a move (e.g. "U'"), and each
resulting value will be a function that mutates a puzzle.
"""
def make_move(new_positions, old_positions):
def move(puzzle):
old_colors = [puzzle[r][c] for r, c in old_positions]
for (r, c), color in zip(new_positions, old_colors):
puzzle[r][c] = color
return move
for turn, pos in moves.items():
# Clockwise variant
yield turn, make_move(pos[-4:] + pos[:-4], pos)
# Counterclockwise variant
yield turn + "'", make_move(pos[4:] + pos[:4], pos)
# Corner-only clockwise variant
yield turn.upper(), make_move([pos[i] for i in range(-4, 8, 4)], pos[::4])
# Corner-only counterclockwise variant
yield turn.upper() + "'", make_move([pos[i] for i in range(-8, 4, 4)], pos[::4])

MOVES = dict(make_moves(
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 8), (0, 3), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
))

def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle





share|improve this answer





















  • Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way you move, it's much clearer now.
    – Ludisposed
    Jul 11 at 6:33














up vote
2
down vote



accepted










Trivial observations



You have a parameter named face_colours, and another parameter named current_color, as well as a function named shift_color. Pick one spelling convention and stick with it. American English generally prevails in the programming world, so I recommend that.




In move_position, the values are lists of lists. Since each coordinate is immutable and always has length 2, you should prefer to write them as tuples.





puzzle = [[c for _ in range(9)] for c in face_colours]



would be better written as



puzzle = [[c] * 9 for c in face_colours]




for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]



would be better written as



for (r, c), color in zip(move_position[move[0]], current_color):
puzzle[r][c] = color


Bug



You have an error that causes test6, test8, and test9 to fail. The cause is that in move_position["r"], coordinates [0, 8] and [0, 3] are swapped.



A contributing factor to this bug is the lack of regularity and organization in the lookup table. If the table had been written like this…



move_position = 
"U": [(0, 0), (3, 8), (2, 4)],
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"L": [(2, 0), (1, 8), (0, 4)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"R": [(3, 0), (0, 8), (1, 4)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 3), (0, 8), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"B": [(1, 0), (2, 8), (3, 4)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],



… then two patterns would have been more apparent:



  • Each lowercase move consists of 12 tiles being permuted: 4 tiles on each of 3 faces.

  • Each uppercase move consists of 3 tiles being permuted. Furthermore, the three tiles involved in the uppercase move are a subset of the corresponding lowercase move: namely, the first coordinates of each row of the corresponding lowercase move. The pattern was broken between move_positions["R"] and move_positions["r"].

Therefore, I suggest that the hard-coded data in the lookup table should be minimized. Rather, the code can automatically produce the uppercase moves, given the lowercase moves.



Then, if we are going to programmatically extend the lookup table, we might as well go a step further and programmatically include both clockwise and counterclockwise moves in the lookup table.



Division of labour



It's not obvious what the shift_color function does. I would prefer to see the helpers designed such that pyraminx_puzzle is entirely straightforward:



def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle


Suggested solution



Most of the work goes into building the table of moves. Applying each move, then, becomes a simple matter of performing a lookup and doing some unconditional swaps.



def make_moves(moves):
"""
Extrapolate from a table of moves to build a table that includes
clockwise moves, counterclockwise moves, and corner-only moves.
Each resulting key will be the name of a move (e.g. "U'"), and each
resulting value will be a function that mutates a puzzle.
"""
def make_move(new_positions, old_positions):
def move(puzzle):
old_colors = [puzzle[r][c] for r, c in old_positions]
for (r, c), color in zip(new_positions, old_colors):
puzzle[r][c] = color
return move
for turn, pos in moves.items():
# Clockwise variant
yield turn, make_move(pos[-4:] + pos[:-4], pos)
# Counterclockwise variant
yield turn + "'", make_move(pos[4:] + pos[:4], pos)
# Corner-only clockwise variant
yield turn.upper(), make_move([pos[i] for i in range(-4, 8, 4)], pos[::4])
# Corner-only counterclockwise variant
yield turn.upper() + "'", make_move([pos[i] for i in range(-8, 4, 4)], pos[::4])

MOVES = dict(make_moves(
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 8), (0, 3), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
))

def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle





share|improve this answer





















  • Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way you move, it's much clearer now.
    – Ludisposed
    Jul 11 at 6:33












up vote
2
down vote



accepted







up vote
2
down vote



accepted






Trivial observations



You have a parameter named face_colours, and another parameter named current_color, as well as a function named shift_color. Pick one spelling convention and stick with it. American English generally prevails in the programming world, so I recommend that.




In move_position, the values are lists of lists. Since each coordinate is immutable and always has length 2, you should prefer to write them as tuples.





puzzle = [[c for _ in range(9)] for c in face_colours]



would be better written as



puzzle = [[c] * 9 for c in face_colours]




for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]



would be better written as



for (r, c), color in zip(move_position[move[0]], current_color):
puzzle[r][c] = color


Bug



You have an error that causes test6, test8, and test9 to fail. The cause is that in move_position["r"], coordinates [0, 8] and [0, 3] are swapped.



A contributing factor to this bug is the lack of regularity and organization in the lookup table. If the table had been written like this…



move_position = 
"U": [(0, 0), (3, 8), (2, 4)],
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"L": [(2, 0), (1, 8), (0, 4)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"R": [(3, 0), (0, 8), (1, 4)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 3), (0, 8), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"B": [(1, 0), (2, 8), (3, 4)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],



… then two patterns would have been more apparent:



  • Each lowercase move consists of 12 tiles being permuted: 4 tiles on each of 3 faces.

  • Each uppercase move consists of 3 tiles being permuted. Furthermore, the three tiles involved in the uppercase move are a subset of the corresponding lowercase move: namely, the first coordinates of each row of the corresponding lowercase move. The pattern was broken between move_positions["R"] and move_positions["r"].

Therefore, I suggest that the hard-coded data in the lookup table should be minimized. Rather, the code can automatically produce the uppercase moves, given the lowercase moves.



Then, if we are going to programmatically extend the lookup table, we might as well go a step further and programmatically include both clockwise and counterclockwise moves in the lookup table.



Division of labour



It's not obvious what the shift_color function does. I would prefer to see the helpers designed such that pyraminx_puzzle is entirely straightforward:



def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle


Suggested solution



Most of the work goes into building the table of moves. Applying each move, then, becomes a simple matter of performing a lookup and doing some unconditional swaps.



def make_moves(moves):
"""
Extrapolate from a table of moves to build a table that includes
clockwise moves, counterclockwise moves, and corner-only moves.
Each resulting key will be the name of a move (e.g. "U'"), and each
resulting value will be a function that mutates a puzzle.
"""
def make_move(new_positions, old_positions):
def move(puzzle):
old_colors = [puzzle[r][c] for r, c in old_positions]
for (r, c), color in zip(new_positions, old_colors):
puzzle[r][c] = color
return move
for turn, pos in moves.items():
# Clockwise variant
yield turn, make_move(pos[-4:] + pos[:-4], pos)
# Counterclockwise variant
yield turn + "'", make_move(pos[4:] + pos[:4], pos)
# Corner-only clockwise variant
yield turn.upper(), make_move([pos[i] for i in range(-4, 8, 4)], pos[::4])
# Corner-only counterclockwise variant
yield turn.upper() + "'", make_move([pos[i] for i in range(-8, 4, 4)], pos[::4])

MOVES = dict(make_moves(
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 8), (0, 3), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
))

def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle





share|improve this answer













Trivial observations



You have a parameter named face_colours, and another parameter named current_color, as well as a function named shift_color. Pick one spelling convention and stick with it. American English generally prevails in the programming world, so I recommend that.




In move_position, the values are lists of lists. Since each coordinate is immutable and always has length 2, you should prefer to write them as tuples.





puzzle = [[c for _ in range(9)] for c in face_colours]



would be better written as



puzzle = [[c] * 9 for c in face_colours]




for idx, pos in enumerate(move_position[move[0]]):
puzzle[pos[0]][pos[1]] = current_color[idx]



would be better written as



for (r, c), color in zip(move_position[move[0]], current_color):
puzzle[r][c] = color


Bug



You have an error that causes test6, test8, and test9 to fail. The cause is that in move_position["r"], coordinates [0, 8] and [0, 3] are swapped.



A contributing factor to this bug is the lack of regularity and organization in the lookup table. If the table had been written like this…



move_position = 
"U": [(0, 0), (3, 8), (2, 4)],
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"L": [(2, 0), (1, 8), (0, 4)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"R": [(3, 0), (0, 8), (1, 4)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 3), (0, 8), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"B": [(1, 0), (2, 8), (3, 4)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],



… then two patterns would have been more apparent:



  • Each lowercase move consists of 12 tiles being permuted: 4 tiles on each of 3 faces.

  • Each uppercase move consists of 3 tiles being permuted. Furthermore, the three tiles involved in the uppercase move are a subset of the corresponding lowercase move: namely, the first coordinates of each row of the corresponding lowercase move. The pattern was broken between move_positions["R"] and move_positions["r"].

Therefore, I suggest that the hard-coded data in the lookup table should be minimized. Rather, the code can automatically produce the uppercase moves, given the lowercase moves.



Then, if we are going to programmatically extend the lookup table, we might as well go a step further and programmatically include both clockwise and counterclockwise moves in the lookup table.



Division of labour



It's not obvious what the shift_color function does. I would prefer to see the helpers designed such that pyraminx_puzzle is entirely straightforward:



def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle


Suggested solution



Most of the work goes into building the table of moves. Applying each move, then, becomes a simple matter of performing a lookup and doing some unconditional swaps.



def make_moves(moves):
"""
Extrapolate from a table of moves to build a table that includes
clockwise moves, counterclockwise moves, and corner-only moves.
Each resulting key will be the name of a move (e.g. "U'"), and each
resulting value will be a function that mutates a puzzle.
"""
def make_move(new_positions, old_positions):
def move(puzzle):
old_colors = [puzzle[r][c] for r, c in old_positions]
for (r, c), color in zip(new_positions, old_colors):
puzzle[r][c] = color
return move
for turn, pos in moves.items():
# Clockwise variant
yield turn, make_move(pos[-4:] + pos[:-4], pos)
# Counterclockwise variant
yield turn + "'", make_move(pos[4:] + pos[:4], pos)
# Corner-only clockwise variant
yield turn.upper(), make_move([pos[i] for i in range(-4, 8, 4)], pos[::4])
# Corner-only counterclockwise variant
yield turn.upper() + "'", make_move([pos[i] for i in range(-8, 4, 4)], pos[::4])

MOVES = dict(make_moves(
"u": [(0, 0), (0, 1), (0, 2), (0, 3),
(3, 8), (3, 3), (3, 7), (3, 6),
(2, 4), (2, 6), (2, 5), (2, 1)],
"l": [(2, 0), (2, 1), (2, 2), (2, 3),
(1, 8), (1, 3), (1, 7), (1, 6),
(0, 4), (0, 6), (0, 5), (0, 1)],
"r": [(3, 0), (3, 1), (3, 2), (3, 3),
(0, 8), (0, 3), (0, 7), (0, 6),
(1, 4), (1, 6), (1, 5), (1, 1)],
"b": [(1, 0), (1, 1), (1, 2), (1, 3),
(2, 8), (2, 3), (2, 7), (2, 6),
(3, 4), (3, 6), (3, 5), (3, 1)],
))

def pyraminx_puzzle(face_colors, moves):
puzzle = [[c] * 9 for c in face_colors]
for move in reversed(moves):
MOVES[move](puzzle)
return puzzle






share|improve this answer













share|improve this answer



share|improve this answer











answered Jul 11 at 4:04









200_success

123k14143399




123k14143399











  • Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way you move, it's much clearer now.
    – Ludisposed
    Jul 11 at 6:33
















  • Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way you move, it's much clearer now.
    – Ludisposed
    Jul 11 at 6:33















Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way you move, it's much clearer now.
– Ludisposed
Jul 11 at 6:33




Yes that bug is pretty bad, i fixed it... but somehow ended up in here anyway, sorry for that. I like the way you move, it's much clearer now.
– Ludisposed
Jul 11 at 6:33












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f197287%2fcodefights-pyraminx-puzzle%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Greedy Best First Search implementation in Rust

Function to Return a JSON Like Objects Using VBA Collections and Arrays

C++11 CLH Lock Implementation