0

i am making a snake game in Raylib using C. I developed a function for handling collision for the snake and the apple (So that i could avoid 5 depth nesting which is quite unreadable). When the snake first picks up the apple, it does not grow, the other times it picks up the apple, it does grow. Why is this the case? Here is my code:

#include <math.h>
#include <raylib.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>

#define MAX_LEN 32768
#define TILE_SIZE 32
#define TILE_SPACING 4

float Distance(Vector2 v1, Vector2 v2) {
    float dx = v2.x - v1.x;
    float dy = v2.y - v1.y;
    return sqrt(dx * dx + dy * dy);
}
void CheckCollision(bool collision, Vector2* applePos, Vector2* snakePos,
                    int* snakeLength, Vector2 appleCenter) {
    // Handle collision with apple

    if (collision) {
        applePos->x = rand() % 1024 + 1;
        applePos->y = rand() % 1024 + 1;

        if (*snakeLength < MAX_LEN) {
            (*snakeLength)++;
            if (*snakeLength > 1) snakePos[*snakeLength] = snakePos[*snakeLength - 1];
        }
    } else {
        ClearBackground(BLACK);
    }
    for (int i = 0; i < *snakeLength - 1; i++) {
        Vector2 centerTile = {snakePos[i].x + TILE_SIZE / 2, snakePos[i].y + TILE_SIZE / 2};
        Vector2 snakeCenter = {snakePos[0].x + TILE_SIZE / 2, snakePos[0].y + TILE_SIZE / 2};
        float dis1 = Distance(centerTile, appleCenter);
        float dis2 = Distance(snakeCenter, centerTile);
        bool collision1 = dis1 < TILE_SIZE + TILE_SPACING;
        bool collision2 = dis2 < TILE_SIZE + TILE_SPACING;
        if (collision1) {
            applePos->x = rand() % 1024 + 1;
            applePos->y = rand() % 1024 + 1;
        }
    }
}
int main(void) {
    const uint16_t screenWidth = 1024;
    const uint16_t screenHeight = 1024;
    const Font font = GetFontDefault();
    char snake[MAX_LEN];
    int snakeLength = 1;
    srand(time(NULL));
    KeyboardKey keyPressed = KEY_W;  // Start with initial direction
    Vector2 snakePos[MAX_LEN];
    snakePos[0].x = 512 - TILE_SIZE / 2;
    snakePos[0].y = 512 - TILE_SIZE / 2;
    Vector2 applePos = {rand() % 1024 + 1, rand() % 1024 + 1};
    InitWindow(screenWidth, screenHeight, "Snake");
    SetTargetFPS(15);

    while (!WindowShouldClose()) {
        BeginDrawing();

        // Handle input and change direction
        if (IsKeyPressed(KEY_W) && keyPressed != KEY_S) keyPressed = KEY_W;
        if (IsKeyPressed(KEY_A) && keyPressed != KEY_D) keyPressed = KEY_A;
        if (IsKeyPressed(KEY_S) && keyPressed != KEY_W) keyPressed = KEY_S;
        if (IsKeyPressed(KEY_D) && keyPressed != KEY_A) keyPressed = KEY_D;

        // Move snake head
        if (keyPressed == KEY_W) snakePos[0].y -= TILE_SIZE + TILE_SPACING;
        if (keyPressed == KEY_A) snakePos[0].x -= TILE_SIZE + TILE_SPACING;
        if (keyPressed == KEY_S) snakePos[0].y += TILE_SIZE + TILE_SPACING;
        if (keyPressed == KEY_D) snakePos[0].x += TILE_SIZE + TILE_SPACING;

        // Update snake body positions
        for (int i = snakeLength - 1; i > 0; i--) {
            if (snakeLength > 1) snakePos[snakeLength] = snakePos[snakeLength - 1];
        }

        // Check collision with apple
        Vector2 snakeCenter = {snakePos[0].x + TILE_SIZE / 2, snakePos[0].y + TILE_SIZE / 2};
        Vector2 appleCenter = {applePos.x + TILE_SIZE / 2, applePos.y + TILE_SIZE / 2};
        float distance = Distance(snakeCenter, appleCenter);
        bool collision = distance <= TILE_SIZE + TILE_SPACING;
        CheckCollision(collision, &applePos, snakePos, &snakeLength, appleCenter);

        // Draw snake
        for (int i = 0; i < snakeLength; i++) {
            float tileX = snakePos[i].x;
            float tileY = snakePos[i].y;
            DrawRectangle(tileX, tileY, TILE_SIZE, TILE_SIZE, GREEN);
        }

        // Draw apple
        DrawRectangle(applePos.x, applePos.y, TILE_SIZE, TILE_SIZE, RED);

        EndDrawing();
    }

    CloseWindow();
    return 0;
}

In these sections of the code:

// Update snake body positions
for (int i = snakeLength - 1; i > 0; i--) {
    if (snakeLength > 1) snakePos[snakeLength] = snakePos[snakeLength - 1];
}

and

