Help, I'm trapped in an infinite factory!

26

This challenge is loosely inspired by the Zachtronics game Infinifactory.

You are given a top-down view of a rectangular grid of conveyors, represented by >v<^. There may be cells without conveyors, represented by spaces. Here is an example:

> <vv    <
 v ^ >v v 
  >v^^>vv^
    ^>^ v 
>  v<v  >>
  >v v<^  

This setup is implicitly surrounded by an infinite number of spaces.

Furthermore, you are given the dimensions of a rectangular piece of cargo which is placed onto the conveyors in the top left corner of the grid. Your task is to figure out whether the cargo ever comes to rest or whether it will end up moving in a loop.

Of course, the cargo is likely to cover several conveyors at once, so here are the rules for figuring out the direction of the cargo in each step:

  1. Opposite conveyors cancel each other. So if a 3x2 cargo covers any of the following patches (outlined with hyphens and pipes for clarity), the result would be the same:

    +---+   +---+   +---+
    |>>^|   |  ^|   |v^^|
    |^<<|   |^  |   |^^v|
    +---+   +---+   +---+
    

    The same goes for these:

    +---+   +---+   +---+
    |v^<|   |   |   |><>|
    |>>>|   |>> |   |>><|
    +---+   +---+   +---+
    

    Since the exact position of a conveyor underneath the cargo is irrelevant, it doesn't matter which pairs you cancel.

    This cancellation is applied before the other rules. Therefore, for the other rules there will only be conveyors in at most two directions.

  2. If the cargo doesn't cover any conveyors at all (either because all conveyors cancel, because it covers only spaces or because it moved fully off the grid), it comes to rest.
  3. If the cargo covers more conveyors of one direction than of the other, the cargo moves in that direction. E.g., if a 3x2 cargo covered the following patch

    >>
    ^>^
    

    it would move to the right, because there are more > than ^. On the other hand, if it covered

    >>^
      ^
    

    this rule would not apply, because there's a tie between > and ^.

  4. This leaves only cases in which there is a tie between adjacent directions (a tie between opposite directions would have cancelled). In this case, the cargo keeps moving along the axis it is already moving in. E.g. if a right-moving or left-moving 3x2 cargo is now covering the patch

    >>^
    ^  
    

    it would move to the right. If it had arrived on this patch moving up or down, it would now move up instead. If this kind of conflict occurs on the very first step of the simulation, assume that the cargo had been moving to the right.

Detailed Examples

Consider the conveyor grid at the top and a 3x2 cargo. The following is a step-by-step visualisation of the process. Each step consists of the grid, with the cargo represented by #, a small box which shows the conveyors covered by the cargo, another box with the conveyors after cancellation, and the rule that determines where the cargo moves:

 ###vv    <    > <vv    <    > <vv    <    > <vv    <    > <vv    <    > <vv    <
 ###^ >v v     ###^ >v v      v ^ >v v      v ^ >v v      v ^ >v v      v ^ >v v 
   >v^^>vv^    ###v^^>vv^    ###v^^>vv^     ###^^>vv^      ###^>vv^      >###>vv^
     ^>^ v         ^>^ v     ### ^>^ v      ###^>^ v       ###>^ v        ###^ v 
 >  v<v  >>    >  v<v  >>    >  v<v  >>    >  v<v  >>    >  v<v  >>    >  v<v  >>
   >v v<^        >v v<^        >v v<^        >v v<^        >v v<^        >v v<^  

+---+  +---+  +---+  +---+  +---+  +---+  +---+  +---+  +---+  +---+  +---+  +---+
|> <|  |   |  | v |  | v |  |  >|  |  >|  | >v|  | >v|  |>v^|  |> ^|  |v^^|  | ^^|
| v |  | v |  |  >|  |  >|  |   |  |   |  |   |  |   |  |  ^|  |   |  | ^>|  |  >|
+---+  +---+  +---+  +---+  +---+  +---+  +---+  +---+  +---+  +---+  +---+  +---+

   Rule 3        Rule 4        Rule 3        Rule 4        Rule 4        Rule 3

 ================================================================================

 > <vv    <    > <###   <    > <vv    <
  v ###v v      v ###v v      v ###v v 
   >###>vv^      >v^^>vv^      >###>vv^
     ^>^ v         ^>^ v         ^>^ v 
 >  v<v  >>    >  v<v  >>    >  v<v  >>
   >v v<^        >v v<^        >v v<^  

