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