Skip to content

Instantly share code, notes, and snippets.

@peterhellberg
Last active October 1, 2024 13:13
Show Gist options
  • Save peterhellberg/124fb025981b9b167f942c39b87e6624 to your computer and use it in GitHub Desktop.
Save peterhellberg/124fb025981b9b167f942c39b87e6624 to your computer and use it in GitHub Desktop.
AI Snake implementation based on https://www.youtube.com/watch?v=_UR3bi5xyaY
#include "aisnake.h"
#define WINDOW_X 1280
#define WINDOW_Y 0
#define WINDOW_W 1280
#define WINDOW_H 1440
#define DELAY 20
#define DIMS 1000
#define GRID 40
const int SIZE = DIMS / GRID;
typedef enum {
DIR_UP,
DIR_DOWN,
DIR_LEFT,
DIR_RIGHT,
} Dir;
typedef enum {
TRY_FORWARD,
TRY_LEFT,
TRY_RIGHT,
} Try;
void render_grid(SDL_Renderer *renderer, int x, int y) {
SDL_Rect cell;
cell.w = SIZE;
cell.h = SIZE;
SDL_SetRenderDrawColor(renderer, 0x22, 0x22, 0x22, 0xFF);
for (int i = 0; i < GRID; i++) {
for (int j = 0; j < GRID; j++) {
cell.x = x + (i * SIZE);
cell.y = y + (j * SIZE);
SDL_RenderDrawRect(renderer, &cell);
}
}
}
struct snake {
int x;
int y;
int d;
struct snake *n;
};
typedef struct snake Snake;
Snake *head;
Snake *tail;
void init_snake() {
Snake *new = malloc(sizeof(Snake));
new->x = rand() % (GRID / 2) + (GRID / 4);
new->y = rand() % (GRID / 2) + (GRID / 4);
new->d = (Dir)rand() % 3;
new->n = NULL;
head = new;
tail = new;
}
void grow_snake() {
Snake *new = malloc(sizeof(Snake));
switch (tail->d) {
case DIR_UP:
new->x = tail->x;
new->y = tail->y + 1;
break;
case DIR_DOWN:
new->x = tail->x;
new->y = tail->y - 1;
break;
case DIR_LEFT:
new->x = tail->x + 1;
new->y = tail->y;
break;
case DIR_RIGHT:
new->x = tail->x - 1;
new->y = tail->y;
break;
}
new->d = tail->d;
new->n = NULL;
tail->n = new;
tail = new;
}
void move_snake() {
int old_x = head->x;
int old_y = head->y;
int old_d = head->d;
switch (head->d) {
case DIR_UP:
head->y--;
break;
case DIR_DOWN:
head->y++;
break;
case DIR_LEFT:
head->x--;
break;
case DIR_RIGHT:
head->x++;
break;
}
Snake *seg = head;
if (seg->n != NULL) {
seg = seg->n;
}
while (seg != NULL) {
int seg_x = seg->x;
int seg_y = seg->y;
int seg_d = seg->d;
seg->x = old_x;
seg->y = old_y;
seg->d = old_d;
seg = seg->n;
old_x = seg_x;
old_y = seg_y;
old_d = seg_d;
}
}
void draw_snake(SDL_Renderer *renderer, int x, int y) {
SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF);
SDL_Rect cell;
cell.w = SIZE;
cell.h = SIZE;
Snake *seg = head;
while (seg != NULL) {
cell.x = x + seg->x * SIZE;
cell.y = y + seg->y * SIZE;
SDL_RenderFillRect(renderer, &cell);
seg = seg->n;
}
}
void reset_snake() {
Snake *seg = head;
Snake *tmp;
while (seg != NULL) {
tmp = seg;
seg = seg->n;
free(tmp);
}
init_snake();
grow_snake();
grow_snake();
}
typedef struct {
int x;
int y;
} apple;
apple Apple;
void init_apple() {
bool in_snake;
do {
in_snake = false;
Apple.x = rand() % GRID;
Apple.y = rand() % GRID;
Snake *seg = head;
while (seg != NULL) {
if (seg->x == Apple.x && seg->y == Apple.y) {
in_snake = true;
}
seg = seg->n;
}
} while (in_snake);
}
void draw_apple(SDL_Renderer *renderer, int x, int y) {
SDL_Rect cell;
cell.w = SIZE;
cell.h = SIZE;
cell.x = x + Apple.x * SIZE;
cell.y = y + Apple.y * SIZE;
SDL_SetRenderDrawColor(renderer, 0xFF, 0x00, 0x00, 0xFF);
SDL_RenderFillRect(renderer, &cell);
}
void detect_apple() {
if (head->x == Apple.x && head->y == Apple.y) {
init_apple();
grow_snake();
}
}
void detect_crash() {
if (head->x < 0 || head->x >= GRID || head->y < 0 || head->y >= GRID) {
return reset_snake();
}
Snake *seg = head;
if (seg->n != NULL) {
seg = seg->n;
}
while (seg != NULL) {
if (seg->x == head->x && seg->y == head->y) {
return reset_snake();
}
seg = seg->n;
}
}
void turn_left() {
switch (head->d) {
case DIR_UP:
head->d = DIR_LEFT;
break;
case DIR_DOWN:
head->d = DIR_RIGHT;
break;
case DIR_LEFT:
head->d = DIR_DOWN;
break;
case DIR_RIGHT:
head->d = DIR_UP;
break;
}
}
void turn_right() {
switch (head->d) {
case DIR_UP:
head->d = DIR_RIGHT;
break;
case DIR_DOWN:
head->d = DIR_LEFT;
break;
case DIR_LEFT:
head->d = DIR_UP;
break;
case DIR_RIGHT:
head->d = DIR_DOWN;
break;
}
}
int state(int try) {
int reward = 0;
int try_x = head->x;
int try_y = head->y;
switch (head->d) {
case DIR_UP:
switch (try) {
case TRY_FORWARD:
try_y--;
break;
case TRY_LEFT:
try_x--;
break;
case TRY_RIGHT:
try_x++;
break;
}
break;
case DIR_DOWN:
switch (try) {
case TRY_FORWARD:
try_y++;
break;
case TRY_LEFT:
try_x++;
break;
case TRY_RIGHT:
try_x--;
break;
}
break;
case DIR_LEFT:
switch (try) {
case TRY_FORWARD:
try_x--;
break;
case TRY_LEFT:
try_y++;
break;
case TRY_RIGHT:
try_y--;
break;
}
break;
case DIR_RIGHT:
switch (try) {
case TRY_FORWARD:
try_x++;
break;
case TRY_LEFT:
try_y--;
break;
case TRY_RIGHT:
try_y++;
break;
}
break;
}
// Detect walls
if (try_x < 0 || try_x > GRID - 1) {
reward += -100;
}
if (try_y < 0 || try_y > GRID - 1) {
reward += -100;
}
// Detect apple
if (try_x == Apple.x && try_y == Apple.y) {
reward += 500;
}
// Move towards apple
int diff_x = abs(head->x - Apple.x);
int diff_y = abs(head->y - Apple.y);
int try_diff_x = abs(try_x - Apple.x);
int try_diff_y = abs(try_y - Apple.y);
if (try_diff_x < diff_x || try_diff_y < diff_y) {
reward += 1;
}
// Detect tail
Snake *seg = head;
if (seg->n != NULL) {
seg = seg->n;
}
while (seg != NULL) {
if (seg->x == try_x && seg->y == try_y) {
reward += -100;
}
seg = seg->n;
}
return reward;
}
void ai() {
int try_f = state(TRY_FORWARD);
int try_l = state(TRY_LEFT);
int try_r = state(TRY_RIGHT);
if (try_f >= try_l && try_f >= try_r) {
return; // Continue forward
}
if (try_l > try_r) {
turn_left();
} else {
turn_right();
}
}
int main() {
srand(time(NULL));
reset_snake();
init_apple();
SDL_Window *window;
SDL_Renderer *renderer;
if (SDL_INIT_VIDEO < 0) {
fprintf(stderr, "ERROR: %s\n", "SDL_INIT_VIDEO");
return 1;
}
window = SDL_CreateWindow("Snake", WINDOW_X, WINDOW_Y, WINDOW_W, WINDOW_H,
SDL_WINDOW_BORDERLESS);
if (!window) {
fprintf(stderr, "ERROR: %s\n", "!window");
return 1;
}
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {
fprintf(stderr, "ERROR: %s\n", "!renderer");
return 1;
}
int grid_x = (WINDOW_W / 2) - (DIMS / 2);
int grid_y = (WINDOW_H / 2) - (DIMS / 2);
bool quit = false;
SDL_Event event;
while (!quit) {
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
quit = true;
break;
case SDL_KEYUP:
break;
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_ESCAPE:
quit = true;
break;
case SDLK_q:
quit = true;
break;
case SDLK_z:
turn_left();
break;
case SDLK_x:
turn_right();
break;
case SDLK_UP:
head->d = DIR_UP;
break;
case SDLK_DOWN:
head->d = DIR_DOWN;
break;
case SDLK_LEFT:
head->d = DIR_LEFT;
break;
case SDLK_RIGHT:
head->d = DIR_RIGHT;
break;
}
break;
}
}
move_snake();
detect_apple();
detect_crash();
ai();
SDL_RenderClear(renderer);
{ // Render loop start
render_grid(renderer, grid_x, grid_y);
draw_snake(renderer, grid_x, grid_y);
draw_apple(renderer, grid_x, grid_y);
} // Render loop end
SDL_SetRenderDrawColor(renderer, 0x11, 0x11, 0x11, 0xFF);
SDL_RenderPresent(renderer);
SDL_Delay(DELAY);
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
#include <stdbool.h>
#include <time.h>
// Graphics
#include <SDL.h>
-Wall
-Wextra
-Werror
-I
/usr/include/SDL2/
CC=zig cc
CFLAGS = -Wall -Wextra -Werror
INCLUDES = -I/usr/include/SDL2/
LIBS = -lSDL2
SRCS = aisnake.c
OBJS = $(SRCS:.c=.o)
MAIN = aisnake
$(MAIN): $(OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $(MAIN) $(OBJS) $(LIBS)
.c.o:
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
clean:
$(RM) *.o *~ $(MAIN)
.PHONY:
run:
killall aisnake; make && ./aisnake
@peterhellberg
Copy link
Author

Screenshot from 2024-09-28 15-26-40

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment