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!

asked Apr 22, 2020 at 4:50
\$\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.

answered Apr 22, 2020 at 17:33
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.