curssses

Snake game for the linux terminal.
git clone git://git.amin.space/curssses.git
Log | Files | Refs | LICENSE

curssses.c (5870B)


      1 #include <curses.h>
      2 #include <locale.h>
      3 #include <stdlib.h>
      4 #include <time.h>
      5 #include <unistd.h>
      6 
      7 #define SECOND 1000
      8 
      9 #define FPS          60
     10 #define MS_PER_FRAME (SECOND / FPS)
     11 
     12 #define UPDATES_PER_SECOND 100
     13 #define MS_PER_UPDATE      (SECOND / UPDATES_PER_SECOND)
     14 
     15 #define MOVEMENTS_PER_SECOND 10
     16 #define UPDATES_PER_MOVEMENT (UPDATES_PER_SECOND / MOVEMENTS_PER_SECOND)
     17 
     18 typedef enum
     19 {
     20     LEFT,
     21     RIGHT,
     22     UP,
     23     DOWN,
     24 } Direction;
     25 
     26 typedef struct Segment Segment;
     27 struct Segment
     28 {
     29     int x;
     30     int y;
     31     Segment *prev;
     32     Segment *next;
     33 };
     34 
     35 typedef struct Snake Snake;
     36 struct Snake
     37 {
     38     char symbol;
     39     int length;
     40     Direction d;
     41     Segment *head;
     42     Segment *tail;
     43 };
     44 
     45 typedef struct Food Food;
     46 struct Food
     47 {
     48     bool eaten;
     49     char symbol;
     50     int x;
     51     int y;
     52 };
     53 
     54 Snake* snake_init(void)
     55 {
     56     Segment *first = malloc(sizeof(Segment));
     57     first->x = COLS / 2;
     58     first->y = LINES / 2;
     59     first->prev = 0;
     60     first->next = 0;
     61 
     62     Snake *s = malloc(sizeof(Snake));
     63     s->symbol = 'o';
     64     s->d = RIGHT;
     65     s->length = 1;
     66     s->head = first;
     67     s->tail = first;
     68 
     69     return s;
     70 }
     71 
     72 void snake_add_segment(Snake *s)
     73 {
     74     Segment *new = malloc(sizeof(Segment));
     75     new->x = s->tail->x;
     76     new->y = s->tail->y;
     77     new->prev = s->tail;
     78     new->next = 0;
     79 
     80     s->tail->next = new;
     81     s->tail = new;
     82     s->length++;
     83 }
     84 
     85 void snake_move(Snake *s)
     86 {
     87     // move body
     88     Segment *current = s->tail;
     89     while (current != s->head)
     90     {
     91         current->x = current->prev->x;
     92         current->y = current->prev->y;
     93         current = current->prev;
     94     }
     95 
     96     // move head
     97     switch (s->d)
     98     {
     99         case LEFT:
    100         {
    101             --s->head->x;
    102         } break;
    103         case RIGHT:
    104         {
    105             ++s->head->x;
    106         } break;
    107         case UP:
    108         {
    109             --s->head->y;
    110         } break;
    111         case DOWN:
    112         {
    113             ++s->head->y;
    114         } break;
    115     }
    116 
    117     if (s->head->y >= LINES)
    118     {
    119         s->head->y = 0;
    120     }
    121     else if (s->head->y < 0)
    122     {
    123         s->head->y = LINES - 1;
    124     }
    125     if (s->head->x >= COLS)
    126     {
    127         s->head->x = 0;
    128     }
    129     else if (s->head->x < 0)
    130     {
    131         s->head->x = COLS - 1;
    132     }
    133 }
    134 
    135 void food_move(Food *f)
    136 {
    137     f->symbol = '#';
    138     f->x = rand() % COLS;
    139     f->y = rand() % LINES;
    140     f->eaten = false;
    141 }
    142 
    143 uint64_t get_current_time_ms(void)
    144 {
    145     struct timespec current;
    146     // TODO(amin): Fallback to other time sources when CLOCK_MONOTONIC is unavailable.
    147     clock_gettime(CLOCK_MONOTONIC, &current);
    148     uint64_t milliseconds = ((current.tv_sec * 1000000000) + current.tv_nsec) / 1000000;
    149     return milliseconds;
    150 }
    151 
    152 int main(void)
    153 {
    154     setlocale(LC_ALL, "");
    155     initscr();
    156     cbreak();
    157     noecho();
    158     nonl();
    159     nodelay(stdscr, TRUE);
    160     intrflush(stdscr, FALSE);
    161     keypad(stdscr, TRUE);
    162     curs_set(0);
    163 
    164     srand((unsigned int)time(0));
    165 
    166     Snake *s = snake_init();
    167 
    168     Food f;
    169     food_move(&f);
    170 
    171     bool debug = false;
    172     int input = 0;
    173     int updates = 0;
    174     int frames = 0;
    175     int movements = 0;
    176     uint64_t lag = 0;
    177     uint64_t previous_ms = get_current_time_ms();
    178 
    179     while (1)
    180     {
    181         uint64_t current_ms = get_current_time_ms();
    182         uint64_t elapsed_ms = current_ms - previous_ms;
    183         previous_ms = current_ms;
    184         lag += elapsed_ms;
    185 
    186         input = getch();
    187 
    188         switch (input)
    189         {
    190             case KEY_LEFT:
    191             case 'h':
    192             {
    193                 s->d = LEFT;
    194             } break;
    195             case KEY_RIGHT:
    196             case 'l':
    197             {
    198                 s->d = RIGHT;
    199             } break;
    200             case KEY_UP:
    201             case 'k':
    202             {
    203                 s->d = UP;
    204             } break;
    205             case KEY_DOWN:
    206             case 'j':
    207             {
    208                 s->d = DOWN;
    209             } break;
    210             case 'p':
    211             {
    212                 // pause until keypress
    213                 nodelay(stdscr, FALSE);
    214                 getch();
    215                 nodelay(stdscr, TRUE);
    216                 previous_ms = get_current_time_ms();
    217             } break;
    218             case 'i':
    219             {
    220                 debug = !debug;
    221             } break;
    222         }
    223 
    224         while (lag >= MS_PER_UPDATE)
    225         {
    226             if (updates % UPDATES_PER_MOVEMENT == 0)
    227             {
    228                 snake_move(s);
    229                 if (s->head->x == f.x && s->head->y == f.y)
    230                 {
    231                     f.eaten = true;
    232                     snake_add_segment(s);
    233                 }
    234                 if (f.eaten)
    235                 {
    236                     food_move(&f);
    237                 }
    238                 movements++;
    239             }
    240             updates++;
    241             lag -= MS_PER_UPDATE;
    242         }
    243 
    244         erase();
    245 
    246         if (debug)
    247         {
    248             mvprintw(
    249                 0, 0,
    250                 "Frame Time: %dms\nLag: %dms\nUpdates: %d\nFrames: %d\nMovements: %d\n"
    251                 "Food: (%d, %d)\n"
    252                 "Screen: [%d, %d]\nSnake Length: %d\nHead: (%d, %d)\nTail: (%d, %d)\n",
    253                 elapsed_ms, lag,
    254                 updates, frames, movements,
    255                 f.x, f.y,
    256                 COLS, LINES,
    257                 s->length,
    258                 s->head->x, s->head->y,
    259                 s->tail->x, s->tail->y);
    260         }
    261 
    262         Segment *current = s->head;
    263         int i = 0;
    264         while (current != 0)
    265         {
    266             mvaddch(current->y, current->x, s->symbol);
    267             if (debug)
    268             {
    269                 mvprintw((i%20)+10, (i/20)*25, "Segment %d (%d, %d)", i+1, current->x, current->y);
    270             }
    271             current = current->next;
    272             i++;
    273         }
    274         mvaddch(f.y, f.x, f.symbol);
    275 
    276         refresh();
    277         frames++;
    278         if (elapsed_ms <= MS_PER_FRAME)
    279         {
    280             usleep((MS_PER_FRAME - elapsed_ms) * 1000);
    281         }
    282     }
    283 
    284     clear();
    285     endwin();
    286     exit(0);
    287 
    288     return 0;
    289 }