+---+  +---+  +---+  +---+  +---+  +---+
|^ >|  |  >|  |vv |  | v |  |^ >|  |  >|
|v^^|  | ^^|  |^ >|  |  >|  |v^^|  | ^^|
+---+  +---+  +---+  +---+  +---+  +---+

   Rule 3        Rule 4        Rule 3

At this point, the cargo enters a loop between the last two frames.

Now consider a 2x3 cargo instead:

 ##<vv    <    >##vv    <    > <vv    <    > <vv    <    > <vv    <    > <vv    <
 ## ^ >v v      ##^ >v v      ##^ >v v      v ^ >v v      v ^ >v v      v ^ >v v 
 ##>v^^>vv^     ##v^^>vv^     ##v^^>vv^     ##v^^>vv^      ##^^>vv^      >v^^>vv^
     ^>^ v         ^>^ v      ## ^>^ v      ## ^>^ v       ##^>^ v       ##^>^ v 
 >  v<v  >>    >  v<v  >>    >  v<v  >>    >##v<v  >>    > ##<v  >>    > ##<v  >>
   >v v<^        >v v<^        >v v<^        >v v<^        >v v<^        ## v<^  

 +--+  +--+    +--+  +--+    +--+  +--+    +--+  +--+    +--+  +--+    +--+  +--+
 |> |  |> |    | <|  |  |    |v |  |v |    | >|  | >|    |>v|  |>v|    |  |  |  |
 | v|  | v|    |v |  |v |    | >|  | >|    |  |  |  |    |  |  |  |    | v|  | v|
 |  |  |  |    | >|  |  |    |  |  |  |    |  |  |  |    | v|  | v|    |>v|  |>v|
 +--+  +--+    +--+  +--+    +--+  +--+    +--+  +--+    +--+  +--+    +--+  +--+

   Rule 4        Rule 3        Rule 4        Rule 3        Rule 3        Rule 3

 ================================================================================

 > <vv    <    > <vv    <    > <vv    <
  v ^ >v v      v ^ >v v      v ^ >v v 
   >v^^>vv^      >v^^>vv^      >v^^>vv^
     ^>^ v         ^>^ v         ^>^ v 
 > ##<v  >>    >  v<v  >>    >  v<v  >>
   ## v<^        ## v<^        >v v<^  
   ##            ##            ##
                 ##            ##
                               ##

 +--+  +--+    +--+  +--+    +--+  +--+
 | v|  | v|    |>v|  |>v|    |  |  |  |
 |>v|  |>v|    |  |  |  |    |  |  |  |
 |  |  |  |    |  |  |  |    |  |  |  |
 +--+  +--+    +--+  +--+    +--+  +--+

   Rule 3        Rule 4        Rule 2

In the last step, rule 2 applies because the cargo has moved off the grid, so it comes to rest and there won't be a loop.

Rules and Assumptions

Your input will be the conveyor grid as described above along with the width and height of the cargo. You may take these three parameters in any convenient order and format. For the grid, this means that you can read a single string with lines separated by newlines or other characters, or an array of strings, or an array of arrays of characters, as long as the individual grid cells are still represented by the characters >v<^ and spaces.

You should output a truthy value if the setup results in a loop of at least two frames or a falsy value if the cargo will come to rest.

You may assume that the grid will be padded to a rectangle with spaces, and that the cargo will initially fit into the grid.

You may write a program or function, taking input via STDIN (or closest alternative), command-line argument or function argument and outputting the result via STDOUT (or closest alternative), function return value or function (out) parameter.

This is code golf, so the shortest answer (in bytes) wins.

Test Cases

The test cases are grouped by grids.

