17
\$\begingroup\$

Rubik's Clock is a round(ish) puzzle with two flat sides. On each side, front and back, there are 9 clock faces, arranged on a 3x3 grid. In addition, there are 4 pegs (switches) in between the clock faces, and they can be set to protrude either from the front or the back. Lastly, there are 4 dials on the edge of the puzzle. You can rotate them to manipulate the 4 clocks in the corners, on the front and the back simultaneously. Here's a schematic.

enter image description here

The switches each have 4 clocks around them, and they act as a lock to those 4 clocks. If a switch is pressed in, any movement of any of the surrounding clocks is synchronised with the three others. If the switch is not pressed in, they move independently. However, in that case, it can be seen as pressed in from the other side. Therefore, the 4 clocks around the switch on the other side of the puzzle are synchronised. The switches thus always lock 4 clocks together, either on the front or the back.

For consistency in clockwiseness and switch settings, we define one side as the front and the other as the back. As seen from the front, the switches are set to 1 if they are pressed in, and 0 if they are not pressed in.

The four corner clocks are inversely synchronised between the front and the back. If you were to turn a dial clockwise, the clock on the backside in the same position would turn counter-clockwise. Therefore, there is a total of 14 independent clocks: 4 corners, 5 independent on the front and 5 independent on the back.

Consider an example where all clocks are set to 0 (or 12), switch 0 is set to 1 and the other switches are set to 0. If we turn dial 0 to position 2, the following happens:

  • Front clocks 1, 3 and 4 are synchronised to clock 0 and all move to position 2
  • Back clock 0 is inversly synchronised to clock 0 and moves to position 10 (= -2 mod 12)

Now consider the same example, but instead both switches 0 and 3 are set to 1, whereas switches 1 and 2 are set to 0. If we turn dial 0 to position 2 again, the following happens:

  • Front clocks 1, 3 and 4 are synchronised to clock 0 and all move to position 2
  • Front clocks 5, 7 and 8 are synchronised to clock 4 and all move to position 2
  • Back clock 0 is inversely synchronised to clock 0 and moves to position 10 (= -2 mod 12)
  • Back clock 8 is inversely synchronised to clock 8 and moves to position 10 (= -2 mod 12)

The aim of the puzzle, given a scrambled set of clocks, is to set all clocks to 0. You do this by flipping switches and turning dials appropriately. This is also the task of the challenge.

The challenge

Given a valid setting for both the front and back clocks, output a sequence of switch settings and dial turns which set all clocks to 0.

Rules

  • Input and output are flexible to the extent permitted by the default rules, provided that they unambiguously describe the clock and the required sequence.
    • You can redefine the indexing.
    • If convenient, you can take only the 14 unique clock faces as input.
    • Clocks on the front must always respond directly (positively) to dial turns.
    • Clocks on the back must always respond inversely to dial turns.
  • Consider all switches set to 0 at the start of the solve.
  • The final switch settings can be nonzero, as long as all clocks are solved.
  • Your program must finish in a reasonable amount of time: no brute-forcing.

Examples

Example input: [9, 9, 0, 9, 9, 0, 9, 9, 0], [3, 0, 0, 0, 0, 0, 3, 0, 0]
Example output: [0, 2, (2, 3)]
                 |  |     +---- Dial turn
                 +--+---------- Switch flips
Step-by-step:

Current state
Switches: 0 0 0 0
Front:      Back:
 9  9  0  |  3  0  0
 9  9  0  |  0  0  0
 9  9  0  |  3  0  0

Flipping switch 0
Flipping switch 2
Turning dial 2; 3 times

Current state
Switches: 1 0 1 0
Front:      Back:
 0  0  0  |  0  0  0
 0  0  0  |  0  0  0
 0  0  0  |  0  0  0
Example input: [11, 0, 11, 8, 8, 8, 8, 8, 8], [1, 1, 1, 1, 1, 1, 4, 0, 4]
Example output: [2, 3, (2, 4), (1, 1)]

Step-by-step:

Current state
Switches: 0 0 0 0
Front:      Back:
11  0 11  |  1  1  1
 8  8  8  |  1  1  1
 8  8  8  |  4  0  4