if (*snakeLength < MAX_LEN) {
     (*snakeLength)++;
     if (*snakeLength > 1) snakePos[*snakeLength] = snakePos[*snakeLength - 1];
}

instead of if (*snakeLength > 1) snakePos[*snakeLength] = snakePos[*snakeLength - 1]; it was,snakePos[*snakeLength] = snakePos[*snakeLength - 1]; previously, even more previously, it was if (*snakeLength > 1) snakePos[*snakeLength - 1] = snakePos[*snakeLength - 2];, and at first, it was snakePos[*snakeLength - 1] = snakePos[*snakeLength - 2];. I though this part of the program was the issue, it probably is, but i cant figure out whats the correct version of this code snippet. The very first version of this code snippet works the best by far, but the first time the snake picks up an apple, it still wont grow. The other version may display the second tile on the top-left corner, or do not make the snake grow at all when picking apples at any time.

EDIT: To make the task of fixing this issue easier, i provided a simpler code example, here it is:

All the libraries needed:

#include <math.h>
#include <raylib.h>
#include <stdint.h>

#define Variables:

#define MAX_LEN 32768
#define TILE_SIZE 32
#define TILE_SPACING 4

The collision checker, briefly outputs text, informing us that the snake collided with the apple:

void CheckCollision(bool collision) {
    if (collision) {
        DrawText("Collided!", 512, 512, 64, WHITE);
    } else {
        ClearBackground(BLACK);
    }
}

The main function:

int main(void) {
    // Initialize variables
    const uint16_t screenWidth = 1024;
    const uint16_t screenHeight = 1024;
    int snakeLength = 1;
    Vector2 snakePos[MAX_LEN];
    snakePos[0].x = 512 - TILE_SIZE / 2;
    snakePos[0].y = 512 - TILE_SIZE / 2;
    Vector2 applePos = {512, 600};
    InitWindow(screenWidth, screenHeight, "Snake");
    SetTargetFPS(15);
    while (!WindowShouldClose()) {
        BeginDrawing();
        snakePos[0].y += 50.0f; // Make the snake go down
        Vector2 snakeCenter = {snakePos[0].x + TILE_SIZE / 2, snakePos[0].y + TILE_SIZE / 2};
        Vector2 appleCenter = {applePos.x + TILE_SIZE / 2, applePos.y + TILE_SIZE / 2};
        float distance = sqrt(snakeCenter.x - appleCenter.x + snakeCenter.y - appleCenter.y);
        bool collision = distance <= TILE_SIZE + TILE_SPACING;
        CheckCollision(collision); // Check collision
        DrawRectangle(snakePos[0].x, snakePos[0].y, TILE_SIZE, TILE_SIZE, GREEN);
        DrawRectangle(applePos.x, applePos.y, TILE_SIZE, TILE_SIZE, RED);
        EndDrawing();
    }
    CloseWindow();
    return 0;
}

I also tested this out with input and output as well, and there seem to be random places where the text "Collided!" Appears? I do not know why this is the case, i tried figuring out why but i couldn't.

George
  • 101
  • 8
  • 1
    Could you make a more [mre] by removing all the input and output parts and only create a condition in which the snake will run over an apple and your observastion becomes visible in a variable? That will help you to focus your own debugging on the problem and by chance also make helpging you easier for other users. And if you can't it means that the problem IS in fact in input or output, while my current hunch is that it is in the logic... If it is not logic I'd look to the output immediatly at/after eating. – Yunnosch May 27 '23 at 12:00
  • Ill provide the code, i may misunderstand what you have just said, but hopefully i am doing something right. Based on my observation, i found out that: 1. Collision does happen, so that is not the reason for the bug 2. A new tile (or block or snake block, whatever you want to call it) actually does appear and the snake grows, but that new tile only appears very briefly, and its when you are colliding with the apple, in other moments, it disappears. I will show you a bit shorter example, ill edit my original question to show you. – George May 27 '23 at 12:22
  • Have you tried running your code line-by-line in a debugger while monitoring the control flow and the values of all variables, in order to determine in which line your program stops behaving as intended? If you did not try this, then you may want to read this: [What is a debugger and how can it help me diagnose problems?](https://stackoverflow.com/q/25385173/12149471) You may also want to read this: [How to debug small programs?](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/) – Andreas Wenzel May 28 '23 at 02:37
  • 1
    In addition to the advice mentioned by @Yunnosch, I suggest that you replace `srand(time(NULL));` with `srand(1);`. That way, the same sequence of random numbers will be generated each time you run your program, so that your program will behave the same way every time you run your program. This will make debugging easier. Note, however, that running the program on a different computer or platform may cause a different sequence of random numbers to be generated, even if the seed is the same. This is because the algorithm for generating random numbers may be different. – Andreas Wenzel May 28 '23 at 03:09
  • @AndreasWenzel I like the `srand(1)` for repeatable debugging purposes, in its charming contrast to the opposite in normal circumstances. It matches with other trickes I learned (e.g. `#error This file is indeed compiled` if in doubt) and it is so obvious after being told. I learned something new. Thank you. – Yunnosch May 28 '23 at 05:53

0 Answers0