Проблема, вероятно, заключается в том, что направление изменяется дважды до обновления формы змеи, и поэтому первое из этих двух изменений направления фактически игнорируется.
Способ преодолеть это состоит в том, чтобы буферизовать изменения направления в очереди (реализовано как массив).
Таким образом, в вашем ключевом обработчике событий вы бы этого не сделали:
if (snake.direction != 1)
snake.direction = 3;
Но вместо этого:
if ((snake.queue.length ? snake.queue[0] : snake.direction) != 1)
snake.queue.unshift(3);
Эта очередь должна быть инициализирована в конструкторе Snake
:
this.queue = [];
Затем, когда вы обновляете положение змеи (через определенный промежуток времени), вы потребляете эту очередь, если в ней что-то есть:
if (snake.queue.length)
snake.direction = snake.queue.pop();
// Now apply this direction:
// ... this would be code you already have...
Вы можете установить максимум для этого размера очереди, так как он может стать неловким, если пользователь продолжает нажимать клавиши быстрее, чем обновления змеи.