23
\$\begingroup\$

Rolling a 1x1x2 block

This challenge is inspired by the game Bloxorz. Like that game, there is a 1x1x2 block, which may be moved on a square grid in any of the four cardinal directions. It moves by rotating 90 degrees about one of its edges which is touching the ground. Initially, the block is standing upright on a single square in a square grid. After some sequence of moves, determine whether it is:

  • Standing upright on the starting square,
  • Laying half-on, half-off the starting square, or
  • Not on the starting square at all.

Input

Input is given as a string consisting of the characters F, B, L, and R, which represent moving forward, backward, left, and right.

Output

Output can be 1, 0.5, or 0, representing the fraction of the block resting on the starting square. Your program may use some other set of three distinct outputs, as long as they consistently represent the three possibilities.

Test cases

"FFFBBB" -> 1
"" -> 1
"FRRRBLL" -> 1
"FRBL" -> 0.5
"FRBBL" -> 0
\$\endgroup\$
5
  • \$\begingroup\$ What does “half-off, half-on” mean? \$\endgroup\$ Dec 9, 2022 at 18:26
  • 1
    \$\begingroup\$ @UndoneStudios not OP but it means some part of the 2-length side will be on the starting square. \$\endgroup\$ Dec 9, 2022 at 18:29
  • \$\begingroup\$ So like anything except the square opposite to the starting square? \$\endgroup\$ Dec 9, 2022 at 18:31
  • \$\begingroup\$ @UndoneStudios I want to make my description clearer, but I'm not sure what you mean by "the square opposite to the starting square". You mean if it ends up on the starting square upside-down (compared to its original orientation)? In that case, the output is still 1 (this happens in test case #3). I realize I said "upright", which might be confusing. If that doesn't answer your question, maybe look at gameplay? \$\endgroup\$ Dec 9, 2022 at 19:03
  • \$\begingroup\$ Yes I meant that; but now I understand that it was the lengths, not the square. \$\endgroup\$ Dec 10, 2022 at 11:07

8 Answers 8

7
\$\begingroup\$

JavaScript (Node.js), 111 bytes

-3 thanks to @Steffan, and -2 from there

Expects an array of ASCII codes. Returns \$0,1,2\$ instead of \$0,1/2,1\$.

a=>a.map(d=>o=(p[d<76|0]+="011343011334"[o+=d%7%4*3]-2,2-o%9&7)%3,p=[o=0,0])|o?3>>p[o-1]+1&!p[2-o]:(p=='0,0')*2

Try it online!

How?

We keep track of the orientation \$o\in[0,1,2]\$ of the rolling block and the position \$p=(x,y)\$ of its top-left cube as described in this diagram:

orientations

Given the ASCII code \$d\$ of a direction character, we can turn it into a value in \$[0,1,2,3]\$ by reducing it modulo \$7\$ and then modulo \$4\$. To determine whether it's a vertical or horizontal move, we can simply test \$d<76\$.

Character d = ASCII code d mod 7 (d mod 7) mod 4 d < 76
"B" 66 3 3 true
"F" 70 0 0 true
"L" 76 6 2 false
"R" 82 5 1 false

We use a lookup string of digits in \$[0\dots4]\$ to get either \$dx+2\$ or \$dy+2\$:

p[d < 76 | 0] += "011343011334"[o += d % 7 % 4 * 3] - 2

The new orientation is given by the following magic formula (found by Steffan):

o = (2 - o % 9 & 7) % 3

Once all iterations have been processed, the test to be applied depends on the final value of \$o\$.

o ?
  3 >> p[o - 1] + 1 & !p[2 - o]
:
  (p == '0,0') * 2
  • \$o=0\$ : return \$2\$ if \$x=0\$ and \$y=0\$, or \$0\$ otherwise
  • \$o=1\$ : return \$1\$ if \$y=0\$ and \$x\in[-1,0]\$, or \$0\$ otherwise
  • \$o=2\$ : return \$1\$ if \$x=0\$ and \$y\in[-1,0]\$, or \$0\$ otherwise
