]> git.gir.st - minesVIiper.git/blob - mines.c
finish resize mode
[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 screen_setup(1); /* clears the screen */
188 show_minefield (RESIZEMODE);
189
190 free_field(); /* must free before resizing! */
191 unsigned char buf[3];
192 switch (getctrlseq(buf)) {
193 case 'q': return GAME_NEW;
194 case 'h': f.w--; break;
195 case 'j': f.h++; break;
196 case 'k': f.h--; break;
197 case 'l': f.w++; break;
198 }
199
200 clamp_fieldsize();
201 f.m = mines_percentage(f.w, f.h);
202 f.c = alloc_array (f.h, f.w);
203 }
204 case '\\':
205 if (g.n == GAME_NEW) break; /* must open a cell first */
206 g.c = !g.c;
207 show_minefield (g.c?SHOWMINES:NORMAL);
208 break;
209 }
210 }
211
212 }
213
214 void quit (void) {
215 screen_setup(0);
216 free_field ();
217 }
218
219 /* I haven't won as long as a cell exists, that
220 - I haven't opened, and
221 - is not a mine */
222 int everything_opened (void) {
223 for (int row = 0; row < f.h; row++)
224 for (int col = 0; col < f.w; col++)
225 if (f.c[row][col].o == CLOSED &&
226 f.c[row][col].m == NO_MINE ) return 0;
227 return 1;
228 }
229
230 int wait_mouse_up (int l, int c) { /* TODO: should not take minefield-coords but absolute ones */
231 unsigned char mouse2[3];
232 int level = 1;
233 int l2, c2;
234
235 /* show :o face */
236 move_ph (1, field2screen_c (f.w/2)-1); print (EMOT(OHH));
237
238 if (!(l < 0 || l >= f.h || c < 0 || c >= f.w)) {
239 /* show a pushed-in button if cursor is on minefield */
240 partial_show_minefield (l, c, HIGHLIGHT);
241 }
242
243 while (level > 0) {
244 if (getctrlseq (mouse2) == CTRSEQ_MOUSE) {
245 /* ignore mouse wheel events: */
246 if (mouse2[0] & 0x40) continue;
247
248 else if (mouse2[0]&3 == 3) level--; /* release event */
249 else level++; /* another button pressed */
250 }
251 }
252
253 move_ph (1, field2screen_c (f.w/2)-1); print (get_emoticon());
254
255 if (!(l < 0 || l >= f.h || c < 0 || c >= f.w)) {
256 partial_show_minefield (l, c, NORMAL);
257 }
258 c2 = screen2field_c(mouse2[1]);
259 l2 = screen2field_l(mouse2[2]);
260 return ((l2 == l) && (c2 == c));
261 }
262
263 int choord_square (int line, int col) {
264 if (uncover_square (line, col)) return 1;
265 AROUND(line,col)
266 if (CELL.f != FLAG) {
267 if (uncover_square (ROW, COL))
268 return 1;
269 }
270
271 return 0;
272 }
273
274 int uncover_square (int l, int c) {
275 if (f.c[l][c].o == OPENED)
276 return 0; /* nothing to do */
277
278 f.c[l][c].o = OPENED;
279 f.c[l][c].f = NOFLAG; /*must not be QUESM, otherwise rendering issues*/
280 partial_show_minefield (l, c, NORMAL);
281
282 if (f.c[l][c].m) {
283 f.c[l][c].m = DEATH_MINE;
284 return 1;
285 }
286
287 /* check for chording */
288 if (f.c[l][c].n == 0) {
289 AROUND(l, c)
290 if (uncover_square (ROW, COL)) return 1;
291 }
292
293 return 0;
294 }
295
296 void flag_square (int l, int c) {
297 static char modechar[] = {'*', '!', '?'};
298
299 if (f.c[l][c].o != CLOSED) return;
300 /* cycle through flag/quesm/noflag: */
301 f.c[l][c].f = (f.c[l][c].f + 1) % (op.mode + 1);
302 if (f.c[l][c].f==FLAG) g.f++;
303 else if ((op.mode==FLAG && f.c[l][c].f==NOFLAG) ||
304 (op.mode==QUESM && f.c[l][c].f==QUESM)) g.f--;
305 partial_show_minefield (l, c, NORMAL);
306 move_ph (1, op.scheme->cell_width);
307 printf ("[%03d%c]", f.m - g.f, modechar[g.s]);
308 }
309
310 void quesm_square (int l, int c) {
311 /* toggle question mark / none. won't turn flags into question marks.
312 unlike flag_square, this function doesn't respect `-q'. */
313 if (f.c[l][c].o != CLOSED) return;
314 else if (f.c[l][c].f == NOFLAG) f.c[l][c].f = QUESM;
315 else if (f.c[l][c].f == QUESM) f.c[l][c].f = NOFLAG;
316 partial_show_minefield (l, c, NORMAL);
317 }
318
319 int do_uncover (int* is_newgame) {
320 if (*is_newgame == GAME_NEW) {
321 *is_newgame = GAME_INPROGRESS;
322 fill_minefield (g.p[0], g.p[1]);
323 timer_setup(1);
324 }
325
326 if (f.c[g.p[0]][g.p[1]].f == FLAG ) return GAME_INPROGRESS;
327 if (f.c[g.p[0]][g.p[1]].o == CLOSED) {
328 if (uncover_square (g.p[0], g.p[1])) return GAME_LOST;
329 } else if (get_neighbours (g.p[0], g.p[1], 1) == 0) {
330 if (choord_square (g.p[0], g.p[1])) return GAME_LOST;
331 } else {
332 /* show the stomp size if we aren't fully flagged */
333 AROUND(g.p[0], g.p[1])
334 if (CELL.o == CLOSED && CELL.f !=FLAG)
335 partial_show_minefield (ROW, COL, HIGHLIGHT); //TODO: hide after timeout
336 /* 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 */
337 }
338 if (everything_opened()) return GAME_WON;
339
340 return GAME_INPROGRESS;
341 }
342
343 void set_mark(void) {
344 int mark = tolower(getch_wrapper());
345 if (mark < 'a' || mark > 'z') return; /*out of bound*/
346
347 g.m[mark-'a'].l = g.p[0];
348 g.m[mark-'a'].c = g.p[1];
349 g.m[mark-'a'].s = 1;
350 }
351
352 void jump_mark(void) {
353 int mark = tolower(getch_wrapper());
354 /* check bounds and if set: */
355 if (mark < 'a' || mark > 'z' || !g.m[mark-'a'].s) return;
356 move_hi (g.m[mark-'a'].l, g.m[mark-'a'].c);
357 }
358
359 void fill_minefield (int l, int c) {
360 srand (time(0));
361 int mines_set = f.m;
362 while (mines_set) {
363 int line = rand() % f.h;
364 int col = rand() % f.w;
365
366 if (f.c[line][col].m) {
367 /* skip if field already has a mine */
368 continue;
369 } else if ((line == l) && (col == c)) {
370 /* don't put a mine on the already opened (first click) field */
371 continue;
372 } else {
373 mines_set--;
374 f.c[line][col].m = STD_MINE;
375 }
376 }
377
378 /* precalculate neighbours */
379 for (int l=0; l < f.h; l++)
380 for (int c=0; c < f.w; c++)
381 f.c[l][c].n = get_neighbours (l, c, NORMAL);
382 }
383
384 void move_ph (int line, int col) {
385 /* move printhead to zero-indexed position */
386 printf ("\033[%d;%dH", line+1, col+1);
387 }
388
389 void move_hi (int l, int c) {
390 /* move cursor and highlight to absolute coordinates */
391
392 partial_show_minefield (g.p[0], g.p[1], NORMAL);
393 /* update g.p */
394 g.p[0] = CLAMP(l, 0, f.h-1);
395 g.p[1] = CLAMP(c, 0, f.w-1);
396 move_ph (g.p[0]+LINE_OFFSET, field2screen_c(g.p[1]));
397
398 partial_show_minefield (g.p[0], g.p[1], HIGHLIGHT);
399 }
400
401 /* to_next_boundary(): move into the supplied direction until a change in open-
402 state or flag-state is found and move there. falls back to BIG_MOVE. */
403 #define FIND_NEXT(X, L, C, L1, C1, MAX, OP) do {\
404 new_ ## X OP ## = BIG_MOVE;\
405 for (int i = X OP 1; i > 0 && i < f.MAX-1; i OP ## OP)\
406 if (((f.c[L ][C ].o<<2) + f.c[L ][C ].f) \
407 != ((f.c[L1][C1].o<<2) + f.c[L1][C1].f)) {\
408 new_ ## X = i OP 1;\
409 break;\
410 }\
411 } while(0)
412 void to_next_boundary (int l, int c, char direction) {
413 int new_l = l;
414 int new_c = c;
415 switch (direction) {
416 case '>': FIND_NEXT(c, l, i, l, i+1, w, +); break;
417 case '<': FIND_NEXT(c, l, i, l, i-1, w, -); break;
418 case '^': FIND_NEXT(l, i, c, i-1, c, h, -); break;
419 case 'v': FIND_NEXT(l, i, c, i+1, c, h, +); break;
420 }
421
422 move_hi (new_l, new_c);
423 }
424 #undef FIND_NEXT
425
426 char* cell2schema (int l, int c, int mode) {
427 struct minecell cell = f.c[l][c];
428
429 if (mode == SHOWMINES) return (
430 cell.f == FLAG && cell.m ? op.scheme->field_flagged:
431 cell.f == FLAG && !cell.m ? op.scheme->mine_wrongf:
432 cell.m == STD_MINE ? op.scheme->mine_normal:
433 cell.m == DEATH_MINE ? op.scheme->mine_death:
434 cell.o == CLOSED ? op.scheme->field_closed:
435 /*.......................*/ op.scheme->number[f.c[l][c].n]);
436 else return (
437 cell.f == FLAG ? op.scheme->field_flagged:
438 cell.f == QUESM ? op.scheme->field_question:
439 cell.o == CLOSED ? op.scheme->field_closed:
440 cell.m == STD_MINE ? op.scheme->mine_normal:
441 cell.m == DEATH_MINE ? op.scheme->mine_death:
442 /*.......................*/ op.scheme->number[f.c[l][c].n]);
443 }
444
445 void partial_show_minefield (int l, int c, int mode) {
446 move_ph (l+LINE_OFFSET, field2screen_c(c));
447 if (mode == HIGHLIGHT) printf ("\033[7m"); /* reverse video */
448 print (cell2schema(l, c, mode));
449 if (mode == HIGHLIGHT) printf ("\033[0m"); /* reset all */
450 }
451
452 char* get_emoticon(void) {
453 return g.o==GAME_WON ? EMOT(WON):
454 g.o==GAME_LOST? EMOT(DEAD):
455 EMOT(SMILE);
456 }
457
458 /* https://zserge.com/blog/c-for-loop-tricks.html */
459 #define print_line(which) \
460 for (int _break = (printf("%s", BORDER(which,LEFT)), 1); _break; \
461 _break = 0, printf("%s\r\n", BORDER(which,RIGHT)))
462 #define print_border(which, width) \
463 print_line(which) printm (width, BORDER(which,MIDDLE))
464 void show_minefield (int mode) {
465 int dtime = difftime (time(NULL), g.t)*!!g.t;
466 int half_spaces = f.w*op.scheme->cell_width/2;
467 int left_spaces = MAX(0,half_spaces-7-(f.m-g.f>999));
468 int right_spaces = MAX(0,half_spaces-6-(dtime>999));
469 static char modechar[] = {'*', '!', '?'};
470
471 move_ph (0,0);
472
473 print_border(TOP, f.w);
474 print_line(STATUS) {
475 if (mode == RESIZEMODE) printf ("%-*s", 2*half_spaces,
476 "Resize Mode: hjkl to resize, q to exit");
477 else printf("[%03d%c]%*s%s%*s[%03d]",
478 /* [ */ f.m - g.f, modechar[g.s], /* ] */
479 left_spaces,"", get_emoticon(), right_spaces,"",
480 /* [ */ dtime /* ] */);
481 }
482 print_border(DIVIDER, f.w);
483 /* main body */
484 for (int l = 0; l < f.h; l++) print_line(FIELD)
485 printm (f.w, cell2schema(l, _loop, mode));
486 print_border(BOTTOM, f.w);
487 }
488
489 int get_neighbours (int line, int col, int reduced_mode) {
490 /* counts mines surrounding a square
491 modes: 0=normal; 1=reduced */
492
493 int count = 0;
494 AROUND(line, col) {
495 count += !!CELL.m;
496 count -= reduced_mode * CELL.f==FLAG;
497 }
498 return count;
499 }
500
501 struct minecell** alloc_array (int lines, int cols) {
502 free_field (); /* TODO: just memset */
503 struct minecell** a = malloc (lines * sizeof(struct minecell*));
504 if (a == NULL) return NULL;
505 for (int l = 0; l < lines; l++) {
506 a[l] = calloc (cols, sizeof(struct minecell));
507 if (a[l] == NULL) goto unalloc;
508 }
509
510 return a;
511 unalloc:
512 for (int l = 0; l < lines; l++)
513 free (a[l]);
514 return NULL;
515 }
516
517 void free_field (void) {
518 if (f.c == NULL) return;
519 for (int l = 0; l < f.h; l++) {
520 free (f.c[l]);
521 }
522
523 free (f.c);
524 f.c = NULL;
525 }
526
527 int screen2field_l (int l) {
528 return (l-LINE_OFFSET) - 1;
529 }
530 int screen2field_c (int c) {
531 /* this depends on the cell width and only works when it is 1 or 2. */
532 return (c-COL_OFFSET+1 - 2*(CW%2))/2 - CW/2;
533 }
534 int field2screen_c (int c) {
535 return (CW*c+COL_OFFSET - (CW%2));
536 }
537 int clicked_emoticon (unsigned char* mouse) {
538 return (mouse[2] == LINE_OFFSET-1 && (
539 mouse[1] == f.w+COL_OFFSET ||
540 mouse[1] == f.w+COL_OFFSET+1));
541 }
542
543 enum esc_states {
544 START,
545 ESC_SENT,
546 CSI_SENT,
547 MOUSE_EVENT,
548 };
549 int getctrlseq (unsigned char* buf) {
550 int c;
551 int state = START;
552 int offset = 0x20; /* never sends control chars as data */
553 while ((c = getchar()) != EOF) {
554 switch (state) {
555 case START:
556 switch (c) {
557 case '\033': state=ESC_SENT; break;
558 default: return c;
559 }
560 break;
561 case ESC_SENT:
562 switch (c) {
563 case '[': state=CSI_SENT; break;
564 default: return CTRSEQ_INVALID;
565 }
566 break;
567 case CSI_SENT:
568 switch (c) {
569 case 'A': return CTRSEQ_CURSOR_UP;
570 case 'B': return CTRSEQ_CURSOR_DOWN;
571 case 'C': return CTRSEQ_CURSOR_RIGHT;
572 case 'D': return CTRSEQ_CURSOR_LEFT;
573 case 'M': state=MOUSE_EVENT; break;
574 default: return CTRSEQ_INVALID;
575 }
576 break;
577 case MOUSE_EVENT:
578 buf[0] = c - offset;
579 buf[1] = getchar() - offset;
580 buf[2] = getchar() - offset;
581 return CTRSEQ_MOUSE;
582 default:
583 return CTRSEQ_INVALID;
584 }
585 }
586 return 2;
587 }
588
589 int getch(unsigned char* buf) {
590 /* returns a character, EOF, or constant for an escape/control sequence - NOT
591 compatible with the ncurses implementation of same name */
592 int action = getctrlseq(buf);
593 int l, c;
594 switch (action) {
595 case CTRSEQ_MOUSE:
596 l = screen2field_l (buf[2]);
597 c = screen2field_c (buf[1]);
598
599 if (buf[0] > 3) break; /* ignore all but left/middle/right/up */
600 int success = wait_mouse_up(l, c);
601
602 /* mouse moved while pressed: */
603 if (!success) return CTRSEQ_INVALID;
604
605 switch (buf[0]) {
606 case 0: return CTRSEQ_MOUSE_LEFT;
607 case 1: return CTRSEQ_MOUSE_MIDDLE;
608 case 2: return CTRSEQ_MOUSE_RIGHT;
609 }
610 }
611
612 return action;
613 }
614
615 int getch_wrapper (void) {
616 unsigned char mouse[3];
617 int c = getch(mouse);
618
619 if (c == CTRSEQ_MOUSE_LEFT || c == CTRSEQ_MOUSE_RIGHT) {
620 if (clicked_emoticon(mouse))
621 return WRAPPER_EMOTICON;
622
623 if (screen2field_c (mouse[1]) < 0
624 || screen2field_c (mouse[1]) >= f.w
625 || screen2field_l (mouse[2]) < 0
626 || screen2field_l (mouse[2]) >= f.h)
627 return CTRSEQ_INVALID;
628
629 g.p[0] = screen2field_l (mouse[2]);
630 g.p[1] = screen2field_c (mouse[1]);
631
632 return c; /* CTRSEQ_MOUSE_LEFT || CTRSEQ_MOUSE_RIGHT */
633 }
634
635 return c;
636 }
637
638 void clamp_fieldsize (void) {
639 /* clamp field size to terminal size and mouse maximum: */
640 struct winsize w;
641 ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
642
643 if (COL_OFFSET + f.w*CW + COL_OFFSET > w.ws_col)
644 f.w = w.ws_col/CW - (COL_OFFSET+COL_OFFSET);
645 if (LINE_OFFSET + f.h + LINES_AFTER > w.ws_row)
646 f.h = w.ws_row - (LINE_OFFSET+LINES_AFTER);
647 if (COL_OFFSET + f.w*CW > MOUSE_MAX)
648 f.w = MOUSE_MAX/CW - COL_OFFSET;
649 if (LINE_OFFSET + f.h > MOUSE_MAX)
650 f.h = MOUSE_MAX - LINE_OFFSET;
651 }
652
653 int mines_percentage(int w, int h) {
654 return w*h*(w*h<30*16?.15625:.20625);
655 }
656
657 int parse_fieldspec(char* str) {
658 /* parses the FIELDSPEC (WxHxM); returns 1 on error */
659 int n = sscanf (str, "%dx%dx%d", &f.w, &f.h, &f.m);
660
661 clamp_fieldsize();
662
663 if (n < 2) {
664 return 1; /* error */
665 } else if (n == 2) {
666 f.m = mines_percentage(f.w, f.h);
667 }
668
669 return 0;
670 }
671
672 void timer_setup (int enable) {
673 static struct itimerval tbuf;
674 tbuf.it_interval.tv_sec = 1;
675 tbuf.it_interval.tv_usec = 0;
676
677 if (enable) {
678 g.t = time(NULL);
679 tbuf.it_value.tv_sec = 1;
680 tbuf.it_value.tv_usec = 0;
681 if (setitimer(ITIMER_REAL, &tbuf, NULL) == -1) {
682 perror("setitimer");
683 exit(1);
684 }
685 } else {
686 tbuf.it_value.tv_sec = 0;
687 tbuf.it_value.tv_usec = 0;
688 if ( setitimer(ITIMER_REAL, &tbuf, NULL) == -1 ) {
689 perror("setitimer");
690 exit(1);
691 }
692 }
693
694 }
695
696 void signal_setup (void) {
697 struct sigaction saction;
698
699 saction.sa_handler = signal_handler;
700 sigemptyset(&saction.sa_mask);
701 saction.sa_flags = 0;
702 if (sigaction(SIGALRM, &saction, NULL) < 0 ) {
703 perror("SIGALRM");
704 exit(1);
705 }
706
707 if (sigaction(SIGINT, &saction, NULL) < 0 ) {
708 perror ("SIGINT");
709 exit (1);
710 }
711 }
712
713 void signal_handler (int signum) {
714 int dtime;
715 switch (signum) {
716 case SIGALRM:
717 dtime = difftime (time(NULL), g.t);
718 move_ph (1, f.w*CW-(CW%2)-3-(dtime>999));
719 printf ("[%03d]", g.t?dtime:0);
720 break;
721 case SIGINT:
722 exit(128+SIGINT);
723 }
724 }
725
726 void screen_setup (int enable) {
727 if (enable) {
728 raw_mode(1);
729 printf ("\033[s\033[?47h"); /* save cursor, alternate screen */
730 printf ("\033[H\033[J"); /* reset cursor, clear screen */
731 printf ("\033[?1000h\033[?25l"); /* enable mouse, hide cursor */
732 print (op.scheme->init_seq); /* swich charset, if necessary */
733 } else {
734 print (op.scheme->reset_seq); /* reset charset, if necessary */
735 printf ("\033[?9l\033[?25h"); /* disable mouse, show cursor */
736 printf ("\033[?47l\033[u"); /* primary screen, restore cursor */
737 raw_mode(0);
738 }
739 }
740
741 /* http://users.csc.calpoly.edu/~phatalsk/357/lectures/code/sigalrm.c */
742 void raw_mode(int enable) {
743 static struct termios saved_term_mode;
744 struct termios raw_term_mode;
745
746 if (enable) {
747 tcgetattr(STDIN_FILENO, &saved_term_mode);
748 raw_term_mode = saved_term_mode;
749 raw_term_mode.c_lflag &= ~(ICANON | ECHO);
750 raw_term_mode.c_cc[VMIN] = 1 ;
751 raw_term_mode.c_cc[VTIME] = 0;
752 tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_term_mode);
753 } else {
754 tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_term_mode);
755 }
756 }
Imprint / Impressum