Grid (2x2):

>v
^<

Width  Height  Loop?
1      1       True
1      2       True
2      1       True
2      2       False

Grid (3x3):

> v

^ <

Width  Height  Loop?
1      1       False
1      2       False
1      3       False
2      1       False
2      2       True
2      3       True
3      1       False
3      2       True
3      3       False

Grid (4x3):

>^>v
v^v 
^ <<

Width  Height  Loop?
2      2       False

Grid (6x5):

>v>v>v
^v^v^v
^v^v^v
^>^>^v
^<<<<<

Width  Height  Loop?
1      1       True
1      2       False
2      1       True
2      2       True
2      4       True
2      5       False
3      1       False
3      2       True
3      3       True
3      5       True
6      2       False
6      3       True
6      5       False

Grid (10x6):

> <vv    <
 v ^ >v v 
  >v^^>vv^
    ^>^ v 
>  v<v  >>
  >v v<^  

Width  Height  Loop?
1      1       False
2      3       False
2      6       False
3      2       True
5      4       False
6      1       True
10     6       False

As an additional set of test cases, consider that any input where the grid consists solely of spaces must yield a falsy result.

I've checked all the test cases manually, so let me know if you think I've made a mistake.

Martin Ender

Posted 2015-08-16T10:58:42.243

Reputation: 184 808

10Fitting music – Fatalize – 2015-08-16T11:22:44.203

4@Fatalize I'm getting Twitch Plays Pokemon flashbacks... – undergroundmonorail – 2015-08-16T11:34:06.330

"Your input will be the conveyor grid as described above along with the width and height of the cargo. You may take these three parameters in any convenient order and format." - does this mean we can have the conveyor grid taken in as an array of arrays? That is, a 2x2 grid with [^^/v<] becomes [[0,1] [0,1];[0,-1] [-1,0]]? Or do you mean it's up to us whether it's STDIN, a string input, a char array input, etc, but it still has to be in the form of ^, v, >, and <? – Glen O – 2015-08-16T11:39:44.767

@GlenO You can take a newline (or other character) separated string, an array of strings, or an array of arrays of characters, but each cell should still be represented by the characters ><^v or a space. I'll clarify that. – Martin Ender – 2015-08-16T11:41:09.040

So what happens if the cargo moves onto a conflict where the continued direction is not one of the choices? That is, if it was moving right, and now must choose between up and left. – Joshua – 2015-08-16T20:47:33.747

@Joshua What's important is the axis the cargo was moving on, not the direction. So in your example it would then move to the left, because it was moving along the left-right axis. – Martin Ender – 2015-08-16T20:48:31.460

At first sight, I had read "the direction", instead of "the axis". What matters is in fact the direction — in the maths sense. – Nicolas Barbulesco – 2015-08-17T09:57:55.690

Your task is to figure out whether the cargo ever comes to rest or whether it will end up moving in a loop. Did you seriously just ask us to solve the Halting Problem in a Code Golf exercise? ;) – Mason Wheeler – 2015-08-17T18:11:21.463

@MasonWheeler Nope, not really. :P – Martin Ender – 2015-08-17T18:39:56.437

Answers

7

Ruby, 306 298 251 204 198

->g,w,h{m=->y,x,d,v=[]{q=y,x
r=->s{([""]*h+g)[y+h,h].map{|l|(?x*w+l)[x+w,w]}.join.count s}
z=k=r[?v]-r[?^],j=r[?>]-r[?<]
q[d=[d,1,0][j*j<=>k*k]]+=z[d]<=>0
v&[q<<d]!=[]?q!=v[-1]:m[*q,v<<q]}
m[0,0,1]}

Edit: Thanks a lot to Ventero who shortened the code a lot by applying some amazing tricks!

Input and output

The code represents a ruby function that takes three parameters:

  • the grid, represented as an array of strings (each row is a different string)
  • the width of the cargo
  • the height of the cargo

It returns 1 (truthy) in case there's a loop, or nil (falsy) in case the cargo rests.

Tests

