I made a Coin Pyraminx random-state scrambler!

Hazel

Super Moderator
Staff member
Joined
Apr 1, 2017
Messages
1,805
Location
United States
I got a QiYi Coin Pyraminx from Puzzlcrate (image link), and over the course of 3 days, I made a random-state scrambler for the puzzle!

Link: https://onlinegdb.com/rJPKF2ZzP

Press "Run" or the F9 key to run the program.
Scramble notation: Uppercase letters are for corners, lowercase letters are for centers.

The program generates 5 scrambles. If you want to change that number, scroll to the very top of the program where it says "number_of_scrambles = 5" and replace the 5 with whatever integer you like. This can't be done at this link, though—you'll have to copy and paste the code somewhere. It'll output the scrambles as it makes them. It runs very slowly on this website, so if you have the means to run Python programs on your computer, I'd recommend copying and pasting the code there.
Also, please ignore how messy my code is, I didn't originally intend for other people to see it :p

How it works:
The entire program is written in Python 3.7.3. It starts by generating a random permutation with the help of the random.randint() function from the random module. From a solved state, it turns each of the four corners either clockwise, counterclockwise, or not at all. Then, it randomly places the (moveable) centers around the puzzle, resulting in a scrambled position with equal probability of every configuration. There aren't any unsolvable positions on the Coin Pyraminx, so I don't have to worry about that.
It then generates a solution to the permutation using what I call the five-phase algorithm. It generates an optimal solution to each of the five steps I've defined, which are as follows:

1) Solve a 1x5 block (i.e. a face minus two stickers) on the left side of the D face <U, L, R, B, d, l, r, f>
2) Finish the D face by solving the two rightmost stickers <U, L, R, B, l, r, f>
3) Solve the bottom two centers of the L face <U, l, r, f>
4) Solve the bottom two centers of the F face <U, r, f>
5) Solve the bottom 2 centers of the R face along with all 3 top centers and the U corner <U, r>

Once the solution is generated, it's inversed to get the scramble, and the program attempts to cancel moves to shorten it.
The average scramble length is 15.9 moves with a sample size of 1,000 scrambles. Within that sample, the longest scramble was 24 moves, and the shortest one was 7 moves.

I don't think there are any bugs in the program, but if you find any, please let me know and I'll try to fix them :)
Also, I didn't implement every possible move cancellation—so if you see moves that can be canceled (like U L U' or something like that), let me know and I'll add it to the list.
This is my first time attempting to make a random-state scrambler, so I'm proud of how it turned out!
 
Last edited:
I got a QiYi Coin Pyraminx from Puzzlcrate (image link), and over the course of 3 days, I made a random-state scrambler for the puzzle!

Link: https://onlinegdb.com/rJPKF2ZzP

Press "Run" or the F9 key to run the program.
Scramble notation: Uppercase letters are for corners, lowercase letters are for centers.

The program generates 5 scrambles. If you want to change that number, scroll to the very top of the program where it says "number_of_scrambles = 5" and replace the 5 with whatever integer you like. It'll output the scrambles as it makes them. It runs very slowly on this website, so if you have the means to run Python programs on your computer, I'd recommend copying and pasting the code there.
Also, please ignore how messy my code is, I didn't originally intend for other people to see it :p

How it works:
The entire program is written in Python 3.7.3. It starts by generating a random permutation with the help of the random.randint() function from the random module. From a solved state, it turns each of the four corners either clockwise, counterclockwise, or not at all. Then, it randomly places the (moveable) centers around the puzzle, resulting in a scrambled position with equal probability of every configuration. There aren't any unsolvable positions on the Coin Pyraminx, so I don't have to worry about that.
It then generates a solution to the permutation using what I call the five-phase algorithm. It generates an optimal solution to each of the five steps I've defined, which are as follows:

1) Solve a 1x5 block (i.e. a face minus two stickers) on the left side of the D face <U, L, R, B, d, l, r, f>
2) Finish the D face by solving the two rightmost stickers <U, L, R, B, l, r, f>
3) Solve the bottom two centers of the L face <U, l, r, f>
4) Solve the bottom two centers of the F face <U, r, f>
5) Solve the bottom 2 centers of the R face along with all 3 top centers and the U corner <U, r>

Once the solution is generated, it's inversed to get the scramble, and the program attempts to cancel moves to shorten it.
The average scramble length is 15.9 moves with a sample size of 1,000 scrambles. Within that sample, the longest scramble was 24 moves, and the shortest one was 7 moves.

I don't think there are any bugs in the program, but if you find any, please let me know and I'll try to fix them :)
Also, I didn't implement every possible move cancellation—so if you see moves that can be canceled (like U L U' or something like that), let me know and I'll add it to the list.
This is my first time attempting to make a random-state scrambler, so I'm proud of how it turned out!
This may seem stupid, but why not python 3.8?
 
This may seem stupid, but why not python 3.8?
The IDE I wrote this in—Wing101—just happens to use 3.7.3, and I'm not sure how to upgrade it. I looked through the changes in Python 3.8, and I don't think any of it is relavent to this program.
 
Last edited:
The IDE I wrote this in—Wing101—just happens to use 3.7.3, and I'm not sure how to upgrade it. I looked through the changes in Python 8, and I don't think any of it is relavent to this program.
Oh, ok, why don't you switch to PyCharm?
Is it because you already have a lot of projects on Wing101?
 
Also, please ignore how messy my code is, I didn't originally intend for other people to see it :p
Bad code is still better than no code, really. (Says me, a person who almost exclusively writes bad code…)
Python:
        if (self.piece == 'center'):
            temp = cubestate[self.cycle_nums[2]]
            cubestate[self.cycle_nums[2]] = cubestate[self.cycle_nums[1]]
            cubestate[self.cycle_nums[1]] = cubestate[self.cycle_nums[0]]
            cubestate[self.cycle_nums[0]] = temp
