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