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