# One-Dimensional Pinball

Consider a finite, one-dimensional grid where each cell is marked with one of two symbols (I will use the symbols < and >, but you can use other symbols).

When a pinball is placed on one of the cells, it moves according to the following rules:

• If the pinball is on a cell that is marked with <, the pinball moves one cell left in the next second, and if marked with >, it moves one cell right in the next second.
• After the pinball has moved, the marker on the cell is inverted (i. e. if cell was marked with <, it becomes >, and vice versa).
• The pinball stops moving when it leaves the grid.

## Challenge

Given the initial markers on the grid and a starting location for the pinball, calculate how many seconds it would take for the pinball to leave the grid.

## Input Format

• You may represent the markers on the initial grid as an array/string containing two distinct values.
• You may take the starting position as a 0-indexed or 1-indexed value.

## Worked example

initial grid = ><<, start = 1
*    *      *      *    *    *    *
><<   >><   <><   <<<   <<>   <>>   >>>
1     2     3     4     5     6


Here the * represents the pinball, and the numbers on the bottom represent the time elapsed.

## Testcases

><<, 0 -> 3
><<, 1 -> 6
><<, 2 -> 5

<<<<, 0 -> 1
<<<<, 3 -> 4

<><<<>, 1 -> 4
<><<<>, 3 -> 10
<><<<>, 4 -> 8
<><<<>, 5 -> 1


Based on this Codeforces problem (Same problem but you need to solve for all starting locations in linear time)

• Related Commented Mar 6 at 3:26

# Minecraft Functions, 378 + 141 + 541 = 1060 bytes

Made up of 3 functions in a data pack named a:

a.mcfunction:

scoreboard objectives add a dummy
data modify storage s a set from storage s c[0]
data remove storage s c[0]
execute at @p if data storage s {a:0} run setblock ~ ~ ~ mud
execute at @p if data storage s {a:1} run setblock ~ ~ ~ tnt
execute at @p run tp @p ~1 ~ ~
execute if data storage s c[0] run schedule function a:a 1
execute at @p unless data storage s c[0] run function a:b


b.mcfunction:

summon cat
data modify entity @e[type=cat,limit=1] Pos[0] set from storage s i
execute at @e[type=cat,limit=1] run tp @p ~.5 ~ ~
function a:c


c.mcfunction:

execute at @p if block ~ ~ ~ air run tellraw @p {"score":{"name":"a","objective":"a"}}
execute at @p unless block ~ ~ ~ air run schedule function a:c 1
execute at @p if block ~ ~ ~ mud run data merge storage s {a:0}
execute at @p if block ~ ~ ~ tnt run data merge storage s {a:1}
execute at @p if data storage s {a:0} run setblock ~ ~ ~ tnt
execute at @p if data storage s {a:1} run setblock ~ ~ ~ mud
execute at @p if data storage s {a:0} run tp @p ~-1 ~ ~
execute at @p if data storage s {a:1} run tp @p ~1 ~ ~
scoreboard players add a a 1


The input grid is given as an array c of 0 (left) and 1 (right) in storage s. The starting position at is given as a double i in the same storage. They can be set with these commands:

# Set grid (<><<<>)
data modify storage s c set value [0,1,0,0,0,1]
# Set starting position (position 3)
data modify storage s i set value 3.0d


It's run with /function a:a and outputs the number of seconds to chat. For best results (well, correct results), run in a void world in Spectator mode at x=0.

Here's a slowed down GIF of it in action for test case #7 (RIP the cat).

Definitely not anywhere near optimal, but it was a fun challenge!

# K (ngn/k), 25 bytes

Takes markers as 1/-1 for left and right.

{1+.[o;(x;y-x[y]*:-1);0]}


Try it online!

# JavaScript (Node.js), 30 bytes

s=>g=p=>s[p]&&-~g(p-=s[p]*=-1)


Try it online!

-1B from Arnauld

32 if it need to be one char

• 30 bytes Commented Mar 6 at 7:20

# Excel ms365, 138 129 bytes