Flipping switch 2
Flipping switch 3
Turning dial 2; 4 times

Current state
Switches: 0 0 1 1
Front:      Back:
11  0 11  |  1  1  1
 0  0  0  |  1  1  1
 0  0  0  |  0  0  0

Turning dial 1; 1 times

Current state
Switches: 0 0 1 1
Front:      Back:
 0  0  0  |  0  0  0
 0  0  0  |  0  0  0
 0  0  0  |  0  0  0
Example input: [3, 11, 0, 11, 11, 5, 2, 11, 1], [9, 8, 0, 8, 8, 4, 10, 8, 11]
Example output: [1, 3, (2, 9), (0, 7), 0, 1, 2, 3, (0, 6), 2, 3, (3, 7), 0, 1, 2, 3, (0, 4)]

Step-by-step:

Current state
Switches: 0 0 0 0
Front:      Back:
 3 11  0  |  9  8  0
11 11  5  |  8  8  4
 2 11  1  | 10  8 11

Flipping switch 1
Flipping switch 3
Turning dial 2; 9 times

Current state
Switches: 0 1 0 1
Front:      Back:
 0 11  0  |  0 11  0
11 11  5  | 11 11  4
11 11  1  |  1 11 11

Turning dial 0; 7 times

Current state
Switches: 0 1 0 1
Front:      Back:
 7 11  0  |  5  4  0
11 11  5  |  4  4  4
 6 11  1  |  6  4 11

Flipping switch 0
Flipping switch 1
Flipping switch 2
Flipping switch 3
Turning dial 0; 6 times

Current state
Switches: 1 0 1 0
Front:      Back:
 1  5  0  | 11  4  0
 5  5  5  |  4  4  4
 0  5  1  |  0  4 11

Flipping switch 2
Flipping switch 3
Turning dial 3; 7 times

Current state
Switches: 1 0 0 1
Front:      Back:
 8  0  0  |  4  4  0
 0  0  0  |  4  4  4
 0  0  8  |  0  4  4

Flipping switch 0
Flipping switch 1
Flipping switch 2
Flipping switch 3
Turning dial 0; 4 times

Current state
Switches: 0 1 1 0
Front:      Back:
 0  0  0  |  0  0  0
 0  0  0  |  0  0  0
 0  0  0  |  0  0  0
\$\endgroup\$
16
  • 1
    \$\begingroup\$ To be clear, flipping a switch does not move anything, right? And the clocks that are not in the corners cannot move unless synchronized with another clock? Just making sure I understand correctly, because at first I had some trouble understanding the spec. \$\endgroup\$
    – ophact
    Feb 3, 2022 at 9:27
  • \$\begingroup\$ @ThisFieldIsRequired Both of your statements are correct. Flipping the switches does not move anything, but it changes which clocks are synchronized when you move. The clocks which are not in the corner can only move when they are synchronised with a corner clock. \$\endgroup\$
    – Jitse
    Feb 3, 2022 at 9:29
  • \$\begingroup\$ Thanks for that, I think it's a great challenge, just rather complicated. Especially because no brute-force is allowed ;) \$\endgroup\$
    – ophact
    Feb 3, 2022 at 9:30
  • 3
    \$\begingroup\$ Thanks! I like this puzzle in particular, because if you hold it physically, it is not crazy hard. I suspect coming up with an efficient algorithm might be, though. \$\endgroup\$
    – Jitse
    Feb 3, 2022 at 9:32
  • 1
    \$\begingroup\$ @KevinCruijssen as long as it unambiguous and gets the job done, I don't see why not. \$\endgroup\$
    – Jitse
    Feb 3, 2022 at 12:44

3 Answers 3

18
\$\begingroup\$

05AB1E, 203 202 193 192 bytes

