]> git.gir.st - minesVIiper.git/blob - mines_2017.c
clamp field size to window/mouse-max, dynamic minenum if not given
[minesVIiper.git] / mines_2017.c
1 /*******************************************************************************
2 minesviiper 0.3.1459
3 By Tobias Girstmair, 2015 - 2018
4
5 ./minesviiper 16x16x40
6 (see ./minesviiper -h 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 - o 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/ioctl.h>
25 #include <sys/time.h>
26 #include <termios.h>
27 #include <time.h>
28 #include <unistd.h>
29
30 #include "schemes.h"
31
32 #define LINE_OFFSET 3
33 #define COL_OFFSET 2
34 #define BIG_MOVE 5
35
36 #define MIN(a,b) (a>b?b:a)
37 #define MAX(a,b) (a>b?a:b)
38 #define CLAMP(a,m,M) (a<m?m:(a>M?M:a))
39 #define printm(num, str) for (int i = 0; i < num; i++) fputs (str, stdout)
40 #define print(str) fputs (str, stdout)
41 #define EMOT(e) op.scheme->emoticons[EMOT_ ## e]
42
43 struct minecell {
44 unsigned m:2; /* mine?1:killmine?2:0 */
45 unsigned o:1; /* open?1:0 */
46 unsigned f:2; /* flagged?1:questioned?2:0 */
47 unsigned n:4; /* 0<= neighbours <=8 */
48 };
49 struct minefield {
50 struct minecell **c;
51 //TODO: rename w, h to L, C
52 int w; /* width */
53 int h; /* height */
54 int m; /* number of mines */
55
56 int f; /* flags counter */
57 int t; /* time of game start */
58 int p[2]; /* cursor position {line, col} */
59 int s; /* space mode */
60 } f;
61
62 struct opt {
63 struct minescheme* scheme;
64 int mode; /* allow flags? quesm? */
65 } op;
66
67 int alt_screen = 0;
68
69 struct line_col {
70 int l;
71 int c;
72 };
73
74 void fill_minefield (int, int);
75 void move (int, int);
76 void cursor_move (int, int);
77 void to_next_boundary (int l, int c, char direction);
78 int getch (unsigned char*);
79 int getctrlseq (unsigned char*);
80 int everything_opened ();
81 int wait_mouse_up (int, int);
82 void partial_show_minefield (int, int, int);
83 void show_minefield (int);
84 int get_neighbours (int, int, int);
85 int uncover_square (int, int);
86 void flag_square (int, int);
87 void quesm_square (int, int);
88 int choord_square (int, int);
89 int do_uncover (int*);
90 struct minecell** alloc_array (int, int);
91 void free_field ();
92 int screen2field_l (int);
93 int screen2field_c (int);
94 int field2screen_l (int);
95 int field2screen_c (int);
96 void quit();
97 void signal_handler (int signum);
98 void timer_setup (int);
99
100 enum modes {
101 NORMAL,
102 REDUCED,
103 SHOWMINES,
104 HIGHLIGHT,
105 };
106 enum flagtypes {
107 NOFLAG,
108 FLAG,
109 QUESM,
110 };
111 enum fieldopenstates {
112 CLOSED,
113 OPENED,
114 };
115 enum game_states {
116 GAME_INPROGRESS,
117 GAME_NEW,
118 GAME_WON,
119 GAME_LOST,
120 };
121 enum space_modes {
122 MODE_OPEN,
123 MODE_FLAG,
124 MODE_QUESM,
125 };
126 enum event {
127 /* for getctrlseq() */
128 CTRSEQ_NULL = 0,
129 CTRSEQ_EOF = -1,
130 CTRSEQ_INVALID = -2,
131 CTRSEQ_MOUSE = -3,
132 /* for getch() */
133 CTRSEQ_MOUSE_LEFT = -4,
134 CTRSEQ_MOUSE_MIDDLE = -5,
135 CTRSEQ_MOUSE_RIGHT = -6,
136 };
137 enum mine_types {
138 NO_MINE,
139 STD_MINE,
140 DEATH_MINE,
141 };
142
143 void signal_handler (int signum) {
144 switch (signum) {
145 case SIGALRM:
146 move (1, f.w*op.scheme->cell_width-(op.scheme->cell_width%2)-3);
147 printf ("[%03d]", f.t?(int)difftime (time(NULL), f.t):0);
148 break;
149 case SIGINT:
150 exit(128+SIGINT);
151 }
152 }
153
154 /* http://users.csc.calpoly.edu/~phatalsk/357/lectures/code/sigalrm.c */
155 struct termios saved_term_mode;
156 struct termios set_raw_term_mode() {
157 struct termios cur_term_mode, raw_term_mode;
158
159 tcgetattr(STDIN_FILENO, &cur_term_mode);
160 raw_term_mode = cur_term_mode;
161 raw_term_mode.c_lflag &= ~(ICANON | ECHO);
162 raw_term_mode.c_cc[VMIN] = 1 ;
163 raw_term_mode.c_cc[VTIME] = 0;
164 tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_term_mode);
165
166 return cur_term_mode;
167 }
168 void restore_term_mode(struct termios saved_term_mode) {
169 tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_term_mode);
170 }
171
172
173 int main (int argc, char** argv) {
174 struct sigaction saction;
175 saved_term_mode = set_raw_term_mode();
176
177 atexit (*quit);
178
179 saction.sa_handler = signal_handler;
180 sigemptyset(&saction.sa_mask);
181 saction.sa_flags = 0;
182 if (sigaction(SIGALRM, &saction, NULL) < 0 ) {
183 perror("SIGALRM");
184 exit(1);
185 }
186
187 if (sigaction(SIGINT, &saction, NULL) < 0 ) {
188 perror ("SIGINT");
189 exit (1);
190 }
191 /* end screen setup */
192
193 op.scheme = &symbols_mono;
194 op.mode = FLAG;
195 /* end defaults */
196 /* parse options */
197 int optget;
198 opterr = 0; /* don't print message on unrecognized option */
199 while ((optget = getopt (argc, argv, "+hnfqcdx")) != -1) {
200 switch (optget) {
201 case 'n': op.mode = NOFLAG; break;
202 case 'f': op.mode = FLAG; break; /*default*/
203 case 'q': op.mode = QUESM; break;
204 case 'c': op.scheme = &symbols_col1; break;
205 case 'd': op.scheme = &symbols_doublewidth; break;
206 case 'h':
207 default:
208 fprintf (stderr, "%s [OPTIONS] [FIELDSPEC]\n"
209 "OPTIONS:\n"
210 " -n(o flagging)\n"
211 " -f(lagging)\n"
212 " -q(uestion marks)\n"
213 " -c(olored symbols)\n"
214 " -d(ec charset symbols)\n"
215 "FIELDSPEC:\n"
216 " WxH[xM] (width 'x' height 'x' mines)\n"
217 " defaults to 30x16x99; mines will be ~20%% if not given\n"
218 "\n"
219 "Keybindings:\n"
220 " hjkl: move left/down/up/right\n"
221 " bduw: move to next boundary\n"
222 " ^Gg$: move to the left/bottom/top/right\n"
223 " o: open/choord\n"
224 " i: flag/unflag\n"
225 " space:modeful cursor (either open or flag)\n"
226 " a: toggle mode for space (open/flag)\n"
227 " mX: set a mark for letter X\n"
228 " `X: move to mark X (aliased to ')\n"
229 " r: start a new game\n"
230 " q: quit\n", argv[0]);
231 _exit(0);
232 }
233 }
234 /* end parse options*/
235 if (optind < argc) { /* parse Fieldspec */
236 int n = sscanf (argv[optind], "%dx%dx%d", &f.w, &f.h, &f.m);
237 struct winsize w;
238 ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
239 #define CW op.scheme->cell_width
240 #define WW w.ws_col /*window width */
241 #define WH w.ws_row /*window height */
242 #define FW f.w /* field width */
243 #define FH f.h /* field height */
244 #define MM 231 /* mouse maximum*/
245 #define LB 2 /* left border */
246 #define RB 2 /* right border */
247 #define TB 3 /* top border */
248 #define BB 2 /*bottom border */
249
250 if (LB + FW*CW + RB > WW) FW = WW/CW - (LB+RB);
251 if (TB + FH + BB > WH) FH = WH - (TB+BB);
252 if (LB + FW*CW > MM) FW = MM/CW - LB;
253 if (TB + FH > MM) FH = MM - TB;
254
255 if (n == 1) {
256 fprintf (stderr, "Provide at least width and height to the Fieldspec.\r\n");
257 return 0;
258 } else if (n == 2) {
259 if (f.w < 30) f.m = f.w*f.h*.15625;
260 else f.m = f.w*f.h*.20625;
261 //hc: .35416_
262 }
263 #undef CW
264 #undef WW
265 #undef WH
266 #undef FW
267 #undef FH
268 #undef MM
269 #undef LB
270 #undef RB
271 #undef TB
272 #undef BB
273 } else { /* use defaults */
274 f.w = 30;
275 f.h = 16;
276 f.m = 99;
277 }
278 /* check boundaries */
279 if (f.m > (f.w-1) * (f.h-1)) {
280 f.m = (f.w-1) * (f.h-1);
281 fprintf (stderr, "too many mines. reduced to %d.\r\n", f.m);
282 }
283 /* end check */
284
285 newgame:
286 f.c = alloc_array (f.h, f.w);
287
288 f.f = 0;
289 f.t = 0;
290 f.p[0] = 0;
291 f.p[1] = 0;
292
293 int is_newgame = 1;
294 int cheatmode = 0;
295 f.s = MODE_OPEN;
296 struct line_col markers[26];
297 for (int i=26; i; markers[--i].l = -1);
298
299 /* switch to alternate screen */
300 printf ("\033[?47h");
301 alt_screen = 1;
302 /* reset cursor, clear screen */
303 printf ("\033[H\033[J");
304
305 /* swich charset, if necessary */
306 if (op.scheme->init_seq != NULL) print (op.scheme->init_seq);
307
308 show_minefield (NORMAL);
309
310 /* enable mouse, hide cursor */
311 printf ("\033[?1000h\033[?25l");
312
313 while (1) {
314 int l, c;
315 int action;
316 unsigned char mouse[3];
317
318 action = getch(mouse);
319 switch (action) {
320 case ' ':
321 if (f.s == MODE_OPEN ||
322 f.c[f.p[0]][f.p[1]].o == OPENED) {
323 switch (do_uncover(&is_newgame)) {
324 case GAME_LOST: goto lose;
325 case GAME_WON: goto win;
326 }
327 } else if (f.s == MODE_FLAG) {
328 flag_square (f.p[0], f.p[1]);
329 } else if (f.s == MODE_QUESM) {
330 quesm_square (f.p[0], f.p[1]);
331 }
332 break;
333 case 'a':
334 f.s = (f.s+1)%(op.mode+1);
335 show_minefield (cheatmode?SHOWMINES:NORMAL);
336 break;
337 case CTRSEQ_MOUSE_LEFT:
338 f.p[0] = screen2field_l (mouse[2]);
339 f.p[1] = screen2field_c (mouse[1]);
340 /* :D clicked: TODO: won't work in single-width mode! */
341 if (mouse[2] == LINE_OFFSET-1 &&
342 (mouse[1] == f.w+COL_OFFSET ||
343 mouse[1] == f.w+COL_OFFSET+1)) {
344 free_field ();
345 goto newgame;
346 }
347 if (f.p[1] < 0 || f.p[1] >= f.w ||
348 f.p[0] < 0 || f.p[0] >= f.h) break; /*out of bound*/
349 /* fallthrough */
350 case 'o':
351 switch (do_uncover(&is_newgame)) {
352 case GAME_LOST: goto lose;
353 case GAME_WON: goto win;
354 }
355 break;
356 case CTRSEQ_MOUSE_RIGHT:
357 f.p[0] = screen2field_l (mouse[2]);
358 f.p[1] = screen2field_c (mouse[1]);
359 if (f.p[1] < 0 || f.p[1] >= f.w ||
360 f.p[0] < 0 || f.p[0] >= f.h) break; /*out of bound*/
361 /* fallthrough */
362 case 'i': flag_square (f.p[0], f.p[1]); break;
363 case '?':quesm_square (f.p[0], f.p[1]); break;
364 case 'h': cursor_move (f.p[0], f.p[1]-1 ); break;
365 case 'j': cursor_move (f.p[0]+1, f.p[1] ); break;
366 case 'k': cursor_move (f.p[0]-1, f.p[1] ); break;
367 case 'l': cursor_move (f.p[0], f.p[1]+1 ); break;
368 case 'w': to_next_boundary (f.p[0], f.p[1], '>'); break;
369 case 'b': to_next_boundary (f.p[0], f.p[1], '<'); break;
370 case 'u': to_next_boundary (f.p[0], f.p[1], '^'); break;
371 case 'd': to_next_boundary (f.p[0], f.p[1], 'v'); break;
372 case '0': /* fallthrough */
373 case '^': cursor_move (f.p[0], 0 ); break;
374 case '$': cursor_move (f.p[0], f.w-1 ); break;
375 case 'g': cursor_move (0, f.p[1] ); break;
376 case 'G': cursor_move (f.h-1, f.p[1] ); break;
377 case 'm':
378 action = tolower(getch(mouse));
379 if (action < 'a' || action > 'z') break;/*out of bound*/
380 markers[action-'a'].l = f.p[0];
381 markers[action-'a'].c = f.p[1];
382 break;
383 case'\'': /* fallthrough */
384 case '`':
385 action = tolower(getch(mouse));
386 if (action < 'a' || action > 'z' /* out of bound or */
387 || markers[action-'a'].l == -1) break; /* unset */
388 cursor_move (markers[action-'a'].l, markers[action-'a'].c);
389 break;
390 case 'r': /* start a new game */
391 free_field ();
392 goto newgame;
393 case 'q':
394 goto quit;
395 case '\014': /* Ctrl-L -- redraw */
396 show_minefield (NORMAL);
397 break;
398 case '\\':
399 if (is_newgame) {
400 is_newgame = 0;
401 fill_minefield (-1, -1);
402 timer_setup(1);
403 }
404 show_minefield (cheatmode?NORMAL:SHOWMINES);
405 cheatmode = !cheatmode;
406 break;
407 }
408 }
409
410 win:
411 lose:
412 timer_setup(0); /* stop timer */
413 show_minefield (SHOWMINES);
414 int gotaction;
415 do {
416 unsigned char mouse[3];
417 gotaction = getch(mouse);
418 /* :D clicked: TODO: won't work in single-width mode! */
419 if (gotaction==CTRSEQ_MOUSE_LEFT && mouse[2]==LINE_OFFSET-1 &&
420 (mouse[1]==f.w+COL_OFFSET || mouse[1]==f.w+COL_OFFSET+1)) {
421 free_field ();
422 goto newgame;
423 } else if (gotaction == 'r') {
424 free_field ();
425 goto newgame;
426 } else if (gotaction == 'q') {
427 goto quit;
428 }
429 } while (1);
430
431 quit:
432 return 0;
433 }
434
435 void quit () {
436 move(0,0); //move(f.h+LINE_OFFSET+2, 0);
437 /* disable mouse, show cursor */
438 printf ("\033[?9l\033[?25h");
439 /* reset charset, if necessary */
440 if (op.scheme && op.scheme->reset_seq) print (op.scheme->reset_seq);
441 /* revert to primary screen */
442 if (alt_screen) printf ("\033[?47l");
443 free_field ();
444 restore_term_mode(saved_term_mode);
445 }
446
447 /* I haven't won as long as a cell exists, that
448 - I haven't opened, and
449 - is not a mine */
450 int everything_opened () {
451 for (int row = 0; row < f.h; row++)
452 for (int col = 0; col < f.w; col++)
453 if (f.c[row][col].o == CLOSED &&
454 f.c[row][col].m == NO_MINE ) return 0;
455 return 1;
456 }
457
458 int wait_mouse_up (int l, int c) {
459 unsigned char mouse2[3];
460 int level = 1;
461 int l2, c2;
462
463 /* show :o face */
464 move (1, field2screen_c (f.w/2)-1); print (EMOT(OHH));
465
466 if (!(l < 0 || l >= f.h || c < 0 || c >= f.w)) {
467 /* show a pushed-in button if cursor is on minefield */
468 move (l+LINE_OFFSET, field2screen_c(c));
469 fputs (op.scheme->mouse_highlight, stdout);
470 }
471
472 while (level > 0) {
473 if (getctrlseq (mouse2) == CTRSEQ_MOUSE) {
474 /* ignore mouse wheel events: */
475 if (mouse2[0] & 0x40) continue;
476
477 else if (mouse2[0]&3 == 3) level--; /* release event */
478 else level++; /* another button pressed */
479 }
480 }
481
482 move (1, field2screen_c (f.w/2)-1); print (EMOT(SMILE)); //TODO: does not respect mode (SHOWMINES, GAME_WON, GAME_LOST)
483 if (!(l < 0 || l >= f.h || c < 0 || c >= f.w)) {
484 partial_show_minefield (l, c, NORMAL);
485 }
486 c2 = screen2field_c(mouse2[1]);
487 l2 = screen2field_l(mouse2[2]);
488 return ((l2 == l) && (c2 == c));
489 }
490
491 int choord_square (int line, int col) {
492 for (int l = MAX(line-1, 0); l <= MIN(line+1, f.h-1); l++) {
493 for (int c = MAX(col-1, 0); c <= MIN(col+1, f.w-1); c++) {
494 if (f.c[l][c].f != FLAG) {
495 if (uncover_square (l, c))
496 return 1;
497 }
498 }
499 }
500
501 return 0;
502 }
503
504 int uncover_square (int l, int c) {
505 f.c[l][c].o = OPENED;
506 f.c[l][c].f = NOFLAG; /*must not be QUESM, otherwise rendering issues*/
507 partial_show_minefield (l, c, NORMAL);
508
509 if (f.c[l][c].m) {
510 f.c[l][c].m = DEATH_MINE;
511 return 1;
512 }
513
514 /* check for chording */
515 if (f.c[l][c].n == 0) {
516 for (int choord_l = -1; choord_l <= 1; choord_l++) {
517 for (int choord_c = -1; choord_c <= 1; choord_c++) {
518 int newl = l + choord_l;
519 int newc = c + choord_c;
520 if (newl >= 0 && newl < f.h &&
521 newc >= 0 && newc < f.w &&
522 f.c[newl][newc].o == CLOSED &&
523 uncover_square (newl, newc)) {
524 return 1;
525 }
526 }
527 }
528 }
529
530 return 0;
531 }
532
533 void flag_square (int l, int c) {
534 if (f.c[l][c].o != CLOSED) return;
535 /* cycles through flag/quesm/noflag (uses op.mode to detect which ones
536 are allowed) */
537 f.c[l][c].f = (f.c[l][c].f + 1) % (op.mode + 1);
538 if (f.c[l][c].f==FLAG) f.f++;
539 else f.f--; //WARN: breaks on `-q'!
540 partial_show_minefield (l, c, NORMAL);
541 move (1, op.scheme->cell_width);
542 printf ("[%03d]", f.m - f.f);
543 }
544
545 void quesm_square (int l, int c) {
546 /* toggle question mark / none. won't turn flags into question marks.
547 unlike flag_square, this function doesn't respect `-q'. */
548 if (f.c[l][c].o != CLOSED) return;
549 else if (f.c[l][c].f == NOFLAG) f.c[l][c].f = QUESM;
550 else if (f.c[l][c].f == QUESM) f.c[l][c].f = NOFLAG;
551 partial_show_minefield (l, c, NORMAL);
552 }
553
554 int do_uncover (int* is_newgame) {
555 if (*is_newgame) {
556 *is_newgame = 0;
557 fill_minefield (f.p[0], f.p[1]);
558 timer_setup(1);
559 }
560
561 if (f.c[f.p[0]][f.p[1]].f == FLAG ) return GAME_INPROGRESS;
562 if (f.c[f.p[0]][f.p[1]].o == CLOSED) {
563 if (uncover_square (f.p[0], f.p[1])) return GAME_LOST;
564 } else if (get_neighbours (f.p[0], f.p[1], 1) == 0) {
565 if (choord_square (f.p[0], f.p[1])) return GAME_LOST;
566 }
567 if (everything_opened()) return GAME_WON;
568
569 return GAME_INPROGRESS;
570 }
571
572 void fill_minefield (int l, int c) {
573 srand (time(0));
574 int mines_set = f.m;
575 while (mines_set) {
576 int line = rand() % f.h;
577 int col = rand() % f.w;
578
579 if (f.c[line][col].m) {
580 /* skip if field already has a mine */
581 continue;
582 } else if ((line == l) && (col == c)) {
583 /* don't put a mine on the already opened (first click) field */
584 continue;
585 } else {
586 mines_set--;
587 f.c[line][col].m = STD_MINE;
588 }
589 }
590
591 /* precalculate neighbours */
592 for (int l=0; l < f.h; l++)
593 for (int c=0; c < f.w; c++)
594 f.c[l][c].n = get_neighbours (l, c, NORMAL);
595 }
596
597 void move (int line, int col) {
598 printf ("\033[%d;%dH", line+1, col+1);
599 }
600
601 /* absolute coordinates! */
602 void cursor_move (int l, int c) {
603 partial_show_minefield (f.p[0], f.p[1], NORMAL);
604 /* update f.p */
605 f.p[0] = CLAMP(l, 0, f.h-1);
606 f.p[1] = CLAMP(c, 0, f.w-1);
607 move (f.p[0]+LINE_OFFSET, field2screen_c(f.p[1]));
608 //fputs (op.scheme->mouse_highlight, stdout);
609
610 if (!f.c[f.p[0]][f.p[1]].f) print("\033[7m");//invert unless ! or ?
611 partial_show_minefield (f.p[0], f.p[1], HIGHLIGHT);
612 print("\033[0m");//un-invert
613 }
614
615 /* to_next_boundary(): move into the supplied direction until a change in open-
616 state or flag-state is found and move there. falls back to BIG_MOVE. */
617 #define FIND_NEXT(X, L, C, L1, C1, MAX, OP) do {\
618 new_ ## X OP ## = BIG_MOVE;\
619 for (int i = X OP 1; i > 0 && i < f.MAX-1; i OP ## OP)\
620 if ((f.c[L ][C ].o<<2 + f.c[L ][C ].f) \
621 != (f.c[L1][C1].o<<2 + f.c[L1][C1].f)) {\
622 new_ ## X = i OP 1;\
623 break;\
624 }\
625 } while(0)
626 void to_next_boundary (int l, int c, char direction) {
627 int new_l = l;
628 int new_c = c;
629 switch (direction) {
630 case '>': FIND_NEXT(c, l, i, l, i+1, w, +); break;
631 case '<': FIND_NEXT(c, l, i, l, i-1, w, -); break;
632 case '^': FIND_NEXT(l, i, c, i-1, c, h, -); break;
633 case 'v': FIND_NEXT(l, i, c, i+1, c, h, +); break;
634 }
635
636 cursor_move (new_l, new_c);
637 }
638 #undef FIND_NEXT
639
640 char* cell2schema (int l, int c, int mode) {
641 struct minecell cell = f.c[l][c];
642 /* move past invert-ctrlsequence when highlighting the cursor: */
643 int offset = ((mode==HIGHLIGHT)*op.scheme->flag_offset);
644
645 if (mode == SHOWMINES) return (
646 cell.f == FLAG && cell.m ? op.scheme->field_flagged+offset:
647 cell.f == FLAG && !cell.m ? op.scheme->mine_wrongf+offset:
648 cell.m == STD_MINE ? op.scheme->mine_normal:
649 cell.m == DEATH_MINE ? op.scheme->mine_death:
650 cell.o == CLOSED ? op.scheme->field_closed:
651 /*.......................*/ op.scheme->number[f.c[l][c].n]);
652 else return (
653 cell.f == FLAG ? op.scheme->field_flagged+offset:
654 cell.f == QUESM ? op.scheme->field_question+offset:
655 cell.o == CLOSED ? op.scheme->field_closed:
656 cell.m == STD_MINE ? op.scheme->mine_normal:
657 cell.m == DEATH_MINE ? op.scheme->mine_death:
658 /*.......................*/ op.scheme->number[f.c[l][c].n]);
659 }
660
661 void partial_show_minefield (int l, int c, int mode) {
662 move (l+LINE_OFFSET, field2screen_c(c));
663
664 print (cell2schema(l, c, mode));
665 }
666
667 void show_minefield (int mode) {
668 int dtime;
669 static char modechar[] = {'*', '!', '?'};
670
671 move (0,0);
672
673 if (f.t == 0) {
674 dtime = 0;
675 } else {
676 dtime = difftime (time(NULL), f.t);
677 }
678
679 /* first line */
680 print (op.scheme->border_top_l);
681 printm (f.w*op.scheme->cell_width,op.scheme->border_top_m);
682 printf ("%s\r\n", op.scheme->border_top_r);
683 /* second line */
684 print (op.scheme->border_status_l);
685 printf("[%03d]", f.m - f.f); //TODO: breaks layout if >999 mines
686 printm (f.w*op.scheme->cell_width/2-6, " ");
687 printf ("%s", mode==SHOWMINES?everything_opened()?
688 EMOT(WON) : EMOT(DEAD) : EMOT(SMILE));
689 printm (f.w*op.scheme->cell_width/2-6-4, " ");
690 printf ("[%c] [%03d]", modechar[f.s], dtime); //TODO: modechar is too large for 8x8 field
691 print (op.scheme->border_status_r);
692 print ("\r\n");
693 /* third line */
694 print (op.scheme->border_spacer_l);
695 printm (f.w*op.scheme->cell_width,op.scheme->border_spacer_m);
696 print (op.scheme->border_spacer_r);
697 print ("\r\n");
698 /* main body */
699 for (int l = 0; l < f.h; l++) {
700 print (op.scheme->border_field_l);
701 for (int c = 0; c < f.w; c++) {
702 print (cell2schema(l, c, mode));
703 }
704 print (op.scheme->border_field_r); print ("\r\n");
705 }
706 /* last line */
707 print (op.scheme->border_bottom_l);
708 printm (f.w*op.scheme->cell_width,op.scheme->border_bottom_m);
709 print (op.scheme->border_bottom_r);
710 print ("\r\n");
711 }
712
713 int get_neighbours (int line, int col, int reduced_mode) {
714 /* counts mines surrounding a square
715 modes: 0=normal; 1=reduced */
716
717 int count = 0;
718
719 for (int l = MAX(line-1, 0); l <= MIN(line+1, f.h-1); l++) {
720 for (int c = MAX(col-1, 0); c <= MIN(col+1, f.w-1); c++) {
721 if (!l && !c) continue;
722
723 count += !!f.c[l][c].m;
724 count -= reduced_mode * f.c[l][c].f==FLAG;
725 }
726 }
727 return count;
728 }
729
730 struct minecell** alloc_array (int lines, int cols) {
731 struct minecell** a = malloc (lines * sizeof(struct minecell*));
732 if (a == NULL) return NULL;
733 for (int l = 0; l < lines; l++) {
734 a[l] = calloc (cols, sizeof(struct minecell));
735 if (a[l] == NULL) goto unalloc;
736 }
737
738 return a;
739 unalloc:
740 for (int l = 0; l < lines; l++)
741 free (a[l]);
742 return NULL;
743 }
744
745 void free_field () {
746 if (f.c == NULL) return;
747 for (int l = 0; l < f.h; l++) {
748 free (f.c[l]);
749 }
750
751 free (f.c);
752 f.c = NULL;
753 }
754
755 int screen2field_l (int l) {
756 return (l-LINE_OFFSET) - 1;
757 }
758 /* some trickery is required to extract the mouse position from the cell width,
759 depending on wheather we are using full width characters or double line width.
760 WARN: tested only with scheme.cell_width = 1 and scheme.cell_width = 2. */
761 int screen2field_c (int c) {
762 return (c-COL_OFFSET+1 - 2*(op.scheme->cell_width%2))/2 - op.scheme->cell_width/2;
763 }
764 int field2screen_l (int l) {
765 return 0; //TODO: is never used, therefore not implemented
766 }
767 int field2screen_c (int c) {
768 return (op.scheme->cell_width*c+COL_OFFSET - (op.scheme->cell_width%2));
769 }
770
771 enum esc_states {
772 START,
773 ESC_SENT,
774 CSI_SENT,
775 MOUSE_EVENT,
776 };
777 int getctrlseq (unsigned char* buf) {
778 int c;
779 int state = START;
780 int offset = 0x20; /* never sends control chars as data */
781 while ((c = getchar()) != EOF) {
782 switch (state) {
783 case START:
784 switch (c) {
785 case '\033': state=ESC_SENT; break;
786 default: return c;
787 }
788 break;
789 case ESC_SENT:
790 switch (c) {
791 case '[': state=CSI_SENT; break;
792 default: return CTRSEQ_INVALID;
793 }
794 break;
795 case CSI_SENT:
796 switch (c) {
797 case 'M': state=MOUSE_EVENT; break;
798 default: return CTRSEQ_INVALID;
799 }
800 break;
801 case MOUSE_EVENT:
802 buf[0] = c - offset;
803 buf[1] = getchar() - offset;
804 buf[2] = getchar() - offset;
805 return CTRSEQ_MOUSE;
806 default:
807 return CTRSEQ_INVALID;
808 }
809 }
810 return 2;
811 }
812
813 int getch(unsigned char* buf) {
814 /* returns a character, EOF, or constant for an escape/control sequence - NOT
815 compatible with the ncurses implementation of same name */
816 int action = getctrlseq(buf);
817 int l, c;
818 switch (action) {
819 case CTRSEQ_MOUSE:
820 l = screen2field_l (buf[2]);
821 c = screen2field_c (buf[1]);
822
823 if (buf[0] > 3) break; /* ignore all but left/middle/right/up */
824 int success = wait_mouse_up(l, c);
825
826 /* mouse moved while pressed: */
827 if (!success) return CTRSEQ_INVALID;
828
829 switch (buf[0]) {
830 case 0: return CTRSEQ_MOUSE_LEFT;
831 case 1: return CTRSEQ_MOUSE_MIDDLE;
832 case 2: return CTRSEQ_MOUSE_RIGHT;
833 }
834 }
835
836 return action;
837 }
838
839 void timer_setup (int enable) {
840 static struct itimerval tbuf;
841 tbuf.it_interval.tv_sec = 1;
842 tbuf.it_interval.tv_usec = 0;
843
844 if (enable) {
845 f.t = time(NULL);
846 tbuf.it_value.tv_sec = 1;
847 tbuf.it_value.tv_usec = 0;
848 if (setitimer(ITIMER_REAL, &tbuf, NULL) == -1) {
849 perror("setitimer");
850 exit(1);
851 }
852 } else {
853 tbuf.it_value.tv_sec = 0;
854 tbuf.it_value.tv_usec = 0;
855 if ( setitimer(ITIMER_REAL, &tbuf, NULL) == -1 ) {
856 perror("setitimer");
857 exit(1);
858 }
859 }
860
861 }
Imprint / Impressum