]>
git.gir.st - minesVIiper.git/blob - mines.c
1 /*******************************************************************************
3 By Tobias Girstmair, 2015 - 2018
6 (see ./minesviiper -h for full list of options)
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 - o/space to open and choord
13 - (see `./minesviiper -h' for all keybindings)
15 GNU GPL v3, see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt
16 *******************************************************************************/
19 #define _POSIX_C_SOURCE 2 /*for getopt, sigaction in c99*/
24 #include <sys/ioctl.h>
39 #define MIN(a,b) (a>b?b:a)
40 #define MAX(a,b) (a>b?a:b)
41 #define CLAMP(a,m,M) (a<m?m:(a>M?M:a))
42 #define printm(n, s) for (int _loop = 0; _loop < n; _loop++) fputs (s, stdout)
43 #define print(str) fputs (str?str: "" , stdout)
44 #define EMOT(e) op.scheme->emoticons[EMOT_ ## e]
45 #define BORDER(l, c) op.scheme->border[B_ ## l][B_ ## c]
46 #define CW op.scheme->cell_width /* for brevity */
48 #define AROUND(_line, _column) TODO: NOTIMPL
54 int main ( int argc
, char ** argv
) {
59 op
. scheme
= & symbols_mono
;
63 opterr
= 0 ; /* don't print message on unrecognized option */
64 while (( optget
= getopt ( argc
, argv
, "+hnfqcdx" )) != - 1 ) {
66 case 'n' : op
. mode
= NOFLAG
; break ;
67 case 'f' : op
. mode
= FLAG
; break ; /*default*/
68 case 'q' : op
. mode
= QUESM
; break ;
69 case 'c' : op
. scheme
= & symbols_col1
; break ;
70 case 'd' : op
. scheme
= & symbols_doublewidth
; break ;
73 fprintf ( stderr
, SHORTHELP LONGHELP
, argv
[ 0 ]);
74 return !( optget
== 'h' );
76 } if ( optind
< argc
) { /* parse Fieldspec */
77 if ( parse_fieldspec ( argv
[ optind
])) {
78 fprintf ( stderr
, "FIELDSPEC: WxH[xM]"
79 " (width 'x' height 'x' mines) \n " );
88 /* check boundaries */
89 if ( f
. m
> ( f
. w
- 1 ) * ( f
. h
- 1 )) {
90 move_ph ( LINE_OFFSET
+ f
. h
+ LINES_AFTER
- 1 , 0 );
91 f
. m
= ( f
. w
- 1 ) * ( f
. h
- 1 );
92 fprintf ( stdout
, "too many mines. reduced to %d. \r\n " , f
. m
); //TODO: should disappear after a timeout (stomp-hi?)
97 switch ( minesviiper ()) {
98 case GAME_NEW
: goto newgame
;
99 case GAME_WON
: g
. o
= GAME_WON
; break ;
100 case GAME_LOST
: g
. o
= GAME_LOST
; break ;
101 case GAME_QUIT
: goto quit
;
104 timer_setup ( 0 ); /* stop timer */
105 show_minefield ( SHOWMINES
);
107 switch ( getch_wrapper ()) {
108 case WRAPPER_EMOTICON
:
120 int minesviiper ( void ) {
121 f
. c
= alloc_array ( f
. h
, f
. w
); /* TODO: just memset it */
122 g
= ( const struct game
){ 0 }; /* reset all game-specific parameters */
124 show_minefield ( NORMAL
);
127 switch ( getch_wrapper ()) {
129 if ( g
. s
== MODE_OPEN
|| f
. c
[ g
. p
[ 0 ]][ g
. p
[ 1 ]]. o
== OPENED
)
131 if ( g
. s
== MODE_FLAG
)
133 if ( g
. s
== MODE_QUESM
)
137 g
. s
= ( g
. s
+ 1 )%( op
. mode
+ 1 );
138 show_minefield ( g
. c
? SHOWMINES
: NORMAL
);
141 case CTRSEQ_MOUSE_LEFT
:
143 switch ( do_uncover (&( g
. n
))) {
144 case GAME_LOST
: return GAME_LOST
;
145 case GAME_WON
: return GAME_WON
;
149 case CTRSEQ_MOUSE_RIGHT
:
150 case 'i' : flag_square ( g
. p
[ 0 ], g
. p
[ 1 ]); break ;
152 case '?' : quesm_square ( g
. p
[ 0 ], g
. p
[ 1 ]); break ;
153 case CTRSEQ_CURSOR_LEFT
:
154 case 'h' : move_hi ( g
. p
[ 0 ], g
. p
[ 1 ]- 1 ); break ;
155 case CTRSEQ_CURSOR_DOWN
:
156 case 'j' : move_hi ( g
. p
[ 0 ]+ 1 , g
. p
[ 1 ] ); break ;
157 case CTRSEQ_CURSOR_UP
:
158 case 'k' : move_hi ( g
. p
[ 0 ]- 1 , g
. p
[ 1 ] ); break ;
159 case CTRSEQ_CURSOR_RIGHT
:
160 case 'l' : move_hi ( g
. p
[ 0 ], g
. p
[ 1 ]+ 1 ); break ;
161 case 'w' : to_next_boundary ( g
. p
[ 0 ], g
. p
[ 1 ], '>' ); break ;
162 case 'b' : to_next_boundary ( g
. p
[ 0 ], g
. p
[ 1 ], '<' ); break ;
163 case 'u' : to_next_boundary ( g
. p
[ 0 ], g
. p
[ 1 ], '^' ); break ;
164 case 'd' : to_next_boundary ( g
. p
[ 0 ], g
. p
[ 1 ], 'v' ); break ;
165 case '0' : /* fallthrough */
166 case '^' : move_hi ( g
. p
[ 0 ], 0 ); break ;
167 case '$' : move_hi ( g
. p
[ 0 ], f
. w
- 1 ); break ;
168 case 'g' : move_hi ( 0 , g
. p
[ 1 ] ); break ;
169 case 'G' : move_hi ( f
. h
- 1 , g
. p
[ 1 ] ); break ;
170 case 'z' : move_hi ( f
. h
/ 2 , f
. w
/ 2 ); break ;
171 case 'm' : set_mark (); break ;
172 case ' \' ' : /* fallthrough */
173 case '`' : jump_mark (); break ;
174 case WRAPPER_EMOTICON
:
175 case 'r' : return GAME_NEW
;
176 case 'q' : return GAME_QUIT
;
177 case CTRL_
'L' : show_minefield ( NORMAL
); break ;
179 if ( g
. n
== GAME_NEW
) break ; /* must open a cell first */
180 show_minefield ( g
. c
? NORMAL
: SHOWMINES
);
193 /* I haven't won as long as a cell exists, that
194 - I haven't opened, and
196 int everything_opened ( void ) {
197 for ( int row
= 0 ; row
< f
. h
; row
++)
198 for ( int col
= 0 ; col
< f
. w
; col
++)
199 if ( f
. c
[ row
][ col
]. o
== CLOSED
&&
200 f
. c
[ row
][ col
]. m
== NO_MINE
) return 0 ;
204 int wait_mouse_up ( int l
, int c
) { /* TODO: should not take minefield-coords but absolute ones */
205 unsigned char mouse2
[ 3 ];
210 move_ph ( 1 , field2screen_c ( f
. w
/ 2 )- 1 ); print ( EMOT ( OHH
));
212 if (!( l
< 0 || l
>= f
. h
|| c
< 0 || c
>= f
. w
)) {
213 /* show a pushed-in button if cursor is on minefield */
214 move_ph ( l
+ LINE_OFFSET
, field2screen_c ( c
));
215 fputs ( op
. scheme
-> mouse_highlight
, stdout
);
219 if ( getctrlseq ( mouse2
) == CTRSEQ_MOUSE
) {
220 /* ignore mouse wheel events: */
221 if ( mouse2
[ 0 ] & 0x40 ) continue ;
223 else if ( mouse2
[ 0 ]& 3 == 3 ) level
--; /* release event */
224 else level
++; /* another button pressed */
228 move_ph ( 1 , field2screen_c ( f
. w
/ 2 )- 1 ); print ( get_emoticon ());
230 if (!( l
< 0 || l
>= f
. h
|| c
< 0 || c
>= f
. w
)) {
231 partial_show_minefield ( l
, c
, NORMAL
);
233 c2
= screen2field_c ( mouse2
[ 1 ]);
234 l2
= screen2field_l ( mouse2
[ 2 ]);
235 return (( l2
== l
) && ( c2
== c
));
238 int choord_square ( int line
, int col
) {
239 for ( int l
= MAX ( line
- 1 , 0 ); l
<= MIN ( line
+ 1 , f
. h
- 1 ); l
++) {
240 for ( int c
= MAX ( col
- 1 , 0 ); c
<= MIN ( col
+ 1 , f
. w
- 1 ); c
++) {
241 if ( f
. c
[ l
][ c
]. f
!= FLAG
) {
242 if ( uncover_square ( l
, c
))
251 int uncover_square ( int l
, int c
) {
252 f
. c
[ l
][ c
]. o
= OPENED
;
253 f
. c
[ l
][ c
]. f
= NOFLAG
; /*must not be QUESM, otherwise rendering issues*/
254 partial_show_minefield ( l
, c
, NORMAL
);
257 f
. c
[ l
][ c
]. m
= DEATH_MINE
;
261 /* check for chording */
262 if ( f
. c
[ l
][ c
]. n
== 0 ) {
263 for ( int choord_l
= - 1 ; choord_l
<= 1 ; choord_l
++) {
264 for ( int choord_c
= - 1 ; choord_c
<= 1 ; choord_c
++) {
265 int newl
= l
+ choord_l
;
266 int newc
= c
+ choord_c
;
267 if ( newl
>= 0 && newl
< f
. h
&&
268 newc
>= 0 && newc
< f
. w
&&
269 f
. c
[ newl
][ newc
]. o
== CLOSED
&&
270 uncover_square ( newl
, newc
)) {
280 void flag_square ( int l
, int c
) {
281 static char modechar
[] = { '*' , '!' , '?' };
283 if ( f
. c
[ l
][ c
]. o
!= CLOSED
) return ;
284 /* cycle through flag/quesm/noflag: */
285 f
. c
[ l
][ c
]. f
= ( f
. c
[ l
][ c
]. f
+ 1 ) % ( op
. mode
+ 1 );
286 if ( f
. c
[ l
][ c
]. f
== FLAG
) g
. f
++;
287 else if (( op
. mode
== FLAG
&& f
. c
[ l
][ c
]. f
== NOFLAG
) ||
288 ( op
. mode
== QUESM
&& f
. c
[ l
][ c
]. f
== QUESM
)) g
. f
--;
289 partial_show_minefield ( l
, c
, NORMAL
);
290 move_ph ( 1 , op
. scheme
-> cell_width
);
291 printf ( "[%03d%c]" , f
. m
- g
. f
, modechar
[ g
. s
]);
294 void quesm_square ( int l
, int c
) {
295 /* toggle question mark / none. won't turn flags into question marks.
296 unlike flag_square, this function doesn't respect `-q'. */
297 if ( f
. c
[ l
][ c
]. o
!= CLOSED
) return ;
298 else if ( f
. c
[ l
][ c
]. f
== NOFLAG
) f
. c
[ l
][ c
]. f
= QUESM
;
299 else if ( f
. c
[ l
][ c
]. f
== QUESM
) f
. c
[ l
][ c
]. f
= NOFLAG
;
300 partial_show_minefield ( l
, c
, NORMAL
);
303 int do_uncover ( int * is_newgame
) {
304 if (* is_newgame
== GAME_NEW
) {
305 * is_newgame
= GAME_INPROGRESS
;
306 fill_minefield ( g
. p
[ 0 ], g
. p
[ 1 ]);
310 if ( f
. c
[ g
. p
[ 0 ]][ g
. p
[ 1 ]]. f
== FLAG
) return GAME_INPROGRESS
;
311 if ( f
. c
[ g
. p
[ 0 ]][ g
. p
[ 1 ]]. o
== CLOSED
) {
312 if ( uncover_square ( g
. p
[ 0 ], g
. p
[ 1 ])) return GAME_LOST
;
313 } else if ( get_neighbours ( g
. p
[ 0 ], g
. p
[ 1 ], 1 ) == 0 ) {
314 if ( choord_square ( g
. p
[ 0 ], g
. p
[ 1 ])) return GAME_LOST
;
316 /* show the stomp size if we aren't fully flagged */
317 for ( int l
= MAX ( g
. p
[ 0 ]- 1 , 0 ); l
<= MIN ( g
. p
[ 0 ]+ 1 , f
. h
- 1 ); l
++) {
318 for ( int c
= MAX ( g
. p
[ 1 ]- 1 , 0 ); c
<= MIN ( g
. p
[ 1 ]+ 1 , f
. w
- 1 ); c
++) {
319 if ( f
. c
[ l
][ c
]. o
== CLOSED
&& f
. c
[ l
][ c
]. f
!= FLAG
)
320 partial_show_minefield ( l
, c
, HIGHLIGHT
); //TODO: hide after timeout
321 /* save the highlight position, timeout-end, and setup a timer. if another tiemout comes in before the timeout is over, the old one shall be removed and the timer cancelled before drawing the new one. */
325 if ( everything_opened ()) return GAME_WON
;
327 return GAME_INPROGRESS
;
330 void set_mark ( void ) {
331 int mark
= tolower ( getch_wrapper ());
332 if ( mark
< 'a' || mark
> 'z' ) return ; /*out of bound*/
334 g
. m
[ mark
- 'a' ]. l
= g
. p
[ 0 ];
335 g
. m
[ mark
- 'a' ]. c
= g
. p
[ 1 ];
339 void jump_mark ( void ) {
340 int mark
= tolower ( getch_wrapper ());
341 /* check bounds and if set: */
342 if ( mark
< 'a' || mark
> 'z' || ! g
. m
[ mark
- 'a' ]. s
) return ;
343 move_hi ( g
. m
[ mark
- 'a' ]. l
, g
. m
[ mark
- 'a' ]. c
);
346 void fill_minefield ( int l
, int c
) {
350 int line
= rand () % f
. h
;
351 int col
= rand () % f
. w
;
353 if ( f
. c
[ line
][ col
]. m
) {
354 /* skip if field already has a mine */
356 } else if (( line
== l
) && ( col
== c
)) {
357 /* don't put a mine on the already opened (first click) field */
361 f
. c
[ line
][ col
]. m
= STD_MINE
;
365 /* precalculate neighbours */
366 for ( int l
= 0 ; l
< f
. h
; l
++)
367 for ( int c
= 0 ; c
< f
. w
; c
++)
368 f
. c
[ l
][ c
]. n
= get_neighbours ( l
, c
, NORMAL
);
371 void move_ph ( int line
, int col
) {
372 /* move printhead to zero-indexed position */
373 printf ( " \033 [%d;%dH" , line
+ 1 , col
+ 1 );
376 void move_hi ( int l
, int c
) {
377 /* move cursor and highlight to absolute coordinates */
379 partial_show_minefield ( g
. p
[ 0 ], g
. p
[ 1 ], NORMAL
);
381 g
. p
[ 0 ] = CLAMP ( l
, 0 , f
. h
- 1 );
382 g
. p
[ 1 ] = CLAMP ( c
, 0 , f
. w
- 1 );
383 move_ph ( g
. p
[ 0 ]+ LINE_OFFSET
, field2screen_c ( g
. p
[ 1 ]));
385 print ( " \033 [7m" ); /* reverse video */
386 partial_show_minefield ( g
. p
[ 0 ], g
. p
[ 1 ], HIGHLIGHT
);
387 print ( " \033 [0m" ); /* un-invert */
390 /* to_next_boundary(): move into the supplied direction until a change in open-
391 state or flag-state is found and move there. falls back to BIG_MOVE. */
392 #define FIND_NEXT(X, L, C, L1, C1, MAX, OP) do {\
393 new_ ## X OP ## = BIG_MOVE;\
394 for (int i = X OP 1; i > 0 && i < f.MAX-1; i OP ## OP)\
395 if (((f.c[L ][C ].o<<2) + f.c[L ][C ].f) \
396 != ((f.c[L1][C1].o<<2) + f.c[L1][C1].f)) {\
401 void to_next_boundary ( int l
, int c
, char direction
) {
405 case '>' : FIND_NEXT ( c
, l
, i
, l
, i
+ 1 , w
, +); break ;
406 case '<' : FIND_NEXT ( c
, l
, i
, l
, i
- 1 , w
, -); break ;
407 case '^' : FIND_NEXT ( l
, i
, c
, i
- 1 , c
, h
, -); break ;
408 case 'v' : FIND_NEXT ( l
, i
, c
, i
+ 1 , c
, h
, +); break ;
411 move_hi ( new_l
, new_c
);
415 char * cell2schema ( int l
, int c
, int mode
) {
416 struct minecell cell
= f
. c
[ l
][ c
];
418 if ( mode
== SHOWMINES
) return (
419 cell
. f
== FLAG
&& cell
. m
? op
. scheme
-> field_flagged
:
420 cell
. f
== FLAG
&& ! cell
. m
? op
. scheme
-> mine_wrongf
:
421 cell
. m
== STD_MINE
? op
. scheme
-> mine_normal
:
422 cell
. m
== DEATH_MINE
? op
. scheme
-> mine_death
:
423 cell
. o
== CLOSED
? op
. scheme
-> field_closed
:
424 /*.......................*/ op
. scheme
-> number
[ f
. c
[ l
][ c
]. n
]);
426 cell
. f
== FLAG
? op
. scheme
-> field_flagged
:
427 cell
. f
== QUESM
? op
. scheme
-> field_question
:
428 cell
. o
== CLOSED
? op
. scheme
-> field_closed
:
429 cell
. m
== STD_MINE
? op
. scheme
-> mine_normal
:
430 cell
. m
== DEATH_MINE
? op
. scheme
-> mine_death
:
431 /*.......................*/ op
. scheme
-> number
[ f
. c
[ l
][ c
]. n
]);
434 void partial_show_minefield ( int l
, int c
, int mode
) {
435 if ( mode
== HIGHLIGHT
) printf ( " \033 [7m" ); /* reverse video */
436 move_ph ( l
+ LINE_OFFSET
, field2screen_c ( c
));
437 print ( cell2schema ( l
, c
, mode
));
438 if ( mode
== HIGHLIGHT
) printf ( " \033 [0m" ); /* reset all */
441 char * get_emoticon ( void ) {
442 return g
. o
== GAME_WON
? EMOT ( WON
):
443 g
. o
== GAME_LOST
? EMOT ( DEAD
):
447 /* https://zserge.com/blog/c-for-loop-tricks.html */
448 #define print_line(which) \
449 for (int _break = (printf( "%s" , BORDER(which,LEFT)), 1); _break; \
450 _break = 0, printf( "%s \r\n " , BORDER(which,RIGHT)))
451 #define print_border(which, width) \
452 print_line(which) printm (width, BORDER(which,MIDDLE))
453 void show_minefield ( int mode
) {
454 int dtime
= difftime ( time ( NULL
), g
. t
)*!! g
. t
;
455 int half_spaces
= f
. w
* op
. scheme
-> cell_width
/ 2 ;
456 int left_spaces
= MAX ( 0 , half_spaces
- 7 -( f
. m
- g
. f
> 999 ));
457 int right_spaces
= MAX ( 0 , half_spaces
- 6 -( dtime
> 999 ));
458 static char modechar
[] = { '*' , '!' , '?' };
462 print_border ( TOP
, f
. w
);
464 printf ( "[%03d%c]%*s%s%*s[%03d]" ,
465 /* [ */ f
. m
- g
. f
, modechar
[ g
. s
], /* ] */
466 left_spaces
, "" , get_emoticon (), right_spaces
, "" ,
467 /* [ */ dtime
/* ] */ );
469 print_border ( DIVIDER
, f
. w
);
471 for ( int l
= 0 ; l
< f
. h
; l
++) print_line ( FIELD
)
472 printm ( f
. w
, cell2schema ( l
, _loop
, mode
));
473 print_border ( BOTTOM
, f
. w
);
476 int get_neighbours ( int line
, int col
, int reduced_mode
) {
477 /* counts mines surrounding a square
478 modes: 0=normal; 1=reduced */
482 for ( int l
= MAX ( line
- 1 , 0 ); l
<= MIN ( line
+ 1 , f
. h
- 1 ); l
++) {
483 for ( int c
= MAX ( col
- 1 , 0 ); c
<= MIN ( col
+ 1 , f
. w
- 1 ); c
++) {
484 if (! l
&& ! c
) continue ;
486 count
+= !! f
. c
[ l
][ c
]. m
;
487 count
-= reduced_mode
* f
. c
[ l
][ c
]. f
== FLAG
;
493 struct minecell
** alloc_array ( int lines
, int cols
) {
494 free_field (); /* TODO: just memset */
495 struct minecell
** a
= malloc ( lines
* sizeof ( struct minecell
*));
496 if ( a
== NULL
) return NULL
;
497 for ( int l
= 0 ; l
< lines
; l
++) {
498 a
[ l
] = calloc ( cols
, sizeof ( struct minecell
));
499 if ( a
[ l
] == NULL
) goto unalloc
;
504 for ( int l
= 0 ; l
< lines
; l
++)
509 void free_field ( void ) {
510 if ( f
. c
== NULL
) return ;
511 for ( int l
= 0 ; l
< f
. h
; l
++) {
519 int screen2field_l ( int l
) {
520 return ( l
- LINE_OFFSET
) - 1 ;
522 int screen2field_c ( int c
) {
523 /* this depends on the cell width and only works when it is 1 or 2. */
524 return ( c
- COL_OFFSET
+ 1 - 2 *( CW
% 2 ))/ 2 - CW
/ 2 ;
526 int field2screen_c ( int c
) {
527 return ( CW
* c
+ COL_OFFSET
- ( CW
% 2 ));
529 int clicked_emoticon ( unsigned char * mouse
) {
530 return ( mouse
[ 2 ] == LINE_OFFSET
- 1 && (
531 mouse
[ 1 ] == f
. w
+ COL_OFFSET
||
532 mouse
[ 1 ] == f
. w
+ COL_OFFSET
+ 1 ));
541 int getctrlseq ( unsigned char * buf
) {
544 int offset
= 0x20 ; /* never sends control chars as data */
545 while (( c
= getchar ()) != EOF
) {
549 case ' \033 ' : state
= ESC_SENT
; break ;
555 case '[' : state
= CSI_SENT
; break ;
556 default : return CTRSEQ_INVALID
;
561 case 'A' : return CTRSEQ_CURSOR_UP
;
562 case 'B' : return CTRSEQ_CURSOR_DOWN
;
563 case 'C' : return CTRSEQ_CURSOR_RIGHT
;
564 case 'D' : return CTRSEQ_CURSOR_LEFT
;
565 case 'M' : state
= MOUSE_EVENT
; break ;
566 default : return CTRSEQ_INVALID
;
571 buf
[ 1 ] = getchar () - offset
;
572 buf
[ 2 ] = getchar () - offset
;
575 return CTRSEQ_INVALID
;
581 int getch ( unsigned char * buf
) {
582 /* returns a character, EOF, or constant for an escape/control sequence - NOT
583 compatible with the ncurses implementation of same name */
584 int action
= getctrlseq ( buf
);
588 l
= screen2field_l ( buf
[ 2 ]);
589 c
= screen2field_c ( buf
[ 1 ]);
591 if ( buf
[ 0 ] > 3 ) break ; /* ignore all but left/middle/right/up */
592 int success
= wait_mouse_up ( l
, c
);
594 /* mouse moved while pressed: */
595 if (! success
) return CTRSEQ_INVALID
;
598 case 0 : return CTRSEQ_MOUSE_LEFT
;
599 case 1 : return CTRSEQ_MOUSE_MIDDLE
;
600 case 2 : return CTRSEQ_MOUSE_RIGHT
;
607 int getch_wrapper ( void ) {
608 unsigned char mouse
[ 3 ];
609 int c
= getch ( mouse
);
611 if ( c
== CTRSEQ_MOUSE_LEFT
|| c
== CTRSEQ_MOUSE_RIGHT
) {
612 if ( clicked_emoticon ( mouse
))
613 return WRAPPER_EMOTICON
;
615 if ( screen2field_c ( mouse
[ 1 ]) < 0
616 || screen2field_c ( mouse
[ 1 ]) >= f
. w
617 || screen2field_l ( mouse
[ 2 ]) < 0
618 || screen2field_l ( mouse
[ 2 ]) >= f
. h
)
619 return CTRSEQ_INVALID
;
621 g
. p
[ 0 ] = screen2field_l ( mouse
[ 2 ]);
622 g
. p
[ 1 ] = screen2field_c ( mouse
[ 1 ]);
624 return c
; /* CTRSEQ_MOUSE_LEFT || CTRSEQ_MOUSE_RIGHT */
630 int parse_fieldspec ( char * str
) {
631 /* parses the FIELDSPEC (WxHxM); returns 1 on error */
632 int n
= sscanf ( str
, "%dx%dx%d" , & f
. w
, & f
. h
, & f
. m
);
634 ioctl ( STDOUT_FILENO
, TIOCGWINSZ
, & w
);
636 /* clamp field size to terminal size and mouse maximum: */
637 if ( COL_OFFSET
+ f
. w
* CW
+ COL_OFFSET
> w
. ws_col
)
638 f
. w
= w
. ws_col
/ CW
- ( COL_OFFSET
+ COL_OFFSET
);
639 if ( LINE_OFFSET
+ f
. h
+ LINES_AFTER
> w
. ws_row
)
640 f
. h
= w
. ws_row
- ( LINE_OFFSET
+ LINES_AFTER
);
641 if ( COL_OFFSET
+ f
. w
* CW
> MOUSE_MAX
)
642 f
. w
= MOUSE_MAX
/ CW
- COL_OFFSET
;
643 if ( LINE_OFFSET
+ f
. h
> MOUSE_MAX
)
644 f
. h
= MOUSE_MAX
- LINE_OFFSET
;
647 return 1 ; /* error */
649 if ( f
. w
< 30 ) f
. m
= f
. w
* f
. h
* .15625 ;
650 else f
. m
= f
. w
* f
. h
* .20625 ;
656 void timer_setup ( int enable
) {
657 static struct itimerval tbuf
;
658 tbuf
. it_interval
. tv_sec
= 1 ;
659 tbuf
. it_interval
. tv_usec
= 0 ;
663 tbuf
. it_value
. tv_sec
= 1 ;
664 tbuf
. it_value
. tv_usec
= 0 ;
665 if ( setitimer ( ITIMER_REAL
, & tbuf
, NULL
) == - 1 ) {
670 tbuf
. it_value
. tv_sec
= 0 ;
671 tbuf
. it_value
. tv_usec
= 0 ;
672 if ( setitimer ( ITIMER_REAL
, & tbuf
, NULL
) == - 1 ) {
680 void signal_setup ( void ) {
681 struct sigaction saction
;
683 saction
. sa_handler
= signal_handler
;
684 sigemptyset (& saction
. sa_mask
);
685 saction
. sa_flags
= 0 ;
686 if ( sigaction ( SIGALRM
, & saction
, NULL
) < 0 ) {
691 if ( sigaction ( SIGINT
, & saction
, NULL
) < 0 ) {
697 void signal_handler ( int signum
) {
701 dtime
= difftime ( time ( NULL
), g
. t
);
702 move_ph ( 1 , f
. w
* CW
-( CW
% 2 )- 3 -( dtime
> 999 ));
703 printf ( "[%03d]" , g
. t
? dtime
: 0 );
710 void screen_setup ( int enable
) {
713 printf ( " \033 [s \033 [?47h" ); /* save cursor, alternate screen */
714 printf ( " \033 [H \033 [J" ); /* reset cursor, clear screen */
715 printf ( " \033 [?1000h \033 [?25l" ); /* enable mouse, hide cursor */
716 print ( op
. scheme
-> init_seq
); /* swich charset, if necessary */
718 print ( op
. scheme
-> reset_seq
); /* reset charset, if necessary */
719 printf ( " \033 [?9l \033 [?25h" ); /* disable mouse, show cursor */
720 printf ( " \033 [?47l \033 [u" ); /* primary screen, restore cursor */
725 /* http://users.csc.calpoly.edu/~phatalsk/357/lectures/code/sigalrm.c */
726 void raw_mode ( int enable
) {
727 static struct termios saved_term_mode
;
728 struct termios raw_term_mode
;
731 tcgetattr ( STDIN_FILENO
, & saved_term_mode
);
732 raw_term_mode
= saved_term_mode
;
733 raw_term_mode
. c_lflag
&= ~( ICANON
| ECHO
);
734 raw_term_mode
. c_cc
[ VMIN
] = 1 ;
735 raw_term_mode
. c_cc
[ VTIME
] = 0 ;
736 tcsetattr ( STDIN_FILENO
, TCSAFLUSH
, & raw_term_mode
);
738 tcsetattr ( STDIN_FILENO
, TCSAFLUSH
, & saved_term_mode
);