]> git.gir.st - minesVIiper.git/blob - mines_2017.c
crude patch for >999 mines
[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 int n; /* determine size of variable width fields */
670 static char modechar[] = {'*', '!', '?'};
671
672 move (0,0);
673
674 if (f.t == 0) {
675 dtime = 0;
676 } else {
677 dtime = difftime (time(NULL), f.t);
678 }
679
680 /* first line */
681 print (op.scheme->border_top_l);
682 printm (f.w*op.scheme->cell_width,op.scheme->border_top_m);
683 printf ("%s\r\n", op.scheme->border_top_r);
684 /* second line */
685 print (op.scheme->border_status_l);
686 n = printf("[%03d]", f.m - f.f); //TODO: breaks layout if >999 mines
687 printm (f.w*op.scheme->cell_width/2-(n+1), " ");
688 printf ("%s", mode==SHOWMINES?everything_opened()?
689 EMOT(WON) : EMOT(DEAD) : EMOT(SMILE));
690 printm (f.w*op.scheme->cell_width/2-6-4, " ");
691 printf ("[%c] [%03d]", modechar[f.s], dtime); //TODO: modechar is too large for 8x8 field
692 print (op.scheme->border_status_r);
693 print ("\r\n");
694 /* third line */
695 print (op.scheme->border_spacer_l);
696 printm (f.w*op.scheme->cell_width,op.scheme->border_spacer_m);
697 print (op.scheme->border_spacer_r);
698 print ("\r\n");
699 /* main body */
700 for (int l = 0; l < f.h; l++) {
701 print (op.scheme->border_field_l);
702 for (int c = 0; c < f.w; c++) {
703 print (cell2schema(l, c, mode));
704 }
705 print (op.scheme->border_field_r); print ("\r\n");
706 }
707 /* last line */
708 print (op.scheme->border_bottom_l);
709 printm (f.w*op.scheme->cell_width,op.scheme->border_bottom_m);
710 print (op.scheme->border_bottom_r);
711 print ("\r\n");
712 }
713
714 int get_neighbours (int line, int col, int reduced_mode) {
715 /* counts mines surrounding a square
716 modes: 0=normal; 1=reduced */
717
718 int count = 0;
719
720 for (int l = MAX(line-1, 0); l <= MIN(line+1, f.h-1); l++) {
721 for (int c = MAX(col-1, 0); c <= MIN(col+1, f.w-1); c++) {
722 if (!l && !c) continue;
723
724 count += !!f.c[l][c].m;
725 count -= reduced_mode * f.c[l][c].f==FLAG;
726 }
727 }
728 return count;
729 }
730
731 struct minecell** alloc_array (int lines, int cols) {
732 struct minecell** a = malloc (lines * sizeof(struct minecell*));
733 if (a == NULL) return NULL;
734 for (int l = 0; l < lines; l++) {
735 a[l] = calloc (cols, sizeof(struct minecell));
736 if (a[l] == NULL) goto unalloc;
737 }
738
739 return a;
740 unalloc:
741 for (int l = 0; l < lines; l++)
742 free (a[l]);
743 return NULL;
744 }
745
746 void free_field () {
747 if (f.c == NULL) return;
748 for (int l = 0; l < f.h; l++) {
749 free (f.c[l]);
750 }
751
752 free (f.c);
753 f.c = NULL;
754 }
755
756 int screen2field_l (int l) {
757 return (l-LINE_OFFSET) - 1;
758 }
759 /* some trickery is required to extract the mouse position from the cell width,
760 depending on wheather we are using full width characters or double line width.
761 WARN: tested only with scheme.cell_width = 1 and scheme.cell_width = 2. */
762 int screen2field_c (int c) {
763 return (c-COL_OFFSET+1 - 2*(op.scheme->cell_width%2))/2 - op.scheme->cell_width/2;
764 }
765 int field2screen_l (int l) {
766 return 0; //TODO: is never used, therefore not implemented
767 }
768 int field2screen_c (int c) {
769 return (op.scheme->cell_width*c+COL_OFFSET - (op.scheme->cell_width%2));
770 }
771
772 enum esc_states {
773 START,
774 ESC_SENT,
775 CSI_SENT,
776 MOUSE_EVENT,
777 };
778 int getctrlseq (unsigned char* buf) {
779 int c;
780 int state = START;
781 int offset = 0x20; /* never sends control chars as data */
782 while ((c = getchar()) != EOF) {
783 switch (state) {
784 case START:
785 switch (c) {
786 case '\033': state=ESC_SENT; break;
787 default: return c;
788 }
789 break;
790 case ESC_SENT:
791 switch (c) {
792 case '[': state=CSI_SENT; break;
793 default: return CTRSEQ_INVALID;
794 }
795 break;
796 case CSI_SENT:
797 switch (c) {
798 case 'M': state=MOUSE_EVENT; break;
799 default: return CTRSEQ_INVALID;
800 }
801 break;
802 case MOUSE_EVENT:
803 buf[0] = c - offset;
804 buf[1] = getchar() - offset;
805 buf[2] = getchar() - offset;
806 return CTRSEQ_MOUSE;
807 default:
808 return CTRSEQ_INVALID;
809 }
810 }
811 return 2;
812 }
813
814 int getch(unsigned char* buf) {
815 /* returns a character, EOF, or constant for an escape/control sequence - NOT
816 compatible with the ncurses implementation of same name */
817 int action = getctrlseq(buf);
818 int l, c;
819 switch (action) {
820 case CTRSEQ_MOUSE:
821 l = screen2field_l (buf[2]);
822 c = screen2field_c (buf[1]);
823
824 if (buf[0] > 3) break; /* ignore all but left/middle/right/up */
825 int success = wait_mouse_up(l, c);
826
827 /* mouse moved while pressed: */
828 if (!success) return CTRSEQ_INVALID;
829
830 switch (buf[0]) {
831 case 0: return CTRSEQ_MOUSE_LEFT;
832 case 1: return CTRSEQ_MOUSE_MIDDLE;
833 case 2: return CTRSEQ_MOUSE_RIGHT;
834 }
835 }
836
837 return action;
838 }
839
840 void timer_setup (int enable) {
841 static struct itimerval tbuf;
842 tbuf.it_interval.tv_sec = 1;
843 tbuf.it_interval.tv_usec = 0;
844
845 if (enable) {
846 f.t = time(NULL);
847 tbuf.it_value.tv_sec = 1;
848 tbuf.it_value.tv_usec = 0;
849 if (setitimer(ITIMER_REAL, &tbuf, NULL) == -1) {
850 perror("setitimer");
851 exit(1);
852 }
853 } else {
854 tbuf.it_value.tv_sec = 0;
855 tbuf.it_value.tv_usec = 0;
856 if ( setitimer(ITIMER_REAL, &tbuf, NULL) == -1 ) {
857 perror("setitimer");
858 exit(1);
859 }
860 }
861
862 }
Imprint / Impressum