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:
Am I using headers correctly? Or is it a mess? What can I do to improve the organization?
Is my implementation of a linked list alright?
The
snake
'sdx
anddy
values are either 1, 0, or -1. Should I make it ashort
instead? Or evenchar
?How can I make
struct TailNode
private tosnake.c
?When do I use
static const
over macro#define
s? Do I usestatic const
when I want my variable private?
Thanks!