Hash table with complex variable-length keys
So I'm looking for the go-to solution for situation like this, if one exists


Big step, small step. Rather than run the snake once, you run it twice in parallel, but the two instances run at different speeds:

snake1 = ...;
snake2 = snake1.clone();
while (true) {
    snake1.step();
    snake2.step();
    snake2.step();
    if (snake1.equals(snake2)) {
       // Loop detected
    }
}


If the cycle is of length \$n\$ and you enter the cycle for the first time at \$t_0\$ then this will detect the cycle as long as there's a number \$t \ge t_0\$ for which \$t \equiv 2t \pmod n\$. But that reduces to \$t \equiv 0 \pmod n\$, so \$t\$ is the first multiple of \$n\$ greater than or equal to \$t_0\$, and it's guaranteed to find the loop.

Essentially you trade memory saving (no history required) for execution time (it takes \$3t\$ calls to step() rather than \$t_0 + n\$).



That renders rather unnecessary the following observation:


    const arr = [this.direction.hash(), this.pallet.hash(), this.head.hash(), ...this.body.map(x =&gt; x.hash())];



Most of those don't need to use a Vector2 with a hash: there are 4 possibilities for the direction, and the body can be represented by a series of directions. Using arrays of length 4 might be more efficient than object properties.