Lovely challenge! I decided to make a recursive function that will keep calling itself through LAMBDA():

Formula in A3:

=LET(v,TOCOL(1:1,1)="<",i,SEQUENCE(ROWS(v)),x,LAMBDA(f,g,s,p,IF(OR(i=p),f(f,IF(i=p,1-g,g),s+1,p-2*INDEX(g,p)+1),s)),x(x,v,,A2+1))


The answer is 0-indexed and is made in such a way you can alter the values in the 1st row and the starting position in cell A2.

The actuall LAMBDA() has 4 parameters;

• f - A call to itself (to make this recursive);
• g - A grid made up of 0's and 1's based on symbol;
• s - Seconds elapsed;
• p - Current position in the grid.
• Nice! Save 8 bytes by replacing not(g) with 1-g and p+if(index(g,p),-1,1)) with p-2*index(g,p)+1). Commented Mar 6 at 16:31
• I think this can be thinned down to 129 bytes. See my Google Sheets answer. Commented Mar 6 at 17:52
• @doubleunary, thanks for the suggestions! Edited my answer accordingly =)
– JvdV
Commented Mar 7 at 16:29

# 05AB1E, 2724 22 bytes

V[ā<Yå_#DYè©(YǝY®+V¼}¾


Inputs in the order $$\startingPosition,gridList\$$, where the $$\gridList\$$ is a list of -1 and 1 for < and > respectively and $$\startingPosition\$$ is 0-based.

Explanation:

V         # Store the first (implicit) input-index in variable Y
[         # Start an infinite loop:
ā<       #  Push a list in the range [0,length) (without popping)
#  (which will use the second implicit input-list in the first iteration)
Yå_    #  Check whether Y is NOT in this list
#   #  If it isn't (aka it's out of bounds): stop the infinite loop
D        #  Duplicate the current list
Yè      #  Pop the copy, and get its 0-based Y'th value
©     #  Store this value in variable ® (without popping)
(    #  Pop and negate it (-1 becomes 1 and vice versa)
Yǝ  #  Insert it back into the list at 0-based index Y
Y        #  Push 0-based index Y again
®       #  Push the current value ® again
+      #  Add that to Y
V     #  Overwrite the previous Y with this new position
¼        #  Increase counter variable ¾ by 1 (0 by default)
}¾        # After the infinite loop: push counter variable ¾
# (which is output implicitly as result)


# Charcoal, 24 bytes

ＳＪＮ⁰≔⁰ζＷＫＫ«≦⊕ζ✳ι⁻lrι»⎚Ｉζ


Try it online! Link is to verbose version of code. Takes input as a string of ls and rs. Explanation:

Ｓ


Write the pinball layout to the canvas.

ＪＮ⁰


≔⁰ζ


Start with 0 steps.

ＷＫＫ«


While the pinball is on an l or an r, ...

≦⊕ζ


... increment the number of steps, and...

✳ι⁻lrι


... overwrite the current cell with the letter of lr that it isn't, moving the pinball in the appropriate direction.

»⎚Ｉζ


Clear the canvas and output the final number of steps.

• For comparison, the best I could do with an array manipulation method was 26 bytes and I came up with the ingenious idea of reflecting the canvas rather than the pinball direction but that was still 25 bytes.
– Neil
Commented Mar 7 at 0:16

# Python 3, 6460 56 bytes

-8 bytes thanks to Mukundan314.

f=lambda g,p:g[p]and-~f(g[:p]+[-g[p],*g[p+1:],0],p+g[p])


Try it online!

• g[:p]+[-g[p]]+g[p+1:] saves 4 bytes Commented Mar 6 at 5:49
• @Mukundan314 ...I don't wanna talk about -1*g[p]. >< Thanks! Commented Mar 6 at 5:54
• [*g,0][p] instead of 0<=p<len(g) saves 2 bytes Commented Mar 6 at 6:36
• @Mukundan314 Ooh, nice. Commented Mar 6 at 6:41
• 2 more bytes: f=lambda g,p:g[p]and-~f(g[:p]+[-g[p],*g[p+1:],0],p+g[p]) Commented Mar 6 at 6:45

# UiuaSBCS, 32 bytes

◌◌⍢(⊙⊙(+1)+⟜⊃⊡⍜⊡¯|↧≥0,>,⧻,)⊙⊙0


Try it!

Takes 0-indexed starting index as an integer and markers as a list of -1 for left and 1 for right.

Someone crush this answer, I beg of you. I tried the approach of padding the markers with a 0 at the front and back and taking a fixed point, but the best I could do was tie 32 bytes.

◌◌⍢(⊙⊙(+1)+⟜⊃⊡⍜⊡¯|↧≥0,>,⧻,)⊙⊙0
⊙⊙0  # store the count under inputs
⍢(             |        )     # while...
↧≥0,>,⧻,      # index is in-bounds...
⍜⊡¯               # negate at index
+⟜⊃⊡                  # and update index
⊙⊙(+1)                      # increment count
◌◌                              # pop markers and index


# PARI/GP, 35 bytes

f(s,g)=iferr(1+f(s-=g[s]*=-1,g),e,)


Attempt This Online!

The grid g is represented as a list of 1s and -1s. The starting position s is 1-indexed.

=sort(let(v,tocol(2:2,1),i,sequence(rows(v)),_,lambda(_,g,s,p,if(or(i=p),_(_,if(i=p,-g,g),s+1,p+index(g,p)),s)),_(_,v,,A3)))


Takes an array of -1 and +1 that represent < and >, and the starting position as a 1-indexed positive integer.

Based on JvdV's original Excel answer with some optimizations.

In Google Sheets, array iteration needs to be enabled by wrapping the formula in arrayformula(), sumproduct(), filter(), index(), sortn() or sort(). This formula uses sort() that manipulates data but that does not matter here because the final result is just one value.

# Perl 5-plF, 49 bytes

$i=<>;$\++,$i+=1-2*($F[$i]^=1)while$i>=0&&$i<@F}{  Try it online! Uses 0 for move right and 1 for move left. • Very nice solution and smart to change the input format! Another -1 if you use $_ instead of $i and check !/-/ instead of $i>=0: Try it online! Commented Mar 7 at 20:22
• Was thinking about this again and using -1 for > and 1 for < can save a few more bytes (1-2*(...^=1) => (...*=-1): Try it online! Commented Mar 8 at 9:20
• I had thought about that, but it didn't seem to me to fit the spirit of the challenge as -1 takes two characters. Commented Mar 8 at 14:58
• Yeah, fair enough. Looks like a few answers use the same, but the general approach doesn't really change. :) Nice answer nontheless! Commented Mar 8 at 16:15

# Wolfram Language (Mathematica), 69 bytes

1-indexed, markers are {-1,1}

({g,c}={##};t=0;While[0<c<=Tr[1^g],t++;c+=(o=g[[c]]);g[[c-o]]=-o];t)&


Try it online!

function F(G:String;P:Integer;C:Integer:=0)return Integer is(if P<0 or P>=G'Last then C else F(G(1..P)&(if G(P+1)='>'then'<'else'>')&G(P+2..G'Last),(if G(P+1)='>'then P+1 else P-1),C+1));


F is a recursive expression function that:

• Defaults C to 0, and uses it to count iterations
• Returns C if P (the position) is out of bounds
• Calls itself on an altered G array and an updated position
• TIO link - tio.run/… Commented Mar 7 at 2:05

# Ruby, 42 bytes

->s,g{1.step.find{g-=s[g]*=-1;g<0||!s[g]}}


Try it online!

# Jelly, 16 bytes

NJf©¥¦,®ị+ɗɗ/ƬL’


A monadic Link that accepts a pair, [State, Position] and yields the step count as an integer. State is a list of -1 for left and 1 for right; Position is the 1-indexed index at which the ball starts.

Try it online! Or see the test-suite (transforms from the <> and zero-index test cases).

### How?

NJf©¥¦,®ị+ɗɗ/ƬL’ - Link: pair, [State; Position]
ɗ/    -   reduce by last three links as a dyad - f(S=State, P=Position):
¦           -     apply to...
¥            -     ...indices: last two links as a dyad - f(S, P):
J               -                   indices of {S} -> [1,2,...,length(S)]
f              -                   filter keep {P} -> [P] if P is in bounds
[] otherwise
©             -                   (& copy this to the register)
N                -     ...action: negate -> Next_S
®         -       recall from the register
ị        -       {that} index into {S} (vectorises)
+       -       add {P} -> Next_P = [S[P]+P] if P is in bounds
[P] otherwise
,          -     {Next_S} pair {Next_P}
(the next collection all works with Next_P being
a singleton list, so no need to extract the value)
L  - length
’ - decrement


# Swift 5.9, 10488 86 bytes

Shaved off 10 bytes thanks to @RolandSchmitz's suggestion to use the ~= operator and to use [Int] instead of [Bool]; and an additional 6 bytes by shuffling types around and using implicit parameters.

Removed another 2 bytes thanks to another suggestion by @RolandSchmitz to use g.indices instead of 0..<g.count. (This works because the indices property is also a Range when used on an Array — if this wasn't the case, we wouldn't be able to use the pattern matching operator.)

let s={var n=0,g:[Int]=$0,i=$1+0
while g.indices~=i{g[i] *= -1
i-=g[i]
n+=1}
return n}


g is the grid; 1 corresponds to ">", and -1 corresponds to "<". i is the starting position as an Int, 0-indexed.

When f is called:

1. Three variables are created:

• n, which is simply a counter

• g, a mutable copy of the first implicit parameter ($0) This is where we put the type signature for $0 -- this allows us to have a type-less closure header, letting us dispense with the header altogether and use implicit closure parameters instead.

• i, similar in purpose to g (we add 0 to give the type checker a hint)

2. The closure loops until i ceases to be a valid index of g.

In Swift, the ~=(lhs:rhs:) operator is used internally for pattern matching (switch statements, if case, and the like). Here, we call it directly to take advantage of the overload defined by Range, which checks if rhs is contained in lhs.

Inside this loop, we:

1. flip the sign of g[I]

The whitespace around the *= operator is required so the compiler sees infix operator *= followed by prefix operator -, instead of the nonexistent infix operator *=-.

2. decrement i by g[i]

3. increment n

3. Once we're out of the loop, n has the number of iterations it took for i to go out of bounds. This is exactly what we want, so we just go ahead and return that.

• I don't really know swift but is +0 in i+0 necessary, if so curious to know the reason? Also, are spaces around = in g[i] = !g[i] and before ? required as well? Commented Mar 6 at 17:22
• @Mukundan314 The +0 is needed to help the compiler with type inference. There needs to be a space in between = and !, due to there being no =! operator; and since Swift requires infix operators to have equal whitespace on both sides (to differentiate them from prefix and postfix operators), the other space there is also needed. The space before ? is needed to make it clear that it's the ternary conditional operator and not an attempt to optional-chain on a non-Optional type. Commented Mar 6 at 20:07
• Could be improved a bit with an array containing 1 and -1 instead of true and false and with the ~= operator: let s={(g:[Int],i)in var n=0,g=g,i=i+0;while 0..<g.count~=i{g[i] *= -1;i-=g[i];n+=1};return n} Commented Mar 12 at 21:47
• @RolandSchmitz I honestly forgot the ~= operator even existed... thanks for the 10-byte save! Commented Mar 12 at 22:30
• You can save another 2 bytes by replacing the range with g.indices like that: let s={var n=0,g:[Int]=$0,i=$1+0;while g.indices~=i{g[i] *= -1;i-=g[i];n+=1};return n} Commented Mar 13 at 20:43

# C (gcc), 98965956 54 bytes

f(g,n,p)int*g;{return-1<p&p<n?f(g,n,p-=g[p]*=-1)+1:0;}


Try it online!

int f(int* g, int n, int p) {
return -1 < p & p < n ?
f(g, n, p -= (g[p] *= -1)) + 1 :
0;
}

• g is the grid with -1 corresponding to < and 1 to >.
• n is the length of the grid.
• p is the position of the pinball on the grid.

## Edits

-1 thanks to @noodle man

-1 by changing the data type of g from char* to int*

-37 thanks to @ceilingcat

-3 thanks to @Roland Schmitz

-2 thanks to @ceilingcat

• Welcome to Code Golf Stack Exchange! Commented Mar 10 at 15:46
• Can you add a Try it Online link so others can test out your code? Also, return f(g,n,p)+1 can be return-~f(g,n,p). Commented Mar 10 at 16:24
• Hello Johann, this can be simplified further if you use -1 for < with this: f(g,n,p)int*g;{return-1<p&p<n?f(g,n,p-=(g[p]*=-1))+1:0;} Commented Mar 13 at 21:11

# APL+WIN, 66, 64 bytes

2 bytes saved thanks to Mukundan

Prompts for grid followed by pinball position. ¯1 moves left and 1 moves right. Index origin = 1

g←⎕⋄p←⎕⋄n←0
:while ((p>0)^p≤⍴g)
g[p]←-g[p]⋄p←p-g[p]⋄n←n+1
:end
n


Try it online! Thanks to Dyalog Classic

• monadic - (aka negate) instead of ×¯1? Commented Mar 6 at 18:18
• @Mukundan314 Thanks. Edited Commented Mar 6 at 19:01

# PowerShell Core, 85 bytes

filter f{param($c,$s)&({$c},{2*($s[$_]=!$s[$_])-1+$_|f($c+1)$s})[$_+1-in1..$s.Count]}


Try it online!

Recursive filter
Takes an array of booleans

• true for <
• false for >

Returns an integer

### Explanations

param($c,$s)                                                    # As it is a filter, we have 3 parameters $_ the current position,$c the score, $s the pinball directions &({ },{ })[$_+1-in1..$s.Count] # If the ball position is outside of the array of directions$c                                                           # Return the count
($s[$_]=!$s[$_])                                      # Change the direction on the current ball position
2*                -1+$_|f($c+1)\$s                       # Increment the score by 1 and invoke the filter recursively on the new ball position


# Java 8, 7069 61 bytes

a->s->{int c=0;try{for(;;c++)s-=a[s]*=-1;}finally{return c;}}


-8 bytes thanks to a tip from @Neil of using -1,1 for <,> respectively, instead of 1,0.

Grid input as an array with -1 and 1 for < and > respectively.

Try it online.

Explanation:

a->s->{                 // Method with integer-array & integer parameters and integer
// return-type
int c=0;              //  Counter, starting at 0
try{for(;             //  Start an infinite loop:
;             //    After every iteration:
c++)         //     Increase the counter by 1
s-=                 //   Update position s based on the current s'th value:
a[s]*=-1;}       //    Invert the current s'th value (-1 to 1 and vice versa)
finally{              //  If an error occurs (ArrayOutOfBoundsException when s is
//  out of bounds):
return c;}}         //   Return the counter as result


The try{...}finally{...} is 1 byte shorter than a manual in-bounds check s>=0&s<a.length within the loop.

• Surely you can save 3 bytes by using -1 instead of 0 in the input?
– Neil
Commented Mar 8 at 0:19
• Make that 8 bytes.
– Neil
Commented Mar 8 at 0:22
• @Neil Good call. I completely forgot any inputs are allowed instead of just 0/1 bits. Thanks. Commented Mar 8 at 7:58

# TI-Basic, 40 bytes

Prompt L,P
While P and P≤dim(ʟL
~ʟL(P→ʟL(P
P-Ans→P
C+1→C
End
C


Takes input as a list (with 1 for > and ~1 for <) and a 1-indexed position. ~ represents the negative sign while - represents the subtraction symbol.