Here it is passing all of Martin's tests: http://ideone.com/zPPZdR

Explanation

There are no clever tricks in the code; it's a pretty straightforward implementation of the rules.

In the code below, move is a recursive function that makes a move according to the rules, and:

  • returns truthy in case of a loop
  • returns falsy in case of a rest
  • otherwise calls itself to execute the next move

A more readable version is available here.

Note: the golfed code has undergone several modifications and is no longer similar to the readable version.

Cristian Lupascu

Posted 2015-08-16T10:58:42.243

Reputation: 8 369

Since it doesn't matter whether r contains additional entries besides the four directions, r[y>=0&&x>=0&&g[y]&&g[y][x]]+=1 should save a few bytes. – Ventero – 2015-08-16T14:49:50.360

I took the liberty of golfing things a little further, hope you don't mind: http://ideone.com/k69BmH

– Ventero – 2015-08-16T16:11:30.160

@Ventero Wow, you've done amazing things to the code. I would never have thought about transforming the hash into a lambda. I was trying out some of my ideas to shorten the program, but was nowhere near what you did. Thanks a lot! – Cristian Lupascu – 2015-08-16T17:04:40.140

Updated the code to include a fix for otherwise incorrect cycle detection (the previous version would not look at the axis the cargo was moving on when trying to detect cycles). Unfortunately that fix cost 1 byte... – Ventero – 2015-08-16T17:09:09.867

2

Got it down to 200 through slightly shorter handling of negative indices, guess I'll leave it at that for now: http://ideone.com/k69BmH

– Ventero – 2015-08-16T19:16:23.430

2

Actually, 198: http://ideone.com/ptKrzf :)

– Ventero – 2015-08-17T00:21:59.333

8

Julia - 394 300 246 214 bytes

f(A,x,y)=(Z=sign;(S,T)=size(A);X=x+1;Y=y+1;G=fill(5,S+2y,T+2x);G[Y:S+y,X:T+x]=A;C=0G;D=1;while C[Y,X]!=D C[Y,X]=D;i,j=sum(i->1-[19 8]i%82%3,G[Y:Y+y-1,X:X+x-1]);D=Z(2i^2-2j^2+(i!=0)D);X+=Z(i+D*i);Y+=Z(j-D*j)end;D^2)

Returns 1 if the cargo loops, and 0 if it comes to a stop. It's not "strictly" truthy/falsy, in that Julia doesn't permit 0 and 1 in boolean contexts... but I consider values x for which bool(x)==true are truthy and bool(x)==false are falsy.

Input A should be of the form of a character array. If you're copy/pasting the conveyor grid, you'll need to get it into the appropriate form. To save you from having to manually handle it, use the following:

A=foldl(hcat,map(collect,split("""(PASTE GRID HERE)""","\n")))'

