5
\$\begingroup\$

I am total beginner in C and after reading K&R's chapter 5, I felt I was ready to at least make a simple game using C. I know linked-lists, so since I was making an implementation of Snake, why not?

main.h

#include <SDL2/SDL.h>

#ifndef MAIN
#define MAIN

#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480

SDL_Renderer *getRenderer();

void quit_game(void); 
void set_freeze(bool);

#endif

main.c

#include <stdio.h>
#include <stdbool.h>
#include "main.h"
#include "snake.h"
#include "apple.h"

void handle_events(SDL_Event* e);
void quit(void);

SDL_Window *window;
SDL_Renderer *renderer;

bool running = false;
bool frozen = false;

bool init(void){
    bool success = true;
    window = NULL;
    renderer = NULL;

    if(SDL_Init(SDL_INIT_VIDEO) < 0){
        printf("SDL could not be initiliazed. SDL_Error: %s\n", SDL_GetError());
        success = false;
    }

    window = SDL_CreateWindow("snake game", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
    if(!window){
        printf("SDL_Window could not be initialized. SDL_Error: %s\n", SDL_GetError());
        success = false;
    }
    else{
        renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);    
    }

    if(!init_snake()){
        printf("snake could not be initialized.\n");
        success = false;
    }

    generate_new_apple_pos();

    running = true;
    return success;
}

int main(int argc, char* args[])
{
    if(!init())
        return -1;
    else{
        SDL_Event e;

        while(running){
            handle_events(&e);

            if(frozen)
                continue;

            SDL_SetRenderDrawColor(renderer, 255, 255, 224, SDL_ALPHA_OPAQUE);
            SDL_RenderClear(renderer);

            update_snake();
            render_apple();

            SDL_RenderPresent(renderer);

            SDL_Delay(50);
        }
    }

    quit_game();
    return 0;
}

void handle_events(SDL_Event *e)
{
    while(SDL_PollEvent(e) != 0){
        if((*e).type == SDL_QUIT){
            running = false;
        }
        else if((*e).type == SDL_KEYDOWN){
            switch((*e).key.keysym.sym){
                case SDLK_RIGHT:
                    change_snake_direction(RIGHT);
                    break;
                case SDLK_LEFT:
                    change_snake_direction(LEFT);
                    break;
                case SDLK_UP:
                    change_snake_direction(UP);
                    break;
                case SDLK_DOWN:
                    change_snake_direction(DOWN);
                    break;
            }
        }
    }
}

void quit_game(void){
    SDL_DestroyWindow(window);
    window = NULL;

    SDL_DestroyRenderer(renderer);
    renderer = NULL;

    free_tails();
    SDL_Quit();
}

void set_freeze(bool b)
{
    frozen = b;
}

SDL_Renderer* getRenderer() { return renderer; }

snake.h

#include <SDL2/SDL.h>
#ifndef SNAKE
#define SNAKE

static const int DEFAULT_X = 500;
static const int DEFAULT_Y = 10;
static const int DEFAULT_WIDTH = 20;
static const int DEFAULT_HEIGHT = 20;

static const int DEFAULT_TAILS_N = 10;

struct TailNode{
    SDL_Rect rect;
    struct TailNode *next;
    struct TailNode *previous;
};

struct Snake{
    int dx;
    int dy;
    int size;
    struct TailNode head;
};

enum direction{LEFT, RIGHT, UP, DOWN};

bool init_snake(void);
void update_snake(void);
void change_snake_direction(int);
void free_tails(void);

#endif

snake.c

#include <stdio.h>
#include <stdbool.h>
#include <math.h>
#include "snake.h"
#include "main.h"
#include "apple.h"

struct Snake snake;
struct TailNode *lasttail;

void push_tail();

bool init_snake()
{
    // default direction
    snake.dx = -1;
    snake.dy = 0;

    // initializes head
    snake.head.rect.x = DEFAULT_X;
    snake.head.rect.y = DEFAULT_Y;
    snake.head.rect.w = DEFAULT_WIDTH;
    snake.head.rect.h = DEFAULT_HEIGHT;
    snake.head.next = NULL;
    snake.head.previous = NULL;

    // sets pointer of last tail to head
    lasttail = &snake.head;

    // pushes default tails
    for(int i = 0; i < DEFAULT_TAILS_N; ++i)
        push_tail();

    return true;
}


void render_tail(SDL_Rect *tail)
{   // renders individual parts of the snake
    SDL_SetRenderDrawColor(getRenderer(), 204, 175, 175, SDL_ALPHA_OPAQUE);
    SDL_RenderFillRect(getRenderer(), tail);
}

void check_collision()
{
    // fruit collision
    if(abs(snake.head.rect.x - get_apple_posX()) < DEFAULT_WIDTH && abs(snake.head.rect.y - get_apple_posY()) < DEFAULT_HEIGHT){
        push_tail();
        generate_new_apple_pos();
    }

    // border collision
    if(snake.head.rect.x > SCREEN_WIDTH - DEFAULT_WIDTH)
        snake.head.rect.x = 0;
    else if(snake.head.rect.x < 0 - DEFAULT_WIDTH)
        snake.head.rect.x = SCREEN_WIDTH;
    else if(snake.head.rect.y < 0 - DEFAULT_HEIGHT)
        snake.head.rect.y = SCREEN_HEIGHT;
    else if(snake.head.rect.y > SCREEN_HEIGHT - DEFAULT_HEIGHT)
        snake.head.rect.y = 0;
}