\$\endgroup\$
2
  • 2
    \$\begingroup\$ Magic formula to replace 1710150>>o*2&3 with: (7&2-o%9)%3 (saves 3) \$\endgroup\$
    – naffetS
    Dec 11, 2022 at 15:16
  • 1
    \$\begingroup\$ @Steffan Nice one! And it can actually save 5 bytes. \$\endgroup\$
    – Arnauld
    Dec 11, 2022 at 15:26
3
\$\begingroup\$

Charcoal, 78 43 bytes

≔UV1jθ≔E²θηFESXθ⌕FRBιUMη⁺κ×ι⊕№⁺⁻ηι…ηλκI⊘№ηθ

Try it online! Link is to verbose version of code. Explanation:

≔UV1jθ

Get i (square root of -1) into a variable.

≔E²θη

Start off with both cubes of the block on the starting square, which I've chosen to be i as it's golfier that way.

FESXθ⌕FRBι

Convert the movements into powers of i (F = 1, R = i, B = -1, L = -i) and loop over them.

UMη⁺κ×ι⊕№⁺⁻ηι…ηλκ

Calculate which cube (if any) needs to move two squares (if both cubes were on the same square then I move the second cube two squares otherwise if one cube would have moved onto the square the other cube vacated then that cube moves two squares so that both cubes end up on the same square) and move the cubes appropriately.

I⊘№ηθ

Output half of the number of cubes that have returned to the starting square.

\$\endgroup\$
2
  • \$\begingroup\$ Looks like we stumbled on a similar approach. "Output half of the number of cubes that have returned to the starting square" Rules allow you to return just the number, if that saves you any bytes. \$\endgroup\$
    – Jonah
    Dec 10, 2022 at 6:48
  • 1
    \$\begingroup\$ @Jonah It would save me the , but it's not worth my while editing my answer. (I did try using your neat approach of performing set difference, but that means I don't get to use Charcoal's "map in place" command which saves me a byte.) \$\endgroup\$
    – Neil
    Dec 10, 2022 at 10:04
3
\$\begingroup\$

Jelly, 39 bytes

Maybe there is a clever string replacement or complex number algorithm that'll crush this score?

NÑN
=ḞḢH‘ɓ+0¦
ṚÇṚ
Ṛ1ĿṚ
⁹Ŀ
O:5%6çƒØ0AṀ«1

A full program which accepts the string and prints \$0\$ (standing at the origin), \$\frac{1}{2}\$ (covering the origin, but laying down), or \$1\$ (neither).

Try it online!

How?

Starts at \$[0,0]\$ and executes the string as instructions tracking coordinates of the centre of the block (i.e. executing F would lead to \$[0,1.5]\$) then inspects the resulting position.

Note, right and left are actually mirrored to make the code slightly shorter, so executing R would lead to \$[-1.5, 0]\$.

NÑN            - Link 1 (B): [x, y]
N              - negate
 Ñ             - call the next Link (Link 2) as a monad
  N            - negate

=ḞḢH‘ɓ+0¦      - Link 2 (F): [x, y]
 Ḟ             - floor -> [floor(x), floor(y)]
=              - ([x, y]) equals? (that) (vectorises)
  Ḣ            - head -> x eqauls floor(x)?
   H           - halve
    ‘          - increment -> 1 if rolling or 1.5 if toppling or erecting
     ɓ         - start a new chain with swapped arguments
       0¦      - apply to index 0 (the y of [x, y])
      +        -   add

ṚÇṚ            - Link 3 (L): [x, y]
Ṛ              - reverse
 Ç             - call the last Link (Link 2) as a monad
  Ṛ            - reverse