•iN yɯzoи!••)i‰â™â•S£vy,.µNUDX4-5ÖiX9÷©èDÅs12αÐV0s‚,+®ǝD®_èDŽ8SSDUèY-Xǝ®_ǝ12%ë[DX5@è4•EβQÑ´—•Xè‚èË#εD•3bž§j’λ¬Ā%úÍp₄ÞÞ5öćzz…õôty‘[z\Ä≠Îβ¥…uи•S•Eÿ>nºØ1ǦƒÎÖ•S£2äNèXè©èN·<-12%®ǝ}¼}•I¤Ðcû®•X辂,

Input is similar as the example of the challenge. A pair of lists, with the first list being the front-clocks and the second being the back-clocks.

Every two lines of the output are a step. The first line indicates the 0-based indices of the four knobs that are upwards on the front; the first integer on the second line indicates the 0-based index of the wheel we're turning, and the second integer on the second line indicates how many times we turn it clockwise.
For example, this step:

01
[3,6]

means we have the top-left and top-right knobs up on the front, and we then turn the bottom-right 6 times clockwise.

It will also output no-op steps. If the second line of a step is [x,0] it means nothing has changed that step, since it was already correct. (And it might potentially output [x,12] in the three steps where it puts all relevant clocks on 12 o'clock when they were already on 12 o'clock. If this is not allowed, 12% can be added after the α (+3 bytes).)

Try it online or try it online with debug-lines or verify all test cases with debug-lines.

Explanation:

Let me start with a general explanation of the 15 steps I do to solve the Rubik's Clock.

In the pictures below:

  • the orange knobs are the ones that are upward for that face;
  • the purple stars indicate that any of those wheels can be turned for this step;
  • the red circled clocks should all face the same direction after this step.

Step 1-4, done on the front:

enter image description here

In step 1 we have the bottom-left knob up and the other three knobs down. We then turn the bottom-left wheel until the direction of the center is facing the same way as the direction of the top-middle clock.
In step 2 we do the same with the top-left knob and wheel, until the direction of both the center AND top-middle clocks are facing the same way as the direction of the middle-right clock.
Etc.

Then in step 5, we put all four of the knobs upwards, and rotate until the five clocks that form a + are facing 12 o'clock.

For steps 6-10 we basically do the same for the backside, after we've turned the clock upside down. (In our code below we of course only use front knobs and wheel-turns, but during an irl solve you would turn the clock upside down to repeat these steps, and also finish it with the five steps below while still looking at the backside.)

After these 10 steps, the clocks that form a + on both the front and back should all face the same way, with the ones on the back facing 12 o'clock.

After that we do four more steps to fix the corners:

enter image description here

After these 14 steps, all clocks on the backside (you would still be looking at irl) are now facing the same direction; all clocks that form a + on the frontside are already at 12 o'clock; and the four corner-clocks on the frontside should also all be facing the same direction.
We can now put all the knobs up at the backside, and rotate any of the four wheels to put all clocks at 12 o'clock and finish the solve.

As for the actual code:

•iN yɯzoи!•           # Push compressed integer 201010123012023123233210
 •)i‰â™â•              # Push compressed integer 111243332011110
  S                    # Convert it to a list of digits as well
   £                   # Convert the first into parts of the second:
                       #  [2,0,1,"01","0123","012","023",123,23,"",3,2,1,0,""]
                       # (these indicate the front-knobs that are up)
