Архитектурный вопрос, когда есть удобная структура змейки её легко переиспользовать где удобно. И не остаётся ошибок на переполнение контейнера.
Банально заменив игровое поле на условно бесконечное / какое нибудь процедурно генерируемое, нам не потребуется лезть в код змейки и поддерживать его в новых условиях.
Ну... Это уже другая игра... В принципе, можно написать и так, но в целом твоя идея
do_move(int dx, int dy)
{
struct chain * prev_head = head;
erase(tail->x, tail->y);
head = tail;
tail = tail->next;
prev_head->next = head;
head->x = prev_head->x + dx;
head->y = prev_head->y + dy;
draw(head->x, head->y);
}
выглядит несколько более сложно для понмания и для написания (ИМХО), чем
void do_move(int dx, int dy)
{
int next_head = (head + 1) % SLEN;
boa[next_head].x = boa[head].x + dx;
boa[next_head].y = boa[head].y + dy;
head = next_head;
draw(boa[head].x, boa[head].y);
erase(boa[tail].x, boa[tail].y);
tail = (tail + 1) % SLEN;
}