-3 bytes thanks to @l4m2
Expects an array of
[dx,dy] pairs, e.g.
Try it online!
The algorithm used here is pretty straightforward.
Whenever we move in a direction \$(dx,dy)\$, we look for the first empty cell along this ray.
If we find one, we mark it as non-empty anymore.
Otherwise, we increment the total number of empty cells. In both cases, we mark the cell at our new position as empty.
Note that if we move to an empty cell, it will briefly be marked as non-empty and then marked as empty again, which is conceptually silly but functionally correct.
In order to make sure that we look far enough, the length of the test ray is simply the number of moves performed so far (including the current one).
a => // a = input array, re-used to store visited cells
a.map( // for each ...
([h, v], i) => // ... direction [h, v] at index i in a:
X = x, // initialize (X, Y) to (x, y)
Y = y //
]] = // mark this cell as visited (i.e. empty)
!( // by setting it to false
g = _ => // g is a recursive function ignoring its argument
[ // lookup key of the new position
X += h, // defined as (X + h, Y + v)
Y += v //
] // we force the state of this cell to non-empty
] ? // if it already was non-empty:
i-- ? // if i is not equal to 0 (decrement i afterwards):
g() // keep moving along this ray
: // else:
++n // increment the number n of empty cells and stop
: // else:
1 // stop
)( // initial call to g ...
x += h, // ... with (x, y) updated to (x + h, y + v)
y += v //
n = x = y = 0 // start with n = 0 and (x, y) = (0, 0)
) | n // end of map(); return n