]> git.gir.st - minesVIiper.git/blob - mines.c
start with resize mode (segfaults!), mouse highlight, minor cleanups
[minesVIiper.git] / mines.c
1 /*******************************************************************************
2 minesviiper 0.3.145926
3 By Tobias Girstmair, 2015 - 2018
4
5 ./minesviiper 16x16x40
6 (see ./minesviiper -h for full list of options)
7
8 MOUSE MODE: - left click to open and choord
9 - right click to flag/unflag
10 VI MODE: - hjkl to move cursor left/down/up/right
11 - o/space to open and choord
12 - i to flag/unflag
13 - (see `./minesviiper -h' for all keybindings)
14
15 GNU GPL v3, see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt
16 *******************************************************************************/
17
18
19 #define _POSIX_C_SOURCE 2 /*for getopt, sigaction in c99*/
20 #include <ctype.h>
21 #include <signal.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <sys/ioctl.h>
25 #include <sys/time.h>
26 #include <termios.h>
27 #include <time.h>
28 #include <unistd.h>
29
30 #include "mines.h"
31 #include "schemes.h"
32
33 #define LINE_OFFSET 3
34 #define LINES_AFTER 2
35 #define COL_OFFSET 2
36 #define BIG_MOVE 5
37 #define MOUSE_MAX 231
38
39 #define MIN(a,b) (a>b?b:a)
40 #define MAX(a,b) (a>b?a:b)
41 #define CLAMP(a,m,M) (a<m?m:(a>M?M:a))
42 #define printm(n, s) for (int _loop = 0; _loop < n; _loop++) fputs (s, stdout)
43 #define print(str) fputs (str?str:"", stdout)
44 #define EMOT(e) op.scheme->emoticons[EMOT_ ## e]
45 #define BORDER(l, c) op.scheme->border[B_ ## l][B_ ## c]
46 #define CW op.scheme->cell_width /* for brevity */
47 #define CTRL_ 0x1F &
48
49 #define CELL f.c[ROW][COL] /* helper for AROUND() */
50 #define AROUND(l, c) /* gives us loop-variables ROW, COL, CELL */ \
51 for (int ROW = MAX(l-1, 0); ROW <= MIN(l+1, f.h-1); ROW++) \
52 for (int COL = MAX(c-1, 0); COL <= MIN(c+1, f.w-1); COL++) \
53 if (!(ROW == l && COL == c)) /* skip itself */
54
55 struct minefield f;
56 struct game g;
57 struct opt op;
58
59 int main (int argc, char** argv) {
60 /* defaults: */
61 f.w = 30;
62 f.h = 16;
63 f.m = 99;
64 op.scheme = &symbols_mono;
65 op.mode = FLAG;
66
67 int optget;
68 opterr = 0; /* don't print message on unrecognized option */
69 while ((optget = getopt (argc, argv, "+hnfqcdx")) != -1) {
70 switch (optget) {
71 case 'n': op.mode = NOFLAG; break;
72 case 'f': op.mode = FLAG; break; /*default*/
73 case 'q': op.mode = QUESM; break;
74 case 'c': op.scheme = &symbols_col1; break;
75 case 'd': op.scheme = &symbols_doublewidth; break;
76 case 'h':
77 default:
78 fprintf (stderr, SHORTHELP LONGHELP, argv[0]);
79 return !(optget=='h');
80 }
81 } if (optind < argc) { /* parse Fieldspec */
82 if (parse_fieldspec (argv[optind])) {
83 fprintf (stderr, "FIELDSPEC: WxH[xM]"
84 " (width 'x' height 'x' mines)\n");
85 return 1;
86 }
87 }
88
89 signal_setup();
90 screen_setup(1);
91 atexit (*quit);
92
93 /* check boundaries */
94 if (f.m > (f.w-1) * (f.h-1)) {
95 move_ph (LINE_OFFSET+f.h+LINES_AFTER-1,0);
96 f.m = (f.w-1) * (f.h-1);
97 fprintf (stdout, "too many mines. reduced to %d.\r\n", f.m); //TODO: should disappear after a timeout (stomp-hi?)
98 }
99 /* end check */
100
101 newgame:
102 switch (minesviiper()) {
103 case GAME_NEW: goto newgame;
104 case GAME_WON: g.o = GAME_WON; break;
105 case GAME_LOST:g.o = GAME_LOST;break;
106 case GAME_QUIT:goto quit;
107 }
108
109 timer_setup(0); /* stop timer */
110 show_minefield (SHOWMINES);
111 for(;;) {
112 switch(getch_wrapper()) {
113 case WRAPPER_EMOTICON:
114 case 'r':
115 free_field ();
116 goto newgame;
117 case 'q': goto quit;
118 }
119 }
120
121 quit:
122 return 0;
123 }
124
125 int minesviiper(void) {
126 f.c = alloc_array (f.h, f.w); /* TODO: just memset it */
127 g = (const struct game){0}; /* reset all game-specific parameters */
128
129 show_minefield (NORMAL);
130
131 for(;;) {
132 switch (getch_wrapper()) {
133 case ' ':
134 if (f.c[g.p[0]][g.p[1]].o == OPENED) goto open_cell;
135 if (g.s == MODE_OPEN) goto open_cell;
136 if (g.s == MODE_FLAG) goto flag_cell;
137 if (g.s == MODE_QUESM) goto quesm_cell;
138 break;
139 case 'a':
140 g.s = (g.s+1)%(op.mode+1);
141 show_minefield (g.c?SHOWMINES:NORMAL);
142 break;
143 open_cell:
144 case CTRSEQ_MOUSE_LEFT:
145 case 'o':
146 switch (do_uncover(&(g.n))) {
147 case GAME_LOST: return GAME_LOST;
148 case GAME_WON: return GAME_WON;
149 }
150 break;
151 flag_cell:
152 case CTRSEQ_MOUSE_RIGHT:
153 case 'i': flag_square (g.p[0], g.p[1]); break;
154 quesm_cell:
155 case '?':quesm_square (g.p[0], g.p[1]); break;
156 case CTRSEQ_CURSOR_LEFT:
157 case 'h': move_hi (g.p[0], g.p[1]-1); break;
158 case CTRSEQ_CURSOR_DOWN:
159 case 'j': move_hi (g.p[0]+1, g.p[1] ); break;
160 case CTRSEQ_CURSOR_UP:
161 case 'k': move_hi (g.p[0]-1, g.p[1] ); break;
162 case CTRSEQ_CURSOR_RIGHT:
163 case 'l': move_hi (g.p[0], g.p[1]+1); break;
164 case 'w': to_next_boundary (g.p[0], g.p[1], '>'); break;
165 case 'b': to_next_boundary (g.p[0], g.p[1], '<'); break;
166 case 'u': to_next_boundary (g.p[0], g.p[1], '^'); break;
167 case 'd': to_next_boundary (g.p[0], g.p[1], 'v'); break;
168 case '0': /* fallthrough */
169 case '^': move_hi (g.p[0], 0 ); break;
170 case '$': move_hi (g.p[0], f.w-1 ); break;
171 case 'g': move_hi (0, g.p[1] ); break;
172 case 'G': move_hi (f.h-1, g.p[1] ); break;
173 case 'z': move_hi (f.h/2, f.w/2); break;
174 case 'm': set_mark(); break;
175 case'\'': /* fallthrough */
176 case '`': jump_mark(); break;
177 case WRAPPER_EMOTICON:
178 case 'r': return GAME_NEW;
179 case 'q': return GAME_QUIT;
180 case CTRL_'L':
181 screen_setup(1);
182 show_minefield (NORMAL);
183 break;
184 case CTRL_'R':
185 /* resize minefield mode */
186 for(;;) {
187 free_field ();
188 switch (getch_wrapper()) { //TODO: segfault on mouse up
189 case 'q': return GAME_NEW;
190 case 'h': f.w--; break;
191 case 'j': f.h++; break;
192 case 'k': f.h--; break;
193 case 'l': f.w++; break;
194 }
195 // TODO: clamp size to termsize/mousemax
196 /* recalculate mines: */
197 f.m = f.w*f.h*(f.w*f.h<30*16?.15625:.20625); //TODO: should be function
198
199 f.c = alloc_array (f.h, f.w);
200 screen_setup(1); /* clears the screen */
201 show_minefield (RESIZEMODE);
202 }
203 case '\\':
204 if (g.n == GAME_NEW) break; /* must open a cell first */
205 g.c = !g.c;
206 show_minefield (g.c?SHOWMINES:NORMAL);
207 break;
208 }
209 }
210
211 }
212
213 void quit (void) {
214 screen_setup(0);
215 free_field ();
216 }
217
218 /* I haven't won as long as a cell exists, that
219 - I haven't opened, and
220 - is not a mine */
221 int everything_opened (void) {
222 for (int row = 0; row < f.h; row++)
223 for (int col = 0; col < f.w; col++)
224 if (f.c[row][col].o == CLOSED &&
225 f.c[row][col].m == NO_MINE ) return 0;
226 return 1;
227 }
228
229 int wait_mouse_up (int l, int c) { /* TODO: should not take minefield-coords but absolute ones */
230 unsigned char mouse2[3];
231 int level = 1;
232 int l2, c2;
233
234 /* show :o face */
235 move_ph (1, field2screen_c (f.w/2)-1); print (EMOT(OHH));
236
237 if (!(l < 0 || l >= f.h || c < 0 || c >= f.w)) {
238 /* show a pushed-in button if cursor is on minefield */
239 partial_show_minefield (l, c, HIGHLIGHT);
240 }
241
242 while (level > 0) {
243 if (getctrlseq (mouse2) == CTRSEQ_MOUSE) {
244 /* ignore mouse wheel events: */
245 if (mouse2[0] & 0x40) continue;
246
247 else if (mouse2[0]&3 == 3) level--; /* release event */
248 else level++; /* another button pressed */
249 }
250 }
251
252 move_ph (1, field2screen_c (f.w/2)-1); print (get_emoticon());
253
254 if (!(l < 0 || l >= f.h || c < 0 || c >= f.w)) {
255 partial_show_minefield (l, c, NORMAL);
256 }
257 c2 = screen2field_c(mouse2[1]);
258 l2 = screen2field_l(mouse2[2]);
259 return ((l2 == l) && (c2 == c));
260 }
261
262 int choord_square (int line, int col) {
263 if (uncover_square (line, col)) return 1;
264 AROUND(line,col)
265 if (CELL.f != FLAG) {
266 if (uncover_square (ROW, COL))
267 return 1;
268 }
269
270 return 0;
271 }
272
273 int uncover_square (int l, int c) {
274 if (f.c[l][c].o == OPENED)
275 return 0; /* nothing to do */
276
277 f.c[l][c].o = OPENED;
278 f.c[l][c].f = NOFLAG; /*must not be QUESM, otherwise rendering issues*/
279 partial_show_minefield (l, c, NORMAL);
280
281 if (f.c[l][c].m) {
282 f.c[l][c].m = DEATH_MINE;
283 return 1;
284 }
285
286 /* check for chording */
287 if (f.c[l][c].n == 0) {
288 AROUND(l, c)
289 if (uncover_square (ROW, COL)) return 1;
290 }
291
292 return 0;
293 }
294
295 void flag_square (int l, int c) {
296 static char modechar[] = {'*', '!', '?'};
297
298 if (f.c[l][c].o != CLOSED) return;
299 /* cycle through flag/quesm/noflag: */
300 f.c[l][c].f = (f.c[l][c].f + 1) % (op.mode + 1);
301 if (f.c[l][c].f==FLAG) g.f++;
302 else if ((op.mode==FLAG && f.c[l][c].f==NOFLAG) ||
303 (op.mode==QUESM && f.c[l][c].f==QUESM)) g.f--;
304 partial_show_minefield (l, c, NORMAL);
305 move_ph (1, op.scheme->cell_width);
306 printf ("[%03d%c]", f.m - g.f, modechar[g.s]);
307 }
308
309 void quesm_square (int l, int c) {
310 /* toggle question mark / none. won't turn flags into question marks.
311 unlike flag_square, this function doesn't respect `-q'. */
312 if (f.c[l][c].o != CLOSED) return;
313 else if (f.c[l][c].f == NOFLAG) f.c[l][c].f = QUESM;
314 else if (f.c[l][c].f == QUESM) f.c[l][c].f = NOFLAG;
315 partial_show_minefield (l, c, NORMAL);
316 }
317
318 int do_uncover (int* is_newgame) {
319 if (*is_newgame == GAME_NEW) {
320 *is_newgame = GAME_INPROGRESS;
321 fill_minefield (g.p[0], g.p[1]);
322 timer_setup(1);
323 }
324
325 if (f.c[g.p[0]][g.p[1]].f == FLAG ) return GAME_INPROGRESS;
326 if (f.c[g.p[0]][g.p[1]].o == CLOSED) {
327 if (uncover_square (g.p[0], g.p[1])) return GAME_LOST;
328 } else if (get_neighbours (g.p[0], g.p[1], 1) == 0) {
329 if (choord_square (g.p[0], g.p[1])) return GAME_LOST;
330 } else {
331 /* show the stomp size if we aren't fully flagged */
332 AROUND(g.p[0], g.p[1])
333 if (CELL.o == CLOSED && CELL.f !=FLAG)
334 partial_show_minefield (ROW, COL, HIGHLIGHT); //TODO: hide after timeout
335 /* save the highlight position, timeout-end, and setup a timer. if another tiemout comes in before the timeout is over, the old one shall be removed and the timer cancelled before drawing the new one. if we hit any key it shall remove itself aswell */
336 }
337 if (everything_opened()) return GAME_WON;
338
339 return GAME_INPROGRESS;
340 }
341
342 void set_mark(void) {
343 int mark = tolower(getch_wrapper());
344 if (mark < 'a' || mark > 'z') return; /*out of bound*/
345
346 g.m[mark-'a'].l = g.p[0];
347 g.m[mark-'a'].c = g.p[1];
348 g.m[mark-'a'].s = 1;
349 }
350
351 void jump_mark(void) {
352 int mark = tolower(getch_wrapper());
353 /* check bounds and if set: */
354 if (mark < 'a' || mark > 'z' || !g.m[mark-'a'].s) return;
355 move_hi (g.m[mark-'a'].l, g.m[mark-'a'].c);
356 }
357
358 void fill_minefield (int l, int c) {
359 srand (time(0));
360 int mines_set = f.m;
361 while (mines_set) {
362 int line = rand() % f.h;
363 int col = rand() % f.w;
364
365 if (f.c[line][col].m) {
366 /* skip if field already has a mine */
367 continue;
368 } else if ((line == l) && (col == c)) {
369 /* don't put a mine on the already opened (first click) field */
370 continue;
371 } else {
372 mines_set--;
373 f.c[line][col].m = STD_MINE;
374 }
375 }
376
377 /* precalculate neighbours */
378 for (int l=0; l < f.h; l++)
379 for (int c=0; c < f.w; c++)
380 f.c[l][c].n = get_neighbours (l, c, NORMAL);
381 }
382
383 void move_ph (int line, int col) {
384 /* move printhead to zero-indexed position */
385 printf ("\033[%d;%dH", line+1, col+1);
386 }
387
388 void move_hi (int l, int c) {
389 /* move cursor and highlight to absolute coordinates */
390
391 partial_show_minefield (g.p[0], g.p[1], NORMAL);
392 /* update g.p */
393 g.p[0] = CLAMP(l, 0, f.h-1);
394 g.p[1] = CLAMP(c, 0, f.w-1);
395 move_ph (g.p[0]+LINE_OFFSET, field2screen_c(g.p[1]));
396
397 partial_show_minefield (g.p[0], g.p[1], HIGHLIGHT);
398 }
399
400 /* to_next_boundary(): move into the supplied direction until a change in open-
401 state or flag-state is found and move there. falls back to BIG_MOVE. */
402 #define FIND_NEXT(X, L, C, L1, C1, MAX, OP) do {\
403 new_ ## X OP ## = BIG_MOVE;\
404 for (int i = X OP 1; i > 0 && i < f.MAX-1; i OP ## OP)\
405 if (((f.c[L ][C ].o<<2) + f.c[L ][C ].f) \
406 != ((f.c[L1][C1].o<<2) + f.c[L1][C1].f)) {\
407 new_ ## X = i OP 1;\
408 break;\
409 }\
410 } while(0)
411 void to_next_boundary (int l, int c, char direction) {
412 int new_l = l;
413 int new_c = c;
414 switch (direction) {
415 case '>': FIND_NEXT(c, l, i, l, i+1, w, +); break;
416 case '<': FIND_NEXT(c, l, i, l, i-1, w, -); break;
417 case '^': FIND_NEXT(l, i, c, i-1, c, h, -); break;
418 case 'v': FIND_NEXT(l, i, c, i+1, c, h, +); break;
419 }
420
421 move_hi (new_l, new_c);
422 }
423 #undef FIND_NEXT
424
425 char* cell2schema (int l, int c, int mode) {
426 struct minecell cell = f.c[l][c];
427
428 if (mode == SHOWMINES) return (
429 cell.f == FLAG && cell.m ? op.scheme->field_flagged:
430 cell.f == FLAG && !cell.m ? op.scheme->mine_wrongf:
431 cell.m == STD_MINE ? op.scheme->mine_normal:
432 cell.m == DEATH_MINE ? op.scheme->mine_death:
433 cell.o == CLOSED ? op.scheme->field_closed:
434 /*.......................*/ op.scheme->number[f.c[l][c].n]);
435 else return (
436 cell.f == FLAG ? op.scheme->field_flagged:
437 cell.f == QUESM ? op.scheme->field_question:
438 cell.o == CLOSED ? op.scheme->field_closed:
439 cell.m == STD_MINE ? op.scheme->mine_normal:
440 cell.m == DEATH_MINE ? op.scheme->mine_death:
441 /*.......................*/ op.scheme->number[f.c[l][c].n]);
442 }
443
444 void partial_show_minefield (int l, int c, int mode) {
445 move_ph (l+LINE_OFFSET, field2screen_c(c));
446 if (mode == HIGHLIGHT) printf ("\033[7m"); /* reverse video */
447 print (cell2schema(l, c, mode));
448 if (mode == HIGHLIGHT) printf ("\033[0m"); /* reset all */
449 }
450
451 char* get_emoticon(void) {
452 return g.o==GAME_WON ? EMOT(WON):
453 g.o==GAME_LOST? EMOT(DEAD):
454 EMOT(SMILE);
455 }
456
457 /* https://zserge.com/blog/c-for-loop-tricks.html */
458 #define print_line(which) \
459 for (int _break = (printf("%s", BORDER(which,LEFT)), 1); _break; \
460 _break = 0, printf("%s\r\n", BORDER(which,RIGHT)))
461 #define print_border(which, width) \
462 print_line(which) printm (width, BORDER(which,MIDDLE))
463 void show_minefield (int mode) {
464 int dtime = difftime (time(NULL), g.t)*!!g.t;
465 int half_spaces = f.w*op.scheme->cell_width/2;
466 int left_spaces = MAX(0,half_spaces-7-(f.m-g.f>999));
467 int right_spaces = MAX(0,half_spaces-6-(dtime>999));
468 static char modechar[] = {'*', '!', '?'};
469
470 move_ph (0,0);
471
472 print_border(TOP, f.w);
473 print_line(STATUS) {
474 if (mode == RESIZEMODE) printf ("%-*s", 2*half_spaces,
475 "Resize Mode: hjkl to resize, q to exit");
476 else printf("[%03d%c]%*s%s%*s[%03d]",
477 /* [ */ f.m - g.f, modechar[g.s], /* ] */
478 left_spaces,"", get_emoticon(), right_spaces,"",
479 /* [ */ dtime /* ] */);
480 }
481 print_border(DIVIDER, f.w);
482 /* main body */
483 for (int l = 0; l < f.h; l++) print_line(FIELD)
484 printm (f.w, cell2schema(l, _loop, mode));
485 print_border(BOTTOM, f.w);
486 }
487
488 int get_neighbours (int line, int col, int reduced_mode) {
489 /* counts mines surrounding a square
490 modes: 0=normal; 1=reduced */
491
492 int count = 0;
493 AROUND(line, col) {
494 count += !!CELL.m;
495 count -= reduced_mode * CELL.f==FLAG;
496 }
497 return count;
498 }
499
500 struct minecell** alloc_array (int lines, int cols) {
501 free_field (); /* TODO: just memset */
502 struct minecell** a = malloc (lines * sizeof(struct minecell*));
503 if (a == NULL) return NULL;
504 for (int l = 0; l < lines; l++) {
505 a[l] = calloc (cols, sizeof(struct minecell));
506 if (a[l] == NULL) goto unalloc;
507 }
508
509 return a;
510 unalloc:
511 for (int l = 0; l < lines; l++)
512 free (a[l]);
513 return NULL;
514 }
515
516 void free_field (void) {
517 if (f.c == NULL) return;
518 for (int l = 0; l < f.h; l++) {
519 free (f.c[l]);
520 }
521
522 free (f.c);
523 f.c = NULL;
524 }
525
526 int screen2field_l (int l) {
527 return (l-LINE_OFFSET) - 1;
528 }
529 int screen2field_c (int c) {
530 /* this depends on the cell width and only works when it is 1 or 2. */
531 return (c-COL_OFFSET+1 - 2*(CW%2))/2 - CW/2;
532 }
533 int field2screen_c (int c) {
534 return (CW*c+COL_OFFSET - (CW%2));
535 }
536 int clicked_emoticon (unsigned char* mouse) {
537 return (mouse[2] == LINE_OFFSET-1 && (
538 mouse[1] == f.w+COL_OFFSET ||
539 mouse[1] == f.w+COL_OFFSET+1));
540 }
541
542 enum esc_states {
543 START,
544 ESC_SENT,
545 CSI_SENT,
546 MOUSE_EVENT,
547 };
548 int getctrlseq (unsigned char* buf) {
549 int c;
550 int state = START;
551 int offset = 0x20; /* never sends control chars as data */
552 while ((c = getchar()) != EOF) {
553 switch (state) {
554 case START:
555 switch (c) {
556 case '\033': state=ESC_SENT; break;
557 default: return c;
558 }
559 break;
560 case ESC_SENT:
561 switch (c) {
562 case '[': state=CSI_SENT; break;
563 default: return CTRSEQ_INVALID;
564 }
565 break;
566 case CSI_SENT:
567 switch (c) {
568 case 'A': return CTRSEQ_CURSOR_UP;
569 case 'B': return CTRSEQ_CURSOR_DOWN;
570 case 'C': return CTRSEQ_CURSOR_RIGHT;
571 case 'D': return CTRSEQ_CURSOR_LEFT;
572 case 'M': state=MOUSE_EVENT; break;
573 default: return CTRSEQ_INVALID;
574 }
575 break;
576 case MOUSE_EVENT:
577 buf[0] = c - offset;
578 buf[1] = getchar() - offset;
579 buf[2] = getchar() - offset;
580 return CTRSEQ_MOUSE;
581 default:
582 return CTRSEQ_INVALID;
583 }
584 }
585 return 2;
586 }
587
588 int getch(unsigned char* buf) {
589 /* returns a character, EOF, or constant for an escape/control sequence - NOT
590 compatible with the ncurses implementation of same name */
591 int action = getctrlseq(buf);
592 int l, c;
593 switch (action) {
594 case CTRSEQ_MOUSE:
595 l = screen2field_l (buf[2]);
596 c = screen2field_c (buf[1]);
597
598 if (buf[0] > 3) break; /* ignore all but left/middle/right/up */
599 int success = wait_mouse_up(l, c);
600
601 /* mouse moved while pressed: */
602 if (!success) return CTRSEQ_INVALID;
603
604 switch (buf[0]) {
605 case 0: return CTRSEQ_MOUSE_LEFT;
606 case 1: return CTRSEQ_MOUSE_MIDDLE;
607 case 2: return CTRSEQ_MOUSE_RIGHT;
608 }
609 }
610
611 return action;
612 }
613
614 int getch_wrapper (void) {
615 unsigned char mouse[3];
616 int c = getch(mouse);
617
618 if (c == CTRSEQ_MOUSE_LEFT || c == CTRSEQ_MOUSE_RIGHT) {
619 if (clicked_emoticon(mouse))
620 return WRAPPER_EMOTICON;
621
622 if (screen2field_c (mouse[1]) < 0
623 || screen2field_c (mouse[1]) >= f.w
624 || screen2field_l (mouse[2]) < 0
625 || screen2field_l (mouse[2]) >= f.h)
626 return CTRSEQ_INVALID;
627
628 g.p[0] = screen2field_l (mouse[2]);
629 g.p[1] = screen2field_c (mouse[1]);
630
631 return c; /* CTRSEQ_MOUSE_LEFT || CTRSEQ_MOUSE_RIGHT */
632 }
633
634 return c;
635 }
636
637 int parse_fieldspec(char* str) {
638 /* parses the FIELDSPEC (WxHxM); returns 1 on error */
639 int n = sscanf (str, "%dx%dx%d", &f.w, &f.h, &f.m);
640 struct winsize w;
641 ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
642
643 /* clamp field size to terminal size and mouse maximum: */
644 if (COL_OFFSET + f.w*CW + COL_OFFSET > w.ws_col)
645 f.w = w.ws_col/CW - (COL_OFFSET+COL_OFFSET);
646 if (LINE_OFFSET + f.h + LINES_AFTER > w.ws_row)
647 f.h = w.ws_row - (LINE_OFFSET+LINES_AFTER);
648 if (COL_OFFSET + f.w*CW > MOUSE_MAX)
649 f.w = MOUSE_MAX/CW - COL_OFFSET;
650 if (LINE_OFFSET + f.h > MOUSE_MAX)
651 f.h = MOUSE_MAX - LINE_OFFSET;
652
653 if (n < 2) {
654 return 1; /* error */
655 } else if (n == 2) {
656 if (f.w < 30) f.m = f.w*f.h*.15625; //TODO: used twice; make function
657 else f.m = f.w*f.h*.20625;
658 }
659
660 return 0;
661 }
662
663 void timer_setup (int enable) {
664 static struct itimerval tbuf;
665 tbuf.it_interval.tv_sec = 1;
666 tbuf.it_interval.tv_usec = 0;
667
668 if (enable) {
669 g.t = time(NULL);
670 tbuf.it_value.tv_sec = 1;
671 tbuf.it_value.tv_usec = 0;
672 if (setitimer(ITIMER_REAL, &tbuf, NULL) == -1) {
673 perror("setitimer");
674 exit(1);
675 }
676 } else {
677 tbuf.it_value.tv_sec = 0;
678 tbuf.it_value.tv_usec = 0;
679 if ( setitimer(ITIMER_REAL, &tbuf, NULL) == -1 ) {
680 perror("setitimer");
681 exit(1);
682 }
683 }
684
685 }
686
687 void signal_setup (void) {
688 struct sigaction saction;
689
690 saction.sa_handler = signal_handler;
691 sigemptyset(&saction.sa_mask);
692 saction.sa_flags = 0;
693 if (sigaction(SIGALRM, &saction, NULL) < 0 ) {
694 perror("SIGALRM");
695 exit(1);
696 }
697
698 if (sigaction(SIGINT, &saction, NULL) < 0 ) {
699 perror ("SIGINT");
700 exit (1);
701 }
702 }
703
704 void signal_handler (int signum) {
705 int dtime;
706 switch (signum) {
707 case SIGALRM:
708 dtime = difftime (time(NULL), g.t);
709 move_ph (1, f.w*CW-(CW%2)-3-(dtime>999));
710 printf ("[%03d]", g.t?dtime:0);
711 break;
712 case SIGINT:
713 exit(128+SIGINT);
714 }
715 }
716
717 void screen_setup (int enable) {
718 if (enable) {
719 raw_mode(1);
720 printf ("\033[s\033[?47h"); /* save cursor, alternate screen */
721 printf ("\033[H\033[J"); /* reset cursor, clear screen */
722 printf ("\033[?1000h\033[?25l"); /* enable mouse, hide cursor */
723 print (op.scheme->init_seq); /* swich charset, if necessary */
724 } else {
725 print (op.scheme->reset_seq); /* reset charset, if necessary */
726 printf ("\033[?9l\033[?25h"); /* disable mouse, show cursor */
727 printf ("\033[?47l\033[u"); /* primary screen, restore cursor */
728 raw_mode(0);
729 }
730 }
731
732 /* http://users.csc.calpoly.edu/~phatalsk/357/lectures/code/sigalrm.c */
733 void raw_mode(int enable) {
734 static struct termios saved_term_mode;
735 struct termios raw_term_mode;
736
737 if (enable) {
738 tcgetattr(STDIN_FILENO, &saved_term_mode);
739 raw_term_mode = saved_term_mode;
740 raw_term_mode.c_lflag &= ~(ICANON | ECHO);
741 raw_term_mode.c_cc[VMIN] = 1 ;
742 raw_term_mode.c_cc[VTIME] = 0;
743 tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_term_mode);
744 } else {
745 tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_term_mode);
746 }
747 }
Imprint / Impressum