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