vy                     # Foreach over these:
  ,                    #  Pop and print it with trailing newline
 .µ                    #  Reset the counter variable to 0
 NU                    #  Save the 0-based loop-index in variable `X`
 D                     #  Duplicate the current clock-state
  X4-5Öi               #  If the loop-index is 4, 9, or 14 (index-4 is divisible by 5):
                       #  (these are the steps to put all relevant clocks to 12 o'clock)
   X9÷                 #   Integer-divide the loop-index by 9
                       #   (0 for index 4; 1 for indices 9 and 14)
      ©                #   Store this in variable `®` (without popping)
       è               #   Use it to index into the clock-state
        D              #   Duplicate this list
         Ås            #   Pop and push the center value
           12α         #   Pop and push its absolute difference with 12
              Ð        #   Triplicate this abs(center-12)
               V       #   Pop and store one copy in variable `Y`
                0s‚    #   Pair another one with leading 0
                       #   (since it doesn't matter which wheel we turn)
                   ,   #   Pop and print it with trailing newline
   +                   #   Add the remaining third one to each clock in the list
    ®ǝ                 #   Replace the list of clocks at X//9
   D                   #   Duplicate the new clock-state
    ®_è                #   Get the opposite face (using X//9==0)
       D               #   Duplicate this list
        Ž8S            #   Push compressed integer 2068
           S           #   Convert it to a list of digits
            DU         #   Store a copy in variable `X`
              è        #   Get the clocks at these indices (the corners)
               Y-      #   Decrease them by the abs(center-12)
                 Xǝ    #   Insert them back at the corners
                   ®_ǝ #   Replace the list of clocks at X//9==0
   12%                 #   Modulo-12
  ë                    #  Else (this is a regular step):
   [                   #   Loop indefinitely:
    D                  #    Duplicate the clock-state
     X5@               #    X>=5 check
        è              #    Use that to index into the clock-state
                       #    (the front face for the first 5 steps; 
                       #    the back face for the remaining steps)
          •EβQÑ´—•     #    Push compressed integer 15370135708620
           Xè          #    Index the loop-index into that
         4   ‚         #    Pair it with 4 (center-index)
              è        #    Get the clock-values at those positions
               Ë       #    If they are the same clock-value
                #      #     Stop the infinite loop
    ε                  #    Map over both faces:
     D                 #     Duplicate the current clock-list
      •3bž§j’λ¬Ā%úÍp₄ÞÞ5öćzz…õôty‘[z\Ä≠Îβ¥…uи•
                       #     Push compressed integer 346701341245012345830020260280682686020245781245013401234501234567012345780134567812345678
         S             #     Convert it to a list of digits
          •Eÿ>nºØ1ǦƒÎÖ•
                       #     Push compressed integer 4446011120333311120444608888
           S           #     Convert it to a list of digits as well
            £          #     Split the first into parts of the second:
             2ä        #     Split it into two equal-sized parts
                       #      [[[3,4,6,7],[0,1,3,4],[1,2,4,5],[0,1,2,3,4,5],[],[8],[3],[0],[0,2],[],[0,2,6],[0,2,8],[0,6,8],[2,6,8]],
                       #       [[6],[0],[2],[0,2],[],[4,5,7,8],[1,2,4,5],[0,1,3,4],[0,1,2,3,4,5],[],[0,1,2,3,4,5,6,7],[0,1,2,3,4,5,7,8],[0,1,3,4,5,6,7,8],[1,2,3,4,5,6,7,8]]]
               Nè      #     Index the map-index into it
                 Xè    #     Index loop-index `X` into it
                   ©   #     Store it in variable `®` (without popping)
                    è  #     Index it into the clock-list
        N              #     Push the map-index
         ·<            #     Double it, and decrease by 1
                       #     (-1 for index 0; 1 for index 1)
           -           #     Decrease the clock-values by that
                       #     (so it'll increase the front and decrease the back)
            12%        #     Modulo-12 each new clock value
               ®ǝ      #     Insert them back into the clock-list at positions `®`
    }                  #    Close the map
    ¼                  #    Increase the counter variable by 1
   }                   #   Close the infinite loop
    •I¤Ðcû®•           #   Push compressed integer 20100310100123
     Xè                #   Index the loop-index `X` into it
       ¾‚              #   Pair it with the counter variable
         ,             #   Pop and print it with trailing newline

See this 05AB1E tip of mine (section How to compress large integers?) to understand how the compressed integers work.

In the time I wrote this program and answer I probably could have solved multiple hundreds of Rubik's Clocks irl, haha.. xD Solving a Rubik's Clock irl is easy, but translating that to a solver-program isn't as straight-forward as I hoped it would be when I started writing it.

\$\endgroup\$
4
  • 4
    \$\begingroup\$ This is amazing, and fast too! Well done! \$\endgroup\$
    – Jitse
    Feb 3, 2022 at 21:59
  • 3
    \$\begingroup\$ @Jitse Thanks. :) I've collected twisty puzzles for years in the past, but haven't really touched it for the past five years. It was also a long while since I last solved my Clock irl, but luckily I saved step-by-step solutions Word docs for each of my puzzles (the ones I can solve at least), which is also where those screenshots in my answer are from. ;) Converting those steps into code required quite a bit of debugging, though. \$\endgroup\$ Feb 4, 2022 at 7:49
  • 3
    \$\begingroup\$ I'm not surprised this question appealed to a Rubik fan such as yourself. \$\endgroup\$
    – Neil
    Feb 4, 2022 at 14:11
  • 1
    \$\begingroup\$ @Neil Yeah, I like twisty puzzle based challenges indeed. Even made some myself. They're usually not the easiest unfortunately, but I guess that makes it fun as well. \$\endgroup\$ Feb 4, 2022 at 14:58
