32
\$\begingroup\$

You place a standard die at the origin of a 2D grid that stretches infinitely in every direction. You place the die such that the 1 is facing upwards, the 2 is facing in the negative y direction, and the 3 is facing in the positive x direction, as shown in the figure below:

You then proceed to execute a series of moves with the die by rotating it 90 degrees in the direction of motion. For example, if you were to first rotate the die in the negative x direction, a 3 would be upwards, the 2 would be facing in the negative y direction, and a 6 would be facing in the positive x direction.

The series of moves +y, +y, +x, +x, -y is shown in the figure below, along with the net of the die for clarification (sometimes the net is called a 'right-handed die').

We then proceed to read off the top face of the die after every move. In this case it would read 2, 6, 4, 1, 2, which we call a dice path. Note we do not include the top face of the die in its initial position, but it is always 1.

If the path of the die is such that it returns to the square it started on at the end of its movement, we call this a dice path that returns to the origin.

Challenge

Given a nonempty dice path as input (in a list or any other reasonable format), print a truthy value if the dice path returns to the origin, and a falsy value otherwise. Note that:

  • The truthy values and falsy values you output do not have to be consistent, but you can't swap them (eg. output a falsy value for a path that returns to the origin and a truthy value otherwise)
  • The input will be well formed and represent a valid dice path.
  • There is no limit to how far the die can stray from the origin.

Test Cases

Path                            -> Output
2,1                             -> true
3,1                             -> true
5,4,1,5                         -> true
2,4,1,2                         -> true
4,2,4,1                         -> true
2,4,6,2,4,6,5,4                 -> true
2,4,5,1,4,5,3,6,5,1             -> true
5,6,2,3,5,4,6,3,1,5,6,2         -> true
2,4,1,3,5,1,3,5,6,3,5,6,4,5,6,2 -> true
2                               -> false
4,5                             -> false
5,1,2                           -> false
5,6,2,1                         -> false
5,4,6,5,4,6                     -> false
5,6,4,1,5,4,2,6,5,4             -> false
5,1,2,1,5,6,5,1,2,6,4           -> false
4,6,3,1,5,6,2,1,3,6,4,1         -> false

Scoring

Shortest code in bytes wins.

\$\endgroup\$
  • 1
    \$\begingroup\$ Why no being able to swap truthy values with falsy values? Swapping them can really help saving bytes. Why not do it the regular way and just make them have to be consistent? \$\endgroup\$ – Noodle9 Sep 23 at 10:43
  • 1
    \$\begingroup\$ @Noodle9 I'm typically not a fan of allowing golfing the input/output format to the degree that a program can output 'false' to indicate something is true, although others might disagree. \$\endgroup\$ – Sisyphus Sep 23 at 10:55
  • 3
    \$\begingroup\$ It's merely asserting the opposite. In regular programming a negative would be added to the name, eg is_not_prime. \$\endgroup\$ – Noodle9 Sep 23 at 10:58
  • 1
    \$\begingroup\$ Related \$\endgroup\$ – Luis Mendo Sep 23 at 11:04
12
\$\begingroup\$

JavaScript (ES6),  142 ... 122  121 bytes

Expects an array of characters, e.g. ['5','1','2']. Returns 0 or 1.

a=>a.map(n=>D=D.map((_,i)=>D['504405076067'[d*6+i>>1]^i],p+=[w=a.length,-w,1,-1][d=D.indexOf(n)]),p=0,D=[...'254316'])|!p

Try it online!

How?

The array D[] holds the face values in the following order:

 index |    0   |    1   |    2   |    3   |    4   |    5
-------+--------+--------+--------+--------+--------+--------
 face  |  front | behind |  left  |  right |   top  | bottom

We start with D = ['2','5','4','3','1','6'], which is the initial orientation of the die as described in the challenge, using this encoding.

The direction d of the next move is the 0-indexed position of the new top face in D[]:

 index (d) |   0   |   1   |   2   |   3
-----------+-------+-------+-------+-------
 direction |  up   |  down | right |  left

When moving towards direction d, the i-th face in the updated die is the face at the following position in the previous die:

   i = | 0 | 1 | 2 | 3 | 4 | 5