Ṛ1ĿṚ           - Link 4 (R): [x, y]
Ṛ              - reverse
  Ŀ            - monadic call to Link:
 1             -  1
   Ṛ           - reverse

⁹Ŀ             - Link 5 (call correct function): location [x, y]; instruction N
 Ŀ             - monadic call to Link:
⁹              -   chain's right argument = N
               - ...i.e. call Link N with argument [x, y]

O:5%6çƒØ0AṀ«1 - Main Link: list of characters, S
O             - ordinals (S) e.g. "BFLR" ->[66,70,76,82]
 :5           - integer divide by five   ->[13,14,15,16]
   %6         - mod six                  ->[ 1, 2, 3, 4]
      į0     - starting with [x,y]=[0,0] reduce that using:
     ç        -   call the last Link (Link 5) as a dyad
         A    - absolute values (that)
          Ṁ   - maximum
           «1 - minimum of that and one
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Your comment regarding complex numbers inspired me to crush my score, thanks! \$\endgroup\$
    – Neil
    Dec 10, 2022 at 1:10
3
\$\begingroup\$

Python 3.8+, 173 bytes

f=lambda s,c=[0,0]:f(s[1:],[v*n for v in(lambda x,y:[x,y+1.5-x%1])(*[w*n for w in c[::r]])[::r]])if s and[n:=2*(s[0]in"BL")-1,r:=2*(s[0]in"RL")-1]else min(1,max(map(abs,c)))

A recursive function that accepts a string ,s, and returns \$0\$ (standing at the origin), \$\frac{1}{2}\$ (covering the origin, but laying down), or \$1\$ (neither).

Try it online!

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

J, 54 bytes

1#.0=&><@0 0(2$]-.~]+(*1,1+=/))&.>/@,~0j1<@^'RFLB'i.|.

Try it online!

Output:

  • 2 = fully on origin
  • 1 = half on origin
  • 0 = not on origin

idea

We imagine the moving block as two separate squares which move around together, sometimes sliding on top of one another (flat to upright), sometimes spreading out (upright to flat), sometimes moving in parallel (flat rolling). For example, Consider FFRR:

0   0   <- start, both blocks on origin
0j1 0j2 <- F, blocks slide apart
0j3 0j3 <- F, blocks slide together
1j3 2j3 <- R, blocks slide apart again
1j2 2j2 <- B, blocks move in parallel

At a high level, it works like this:

  1. 0j1<@^'RFLB'i.|. Map RFLB to the complex numbers 1 0j1 _1 0j_1, reverse them, and append 0 0 (the starting point of our two sliding blocks), setting ourselves up for a single reduction.
  2. 2$]-.~]+(*1,1+=/) The reduction logic. Multiply the current "step direction" by either 1 1 or 1 2. Multiply by 1 1 if our sliding blocks are currently separate, and by 1 2 (to separate them) if they are currently overlapping.
  3. ]+ Add that adjusted "step direction" elementwise to the current position to create two new positions...
  4. ]-. And set minus the current positions from the new ones.
  5. 2$ Ensure the new position is extended to two elements -- in the case that we go to standing long ways, the previous step will only return a single result -- the new position -- and so we have to duplicate that.
\$\endgroup\$
2
\$\begingroup\$

Python3, 356 bytes:

m={'F':[0,1],'B':[0,-1],'L':[1,-1],'R':[1,1]}
def M(x,c,o):p,v=m[x[0]];J=c[[0,-1][v!=1]][0];K=c[[0,-1][v!=1]][1];return[[([(J+2*v,K),(J+v,K)],1),([(J+v,K)],0),([(x+v,y)for x,y in c],o)][o],[([(J,K+2*v),(J,K+v)],2),([(x,y+v)for x,y in c],o),([(J,K+v)],0)][o]][p]
f=lambda x,c=[(0,0)],o=0:1*((0,0)in c)/len(c)if''==x else f(x[1:],*M(x,sorted(c,reverse=1),o))

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ 321 bytes \$\endgroup\$
    – naffetS
    Dec 9, 2022 at 23:10
  • \$\begingroup\$ Seems to fail for BF, giving 0 instead of 1. \$\endgroup\$ Dec 10, 2022 at 6:31
