Snake in Curses
This is just something I whipped up in my spare time. Another example of an idea turned into software. And once again, the curses library is involved. I present to you, a real-time action-packed snake game. Collect "apples" and extend until you crash.
Take a look...:
...and compile this code to play:
#include <stdlib.h>
#include <ncurses.h>
#include <time.h>
#include <unistd.h>
typedef enum {
DIRECTION_UP,
DIRECTION_DOWN,
DIRECTION_LEFT,
DIRECTION_RIGHT,
} direction_t;
typedef struct direction_queue_s {
direction_t direction;
struct direction_queue_s *next, *prev;
} direction_queue_t;
static direction_queue_t *queue_head = NULL, *queue_tail = NULL;
static int score;
static int pos_y;
static int pos_x;
static int apple_y;
static int apple_x;
static void queue_insert(direction_t direction, int remove_tail)
{
direction_queue_t *new, *temp;
new = (direction_queue_t *)malloc(sizeof(direction_queue_t));
if (new == NULL)
exit(1);
new->direction = direction;
new->next = NULL;
if (queue_head != NULL)
queue_head->next = new;
new->prev = queue_head;
queue_head = new;
if (queue_tail == NULL)
queue_tail = new;
if (remove_tail && queue_tail != NULL) {
temp = queue_tail;
queue_tail = queue_tail->next;
queue_tail->prev = NULL;
free(temp);
}
}
static void update_screen(void)
{
int current_y, current_x;
direction_queue_t *current;
erase();
box(stdscr, '|', '-');
current_y = pos_y;
current_x = pos_x;
for (current = queue_head; current != NULL; current = current->prev) {
mvaddch(current_y, current_x, '#');
switch (current->direction) {
case DIRECTION_UP:
current_y++;
break;
case DIRECTION_DOWN:
current_y--;
break;
case DIRECTION_LEFT:
current_x++;
break;
case DIRECTION_RIGHT:
current_x--;
break;
}
}
mvaddch(apple_y, apple_x, '@');
move(pos_y, pos_x);
refresh();
}
static void place_apple(void)
{
int maxx, maxy;
getmaxyx(stdscr, maxy, maxx);
do {
move((random() % (maxy - 2)) + 1, (random() % (maxx - 2)) + 1);
} while (inch() != ' ');
getyx(stdscr, apple_y, apple_x);
}
static void exit_handler(void)
{
direction_queue_t *current, *last = NULL;
if (queue_tail != NULL) {
for (current = queue_tail; current != NULL; current = current->next) {
if (last != NULL)
free(last);
last = current->prev;
}
if (last != NULL)
free(last);
}
endwin();
printf("\nScore: %d\n", score);
}
int main(void)
{
int c, maxy, maxx;
direction_t current_direction, original_direction;
/* Initialize curses. */
initscr();
atexit(exit_handler);
noecho();
keypad(stdscr, TRUE);
timeout(0); /* Non-blocking mode */
getmaxyx(stdscr, maxy, maxx);
srandom(time(NULL));
/* Set initial variables. */
current_direction = DIRECTION_RIGHT;
queue_insert(DIRECTION_RIGHT, 0);
queue_insert(DIRECTION_RIGHT, 0);
queue_insert(DIRECTION_RIGHT, 0);
score = 0;
pos_y = maxy / 2;
pos_x = maxx / 2;
place_apple();
update_screen();
while (1) {
/* Read character buffer until empty. */
original_direction = current_direction;
while ((c = getch()) != ERR) {
switch (c) {
case KEY_RESIZE:
return 0; /* Not allowed! */
case KEY_UP:
if (original_direction != DIRECTION_DOWN)
current_direction = DIRECTION_UP;
break;
case KEY_DOWN:
if (original_direction != DIRECTION_UP)
current_direction = DIRECTION_DOWN;
break;
case KEY_LEFT:
if (original_direction != DIRECTION_RIGHT)
current_direction = DIRECTION_LEFT;
break;
case KEY_RIGHT:
if (original_direction != DIRECTION_LEFT)
current_direction = DIRECTION_RIGHT;
break;
case 'q':
case 'Q':
case '\e':
exit(0);
}
}
/* Move player. */
switch (current_direction) {
case DIRECTION_UP:
pos_y--;
break;
case DIRECTION_DOWN:
pos_y++;
break;
case DIRECTION_LEFT:
pos_x--;
break;
case DIRECTION_RIGHT:
pos_x++;
break;
}
/* Check for collisions. */
c = mvinch(pos_y, pos_x);
if (c == ' ') { /* Normal progression. */
queue_insert(current_direction, 1);
} else if (c == '@') { /* Expand. */
queue_insert(current_direction, 0);
score++;
place_apple();
} else { /* Died. */
flash();
exit(0);
}
/* Redraw screen. */
update_screen();
usleep(75000);
}
return 0;
}