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