Skip to content

Instantly share code, notes, and snippets.

@Serranya
Last active August 29, 2015 14:04
Show Gist options
  • Save Serranya/7eccdc6e4cf88abc1683 to your computer and use it in GitHub Desktop.
Save Serranya/7eccdc6e4cf88abc1683 to your computer and use it in GitHub Desktop.
Snake in C with ncurses.
//POSIX
#include <limits.h>
//C Standard Library
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
//Third Party
#include <ncurses.h>
enum DIR {
TOP,
LEFT,
RIGHT,
DOWN
};
struct BodyPart {
struct BodyPart *next;
int x, y;
enum DIR dir;
bool hasFoodInside;
};
struct Food {
int x,y;
};
struct GameState {
struct BodyPart *head;
struct Food *food;
FILE *rng;
unsigned char speed;
};
static const char foodChar = '!';
static char headChars[] = {
'^',
'<',
'>',
'v'
};
static char bodyChars[] = {
'H',
'=',
'=',
'H'
};
void mainLoop(void);
struct GameState *initWorld(void);
void destroyWorld(struct GameState *);
bool updateWorld(struct GameState *, int);
void renderWorld(struct GameState *);
int getLastChar(int);
bool foodIsEaten(struct BodyPart *, struct Food *);
bool nextStep(struct BodyPart *, enum DIR);
bool leftScreen(struct BodyPart *);
void moveBodyInDir(struct BodyPart *);
void placeFood(struct GameState *);
void drawSnake(struct BodyPart*);
void drawFood(struct Food*);
unsigned char getRandomNumber(FILE *, unsigned char);
int getDiffInMilliseconds(struct timespec *, struct timespec *);
void freeSnake(struct BodyPart*);
void initGame(void);
int main(void) {
initGame();
mainLoop();
endwin(); //TODO dinitGame();
return EXIT_SUCCESS;
}
void mainLoop() {
struct GameState *state = initWorld();
//TODO null check
int ch;
renderWorld(state);
while((ch = getLastChar(state->speed)) != 'q') {
if(!updateWorld(state, ch)) {
//TODO Game over
break;
}
renderWorld(state);
}
destroyWorld(state);
}
struct GameState *initWorld() {
struct GameState *state = calloc(1, sizeof(struct GameState));
//TODO null checks
state->head = calloc(1, sizeof(struct BodyPart));
state->food = malloc(sizeof(struct Food));
state->rng = fopen("/dev/urandom", "r");
//TODO null checks
state->head->x = 10;
state->head->y = 10;
state->head->dir = RIGHT;
state->speed = 144;
placeFood(state);
return state;
}
void destroyWorld(struct GameState *state) {
freeSnake(state->head);
free(state->food);
fclose(state->rng);
free(state);
}
bool updateWorld(struct GameState *state, int pressedCharacter) {
struct BodyPart *head = state->head;
bool ok;
if(pressedCharacter == KEY_UP && head->dir != DOWN) {
ok = nextStep(head, TOP);
} else if(pressedCharacter == KEY_LEFT && head->dir != RIGHT) {
ok = nextStep(head, LEFT);
} else if(pressedCharacter == KEY_RIGHT && head->dir != LEFT) {
ok = nextStep(head, RIGHT);
} else if (pressedCharacter == KEY_DOWN && head->dir != TOP) {
ok = nextStep(head, DOWN);
} else {
ok = nextStep(head, head->dir);
}
if(foodIsEaten(head, state->food)) {
placeFood(state);
head->hasFoodInside = true;
}
return ok;
}
void renderWorld(struct GameState *state) {
clear();
drawSnake(state->head);
drawFood(state->food);
refresh();
}
int getLastChar(int timeout) {
int tmp, ch;
ch = ERR;
struct timespec loopStartTime, currentTime;
clock_gettime(CLOCK_MONOTONIC, &loopStartTime);
while(timeout > 0) {
timeout(timeout);
tmp = getch();
if(tmp == ERR) {
break;
}
ch = tmp;
clock_gettime(CLOCK_MONOTONIC, &currentTime);
timeout -= getDiffInMilliseconds(&loopStartTime, &currentTime);
}
return ch;
}
bool foodIsEaten(struct BodyPart *head, struct Food *foodPosition) {
if(head->x != foodPosition->x)
return false;
if(head->y != foodPosition->y)
return false;
return true;
}
bool nextStep(struct BodyPart *head, enum DIR dir) {
enum DIR oldDir = dir;
enum DIR tmpDir;
struct BodyPart *next = head;
bool food = false, bitItself = false;
do {
if(food) {
next->hasFoodInside = true;
food = false;
} else {
food = next->hasFoodInside;
next->hasFoodInside = false;
}
if(food && next->next == NULL) {
next->next = calloc(1, sizeof(struct BodyPart));
next->next->x = next->x;
next->next->y = next->y;
next->next->dir = next->dir;
moveBodyInDir(next);
break;
}
tmpDir = next->dir;
next->dir = oldDir;
oldDir = tmpDir;
moveBodyInDir(next);
next = next->next;
if(next != NULL && next->x == head->x && next->y == head->y){
bitItself=true;
break;
}
} while (next);
return !bitItself && !leftScreen(head);
}
bool leftScreen(struct BodyPart *head) {
int maxX, maxY;
getmaxyx(stdscr, maxY, maxX);
if(head->x < 0 || head->x > maxX)
return true;
if(head->y < 0 || head->y > maxY)
return true;
return false;
}
void moveBodyInDir(struct BodyPart *body) {
enum DIR dir = body->dir;
if(dir == TOP) {
body->y -= 1;
} else if(dir == LEFT) {
body->x -= 1;
} else if(dir == RIGHT) {
body->x += 1;
} else {
body->y += 1;
}
}
void placeFood(struct GameState *state) {
int maxX, maxY, rndX, rndY;
getmaxyx(stdscr, maxY, maxX);
rndX = getRandomNumber(state->rng, (unsigned char)maxX - 1);
rndY = getRandomNumber(state->rng, (unsigned char)maxY - 1);
state->food->x = rndX + 1;
state->food->y = rndY + 1;
}
void drawSnake(struct BodyPart *head) {
int x = head->x;
int y = head->y;
char ch = headChars[head->dir];
struct BodyPart *next = head->next;
struct BodyPart *previous = head;
mvaddch(y, x, ch);
while (next) {
x = next->x;
y = next->y;
if(next->next == NULL) {
ch = bodyChars[previous->dir];
} else {
ch = bodyChars[next->dir];
}
next = next->next;
mvaddch(y, x, ch);
}
}
void drawFood(struct Food *foodPosition) {
mvaddch(foodPosition->y, foodPosition->x, foodChar);
}
unsigned char getRandomNumber(FILE *rng, unsigned char limit) {
unsigned char randomNumber, bucketNumber;
do {
randomNumber = (unsigned char)fgetc(rng);
bucketNumber = UCHAR_MAX / limit;
if(randomNumber <= limit) {
break;
} else if(randomNumber < (bucketNumber * limit)) {
do{
randomNumber -= limit;
} while (randomNumber > limit);
break;
}
} while (true);
return randomNumber;
}
int getDiffInMilliseconds(struct timespec *start, struct timespec *end) {
time_t sStart, sEnd;
double msStart, msEnd;
int msResult;
sStart = start->tv_sec;
sEnd = end->tv_sec;
msStart = round(start->tv_nsec / 1.0e6);
msEnd = round(end->tv_nsec / 1.0e6);
msStart += 1000 * sStart;
msEnd += 1000* sEnd;
msResult = (int)(msEnd - msStart);
return msResult;
}
void freeSnake(struct BodyPart *head) {
struct BodyPart *next = head->next;
struct BodyPart *tmp;
while(next) {
tmp = next->next;
free(next);
next = tmp;
}
}
void initGame() {
initscr();
cbreak();
noecho();
keypad(stdscr, true);
curs_set(0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment