Skip to content

Instantly share code, notes, and snippets.

@SocketByte
Created January 15, 2022 14:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SocketByte/4330574d1b3c4c3b4469729bdee4384a to your computer and use it in GitHub Desktop.
Save SocketByte/4330574d1b3c4c3b4469729bdee4384a to your computer and use it in GitHub Desktop.
Simple bare-bones C snake implementation for POSIX.
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
/* Base configuration definitions to avoid magic values */
#define RESOLUTION 40
#define BORDER_FRAME '#'
#define BORDER_WIDTH 1
#define GRID_WIDTH ((RESOLUTION / 2) - BORDER_WIDTH)
#define GRID_HEIGHT ((RESOLUTION / 2) - BORDER_WIDTH)
#define DISPLAY_OFFSET_X 3
#define DISPLAY_OFFSET_Y 2
#define HEAD 'O'
#define BODY 'o'
#define FOOD '*'
#define BOMB '@'
#define TIME '$'
/* Color definitions */
#define COLOR_NC "0"
#define COLOR_BLACK "0;30"
#define COLOR_RED "0;31"
#define COLOR_GREEN "0;32"
#define COLOR_YELLOW "0;33"
#define COLOR_BLUE "0;34"
#define COLOR_MAGENTA "0;35"
#define COLOR_CYAN "0;36"
#define COLOR_WHITE "0;37"
#define COLOR_LIGHT_GREEN "1;32"
#define COLOR_LIGHT_RED "1;31"
/* Key ASCII hex codes */
#define KEY_ESC 0x1B
#define KEY_W 0x77
#define KEY_A 0x61
#define KEY_S 0x73
#define KEY_D 0x64
/* Global game states */
static int SCORE = 0;
static int HIGH_SCORE = 0;
/* A simple grid buffer structure that holds pixel character data */
static char GRID[GRID_WIDTH][GRID_HEIGHT];
/* A simple grid buffer structure that holds pixel color data */
static const char* GRID_COLORS[GRID_WIDTH][GRID_HEIGHT];
/* Keyboard polling utilities */
static void
set_non_canonical_mode(void);
static int
poll_key(void);
/* Screen buffer and graphics utilities */
static void
set_cursor_position(int x, int y);
static void
set_cursor_state(int state);
static void
set_color(const char* color);
static void
set_data_text(const char* text, ...);
static void
put_char(int x, int y, const char* color, char c);
static void
put_grid(int x, int y, const char* color, char c);
static void
clear_grid(void);
static void
clear_screen(void);
static void
put_top_border(void);
static void
put_bottom_border(void);
static void
put_left_right_border(void);
/* Spawners */
static void
random_xy(int* x, int* y);
static void
spawn_food(void);
static void
spawn_bomb(void);
static void
spawn_time(void);
/* Handlers */
static void
handle_interrupt(int sig);
static void
handle_game(float* time_modifier);
/* Turns the terminal into non-canonical (raw) mode */
/* This makes the terminal stop echoing typed characters */
static void
set_non_canonical_mode(void)
{
struct termios term;
tcgetattr(STDIN_FILENO, &term);
term.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &term);
}
/* A simple utility for polling keys */
/* Returns an ASCII keycode or EOF if none was available */
static int
poll_key(void)
{
struct pollfd pfds[1];
int available;
char key;
pfds[0].fd = 0;
pfds[0].events = POLLIN;
available = poll(pfds, 1, 0);
if (available > 0) {
read(0, &key, 1);
return key;
}
return EOF;
}
static void
set_cursor_position(int x, int y)
{
printf("\033[%d;%dH", y, x);
}
static void
set_cursor_state(int state)
{
printf("\033[?25%c", state ? 'h' : 'l');
}
static void
set_color(const char* color)
{
printf("\e[%sm", color);
}
static void
set_data_text(const char* text, ...)
{
va_list args;
va_start(args, text);
set_cursor_position(0, RESOLUTION / 2 + 2);
vprintf(text, args);
va_end(args);
}
static void
put_char(int x, int y, const char* color, char c)
{
set_cursor_position(x, y);
set_color(color);
printf("%c", c);
}
static void
put_grid(int x, int y, const char* color, char c)
{
GRID[x][y] = c;
GRID_COLORS[x][y] = color;
}
static void
clear_grid(void)
{
for (int i = 0; i < GRID_WIDTH; i++) {
for (int j = 0; j < GRID_HEIGHT; j++) {
put_grid(i, j, COLOR_BLACK, ' ');
}
}
}
static void
clear_screen(void)
{
printf("\033[2J");
}
static void
put_top_border(void)
{
for (int i = 1; i <= RESOLUTION; i += 2) {
put_char(i, 0, COLOR_WHITE, BORDER_FRAME);
}
}
static void
put_bottom_border(void)
{
for (int i = 1; i <= RESOLUTION + 1; i += 2) {
put_char(i, RESOLUTION / 2 + 1, COLOR_WHITE, BORDER_FRAME);
}
}
static void
put_left_right_border(void)
{
for (int i = 1; i <= RESOLUTION / 2; i++)
put_char(1, i, COLOR_WHITE, BORDER_FRAME);
for (int i = 1; i <= RESOLUTION / 2; i++)
put_char(RESOLUTION + 1, i, COLOR_WHITE, BORDER_FRAME);
}
static void
random_xy(int* x, int* y)
{
do {
*x = rand() % GRID_WIDTH;
*y = rand() % GRID_HEIGHT;
} while (GRID[*x][*y] != ' ');
}
static void
spawn_food(void)
{
int x, y;
random_xy(&x, &y);
put_grid(x, y, COLOR_YELLOW, FOOD);
}
static void
spawn_bomb(void)
{
int x, y;
random_xy(&x, &y);
put_grid(x, y, COLOR_RED, BOMB);
}
static void
spawn_time(void)
{
int x, y;
random_xy(&x, &y);
put_grid(x, y, COLOR_BLUE, TIME);
}
static void
handle_interrupt(int sig)
{
set_cursor_state(1);
clear_screen();
set_color(COLOR_LIGHT_RED);
printf("\nGame over!\n");
set_color(COLOR_WHITE);
printf("Your score: %d\n", SCORE);
printf("Your high score: %d\n\n", HIGH_SCORE);
exit(sig);
}
static void
handle_game(float* time_modifier)
{
static int x_head = GRID_WIDTH / 2;
static int y_head = GRID_HEIGHT / 2;
static int x_dir = -1;
static int y_dir = 0;
static int body_length = 0;
static int x_body[GRID_WIDTH * GRID_HEIGHT];
static int y_body[GRID_WIDTH * GRID_HEIGHT];
static int x_tail, y_tail;
static int x_food, y_food;
static int bomb_body_radius = 1;
static int bomb_exists = 0;
static int food_amount_on_map = 1;
int key = poll_key();
if (key != EOF) {
switch (key) {
case KEY_ESC:
handle_interrupt(0);
break;
case KEY_W:
x_dir = 0;
y_dir = -1;
break;
case KEY_A:
x_dir = -1;
y_dir = 0;
break;
case KEY_S:
x_dir = 0;
y_dir = 1;
break;
case KEY_D:
x_dir = 1;
y_dir = 0;
break;
}
}
x_head += x_dir;
y_head += y_dir;
if (x_head < 0)
x_head = GRID_WIDTH - 1;
else if (x_head >= GRID_WIDTH)
x_head = 0;
if (y_head < 0)
y_head = GRID_HEIGHT - 1;
else if (y_head >= GRID_HEIGHT)
y_head = 0;
if (GRID[x_head][y_head] == BODY) {
x_head = GRID_WIDTH / 2;
y_head = GRID_HEIGHT / 2;
HIGH_SCORE = SCORE > HIGH_SCORE ? SCORE : HIGH_SCORE;
SCORE = 0;
body_length = 0;
bomb_exists = 0;
food_amount_on_map = 1;
*time_modifier = 1.0;
clear_grid();
spawn_food();
return;
}
/* Set the tail end to the first body position */
x_tail = x_body[0];
y_tail = y_body[0];
/* Move the body */
for (int i = 0; i < body_length; i++) {
x_body[i] = x_body[i + 1];
y_body[i] = y_body[i + 1];
}
if (GRID[x_head][y_head] == FOOD) {
food_amount_on_map--;
body_length++;
SCORE++;
*time_modifier += 0.007f;
if (food_amount_on_map == 0) {
spawn_food();
food_amount_on_map++;
// 20% chance
if (rand() % 5 == 0) {
spawn_food();
food_amount_on_map++;
}
}
if (body_length > bomb_body_radius * 3) {
if (!bomb_exists) {
spawn_bomb();
bomb_exists = 1;
}
bomb_body_radius++;
}
if (body_length > 10 && rand() % 7 == 0) {
spawn_time();
}
}
if (GRID[x_head][y_head] == BOMB) {
for (int i = 0; i < bomb_body_radius; i++)
put_grid(x_body[i], y_body[i], COLOR_BLACK, ' ');
for (int i = bomb_body_radius; i < body_length; i++) {
x_body[i - bomb_body_radius] = x_body[i];
y_body[i - bomb_body_radius] = y_body[i];
}
body_length -= bomb_body_radius;
bomb_exists = 0;
}
if (GRID[x_head][y_head] == TIME) {
*time_modifier -= 0.03;
}
/* Set the top body position to head */
x_body[body_length] = x_head;
y_body[body_length] = y_head;
/* Draw everything */
for (int i = 0; i < body_length; i++) {
put_grid(x_body[i], y_body[i], COLOR_GREEN, BODY);
}
put_grid(x_head, y_head, COLOR_LIGHT_GREEN, HEAD);
put_grid(x_tail, y_tail, COLOR_BLACK, ' ');
set_data_text("SCORE: %d, %d", SCORE, body_length);
set_data_text("\nTIME: x%.2f", *time_modifier);
}
int
main(void)
{
static float time_modifier = 1.0;
srand(time(NULL));
set_non_canonical_mode();
signal(SIGINT, handle_interrupt);
clear_grid();
clear_screen();
spawn_food();
for (;;) {
handle_game(&time_modifier);
set_cursor_state(0);
put_top_border();
put_left_right_border();
for (int x = 0; x < GRID_WIDTH; x++) {
for (int y = 0; y < GRID_HEIGHT; y++) {
put_char(x * 2 + DISPLAY_OFFSET_X,
y + DISPLAY_OFFSET_Y,
GRID_COLORS[x][y],
GRID[x][y]);
}
}
put_bottom_border();
usleep(120000 / time_modifier);
}
handle_interrupt(0);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment