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