Thumbnail

rani/games.git

Clone URL: https://git.buni.party/rani/games.git

Viewing file on branch master

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
41enum rstate {
42 RUNNING,
43 WALL_CRASH,
44 SELF_HIT,
45 QUIT,
46};
47
48
49enum color {
50 RED = 1,
51 MAGENTA,
52 GREEN,
53 CYAN,
54};
55
56
57enum direction {
58 DIR_UP,
59 DIR_DOWN,
60 DIR_LEFT,
61 DIR_RIGHT,
62};
63
64
65struct 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
77struct snake {
78 struct node * nodes;
79 struct node * last;
80
81 int length;
82};
83
84
85struct apple {
86 int x;
87 int y;
88};
89
90
91int nlen(int n) {
92 int len = 0;
93 while (n > 0) {
94 len++;
95 n /= 10;
96 }
97 return len;
98}
99
100
101int 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