Last active
August 29, 2015 14:04
-
-
Save Serranya/7eccdc6e4cf88abc1683 to your computer and use it in GitHub Desktop.
Snake in C with ncurses.
This file contains 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
//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, ¤tTime); | |
timeout -= getDiffInMilliseconds(&loopStartTime, ¤tTime); | |
} | |
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