Last active
October 1, 2024 13:13
-
-
Save peterhellberg/124fb025981b9b167f942c39b87e6624 to your computer and use it in GitHub Desktop.
AI Snake implementation based on https://www.youtube.com/watch?v=_UR3bi5xyaY
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdbool.h> | |
#include <time.h> | |
// Graphics | |
#include <SDL.h> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-Wall | |
-Wextra | |
-Werror | |
-I | |
/usr/include/SDL2/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Author
peterhellberg
commented
Sep 28, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment