]> git.gir.st - minesVIiper.git/blob - mines_2017.c
fix out of bounds check for right_click, use alternate screen
[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 /* switch to alternate screen */
236 printf ("\033[?47h");
237 /* reset cursor, clear screen */
238 printf ("\033[H\033[J");
239
240 /* swich charset, if necessary */
241 if (op.scheme->init_seq != NULL) print (op.scheme->init_seq);
242
243 show_minefield (NORMAL);
244
245 /* enable mouse, hide cursor */
246 printf ("\033[?1000h\033[?25l");
247
248 while (1) {
249 int l, c;
250 int action;
251 unsigned char mouse[3];
252
253 action = getch(mouse);
254 switch (action) {
255 case CTRSEQ_MOUSE_LEFT:
256 f.p[0] = screen2field_l (mouse[2]);
257 f.p[1] = screen2field_c (mouse[1]);
258 /* :D clicked: TODO: won't work in single-width mode! */
259 if (mouse[2] == LINE_OFFSET-1 &&
260 (mouse[1] == f.w+COL_OFFSET ||
261 mouse[1] == f.w+COL_OFFSET+1)) {
262 free_field ();
263 goto newgame;
264 }
265 if (f.p[1] < 0 || f.p[1] >= f.w ||
266 f.p[0] < 0 || f.p[0] >= f.h) break; /*out of bound*/
267 /* fallthrough */
268 case ' ':
269 if (is_newgame) {
270 is_newgame = 0;
271 fill_minefield (f.p[0], f.p[1]);
272 f.t = time(NULL);
273 tbuf.it_value.tv_sec = 1;
274 tbuf.it_value.tv_usec = 0;
275 if (setitimer(ITIMER_REAL, &tbuf, NULL) == -1) {
276 perror("setitimer");
277 exit(1);
278 }
279 }
280
281 if (f.c[f.p[0]][f.p[1]].f == FLAG ) break;
282 if (f.c[f.p[0]][f.p[1]].o == CLOSED) {
283 if (uncover_square (f.p[0], f.p[1])) goto lose;
284 } else if (get_neighbours (f.p[0], f.p[1], 1) == 0) {
285 if (choord_square (f.p[0], f.p[1])) goto lose;
286 }
287 if (everything_opened()) goto win;
288 break;
289 case CTRSEQ_MOUSE_RIGHT:
290 f.p[0] = screen2field_l (mouse[2]);
291 f.p[1] = screen2field_c (mouse[1]);
292 if (f.p[1] < 0 || f.p[1] >= f.w ||
293 f.p[0] < 0 || f.p[0] >= f.h) break; /*out of bound*/
294 /* fallthrough */
295 case 'i': flag_square (f.p[0], f.p[1]); break;
296 case '?':quesm_square (f.p[0], f.p[1]); break;
297 #define BM BIG_MOVE
298 case 'h': set_cursor_pos (f.p[0], f.p[1]-1 ); break;
299 case 'j': set_cursor_pos (f.p[0]+1, f.p[1] ); break;
300 case 'k': set_cursor_pos (f.p[0]-1, f.p[1] ); break;
301 case 'l': set_cursor_pos (f.p[0], f.p[1]+1 ); break;
302 case 'w': set_cursor_pos (f.p[0], f.p[1]+BM); break;
303 case 'b': set_cursor_pos (f.p[0], f.p[1]-BM); break;
304 case 'u': set_cursor_pos (f.p[0]-BM, f.p[1] ); break;
305 case 'd': set_cursor_pos (f.p[0]+BM, f.p[1] ); break;
306 case '0': /* fallthrough */
307 case '^': set_cursor_pos (f.p[0], 0 ); break;
308 case '$': set_cursor_pos (f.p[0], f.w-1 ); break;
309 case 'g': set_cursor_pos (0, f.p[1] ); break;
310 case 'G': set_cursor_pos (f.h-1, f.p[1] ); break;
311 #undef BM
312 case 'r': /* start a new game */
313 free_field ();
314 goto newgame;
315 case 'q':
316 goto quit;
317 case '\014': /* Ctrl-L -- redraw */
318 show_minefield (NORMAL);
319 break;
320 case '\\':
321 if (is_newgame) {
322 is_newgame = 0;
323 fill_minefield (-1, -1);
324 f.t = time(NULL);
325 setitimer (ITIMER_REAL, &tbuf, NULL);
326 }
327 show_minefield (cheatmode?NORMAL:SHOWMINES);
328 cheatmode = !cheatmode;
329 break;
330 }
331 }
332
333 win:
334 lose:
335 /* stop timer: */
336 tbuf.it_value.tv_sec = 0;
337 tbuf.it_value.tv_usec = 0;
338 if ( setitimer(ITIMER_REAL, &tbuf, NULL) == -1 ) {
339 perror("setitimer");
340 exit(1);
341 }
342 show_minefield (SHOWMINES);
343 int gotaction;
344 do {
345 unsigned char mouse[3];
346 gotaction = getch(mouse);
347 /* :D clicked: TODO: won't work in single-width mode! */
348 if (gotaction==CTRSEQ_MOUSE_LEFT && mouse[2]==LINE_OFFSET-1 &&
349 (mouse[1]==f.w+COL_OFFSET || mouse[1]==f.w+COL_OFFSET+1)) {
350 free_field ();
351 goto newgame;
352 } else if (gotaction == 'r') {
353 free_field ();
354 goto newgame;
355 } else if (gotaction == 'q') {
356 goto quit;
357 }
358 } while (1);
359
360 quit:
361 return 0;
362 }
363
364 void quit () {
365 move(f.h+LINE_OFFSET+2, 0);
366 /* disable mouse, show cursor */
367 printf ("\033[?9l\033[?25h");
368 /* reset charset, if necessary */
369 if (op.scheme && op.scheme->reset_seq) print (op.scheme->reset_seq);
370 /* revert to primary screen */
371 printf ("\033[?47l");
372 free_field ();
373 restore_term_mode(saved_term_mode);
374 }
375
376 /* I haven't won as long as a cell exists, that
377 - I haven't opened, and
378 - is not a mine */
379 int everything_opened () {
380 for (int row = 0; row < f.h; row++)
381 for (int col = 0; col < f.w; col++)
382 if (f.c[row][col].o == CLOSED &&
383 f.c[row][col].m == NO_MINE ) return 0;
384 return 1;
385 }
386
387 int wait_mouse_up (int l, int c) {
388 unsigned char mouse2[3];
389 int level = 1;
390 int l2, c2;
391
392 /* show :o face */
393 move (1, field2screen_c (f.w/2)-1); print (":o");
394
395 if (!(l < 0 || l >= f.h || c < 0 || c >= f.w)) {
396 /* show a pushed-in button if cursor is on minefield */
397 move (l+LINE_OFFSET, field2screen_c(c));
398 fputs (op.scheme->mouse_highlight, stdout);
399 }
400
401 while (level > 0) {
402 if (getctrlseq (mouse2) == CTRSEQ_MOUSE) {
403 /* ignore mouse wheel events: */
404 if (mouse2[0] & 0x40) continue;
405
406 else if (mouse2[0]&3 == 3) level--; /* release event */
407 else level++; /* another button pressed */
408 }
409 }
410
411 move (1, field2screen_c (f.w/2)-1); print (":D");
412 if (!(l < 0 || l >= f.h || c < 0 || c >= f.w)) {
413 partial_show_minefield (l, c, NORMAL);
414 }
415 c2 = screen2field_c(mouse2[1]);
416 l2 = screen2field_l(mouse2[2]);
417 return ((l2 == l) && (c2 == c));
418 }
419
420 int choord_square (int line, int col) {
421 for (int l = MAX(line-1, 0); l <= MIN(line+1, f.h-1); l++) {
422 for (int c = MAX(col-1, 0); c <= MIN(col+1, f.w-1); c++) {
423 if (f.c[l][c].f != FLAG) {
424 if (uncover_square (l, c))
425 return 1;
426 }
427 }
428 }
429
430 return 0;
431 }
432
433 int uncover_square (int l, int c) {
434 f.c[l][c].o = OPENED;
435 f.c[l][c].f = NOFLAG; /* must not be QUESM, otherwise rendering issues */
436 partial_show_minefield (l, c, NORMAL);
437
438 if (f.c[l][c].m) {
439 f.c[l][c].m = DEATH_MINE;
440 return 1;
441 }
442
443 /* check for chording */
444 if (f.c[l][c].n == 0) {
445 for (int choord_l = -1; choord_l <= 1; choord_l++) {
446 for (int choord_c = -1; choord_c <= 1; choord_c++) {
447 int newl = l + choord_l;
448 int newc = c + choord_c;
449 if (newl >= 0 && newl < f.h &&
450 newc >= 0 && newc < f.w &&
451 f.c[newl][newc].o == CLOSED &&
452 uncover_square (newl, newc)) {
453 return 1;
454 }
455 }
456 }
457 }
458
459 return 0;
460 }
461
462 void flag_square (int l, int c) {
463 if (f.c[l][c].o != CLOSED) return;
464 /* cycles through flag/quesm/noflag (uses op.mode to detect which ones
465 are allowed) */
466 f.c[l][c].f = (f.c[l][c].f + 1) % (op.mode + 1);
467 if (f.c[l][c].f==FLAG) f.f++;
468 else f.f--;
469 partial_show_minefield (l, c, NORMAL);
470 move (1, op.scheme->cell_width);
471 printf ("[%03d]", f.f);
472 }
473
474 void quesm_square (int l, int c) {
475 /* toggle question mark / none. won't turn flags into question marks.
476 unlike flag_square, this function doesn't respect `-q'. */
477 if (f.c[l][c].o != CLOSED) return;
478 else if (f.c[l][c].f == NOFLAG) f.c[l][c].f = QUESM;
479 else if (f.c[l][c].f == QUESM) f.c[l][c].f = NOFLAG;
480 partial_show_minefield (l, c, NORMAL);
481 }
482
483 void fill_minefield (int l, int c) {
484 srand (time(0));
485 int mines_set = f.m;
486 while (mines_set) {
487 int line = rand() % f.h;
488 int col = rand() % f.w;
489
490 if (f.c[line][col].m) {
491 /* skip if field already has a mine */
492 continue;
493 } else if ((line == l) && (col == c)) {
494 /* don't put a mine on the already opened (first click) field */
495 continue;
496 } else {
497 mines_set--;
498 f.c[line][col].m = STD_MINE;
499 }
500 }
501
502 /* precalculate neighbours */
503 for (int l=0; l < f.h; l++)
504 for (int c=0; c < f.w; c++)
505 f.c[l][c].n = get_neighbours (l, c, NORMAL);
506 }
507
508 void move (int line, int col) {
509 printf ("\033[%d;%dH", line+1, col+1);
510 }
511
512 /* absolute coordinates! */
513 void set_cursor_pos (int l, int c) {
514 partial_show_minefield (f.p[0], f.p[1], NORMAL);
515 /* update f.p */
516 f.p[0] = CLAMP(l, 0, f.h-1);
517 f.p[1] = CLAMP(c, 0, f.w-1);
518 move (f.p[0]+LINE_OFFSET, field2screen_c(f.p[1]));
519 fputs (op.scheme->mouse_highlight, stdout);
520 }
521
522 void partial_show_minefield (int l, int c, int mode) {
523 move (l+LINE_OFFSET, field2screen_c(c));
524
525 if (f.c[l][c].f == FLAG ) print (op.scheme->field_flagged);
526 else if (f.c[l][c].f == QUESM ) print (op.scheme->field_question);
527 else if (f.c[l][c].o == CLOSED ) print (op.scheme->field_closed);
528 else if (f.c[l][c].m == STD_MINE ||
529 f.c[l][c].m == DEATH_MINE) print (op.scheme->mine_normal);
530 else /*.......................*/ print (op.scheme->number[f.c[l][c].n]);
531 }
532
533 void show_minefield (int mode) {
534 int dtime;
535
536 move (0,0);
537
538 if (f.t == 0) {
539 dtime = 0;
540 } else {
541 dtime = difftime (time(NULL), f.t);
542 }
543
544 /* first line */
545 print (op.scheme->border_top_l);
546 printm (f.w*op.scheme->cell_width,op.scheme->border_top_m);
547 printf ("%s\r\n", op.scheme->border_top_r);
548 /* second line */
549 print (op.scheme->border_status_l);
550 printf("[%03d]", f.f);
551 printm (f.w*op.scheme->cell_width/2-6, " ");
552 printf ("%s", mode==SHOWMINES?":C":":D");
553 printm (f.w*op.scheme->cell_width/2-6, " ");
554 printf ("[%03d]", dtime);
555 print (op.scheme->border_status_r);
556 print ("\r\n");
557 /* third line */
558 print (op.scheme->border_spacer_l);
559 printm (f.w*op.scheme->cell_width,op.scheme->border_spacer_m);
560 print (op.scheme->border_spacer_r);
561 print ("\r\n");
562 /* main body */
563 for (int l = 0; l < f.h; l++) {
564 print (op.scheme->border_field_l);
565 for (int c = 0; c < f.w; c++) {
566 if (mode == SHOWMINES) {
567 if (f.c[l][c].f == FLAG &&
568 f.c[l][c].m ) print (op.scheme->field_flagged);
569 else if (f.c[l][c].f == FLAG &&
570 f.c[l][c].m == NO_MINE ) print (op.scheme->mine_wrongf);
571 else if (f.c[l][c].m == STD_MINE ) print (op.scheme->mine_normal);
572 else if (f.c[l][c].m == DEATH_MINE) print (op.scheme->mine_death);
573 else if (f.c[l][c].o == CLOSED ) print (op.scheme->field_closed);
574 else /*.......................*/ print (op.scheme->number[f.c[l][c].n]);
575 } else {
576 if (f.c[l][c].f == FLAG ) print (op.scheme->field_flagged);
577 else if (f.c[l][c].f == QUESM ) print (op.scheme->field_question);
578 else if (f.c[l][c].o == CLOSED ) print (op.scheme->field_closed);
579 else if (f.c[l][c].m == STD_MINE ) print (op.scheme->mine_normal);
580 else if (f.c[l][c].m == DEATH_MINE) print (op.scheme->mine_death);
581 else /*.......................*/ print (op.scheme->number[f.c[l][c].n]);
582 }
583 }
584 print (op.scheme->border_field_r); print ("\r\n");
585 }
586 /* last line */
587 print (op.scheme->border_bottom_l);
588 printm (f.w*op.scheme->cell_width,op.scheme->border_bottom_m);
589 print (op.scheme->border_bottom_r);
590 print ("\r\n");
591 }
592
593 int get_neighbours (int line, int col, int reduced_mode) {
594 /* counts mines surrounding a square
595 modes: 0=normal; 1=reduced */
596
597 int count = 0;
598
599 for (int l = MAX(line-1, 0); l <= MIN(line+1, f.h-1); l++) {
600 for (int c = MAX(col-1, 0); c <= MIN(col+1, f.w-1); c++) {
601 if (!l && !c) continue;
602
603 count += !!f.c[l][c].m;
604 count -= reduced_mode * f.c[l][c].f==FLAG;
605 }
606 }
607 return count;
608 }
609
610 struct minecell** alloc_array (int lines, int cols) {
611 struct minecell** a = malloc (lines * sizeof(struct minecell*));
612 if (a == NULL) return NULL;
613 for (int l = 0; l < lines; l++) {
614 a[l] = calloc (cols, sizeof(struct minecell));
615 if (a[l] == NULL) goto unalloc;
616 }
617
618 return a;
619 unalloc:
620 for (int l = 0; l < lines; l++)
621 free (a[l]);
622 return NULL;
623 }
624
625 void free_field () {
626 if (f.c == NULL) return;
627 for (int l = 0; l < f.h; l++) {
628 free (f.c[l]);
629 }
630 free (f.c);
631 }
632
633 int screen2field_l (int l) {
634 return (l-LINE_OFFSET) - 1;
635 }
636 /* some trickery is required to extract the mouse position from the cell width,
637 depending on wheather we are using full width characters or double line width.
638 WARN: tested only with scheme.cell_width = 1 and scheme.cell_width = 2. */
639 int screen2field_c (int c) {
640 return (c-COL_OFFSET+1 - 2*(op.scheme->cell_width%2))/2 - op.scheme->cell_width/2;
641 }
642 int field2screen_l (int l) {
643 return 0; //TODO: is never used, therefore not implemented
644 }
645 int field2screen_c (int c) {
646 return (op.scheme->cell_width*c+COL_OFFSET - (op.scheme->cell_width%2));
647 }
648
649 enum esc_states {
650 START,
651 ESC_SENT,
652 CSI_SENT,
653 MOUSE_EVENT,
654 };
655 int getctrlseq (unsigned char* buf) {
656 int c;
657 int state = START;
658 int offset = 0x20; /* never sends control chars as data */
659 while ((c = getchar()) != EOF) {
660 switch (state) {
661 case START:
662 switch (c) {
663 case '\033': state=ESC_SENT; break;
664 default: return c;
665 }
666 break;
667 case ESC_SENT:
668 switch (c) {
669 case '[': state=CSI_SENT; break;
670 default: return CTRSEQ_INVALID;
671 }
672 break;
673 case CSI_SENT:
674 switch (c) {
675 case 'M': state=MOUSE_EVENT; break;
676 default: return CTRSEQ_INVALID;
677 }
678 break;
679 case MOUSE_EVENT:
680 buf[0] = c - offset;
681 buf[1] = getchar() - offset;
682 buf[2] = getchar() - offset;
683 return CTRSEQ_MOUSE;
684 default:
685 return CTRSEQ_INVALID;
686 }
687 }
688 return 2;
689 }
690
691 int getch(unsigned char* buf) {
692 /* returns a character, EOF, or constant for an escape/control sequence - NOT
693 compatible with the ncurses implementation of same name */
694 int action = getctrlseq(buf);
695 int l, c;
696 switch (action) {
697 case CTRSEQ_MOUSE:
698 l = screen2field_l (buf[2]);
699 c = screen2field_c (buf[1]);
700
701 if (buf[0] > 3) break; /* ignore all but left/middle/right/up */
702 int success = wait_mouse_up(l, c);
703
704 /* mouse moved while pressed: */
705 if (!success) return CTRSEQ_INVALID;
706
707 switch (buf[0]) {
708 case 0: return CTRSEQ_MOUSE_LEFT;
709 case 1: return CTRSEQ_MOUSE_MIDDLE;
710 case 2: return CTRSEQ_MOUSE_RIGHT;
711 }
712 }
713
714 return action;
715 }
Imprint / Impressum