-------+---+---+---+---+---+---
 d = 0 | 5 | 4 | 2 | 3 | 0 | 1
 d = 1 | 4 | 5 | 2 | 3 | 1 | 0
 d = 2 | 0 | 1 | 5 | 4 | 2 | 3
 d = 3 | 0 | 1 | 4 | 5 | 3 | 2

This table is encoded with the following expression:

'504405076067'[d * 6 + i >> 1] ^ i

Try it online!

At each iteration, we update D[] and the position p. We add \$\pm 1\$ to \$p\$ when moving horizontally, or \$\pm w\$ when moving vertically, where \$w\$ is the length of the input array (an upper bound of the distance that can be traveled in a single direction). We test whether we're back to our starting point at the end of the process.

| improve this answer | |
\$\endgroup\$
8
\$\begingroup\$

J, 79 bytes

Takes in the dice path as a reversed list of boxed 0 to 5.

0 0-:[:+/(0,(,-@|.)=i.2){~]i.~&>[:}.(]A.~0 224 283 389 489{~i.~)&.>/\.@,&(<i.6)

Try it online!

How it works

We have the starting dice as the list 0 1 2 3 4 5. Looking for the next top digit, we have either 1 2 3 4 as its index (0 and 5 would be illegal moves). Taking the anagram indices 224 283 389 489 we permute the list, executing a dice move. We do this for the whole path and gather the intermediate results: 0 1 2 3 4 5│4 0 2 3 5 1│3 0 4 1 5 2. Again, after looking for the indices, we map them to coordination changes _1 0, 0 _1, 1 0, 0 1, sum them up and check if they end up at 0 0.

There should be a byte saving by remapping the dice numbers 6 to 4, 5 to 3, …, 1 to 5 to save the two dummy zeros (0, and 0 ) by shifting the possible indices to 0 1 2 3. But this feels so wrong that I'll try to think of another solution first. :-)

| improve this answer | |
\$\endgroup\$
7
\$\begingroup\$

Charcoal, 55 48 bytes

≔324516θFS«≔⌕θιιM✳⊗ι≔⭆§⪪”)⊟?2p}Þ↔~>”⁶ι§θIκ軬∨ⅈⅉ

Try it online! Link is to verbose version of code. Takes input as a string of digits and outputs a Charcoal boolean, i.e. - for back at the origin, whitespace if not (+2 bytes to remove the whitespace). Explanation:

≔324516θ

The digits on the die, in the order right, up, left, down, top, bottom.

FS«

Loop through the input digits.

≔⌕θιι

Find in which direction the die was rolled.

M✳⊗ι

Move in that direction.

≔⭆§⪪”)⊟?2p}Þ↔~>”⁶ι§θIκ軬∨ⅈⅉ

Permute the digits to their new positions using a lookup table 514302 052413 415320 042531 depending on the direction.

»¬∨ⅈⅉ

Did we end up back at the origin?

| improve this answer | |
\$\endgroup\$
6
\$\begingroup\$

Python 2, 91 bytes

a=b=1
t=0
for c in input():t=1+t*1j**(a*c*(a*a-c*c)*b**5%7*2/3+2*(a==c));a,b=b,c
print t==0

Try it online!

The idea is to translate each triplet of consecutive die faces shown into the corresponding turn made by the die's path. The possible turn directions are left, right, straight, or reversing, all taken relative to the die's previous move. From the sequence of turns, we track the die's current coordinate and check whether it returns to the origin. Instead of tracking the die's facing direction, we simply rotate the whole coordinate system around it when it turns, and then move it.

Doing it this way means we don't have to track the state of the die itself -- just looking at local snippets of the input sequence suffices. We also don't use any hardcoded values or magic numbers.

The tricky bit is extracting the turn direction from the three consecutive die faces. You can think of these as a bug crawling on the from the first face to the second face, and then from the second face to the third face -- which direction does it need to turn on the second face to do this? We can detect that it doubles back if the first and third faces are equal, and that it goes straight forward when the first and third faces are opposite, so they add to 7.

In the remaining cases, it remains to detect whether the bug turns left of right, that is the triple of faces is left-handed or right-handed. For this, we borrow an algebraic trick from my CW solution to Determine dice value from side view. The expression 3*a*c*(a*a-c*c) will equal either b or -b modulo 7 depending on whether the triple is right-handed or left-handed. From there, some massaging takes the four cases of turn directions to numbers that equal 0,1,2,3 modulo 4, so that we can get the right complex rotation using the complex exponent 1j**.