void update_snake(void)
{   // iterates over the head and the tail
    for(struct TailNode *ptr = lasttail; ptr != NULL; ptr = (*ptr).previous){
        if((*ptr).previous == NULL){ // in other words, if this "tail" is the head
            snake.head.rect.x += snake.dx * DEFAULT_WIDTH;
            snake.head.rect.y += snake.dy * DEFAULT_HEIGHT;
        }else{ // if it's the snake's body
            if(abs(snake.head.rect.x - (*ptr).rect.x) < DEFAULT_WIDTH && // checks collision with the head
               abs(snake.head.rect.y - (*ptr).rect.y) < DEFAULT_HEIGHT)
                set_freeze(true);

            (*ptr).rect.x = (*ptr).previous->rect.x;
            (*ptr).rect.y = (*ptr).previous->rect.y;
        }

        render_tail(&(*ptr).rect);
    }

    check_collision(); // head-only collision (fruit, border, etc.)
}

void push_tail()
{   // pushes a new tail inside the linked list
    struct TailNode *new_tail = malloc(sizeof(struct TailNode));
    if(new_tail == NULL) 
        quit_game();

    (*new_tail).rect.x = (*lasttail).rect.x + 30;
    (*new_tail).rect.y = (*lasttail).rect.y;
    (*new_tail).rect.w = DEFAULT_WIDTH;
    (*new_tail).rect.h = DEFAULT_HEIGHT;

    (*new_tail).next = NULL;
    (*new_tail).previous = lasttail;

    (*lasttail).next = new_tail;
    lasttail = new_tail;
}

void change_snake_direction(int dir)
{
    if(dir == RIGHT && snake.dx != -1){
        snake.dx = 1;
        snake.dy = 0;
    }
    else if(dir == LEFT && snake.dx != 1){
        snake.dx = -1;
        snake.dy = 0;
    }
    else if(dir == UP && snake.dy != 1){
        snake.dy = -1;
        snake.dx = 0;
    }
    else if(dir == DOWN && snake.dy != -1){
        snake.dy = 1;
        snake.dx = 0;
    }
}

void free_tails()
{
    struct TailNode *tmp;
    struct TailNode *secondtail;
    secondtail = snake.head.next; // we skip the first node (head) because it's allocated in the stack

    while(secondtail != NULL){
        tmp = secondtail;
        secondtail = (*secondtail).next;
        free(tmp);
    }
}

apple.h

#ifndef APPLE
#define APPLE

static const int DEFAULT_APPLE_WIDTH = 20;
static const int DEFAULT_APPLE_HEIGHT = 20;

void render_apple(void);
void generate_new_apple_pos(void);

int get_apple_posX(void);
int get_apple_posY(void);

#endif

apple.c

#include <SDL2/SDL.h>
#include <stdbool.h>
#include "main.h"
#include "apple.h"

SDL_Rect apple;

void generate_new_apple_pos(void);

void render_apple()
{
    SDL_SetRenderDrawColor(getRenderer(), 226, 106, 106, SDL_ALPHA_OPAQUE);
    SDL_RenderFillRect(getRenderer(), &apple);
}

void generate_new_apple_pos(void)
{
    apple.x = (rand() % (SCREEN_WIDTH - 0 + 1));
    apple.y = (rand() % (SCREEN_HEIGHT - DEFAULT_APPLE_HEIGHT + 1));
    apple.w = DEFAULT_APPLE_WIDTH;
    apple.h = DEFAULT_APPLE_HEIGHT;
}

int get_apple_posX(void)
{
    return apple.x;
}
int get_apple_posY(void)
{
    return apple.y;
}

ANY suggestions and tips to better this code are welcome, I'm trying to improve my fundamentals so I can develop bigger projects in C.

A few questions though:

  1. Am I using headers correctly? Or is it a mess? What can I do to improve the organization?

  2. Is my implementation of a linked list alright?

  3. The snake's dx and dy values are either 1, 0, or -1. Should I make it a short instead? Or even char?

  4. How can I make struct TailNode private to snake.c?

  5. When do I use static const over macro #defines? Do I use static const when I want my variable private?

Thanks!

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Running but failed?

running = true;
return success;

In all cases you set running to true even if success is false. This seems like an odd decision. Also, you init_snake regardless of the success or failure of previous calls, which does not seem correct. You should be early-bailing in such circumstances.

Main's early-return

After this:

if(!init())
    return -1;

your else is redundant and can be removed.

Structural dereferencing

(*e).type

should be

e->type

Similarly for (*ptr).previous, (*new_tail), etc.

const arguments

You do not modify e in void handle_events(SDL_Event *e). As long as it does not cause callback pointer type incompatibility issues, consider making that argument const SDL_Event *e.

That aside: why are you passing in e at all? The way it is written now, e should just be a local variable.

Non-exported functions

For functions like init that are not intended for export to other translation units, mark them static. The same is true for any global variables that will not be referenced in other translation units.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.