| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | #include <ncurses.h> |
| 4 | #include <stdbool.h> |
| 5 | #include <sys/time.h> |
| 6 | |
| 7 | |
| 8 | /* BEGIN CONFIG */ |
| 9 | #ifndef Y |
| 10 | #define Y 16 |
| 11 | #endif /* Y */ |
| 12 | |
| 13 | #ifndef X |
| 14 | #define X 32 |
| 15 | #endif /* X */ |
| 16 | |
| 17 | #ifndef DELAY |
| 18 | // time between movements (ms) |
| 19 | #define DELAY 100 |
| 20 | #endif /* DELAY */ |
| 21 | |
| 22 | #ifndef WALLS |
| 23 | // enable or disable solid walls |
| 24 | #define WALLS false |
| 25 | #endif /* WALLS */ |
| 26 | |
| 27 | #ifndef SEED |
| 28 | #include <time.h> |
| 29 | #define SEED time(NULL) |
| 30 | #endif /* SEED */ |
| 31 | /* END CONFIG */ |
| 32 | |
| 33 | |
| 34 | #if WALLS |
| 35 | #define WALLC '#' |
| 36 | #else |
| 37 | #define WALLC ' ' |
| 38 | #endif |
| 39 | |
| 40 | |
| 41 | enum rstate { |
| 42 | RUNNING, |
| 43 | WALL_CRASH, |
| 44 | SELF_HIT, |
| 45 | QUIT, |
| 46 | }; |
| 47 | |
| 48 | |
| 49 | enum color { |
| 50 | RED = 1, |
| 51 | MAGENTA, |
| 52 | GREEN, |
| 53 | CYAN, |
| 54 | }; |
| 55 | |
| 56 | |
| 57 | enum direction { |
| 58 | DIR_UP, |
| 59 | DIR_DOWN, |
| 60 | DIR_LEFT, |
| 61 | DIR_RIGHT, |
| 62 | }; |
| 63 | |
| 64 | |
| 65 | struct node { |
| 66 | enum direction dir; |
| 67 | |
| 68 | bool pivot; |
| 69 | int x; |
| 70 | int y; |
| 71 | |
| 72 | struct node * next; |
| 73 | struct node * prev; |
| 74 | }; |
| 75 | |
| 76 | |
| 77 | struct snake { |
| 78 | struct node * nodes; |
| 79 | struct node * last; |
| 80 | |
| 81 | int length; |
| 82 | }; |
| 83 | |
| 84 | |
| 85 | struct apple { |
| 86 | int x; |
| 87 | int y; |
| 88 | }; |
| 89 | |
| 90 | |
| 91 | int nlen(int n) { |
| 92 | int len = 0; |
| 93 | while (n > 0) { |
| 94 | len++; |
| 95 | n /= 10; |
| 96 | } |
| 97 | return len; |
| 98 | } |
| 99 | |
| 100 | |
| 101 | int main() { |
| 102 | srand(SEED); |
| 103 | |
| 104 | initscr(); |
| 105 | noecho(); |
| 106 | keypad(stdscr, true); |
| 107 | curs_set(0); |
| 108 | // the snake should move once every DELAY ms, |
| 109 | // no matter if input is recieved |
| 110 | timeout(DELAY); |
| 111 | |
| 112 | use_default_colors(); |
| 113 | start_color(); |
| 114 | init_pair(RED, COLOR_RED, -1); |
| 115 | init_pair(MAGENTA, COLOR_MAGENTA, -1); |
| 116 | init_pair(GREEN, COLOR_GREEN, -1); |
| 117 | init_pair(CYAN, COLOR_CYAN, -1); |
| 118 | |
| 119 | while (true) { |
| 120 | struct snake s = { |
| 121 | .nodes = &(struct node){ |
| 122 | .dir = DIR_RIGHT, |
| 123 | .pivot = false, |
| 124 | .x = X / 2, |
| 125 | .y = Y / 2, |
| 126 | .next = NULL, |
| 127 | .prev = NULL, |
| 128 | }, |
| 129 | .last = NULL, |
| 130 | .length = 1, |
| 131 | }; |
| 132 | s.last = s.nodes; |
| 133 | |
| 134 | struct apple a = { |
| 135 | .x = rand() % X, |
| 136 | .y = rand() % Y, |
| 137 | }; |
| 138 | |
| 139 | int score = 1; |
| 140 | |
| 141 | enum rstate state = RUNNING; |
| 142 | while (true) { |
| 143 | // ate an apple! |
| 144 | if (s.nodes->x == a.x && s.nodes->y == a.y) { |
| 145 | score++; |
| 146 | |
| 147 | struct node * l = s.last; |
| 148 | // add a new node |
| 149 | s.length++; |
| 150 | l->next = malloc(sizeof(struct node)); |
| 151 | struct node * nw = l->next; |
| 152 | nw->dir = l->dir; |
| 153 | nw->pivot = false; |
| 154 | |
| 155 | switch (l->dir) { |
| 156 | case DIR_UP: |
| 157 | nw->x = l->x; |
| 158 | nw->y = l->y + 1; |
| 159 | break; |
| 160 | case DIR_DOWN: |
| 161 | nw->x = l->x; |
| 162 | nw->y = l->y - 1; |
| 163 | break; |
| 164 | case DIR_LEFT: |
| 165 | nw->x = l->x + 1; |
| 166 | nw->y = l->y; |
| 167 | break; |
| 168 | case DIR_RIGHT: |
| 169 | nw->x = l->x - 1; |
| 170 | nw->y = l->y; |
| 171 | break; |
| 172 | } |
| 173 | |
| 174 | nw->prev = l; |
| 175 | nw->next = NULL; |
| 176 | |
| 177 | s.last = l->next; |
| 178 | |
| 179 | a.x = rand() % X; |
| 180 | a.y = rand() % Y; |
| 181 | } |
| 182 | |
| 183 | // move the snake |
| 184 | for (struct node * n = s.last; n; n = n->prev) { |
| 185 | switch (n->dir) { |
| 186 | case DIR_UP: |
| 187 | n->y--; |
| 188 | break; |
| 189 | case DIR_DOWN: |
| 190 | n->y++; |
| 191 | break; |
| 192 | case DIR_LEFT: |
| 193 | n->x--; |
| 194 | break; |
| 195 | case DIR_RIGHT: |
| 196 | n->x++; |
| 197 | break; |
| 198 | } |
| 199 | |
| 200 | // teleport the snake in no-walls mode |
| 201 | if (!WALLS) { |
| 202 | if (n->x < 0) n->x = X - 1; |
| 203 | else if (n->x >= X) n->x = 0; |
| 204 | |
| 205 | if (n->y < 0) n->y = Y - 1; |
| 206 | else if (n->y >= Y) n->y = 0; |
| 207 | } |
| 208 | |
| 209 | // set the direction of the next node to that of the previous one |
| 210 | if (n->prev && n->prev->pivot) { |
| 211 | n->dir = n->prev->dir; |
| 212 | n->prev->pivot = false; |
| 213 | n->pivot = true; |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | // handle wall crash |
| 218 | if (WALLS && (s.nodes->x < 0 || s.nodes->x >= X || s.nodes->y < 0 || s.nodes->y >= Y)) { |
| 219 | state = WALL_CRASH; |
| 220 | goto done; |
| 221 | } |
| 222 | |
| 223 | // handle bumping into self |
| 224 | for (struct node * n = s.nodes->next; n; n = n->next) { |
| 225 | if (s.nodes->x == n->x && s.nodes->y == n->y) { |
| 226 | state = SELF_HIT; |
| 227 | goto done; |
| 228 | } |
| 229 | } |
| 230 | |
| 231 | erase(); |
| 232 | // draw the border |
| 233 | attron(A_REVERSE | A_DIM | COLOR_PAIR(MAGENTA)); |
| 234 | for (int i = 0; i <= X + 1; i++) { // top |
| 235 | move(0, i); |
| 236 | addch(WALLC); |
| 237 | } |
| 238 | for (int i = 1; i < Y + 1; i++) { // middle |
| 239 | move(i, 0); |
| 240 | addch(WALLC); |
| 241 | move(i, X + 1); |
| 242 | addch(WALLC); |
| 243 | |
| 244 | } |
| 245 | for (int i = 0; i <= X + 1; i++) { // bottom |
| 246 | attron(A_REVERSE); |
| 247 | move(Y + 1, i); |
| 248 | addch(WALLC); |
| 249 | } |
| 250 | attroff(A_REVERSE | A_DIM | COLOR_PAIR(MAGENTA)); |
| 251 | // write the score |
| 252 | move(0, X / 2 - 3 - nlen(score) / 2); |
| 253 | attron(A_UNDERLINE); |
| 254 | printw("Score: %d", score); |
| 255 | attroff(A_UNDERLINE); |
| 256 | // draw the snake |
| 257 | move(s.nodes->y + 1, s.nodes->x + 1); |
| 258 | // first the head |
| 259 | attron(COLOR_PAIR(CYAN)); |
| 260 | switch (s.nodes->dir) { |
| 261 | case DIR_UP: |
| 262 | addch('^'); |
| 263 | break; |
| 264 | case DIR_DOWN: |
| 265 | addch('v'); |
| 266 | break; |
| 267 | case DIR_LEFT: |
| 268 | addch('<'); |
| 269 | break; |
| 270 | case DIR_RIGHT: |
| 271 | addch('>'); |
| 272 | break; |
| 273 | } |
| 274 | attroff(COLOR_PAIR(CYAN)); |
| 275 | // then the body |
| 276 | attron(COLOR_PAIR(GREEN)); |
| 277 | for (struct node * n = s.nodes->next; n; n = n->next) { |
| 278 | move(n->y + 1, n->x + 1); |
| 279 | addch('*'); |
| 280 | } |
| 281 | attroff(COLOR_PAIR(GREEN)); |
| 282 | // draw the apple |
| 283 | move(a.y + 1, a.x + 1); |
| 284 | attron(COLOR_PAIR(RED)); |
| 285 | addch('@'); |
| 286 | attroff(COLOR_PAIR(RED)); |
| 287 | refresh(); |
| 288 | |
| 289 | // handle input |
| 290 | struct timeval start; |
| 291 | gettimeofday(&start, NULL); |
| 292 | |
| 293 | int c = getch(); |
| 294 | switch (c) { |
| 295 | case KEY_UP: |
| 296 | if (s.length > 1 && s.nodes->dir == DIR_DOWN) break; |
| 297 | s.nodes->dir = DIR_UP; |
| 298 | s.nodes->pivot = true; |
| 299 | break; |
| 300 | case KEY_DOWN: |
| 301 | if (s.length > 1 && s.nodes->dir == DIR_UP) break; |
| 302 | s.nodes->dir = DIR_DOWN; |
| 303 | s.nodes->pivot = true; |
| 304 | break; |
| 305 | break; |
| 306 | case KEY_LEFT: |
| 307 | if (s.length > 1 && s.nodes->dir == DIR_RIGHT) break; |
| 308 | s.nodes->dir = DIR_LEFT; |
| 309 | s.nodes->pivot = true; |
| 310 | break; |
| 311 | case KEY_RIGHT: |
| 312 | if (s.length > 1 && s.nodes->dir == DIR_LEFT) break; |
| 313 | s.nodes->dir = DIR_RIGHT; |
| 314 | s.nodes->pivot = true; |
| 315 | break; |
| 316 | case ' ': |
| 317 | timeout(-1); |
| 318 | move(Y / 2, X / 2 - 2); |
| 319 | attron(A_REVERSE); |
| 320 | addstr("Paused"); |
| 321 | attroff(A_REVERSE); |
| 322 | int c; |
| 323 | while ((c = getch()) != ' ') { |
| 324 | if (c == 'q') { |
| 325 | state = QUIT; |
| 326 | goto done; |
| 327 | } |
| 328 | } |
| 329 | timeout(DELAY); |
| 330 | break; |
| 331 | case 'q': |
| 332 | state = QUIT; |
| 333 | goto done; |
| 334 | } |
| 335 | |
| 336 | struct timeval end; |
| 337 | gettimeofday(&end, NULL); |
| 338 | |
| 339 | unsigned delta_ms = (end.tv_usec - start.tv_usec) * 1000; |
| 340 | if (delta_ms < DELAY) { |
| 341 | napms(delta_ms); |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | done: |
| 346 | switch (state) { |
| 347 | case QUIT: |
| 348 | goto terminate; |
| 349 | case WALL_CRASH: |
| 350 | move(Y / 2, X / 2 - 12); |
| 351 | attron(A_REVERSE); |
| 352 | addstr("You crashed into the wall"); |
| 353 | attroff(A_REVERSE); |
| 354 | refresh(); |
| 355 | napms(1500); |
| 356 | break; |
| 357 | case SELF_HIT: |
| 358 | move(Y / 2, X / 2 - 10); |
| 359 | attron(A_REVERSE); |
| 360 | addstr("You ran into yourself"); |
| 361 | attroff(A_REVERSE); |
| 362 | refresh(); |
| 363 | napms(1500); |
| 364 | break; |
| 365 | default: break; |
| 366 | } |
| 367 | |
| 368 | for (struct node * n = s.nodes->next; n; n = n->next) { |
| 369 | free(n); |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | terminate: |
| 374 | curs_set(1); |
| 375 | keypad(stdscr, false); |
| 376 | echo(); |
| 377 | endwin(); |
| 378 | } |
| 379 | |