| improve this answer | |
\$\endgroup\$
5
\$\begingroup\$

Jelly, 35 bytes

6RW;⁸œ?@\ḊḢ€⁼
“§ṫ²G‘×J$ṗLçƇ:19Ḣı*S¬

A monadic Link accepting the dice-path as a list of the faces in \$[1,6]\$ which yields 1 (truthy) if it ends back at the start, or 0 (falsey) otherwise.

Try it online! Or see the test-suite (6 longest removed since the method is inefficient).

How?

Forms all the possible paths of the given dice-path's length formed from the four possible directions as the permutations-indices of the next state.
Filters these to find the one that matches the face-up numbers in the given dice-path.
Translates the permutation indices to the four Cartesian directions as complex numbers.
Checks if the sum of these is zero.

“§ṫ²G‘×J$ṗLçƇ:19Ḣı*S¬ - Main Link: dice-path
“§ṫ²G‘                - list of code-page indices = [225,245,130,71]
      ×J$             - multiply by their indices = [225,490,390,284]
                        (these correspond to [up, down, right, left])
          L           - length (of the dice-path)
         ṗ            - Cartesian power (all lists of that length using {225,490,390,284})
            Ƈ         - filter keep those for which:
           ç          -   call Link 1 as a dyad - f(potential-path, dice-path)
             :19      - integer divide by 19 (225,490,390,284 -> 11,25,20,14)
                Ḣ     - head (get the single path that filering found)
                        (having Ḣ here rather than before the :19 saves a byte)
                 ı*   - i exponentiate (that) (11,25,20,14 -> -i,i,1,-1)
                        (yep we've mirrored but it makes no difference)
                   S  - sum
                    ¬ - logical NOT

6RW;⁸œ?@\ḊḢ€⁼ - Link 1: potential-path (as permutation indices), dice-path
6             - six
 R            - range -> [1,2,3,4,5,6]
  W           - wrap -> [[1,2,3,4,5,6]]
   ;⁸         - concatenate with the permutation indices -> [[1,2,3,4,5,6],a,b,c,...]
        \     - cumulative reduce (current-state, permuation index) by:
       @      -   with swapped arguments:
     œ?       -     permuation (of the current state) at index (permutation index)
         Ḋ    - dequeue (remove the leading [1,2,3,4,5,6])
          Ḣ€  - head of each (get the list of face-up pips)
            ⁼ - equals (the dice-path)?
| improve this answer | |
\$\endgroup\$
4
\$\begingroup\$

Python 3, 174 \$\cdots\$ 130 129 bytes

Saved a whopping 15 30 35 bytes thanks to the man himself Arnauld!!!
Saved another whopping 3 9 10 bytes thanks to ovs!!!

def f(l,p=[3,2,4,5,1,6],v=0):
 for d in l:n=p.index(d);v+=1j**n;p=[p[int(i)]for i in'%06d'%ord('񽣾첽񥙘꘣'[n])]
 return v==0

Try it online!

Returns True if we end up back at the origin or False otherwise.

Port of Neil's Charcoal answer using complex arithmetic to find out if we're back where we started.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ You can save one more byte by moving the assignments to the function header. \$\endgroup\$ – ovs Sep 23 at 14:14
  • \$\begingroup\$ @ovs Nice one - thanks! :D \$\endgroup\$ – Noodle9 Sep 23 at 14:21
4
\$\begingroup\$

R, 143 ... 116 102 bytes

Edits: -13 bytes by switching to linear instead of matrix list of transitions, then -4 bytes by halving the list of transitions & calculating the left, back & bottom die values as 7 minus the right, front & top values at each roll, then -3 bytes by switching to a base-7-encoded number to generate the list of transitions, then -14 bytes by rearranging the transition list to up, right, down, left to make calculating the new position easier using powers of i, and -6 bytes by various other minor golfs that didn't alter the approach

p=a=1:4;m=5032105982%/%7^(11:0)%%7;for(i in scan()){p[6:4]=7-p;p=p[m[q<-(p[m[a]]==i)]];F=F+1i^a[q]};!F

Try it online!

How?

(commented code before golfing)

is_dice_loop=
function(s,                     # s = vector of top die values along path
 p=1:6,                         # p = positions of current die values
                                #     (top,front,right,left,back,bot)
 m=matrix(                      # m = matrix of transitions at each roll
  utf8ToInt(                    #     created from ASCII values of
   "bedcfabbccafddfaafeeebcd")  #     this string
   -96,                         #     -96,
  4)                            #     matrix has 4 rows.
 ){
 for(i in s){                   # Now, for each die value i along the path
  r=match(i,p[m[,1]]);          #  calculate the roll direction r
                                #  (1:4 -> up,down,right,left),
  p=p[m[r,]];                   #  then calculate the new positions of die values,
  F=F+(-.5+r%%2)*1i^(r>2)       #  and calculate the new location of the die
                                #  as a complex number (real=left->right, imaginary=down->up)
                                #  (F is initialized as 0+0i by default).
 }
 !F                             # If we end up back at 0+0i, then we've done a loop,
}                               # so NOT F is true.  
| improve this answer | |
\$\endgroup\$
4
\$\begingroup\$

Wolfram Language (Mathematica), 96 91 83 74 bytes

<<Quaternions`
0==Tr[a={-1,-K,J,-J,K,1};(a=#**a**#/2;#-1)&[1+a[[#]]]&/@#]&

Try it online!

Quaternions` must be loaded prior to the function definition.

<<Quaternions`          (* load the Quaternions` package *)
a={-1,-K,J,-J,K,1};     (* (a die roll of i moves the die in direction a[[i]]) *)
(a=#**a**#/2;#-1)&      (* rotate `a` in a direction, returning the direction, *)
% [1+a[[#]]]&/@#        (* taking directions one at a time from input. *)
0==Tr[ % ]&             (* check if sum of those directions returns to the origin. *)
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Any chance of an explanation? I don't understand Wolfram language, but this looks as if it might be different from the other approaches so far... \$\endgroup\$ – Dominic van Essen Sep 25 at 7:37
  • \$\begingroup\$ @DominicvanEssen It's not substantially different, but it's relatively cheap to use built-ins that do the work for you instead of rolling your own number-coordinate mapping. I'll add one though. \$\endgroup\$ – att Sep 25 at 16:50
  • \$\begingroup\$ Thanks very much! Now all I need to do is to try to understand it even with an explanation... I'll get there in the end... \$\endgroup\$ – Dominic van Essen Sep 26 at 9:45
  • \$\begingroup\$ Creative use of quaternions. I really like this approach! \$\endgroup\$ – Sisyphus Sep 26 at 10:31
2
\$\begingroup\$

Perl 5 -pF, 130, 121 bytes

@A=(2,4,-1,1,-@F,@F+!s/^/1/);s/.(?=.(.))/$x+=$A[$&==$1||$&+$1!=7&&($P[$|--]=$&)&&$1==$P[$|]?$A[$|]^=1:$A[$|]]/ge;$_=$x==1

Try it online!

A different solution, the straightforward one was 173 bytes, which could be golfed to 165 bytes.

EDIT : I realized after that the straightforward could be golfed to 136 bytes

But to golf more I though differently. Using the fact that the sum of opposite side is 7. And that keeping a track of some previous number could be sufficent to get the directions.

  • array A :
    • [0]={2|3} and [1]={4|5} : to store the direction on (+/-)x or (+/-)y, where x and y depend on the firsts move's direction on these axes
    • the next [2..5] : to store numbers to add to $x corresponding to direction
  • s/^/1/ : prepend 1 the initial face
  • regex .(?=.(.)) : consumes one die face $& and capture the next next $1
  • $&==$1 the direction is changing backward
  • $&+$1==7 the direction doesn't change, the test is inverted because nothing to do
  • otherwise the direction has changed to rigth or left compared to the previous direction
  • array P : stores the last die face [0] x, [1] y, where x and y are depends on the firsts moves
    • $P[$|--]=$& :
      • $P[$|]=$& to store the die face when turning left or right
      • $|-- switches axis index 0/1 for x/y
  • $1==$P[$|] the next next face is compared to the last when moving on the same axis if is equal the direction is changing backward compared to the previous direction
  • ^=1 : to switch using bytwise xor (2<->3) or (4<->5)
  • $_=$x==1 : the initial position when $x==1 because first move (direction 2 : -1) was not added.
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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