0
\$\begingroup\$

C++ (gcc), 145 bytes

A little late to the party, but here is my two cents (or 145, rather).

Outputs:

  • 2 = Standing upright on origin
  • 1 = Half-on, half-off the origin
  • 0 = Not on the origin
int f(char*s){int d=0,p=0;for(s--;*++s;){int n=*s>71?*s>79?3:-3:*s>69?2:-2;p+=d&&d!=n?n:n*2;if(d==-n||d==n)d=0;else if(!d)d=-n;}return!p+!(p+d);}

Try it online!

Explanation

We store the direction the block is facing in variable d and the position of one side of the block in variable p. We then use numbers 2 and 3 to represent the directions (with ±2 being forward/backward and ±3 being right/left), since 2 and 3 are linearly independent so we can represent any position that way with a single variable.

We then check if p equals 0 and if p+d equals 0, and add those values. If both p == 0 and p+d == 0, then we are standing upright on the origin. If one of p == 0 and p+d == 0 is true, then we are sideways on the origin (since p is always tracking the same end of the block). If neither is true, then we are not on the origin.

Ungolfed

int f(char*s) {
    int d = 0;  // Direction of the block (-2 = back, +2 = front, -3 = left, +3 = right)
    int p = 0;  // Position of the block
    
    // Loop through the string with pointer arithmetic. When we reach \0, we break out.
    for (s--;*++s;) {
        // The current movement of the block. 
        int n = *s > 71 ? (*s > 79 ? 3 : -3) : (*s > 69 ? 2 : -2);
        
        p += (d && d != n) ? n : n*2;  // If (d == 0 || d == n) (if d is upright or d and n are in the same direction), add 2n. Otherwise, add n. (we flipped the conditional to save a byte)
        if (d == -n || d == n) d = 0;  // If (d == -n || d == n) (in other words, if d is aligned with n), set d to be 0 (upright).
        else if (d == 0) d = -n;       // If no direction (block is upright), set d to be -n.
                                       // Otherwise, don't touch d since we are rolling "sideways", so the direction does not have to change.
    }
    return !p+!(p+d);                  // If p+d == 0, then the block is either straight or sideways on the origin. If additionally, p == 0, then the block is straight and on the origin.
}
\$\endgroup\$
2
  • \$\begingroup\$ 116 bytes \$\endgroup\$
    – ceilingcat
    Dec 15, 2022 at 16:25
  • 1
    \$\begingroup\$ Fail on FLLF, this is not how linearly independent used \$\endgroup\$
    – l4m2
    Dec 19, 2022 at 3:52
0
\$\begingroup\$

JavaScript (Node.js), 78 bytes

a=>a.map(d=>p[k=d<76|0]+=~-(d/2&2)*(p[1-k]&1^3),p=[0,0])|'21'[p[0]**2+p[1]**2]

Try it online!

Only even positions are tile. Position [1,0] mean half on [0,0] and half on [2,0]. Position [1,1] unused

Character d = ASCII code d in binary d / 2 & 2 d < 76
"B" 66 1000010 0 true
"F" 70 1000110 2 true
"L" 76 1001100 2 false
"R" 82 1010010 0 false
a=>                   // Take Buffer input instead to save a byte
a.map(d=>             // For each instruction
  p[k=d=='B'|d=='F']  // Dimension
  +=((d/2&2)-1)       // Direction
  *(p[1-k]%2?2:3)     // If lay on another direction, move 1
  ,                   // Else move 1.5
  p=[0,0]
)|(                   // Result
  p[0]**2+p[1]**2==0?2:p[0]**2+p[1]**2==1?1:0
)
\$\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.