]> git.gir.st - VIper.git/blob - viiper.c
found segfault (yay! :/ )
[VIper.git] / viiper.c
1 /*******************************************************************************
2 viiper 0.1
3 By Tobias Girstmair, 2018
4
5 ./viiper 40x25
6 (see ./viiper -h for full list of options)
7
8 KEYBINDINGS: - hjkl to move
9 - p to pause and resume
10 - r to restart
11 - q to quit
12 - (see `./minesviiper -h' for all keybindings)
13
14 GNU GPL v3, see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt
15 *******************************************************************************/
16
17
18 #define _POSIX_C_SOURCE 2 /*for getopt and sigaction in c99, sigsetjmp*/
19 #include <ctype.h>
20 #include <poll.h>
21 #include <setjmp.h>
22 #include <signal.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <sys/ioctl.h>
26 #include <sys/time.h>
27 #include <termios.h>
28 #include <time.h>
29 #include <unistd.h>
30
31 #include "viiper.h"
32 #include "schemes.h"
33
34 #define MIN(a,b) (a>b?b:a)
35 #define MAX(a,b) (a>b?a:b)
36 #define CLAMP(a,m,M) (a<m?m:(a>M?M:a))
37 #define printm(n, s) for (int _loop = 0; _loop < n; _loop++) fputs (s, stdout)
38 #define print(str) fputs (str?str:"", stdout)
39 #define CTRL_ 0x1F &
40
41 #define COL_OFFSET 1
42 #define LINE_OFFSET 1
43 #define LINES_AFTER 1
44 #define CW 2 /* cell width */
45
46 struct game {
47 int w; /* field width */
48 int h; /* field height */
49 int d; /* direction the snake is looking */
50 int t; /* time of game start */
51 int p; /* score */
52 float v; /* velocity in moves per second */
53 struct snake* s; /* snek */
54 struct item* i; /* items (food, boni) */
55 struct directions* n;/* next direction events to process */
56 } g;
57
58 struct opt {
59 int l; /* initial snake length */
60 int s; /* initial snake speed */
61 struct scheme* scheme;
62 } op;
63
64 jmp_buf game_over;
65
66 int main (int argc, char** argv) {
67 /* defaults: */
68 g.w = 30; //two-char-width
69 g.h = 20;
70 op.l = 10;
71 op.s = 8;
72 op.scheme = &unic0de; //TODO: expose to getopt() once more sets are available
73
74 int optget;
75 opterr = 0; /* don't print message on unrecognized option */
76 while ((optget = getopt (argc, argv, "+h")) != -1) {
77 switch (optget) {
78 case 'h':
79 default:
80 fprintf (stderr, SHORTHELP LONGHELP, argv[0]);
81 return !(optget=='h');
82 }
83 } if (optind < argc) { /* parse Fieldspec */
84 }
85
86 srand(time(0));
87 signal_setup();
88 screen_setup(1);
89 atexit (*quit);
90
91 if (sigsetjmp(game_over, 1)) {
92 timer_setup(0);
93 move_ph (g.h/2+LINE_OFFSET, g.w);
94 printf ("you died :(");
95 fflush(stdout);
96 sleep(5);
97 }
98
99 //TODO: call viiper() in a game loop
100 viiper();
101 quit:
102 return 0;
103 }
104
105 int viiper(void) {
106 init_snake();
107 show_playfield ();
108 g.d = EAST;
109 g.v = op.s;
110
111 timer_setup(1);
112
113 spawn_item(FOOD, rand() % NUM_FOODS); //TODO: shape distribution, so bigger values get selected less
114
115 for(;;) {
116 switch (getctrlseq()) {
117 case '+': g.v++;timer_setup(1);break; //TODO: temporary, to set speed
118 case '#': if (g.v > 1) g.v--;timer_setup(1); break; //TODO: temporary, to set speed
119 case '\n': spawn_item(FOOD, rand() % NUM_FOODS); break; //TODO: for debugging segfault
120
121 case CTRSEQ_CURSOR_LEFT:
122 case 'h': append_movement(WEST); break;
123 case CTRSEQ_CURSOR_DOWN:
124 case 'j': append_movement(SOUTH); break;
125 case CTRSEQ_CURSOR_UP:
126 case 'k': append_movement(NORTH); break;
127 case CTRSEQ_CURSOR_RIGHT:
128 case 'l': append_movement(EAST); break;
129 case 'p': /*TODO: pause*/ break;
130 case 'r': /*TODO:restart*/ return 0;
131 case 'q': return 0;
132 case CTRL_'L':
133 screen_setup(1);
134 show_playfield();
135 break;
136 }
137
138 print ("\033[H\033[J");
139 show_playfield ();//TODO: only redraw diff
140 }
141
142 }
143
144 void snake_advance (void) {
145 if (g.n) {/* new direction in the buffer */
146 int possible_new_dir = get_movement();
147 if (g.d == EAST && possible_new_dir == WEST) goto ignore_new;
148 if (g.d == WEST && possible_new_dir == EAST) goto ignore_new;
149 if (g.d == NORTH && possible_new_dir == SOUTH) goto ignore_new;
150 if (g.d == SOUTH && possible_new_dir == NORTH) goto ignore_new;
151 g.d = possible_new_dir; /*pop off a new direction if available*/
152 ignore_new:
153 1;
154 }
155
156 int new_row = g.s->r +(g.d==SOUTH) -(g.d==NORTH);
157 int new_col = g.s->c +(g.d==EAST) -(g.d==WEST);
158
159 /* detect food hit and spawn a new food */
160 for (struct item* i = g.i; i; i = i->next) {
161 if (i->r == new_row && i->c == new_col) {
162 consume_item (i);
163 spawn_item(FOOD, rand() % NUM_FOODS);
164 }
165 }
166
167 /*NOTE:no idea why I have to use g.w+1, but otherwise we die too early*/
168 if (new_row >= g.h || new_col >= g.w+1 || new_row < 0 || new_col < 0)
169 siglongjmp(game_over, 1/*<-will be the retval of setjmp*/);
170
171 struct snake* new_head;
172 struct snake* new_tail; /* former second-to-last element */
173 for (new_tail = g.s; new_tail->next->next; new_tail = new_tail->next)
174 /* use the opportunity of looping to check if we eat ourselves*/
175 if(new_tail->next->r == new_row && new_tail->next->c == new_col)
176 siglongjmp(game_over, 1/*<-will be the retval of setjmp*/);
177 new_head = new_tail->next; /* reuse element instead of malloc() */
178 new_tail->next = NULL;
179
180 new_head->r = new_row;
181 new_head->c = new_col;
182 new_head->next = g.s;
183
184 g.s = new_head;
185 }
186
187 void spawn_item (int type, int value) {
188 int row, col;
189 try_again:
190 row = rand() % g.h;
191 col = rand() % g.w;
192 /* loop through snake to check if we aren't on it */
193 //TODO: inefficient as snake gets longer; near impossible in the end
194 for (struct snake* s = g.s; s; s = s->next)
195 if (s->r == row && s->c == col) goto try_again;
196
197 //3. get item from category TODO
198 struct item* new_item = malloc (sizeof(struct item));
199 new_item->r = row;
200 new_item->c = col;
201 new_item->t = type;
202 new_item->v = value;
203 new_item->s = time(0);
204 if (g.i) g.i->prev = new_item;
205 new_item->next = g.i;
206
207 g.i = new_item;
208 }
209
210 void consume_item (struct item* i) {
211 struct item* predecessor = i->prev;
212 struct item* successor = i->next;
213
214 switch (i->t) {
215 case FOOD:
216 switch (i->v) {
217 case FOOD_5: g.p += 5; break;
218 case FOOD_10: g.p += 10; break;
219 case FOOD_20: g.p += 20; break;
220 }
221 snake_append(&g.s, 0,0); /* position doesn't matter, as item */
222 break; /* will be reused as the head before it is drawn */
223 case BONUS:
224 //TODO: handle bonus
225 break;
226 }
227
228 if (predecessor == NULL) {
229 g.i = successor;
230 if (successor) successor->prev = NULL;
231 } else {
232 predecessor->next = successor;
233 successor->prev = predecessor; //TODO: segfaults here if we eat the first one if more than one is on the screen
234 }
235
236 free (i);
237 }
238
239 void show_playfield (void) {
240 //int score_width = g.p > 9999?6:4;
241 int score_width = 4; // v- = the -| spacer |-
242 float half_width = (g.w - score_width/CW - 2)/2.0; //TODO: this whole endavour is ugly
243 /* top border */
244 print(BORDER(T,L));
245 printm (half_width, BORDER(T,C)); //TODO: i bet this breaks in dec mode
246 printf ("%s %0*d %s", BORDER(S,L), score_width, g.p, BORDER(S,R));
247 printm ((int)(half_width+.5), BORDER(T,C));
248 printf ("%s\n", BORDER(T,R));
249
250 /* main area */
251 for (int row = 0; row < g.h; row++)
252 printf ("%s%*s%s\n", BORDER(C,L), CW*g.w, "", BORDER(C,R));
253
254 /* bottom border */
255 print(BORDER(B,L));
256 printm (g.w, BORDER(B,C));
257 print (BORDER(B,R));
258
259 /* print snake */
260 struct snake* last = NULL;
261 int color = -1; //TODO: that's a hack
262 for (struct snake* s = g.s; s; s = s->next) {
263 move_ph (s->r+COL_OFFSET, s->c*CW+LINE_OFFSET);
264
265 int predecessor = (last==NULL)?NONE:
266 (last->r < s->r) ? NORTH:
267 (last->r > s->r) ? SOUTH:
268 (last->c > s->c) ? EAST:
269 (last->c < s->c) ? WEST:NONE;
270 int successor = (s->next == NULL)?NONE:
271 (s->next->r < s->r) ? NORTH:
272 (s->next->r > s->r) ? SOUTH:
273 (s->next->c > s->c) ? EAST:
274 (s->next->c < s->c) ? WEST:NONE;
275
276 printf ("\033[%sm", color==-1?"1m\033[92":color?"92":"32"); //TODO: clean this up
277 print (op.scheme->snake[predecessor][successor]);
278 printf ("\033[0m");
279 last = s;
280 color = (color+1) % 2;
281 }
282
283 /* print item queue */
284 for (struct item* i = g.i; i; i = i->next) {
285 move_ph (i->r+LINE_OFFSET, i->c*CW+COL_OFFSET);
286 if (i->t == FOOD) print (op.scheme->item[i->v]);
287 else if (i->t==BONUS) /* TODO: print bonus */;
288 }
289
290 //TODO: temporarily print speed
291 move_ph (g.h+LINE_OFFSET, g.w);
292 printf ("%f", g.v);
293 }
294
295 void snake_append (struct snake** s, int row, int col) {
296 struct snake* new = malloc (sizeof(struct snake));
297 new->r = row;
298 new->c = col;
299 new->next = NULL;
300
301 if (*s) {
302 struct snake* p = *s;
303 while (p->next) p = p->next;
304 p->next = new;
305 } else {
306 *s = new;
307 }
308 }
309
310 void init_snake() {
311 for (int i = 0; i < op.l; i++)
312 snake_append(&g.s, g.h/2, g.w/2-i);
313 }
314
315 #define free_ll(head) do{ \
316 while (head) { \
317 void* tmp = head; \
318 head = head->next; \
319 free(tmp); \
320 } \
321 }while(0)
322
323 void quit (void) {
324 screen_setup(0);
325 free_ll(g.s);
326 free_ll(g.i);
327 free_ll(g.n); //TODO: doesn't get free'd correctly
328 }
329
330 enum esc_states {
331 START,
332 ESC_SENT,
333 CSI_SENT,
334 MOUSE_EVENT,
335 };
336 int getctrlseq (void) {
337 int c;
338 int state = START;
339 int offset = 0x20; /* never sends control chars as data */
340 while ((c = getchar()) != EOF) {
341 switch (state) {
342 case START:
343 switch (c) {
344 case '\033': state=ESC_SENT; break;
345 default: return c;
346 }
347 break;
348 case ESC_SENT:
349 switch (c) {
350 case '[': state=CSI_SENT; break;
351 default: return CTRSEQ_INVALID;
352 }
353 break;
354 case CSI_SENT:
355 switch (c) {
356 case 'A': return CTRSEQ_CURSOR_UP;
357 case 'B': return CTRSEQ_CURSOR_DOWN;
358 case 'C': return CTRSEQ_CURSOR_RIGHT;
359 case 'D': return CTRSEQ_CURSOR_LEFT;
360 default: return CTRSEQ_INVALID;
361 }
362 break;
363 default:
364 return CTRSEQ_INVALID;
365 }
366 }
367 return 2;
368 }
369
370 void append_movement (int dir) {
371 struct directions* n;
372 for (n = g.n; n && n->next; n = n->next); /* advance to the end */
373 if (n && n->d == dir) return; /* don't add the same direction twice */
374
375 struct directions* new_event = malloc (sizeof(struct directions));
376 new_event->d = dir;
377 new_event->next = NULL;
378
379 if (g.n == NULL)
380 g.n = new_event;
381 else
382 n->next = new_event;
383 }
384
385 int get_movement (void) {
386 if (g.n == NULL) return -1;
387
388 int retval = g.n->d;
389 struct directions* delet_this = g.n;
390 g.n = g.n->next;
391 free(delet_this);
392 return retval;
393 }
394
395 void move_ph (int line, int col) {
396 /* move printhead to zero-indexed position */
397 printf ("\033[%d;%dH", line+1, col+1);
398 }
399
400 void clamp_fieldsize (void) { //TODO: use
401 /* clamp field size to terminal size and mouse maximum: */
402 struct winsize w;
403 ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
404
405 if (g.w < 1) g.w = 1;
406 if (g.h < 1) g.h = 1;
407
408 if (COL_OFFSET + g.w*CW + COL_OFFSET > w.ws_col)
409 g.w = (w.ws_col - COL_OFFSET - COL_OFFSET)/CW; //TODO: does not work in `-d' (in xterm)
410 if (LINE_OFFSET + g.h + LINES_AFTER > w.ws_row)
411 g.h = w.ws_row - (LINE_OFFSET+LINES_AFTER);
412 }
413
414 void timer_setup (int enable) {
415 static struct itimerval tbuf;
416 tbuf.it_interval.tv_sec = 0;//TODO: make it speed up automatically
417 tbuf.it_interval.tv_usec = (1000000/g.v)-1; /*WARN: 1 <= g.v <= 999999*/
418
419 if (enable) {
420 g.t = time(NULL);//TODO: interferes with 'pause'
421 tbuf.it_value.tv_sec = tbuf.it_interval.tv_sec;
422 tbuf.it_value.tv_usec = tbuf.it_interval.tv_usec;
423 } else {
424 tbuf.it_value.tv_sec = 0;
425 tbuf.it_value.tv_usec = 0;
426 }
427
428 if ( setitimer(ITIMER_REAL, &tbuf, NULL) == -1 ) {
429 perror("setitimer");
430 exit(1);
431 }
432
433 }
434
435 void signal_setup (void) {
436 struct sigaction saction;
437
438 saction.sa_handler = signal_handler;
439 sigemptyset(&saction.sa_mask);
440 saction.sa_flags = 0;
441 if (sigaction(SIGALRM, &saction, NULL) < 0 ) {
442 perror("SIGALRM");
443 exit(1);
444 }
445
446 if (sigaction(SIGINT, &saction, NULL) < 0 ) {
447 perror ("SIGINT");
448 exit (1);
449 }
450 }
451
452 void signal_handler (int signum) {
453 //int dtime;
454 switch (signum) {
455 case SIGALRM:
456 //dtime = difftime (time(NULL), g.t);
457 //move_ph (1, g.w*CW-(CW%2)-3-(dtime>999));
458 //printf ("[%03d]", g.t?dtime:0);
459 snake_advance();
460 break;
461 case SIGINT:
462 exit(128+SIGINT);
463 }
464 }
465
466 void screen_setup (int enable) {
467 if (enable) {
468 raw_mode(1);
469 printf ("\033[s\033[?47h"); /* save cursor, alternate screen */
470 printf ("\033[H\033[J"); /* reset cursor, clear screen */
471 printf ("\033[?25l"); /* hide cursor */
472 print (op.scheme->init_seq); /* swich charset, if necessary */
473 } else {
474 print (op.scheme->reset_seq); /* reset charset, if necessary */
475 printf ("\033[?25h"); /* show cursor */
476 printf ("\033[?47l\033[u"); /* primary screen, restore cursor */
477 raw_mode(0);
478 }
479 }
480
481 /* http://users.csc.calpoly.edu/~phatalsk/357/lectures/code/sigalrm.c */
482 void raw_mode(int enable) {
483 static struct termios saved_term_mode;
484 struct termios raw_term_mode;
485
486 if (enable) {
487 tcgetattr(STDIN_FILENO, &saved_term_mode);
488 raw_term_mode = saved_term_mode;
489 raw_term_mode.c_lflag &= ~(ICANON | ECHO);
490 raw_term_mode.c_cc[VMIN] = 1 ;
491 raw_term_mode.c_cc[VTIME] = 0;
492 tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_term_mode);
493 } else {
494 tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_term_mode);
495 }
496 }
Imprint / Impressum