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, ¤t); 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 }