5
\$\begingroup\$

Charcoal, 117 bytes

§≔θ⁰⁺§θ⁰↨§θ⁴±¹F⁴«≔§θI§5731ιζ≔⁺§θI§2860ι↨ζ±¹εF²«IE⁴∧κ⁼¹﹪⁻λι⁴I⟦⟦ι﹪⎇κε±ε¹²⟧⟧F²«IE⁴⁼‹λ﹪⁻μι⁴κI⟦⟦ι﹪×∨κ±¹⎇λ§ζκ⁻∧¬ι§§θ⁴κ§ζκ¹²

Try it online! Link is to verbose version of code. Takes input as an array of nine clocks, with a single value for the corner clocks and an array of two values for the other clocks, and outputs lists of six integers: the first four are the peg states clockwise from top left, the fifth is the dial to rotate as an integer (counting clockwise with top left as 0) and the sixth is the (clockwise) amount, from 0 (unnecessary moves are not removed) to 11. Explanation:

§≔θ⁰⁺§θ⁰↨§θ⁴±¹

The first set of rotations will correct the centre clocks by rotating the top left clock, so correct its value here.

F⁴«

Loop over each dial.

≔§θI§5731ιζ

Get the pair of clocks three clocks clockwise from that dial.

≔⁺§θI§2860ι↨ζ±¹ε

The clock two clocks clockwise (i.e. the next dial's clock) will be rotated by the difference of those clocks so get the final value here.

F²«

Loop over a) rotating the other clocks to match the next corner or rotating all the clocks back to noon, or b) fixing the clock three clocks clockwise or its reverse.

IE⁴∧κ⁼¹﹪⁻λι⁴I⟦⟦ι﹪⎇κε±ε¹²⟧⟧

Enable either all the peg except the next corner or all the pegs, and then rotate the current dial appropriately. These two rotations fix that clock.

F²«

Loop over the clock three clocks clockwise and its reverse.

IE⁴⁼‹λ﹪⁻μι⁴κI⟦⟦ι﹪×∨κ±¹⎇λ§ζκ⁻∧¬ι§§θ⁴κ§ζκ¹²

Either enable only the dial's peg, or both the dial's peg and the next dial's peg, or for the reverse clock all the pegs except those pegs, then rotate the current dial appropriately, including adjusting for the centre dial on the first rotation.

The solution basically relies on two combinations:

  • If you rotate a dial with just its peg toggled, and then inverse rotate the dial with its peg and the next peg toggled, you end up only rotating the second and third clocks clockwise from the dial. (For the very first rotation, take the opportunity to fix the centre clock as well, although this also rotates the top left clock.)

  • If you rotate any dial with no pegs toggled, then inverse rotate any other dial while a dial has its peg toggled, you end up only rotating that dial's clock.

By accepting the clocks in a custom order it's possible to reduce the byte count to 105:

≔⊟θη⊞θ⁺⊟θ↨η±¹F⁴«≔§θ⊗ιζ≔⁺§θ⊕⊗ι↨ζ±¹εF²«IE⁴∧κ⁼¹﹪⁻λι⁴I⟦⟦ι﹪⎇κε±ε¹²⟧⟧F²«IE⁴⁼‹λ﹪⁻μι⁴κI⟦⟦ι﹪×∨κ±¹⎇λ§ζκ⁻∧¬ι§ηκ§ζκ¹²

Try it online! Link is to verbose version of code. Takes clocks in the order right, top right, bottom, bottom right, left, bottom left, top, top left, centre.

\$\endgroup\$
2
\$\begingroup\$

JavaScript (Node.js), 576 bytes