You don't need to use parens for if/while/for statements in Python, and also you can use destructuring assignment to get rid of the temp variable:
Python:
if self.piece == 'center':
    a, b, c = self.cycle_nums
    cubestate[a], cubestate[b], cubestate[c] = cubestate[c], cubestate[a], cubestate[b]

Python:
    center_list = ['r', 'r', 'r', 'g', 'g', 'g', 'b', 'b', 'b', 'y', 'y', 'y']
    bad_indexes = [0, 3, 5, 6, 7, 10, 12, 13, 14, 17, 19, 20, 21, 24, 26, 27]
    
    for i, piece in enumerate(cubestate):
        if i not in bad_indexes and len(center_list) > 0:
            rand_index = random.randint(0, len(center_list) - 1)
            cubestate[i] = center_list.pop(rand_index)
You can make use of random.shuffle to simplify this a bit:
Python:
    center_list = ['r', 'r', 'r', 'g', 'g', 'g', 'b', 'b', 'b', 'y', 'y', 'y']
    bad_indexes = [0, 3, 5, 6, 7, 10, 12, 13, 14, 17, 19, 20, 21, 24, 26, 27]

    random.shuffle(center_list)
    for i in range(len(cubestate)):
        if i not in bad_indexes:
            cubestate[i] = center_list.pop()
This also avoids the possibility of accidentally making off-by-one errors.

Python:
def alg(a):
    a = a.replace("'", 'i')
    a = a.split()
    for m in a:
        if (m == "U"):
            U.turn()
        elif (m == "Ui"):
            Ui.turn()
        elif (m == "L"):
            L.turn()
        elif (m == "Li"):
            Li.turn()
# etc.
Gigantic if-else chains are sometimes unavoidable in Python because there's no switch statement like in C-based languages, but this particular type is (sort of) avoidable. Define a dictionary that maps the strings "U", "Ui", etc. to the moves U, Ui, etc., then make use of it.
Python:
moves = {"U": U, "Ui": Ui, "L": L, "Li": Li, etc.}

def alg(a):
    for m in a.replace("'", 'i').split():
        moves[m].turn()

In general, avoid one-letter/two-letter variable names, especially for global variables. Using short names for global variables runs the risk that you unintentionally reuse the variable name in another context and end up clobbering it. (And also avoid globals in general, but code that evolves out of random experimentation tends to have globals all over the place anyway, at least in my personal experience. It's not that big of a deal.)
 
Bad code is still better than no code, really. (Says me, a person who almost exclusively writes bad code…)
Python:
        if (self.piece == 'center'):
            temp = cubestate[self.cycle_nums[2]]
            cubestate[self.cycle_nums[2]] = cubestate[self.cycle_nums[1]]
            cubestate[self.cycle_nums[1]] = cubestate[self.cycle_nums[0]]
            cubestate[self.cycle_nums[0]] = temp
You don't need to use parens for if/while/for statements in Python, and also you can use destructuring assignment to get rid of the temp variable:
Python:
if self.piece == 'center':
    a, b, c = self.cycle_nums
    cubestate[a], cubestate[b], cubestate[c] = cubestate[c], cubestate[a], cubestate[b]

Python:
    center_list = ['r', 'r', 'r', 'g', 'g', 'g', 'b', 'b', 'b', 'y', 'y', 'y']
    bad_indexes = [0, 3, 5, 6, 7, 10, 12, 13, 14, 17, 19, 20, 21, 24, 26, 27]
   
    for i, piece in enumerate(cubestate):
        if i not in bad_indexes and len(center_list) > 0:
            rand_index = random.randint(0, len(center_list) - 1)
            cubestate[i] = center_list.pop(rand_index)
You can make use of random.shuffle to simplify this a bit:
Python:
    center_list = ['r', 'r', 'r', 'g', 'g', 'g', 'b', 'b', 'b', 'y', 'y', 'y']
    bad_indexes = [0, 3, 5, 6, 7, 10, 12, 13, 14, 17, 19, 20, 21, 24, 26, 27]

    random.shuffle(center_list)
    for i in range(len(cubestate)):
        if i not in bad_indexes:
            cubestate[i] = center_list.pop()
This also avoids the possibility of accidentally making off-by-one errors.

Python:
def alg(a):
    a = a.replace("'", 'i')
    a = a.split()
    for m in a:
        if (m == "U"):
            U.turn()
        elif (m == "Ui"):
            Ui.turn()
        elif (m == "L"):
            L.turn()
        elif (m == "Li"):
            Li.turn()
# etc.
Gigantic if-else chains are sometimes unavoidable in Python because there's no switch statement like in C-based languages, but this particular type is (sort of) avoidable. Define a dictionary that maps the strings "U", "Ui", etc. to the moves U, Ui, etc., then make use of it.
Python:
moves = {"U": U, "Ui": Ui, "L": L, "Li": Li, etc.}

def alg(a):
    for m in a.replace("'", 'i').split():
        moves[m].turn()

In general, avoid one-letter/two-letter variable names, especially for global variables. Using short names for global variables runs the risk that you unintentionally reuse the variable name in another context and end up clobbering it. (And also avoid globals in general, but code that evolves out of random experimentation tends to have globals all over the place anyway, at least in my personal experience. It's not that big of a deal.)
Hey, thanks so much for this! I only have one semester of school experience with Python, so I know I have a lot of bad habits :)
 
Back
Top