Where obviously, (PASTE GRID HERE) should be replaced with the grid itself. Double-check the spaces at the end of each line, to make sure it actually has all of the spaces (it doesn't check to make sure that all lines are equal length). Note that this line isn't part of the actual solution code, just a convenient piece of code to make using the solution code a bit easier.

Ungolfed:

function f(A,x,y)
  # Determine size of grid for use later
  (S,T)=size(A)
  # Initialise starting position (performed here to save characters)
  X=x+1
  Y=y+1
  # Create an expanded field that is functionally "spaces" (to provide
  # spaces at edges for the cargo to stop in)
  G=fill(5,S+2y,T+2x)
  # Put the conveyor grid into centre of the expanded field
  G[Y:S+y,X:T+x]=A
  # Create an array storing the most recent movement direction:
  # will use 1=horizontal, -1=vertical, 0=stopped
  C=0G
  # Initialise current direction (same system as C)
  D=1
  # Loop until it finds itself repeating a coordinate/direction pair
  while C[Y,X]!=D
    # Mark current coordinate/direction pair in array
    C[Y,X]=D
    # Determine the net coordinate pairing, stored in two variables
    # for golf purposes *SEE NOTE*
    i,j=sum(i->1-[19 8]i%82%3,G[Y:Y+y-1,X:X+x-1])
    # Determine new movement axis (if D=0, cargo stopped)
    D=sign(2i^2-2j^2+(i!=0)D)
    # Update X or Y depending on signs of D and the appropriate direction
    X+=sign(i+D*i)
    Y+=sign(j-D*j)
  end
  # if D=±1, return 1 (cargo is still moving), otherwise return 0
  return D^2
end

Note: 1-[19 8]i%82%3 has been chosen to map the five possible characters to the appropriate coordinate pairs by the most efficient method I could find. This is also the reason for using 5 to fill the spaces when creating G - it's a single-digit character that maps to [0 0].

Example of usage:

julia> A=foldl(hcat,map(collect,split(""">v>v>v
       ^v^v^v
       ^v^v^v
       ^>^>^v
       ^<<<<<""","\n")))';

julia> f(A,2,1)
true

julia> f(A,3,3)
true

julia> f(A,5,2)
false

Glen O

Posted 2015-08-16T10:58:42.243

Reputation: 2 548

f(A,x,y)= is shorter than f=(A,x,y)->. – Alex A. – 2015-08-16T19:26:44.550

@AlexA. - true, but then, I'll probably remove the f= and make it an anonymous function when I finish golfing it. – Glen O – 2015-08-16T19:32:30.727

1It'll be the same length if it's a named function versus an anonymous function when there are multiple parameters. f()= versus ()->. – Alex A. – 2015-08-16T19:43:59.040

8

Python 2, 207 bytes

def f(L,w,h,u=0,v=0,D=1,S=[]):a,b,c,d=map(`[r[u*(u>0):u+w]for r in L[v*(v>0):v+h]]`.count,"v^><");D=cmp(abs(a-b),abs(c-d))<D;T=u,v,D;return T in S or a-b|c-d and f(L,w,h,u+cmp(c,d)*D,v+cmp(a,b)*0**D,D,S+[T])

Input the grid as a list of rows, e.g.

['>v>v>v', '^v^v^v', '^v^v^v', '^>^>^v', '^<<<<<']

followed by the width and height. Returns 0 or True accordingly.

Explanation

def f(L,          # Grid
      w,h,        # Width, height of cargo
      u=0,v=0,    # Position of top-left of cargo, initially (0, 0)
      D=1,        # Moving left/right = 1, up/down = 0
      S=[]        # Seen (pos, axis) pairs, initially empty
     ):     

     # Arrows under cargo - no need for "".join since we only need to count v^<>
     A = `[r[u*(u>0):u+w]for r in L[v*(v>0):v+h]]`

     # Count for each arrow
     a,b,c,d=map(A.count,"v^><")

     # Golfed form of abs(a-b) < abs(c-d) or (abs(a-b) == abs(c-d) and D == 1)
     D=cmp(abs(a-b),abs(c-d))<D
     T=u,v,D

     return (T in S                # Return True if (pos, axis) previously seen
             or a-b|c-d               # Return 0 if all conveyors cancel
             and f(L,w,h,             # Otherwise, recurse
                   u+cmp(c,d)*D,      # Update u if moving left/right
                   v+cmp(a,b)*0**D,   # Update v if moving up/down
                   D,
                   S+[T]          # Add (pos, axis) to seen
                  )
            )

Sp3000

Posted 2015-08-16T10:58:42.243

Reputation: 58 729

Can't you shorten it by assigning cmp to a variable? – Blue – 2015-08-16T16:21:55.613

Is it sufficient to detect cycles by checking for already visited positions? Based on rule 4, the next step can also be influenced by the previous direction. So it seems possible that you could arrive at the same position twice, but not have a cycle because you move in different directions based on different previous directions. – Reto Koradi – 2015-08-16T16:26:27.203

@muddyfish That'd just break even – Sp3000 – 2015-08-16T16:33:13.223

@RetoKoradi Hopefully fixed – Sp3000 – 2015-08-17T09:41:20.423

Yes, adding D to the position key should do it. – Reto Koradi – 2015-08-17T15:19:23.650