(f,b,o=[],s=[])=>('012;301,13;103,01;007,2;005,13;00,3;311,31;113,10;017,2;015,13;01,012;008,31;202,10;300,02;106,2;00'.split`,`.map(S=>((n=>o.push(...[...n].map(i=>(s[i]=!s[i],i))))((T=S.split`;`)[0]),((d,w,z)=>{while((x=+w?b:f)[4]!=(z>=0?x[z]:0)){((d,q=new Set(),W=new Set())=>{[...'0268'].map((C,i,_,E=[...['0134','1254','3674','5784'][i]])=>{if(!s[d]&&!s[i]){W.add(C);E.map(j=>q.add(j))}else if(s[d]&&s[i]){q.add(C);E.map(j=>W.add(j))}});q.forEach(i=>f[i]=(f[i]+1)%12);W.forEach(i=>b[i]=(b[i]+11)%12);o.push(...(l=o.pop()).pop?(l[1]++,[l]):[l,[d,1]])})(d)}})(...T[1]))),o)

Try it online!

This answer adapts the "beginner" method of solving clock to code by walking through the same static steps. It is by no means efficient (since it often generates unnecessary switch toggles) but it is fast and simple.

Ungolfed:

const ungolfed = (f, b) => {
    // Output steps
    const o = [];
    // Switches
    const s = [0,0,0,0];
    // *t*oggle switch(es)
    // Called like t(0,1,2) with variadic argument count
    const t = (...n) => o.push(...n.map(i=>{
        console.log(`${s[i]?'Unp':'P'}ress ${i}`);
        s[i] = !s[i];
        return i;
    }));
    // const - corner clocks (for pressed switch dial turns)
    const c = [0,2,6,8];
    // const - corner/edge clocks (for unpressed switch dial turns)
    const e = [[0,1,3],[1,2,5],[3,6,7],[5,7,8]];
    // *r*otate a dial clockwise
    // Called like r(3)
    const r = d => {
        // Using sets for deduplication
        // Front clocks that will turn
        let q = new Set();
        // Back clocks that will turn
        let w = new Set();
        for (let i = 0; i < 4; i++) {
            if (!s[d]) {
                if (!s[i]) {
                    w.add(c[i]);
                    q.add(4);
                    e[i].map(j=>q.add(j));
                }
            } else {
                if (s[i]) {
                    q.add(c[i]);
                    w.add(4);
                    e[i].map(j=>w.add(j));
                }
            }
        }
        q.forEach(i=>f[i]=(f[i]+1)%12);
        w.forEach(i=>b[i]=(b[i]+11)%12);
        let l = o.pop();
        if (l.push) {
            l[1]++;
        } else {
            o.push(l);
            l = [d,1];
        }
        o.push(l);
    };
    // rotate a dial *u*ntil
    // d - Dial to rotate
    // W - truthy -> front, falsy -> back
    // y - First index to check (in golfed version, always 4)
    // z - Second index, or if -1, use 0 (pointing up) (in golfed version, use undefined in place of -1)
    const u = (d,W,y,z) => {
        x=W?b:f;
        while (x[y]!=(!~z?0:x[z])) {
            r(d);
            x=W?b:f;
            console.log(`Turn d${d} so c${W?'f':'b'}${y} ${!~z?"points up":`= c${W?'f':'b'}${z}`}`,f,b);
        }
    }

    // Actual steps to solve (starting from all unpressed switches)
    t(0,1,2);
    u(3,0,4,1);
    t(1,3);
    u(1,0,4,3);
    t(0,1);
    u(0,0,4,7);
    t(2);
    u(0,0,4,5);
    t(1,3);
    u(0,0,4,-1);
    t(3);
    u(3,1,4,1);
    t(3,1);
    u(1,1,4,3);
    t(1,0);
    u(0,1,4,7);
    t(2);
    u(0,1,4,5);
    t(1,3);
    u(0,1,4,-1);
    t(0,1,2);
    u(0,0,4,8);
    t(3,1);
    u(2,0,4,2);
    t(1,0);
    u(3,0,4,0);
    t(0,2);
    u(1,0,4,6);
    t(2);
    u(0,0,4,-1);
    
    return o;
};
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.