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