1 #define _DEFAULT_SOURCE /* for getopt, sigaction, usleep */
18 /* stores a function pointer for every takeable action; called by game loop */
19 int (*action
[NUM_PLACES
][10])(int,int,int) = {
21 /* 1 2 3 4 5 6 7 stk wst fnd*/
22 /* 1 */ { t2f
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, nop
, nop
, t2f
},
23 /* 2 */ { t2t
, t2f
, t2t
, t2t
, t2t
, t2t
, t2t
, nop
, nop
, t2f
},
24 /* 3 */ { t2t
, t2t
, t2f
, t2t
, t2t
, t2t
, t2t
, nop
, nop
, t2f
},
25 /* 4 */ { t2t
, t2t
, t2t
, t2f
, t2t
, t2t
, t2t
, nop
, nop
, t2f
},
26 /* 5 */ { t2t
, t2t
, t2t
, t2t
, t2f
, t2t
, t2t
, nop
, nop
, t2f
},
27 /* 6 */ { t2t
, t2t
, t2t
, t2t
, t2t
, t2f
, t2t
, nop
, nop
, t2f
},
28 /* 7 */ { t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2f
, nop
, nop
, t2f
},
29 /*stk*/ { nop
, nop
, nop
, nop
, nop
, nop
, nop
, nop
, s2w
, nop
},
30 /*wst*/ { w2t
, w2t
, w2t
, w2t
, w2t
, w2t
, w2t
, w2s
, w2f
, w2f
},
31 /*fnd*/ { f2t
, f2t
, f2t
, f2t
, f2t
, f2t
, f2t
, nop
, nop
, nop
},
33 /* 1 2 3 4 5 6 7 8 9 10*/
34 /* 1 */ { t2f
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
},
35 /* 2 */ { t2t
, t2f
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
},
36 /* 3 */ { t2t
, t2t
, t2f
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
},
37 /* 4 */ { t2t
, t2t
, t2t
, t2f
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
},
38 /* 5 */ { t2t
, t2t
, t2t
, t2t
, t2f
, t2t
, t2t
, t2t
, t2t
, t2t
},
39 /* 6 */ { t2t
, t2t
, t2t
, t2t
, t2t
, t2f
, t2t
, t2t
, t2t
, t2t
},
40 /* 7 */ { t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2f
, t2t
, t2t
, t2t
},
41 /* 8 */ { t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2f
, t2t
, t2t
},
42 /* 9 */ { t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2f
, t2t
},
43 /*10 */ { t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2f
},
44 /*stk*/ { s2t
, s2t
, s2t
, s2t
, s2t
, s2t
, s2t
, s2t
, s2t
, s2t
},
45 #elif defined FREECELL
46 /* 1 2 3 4 5 6 7 8 cll fnd*/
47 /* 1 */ { t2f
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2c
, t2f
},
48 /* 2 */ { t2t
, t2f
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2c
, t2f
},
49 /* 3 */ { t2t
, t2t
, t2f
, t2t
, t2t
, t2t
, t2t
, t2t
, t2c
, t2f
},
50 /* 4 */ { t2t
, t2t
, t2t
, t2f
, t2t
, t2t
, t2t
, t2t
, t2c
, t2f
},
51 /* 5 */ { t2t
, t2t
, t2t
, t2t
, t2f
, t2t
, t2t
, t2t
, t2c
, t2f
},
52 /* 6 */ { t2t
, t2t
, t2t
, t2t
, t2t
, t2f
, t2t
, t2t
, t2c
, t2f
},
53 /* 7 */ { t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2f
, t2t
, t2c
, t2f
},
54 /* 8 */ { t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2t
, t2f
, t2c
, t2f
},
55 /*cll*/ { c2t
, c2t
, c2t
, c2t
, c2t
, c2t
, c2t
, c2t
, c2f
, c2f
},
56 /*fnd*/ { f2t
, f2t
, f2t
, f2t
, f2t
, f2t
, f2t
, f2t
, f2c
, nop
},
61 // argv parsing, game loops, cleanup {{{
62 int main(int argc
, char** argv
) {
63 /* opinionated defaults: */
64 op
.s
= &unicode_large_color
;
65 op
.v
= 1; /* enable fake visbell by default */
71 opterr
= 0; /* don't print message on unrecognized option */
72 while ((optget
= getopt (argc
, argv
, "+:hs:vbcmMV")) != -1) {
75 case 's': /* number of suits */
77 case '1': op
.m
= EASY
; break;
78 case '2': op
.m
= MEDIUM
; break;
79 case '4': op
.m
= NORMAL
; break;
83 case 'b': op
.s
= &unicode_large_mono
; break;
84 case 'c': op
.s
= &unicode_large_color
; break;
85 case 'm': op
.s
= &unicode_small_mono
; break; /* "mini, monochrome" */
86 case 'M': op
.s
= &unicode_small_color
; break; /* "mini, colorful" */
87 case 'V': op
.v
= 0; break; /* WARN: experimental; might change */
88 case 'h': default: goto error
;
90 fprintf (stderr
, SHORTHELP LONGHELP KEYHELP
, argv
[0]);
98 signal_handler(SIGWINCH
); /* initialize window size */
104 case GAME_NEW
: goto newgame
;
106 print_table(NO_HI
, NO_HI
);
108 if (getch(NULL
)=='q') return 0;
110 case GAME_QUIT
: return 0;
114 #define is_tableu(where) (where <= TAB_MAX) /* "card games helper functions" */
117 long seed
= time(NULL
);
125 switch (get_cmd(&from
, &to
, &opt
)) {
127 ret
= action
[from
][to
](from
,to
,opt
);
129 if (ret
== ERR
&& is_tableu(from
) && to
== from
)
130 /* t2f failed? try t2c! */
131 ret
= t2c(from
, STOCK
, 0);
134 if (ret
== ERR
&& is_tableu(from
) && is_tableu(to
))
135 /* try again with from/to swapped: */
136 ret
= action
[to
][from
](to
,from
,opt
);
140 case ERR
: visbell(); break;
141 case WON
: return GAME_WON
;
147 case ERR
: visbell(); break;
148 case WON
: return GAME_WON
;
151 case CMD_HINT
: break;//TODO: show a possible (and sensible) move. if possible, involve active cursor
153 f
.h
[0] = getchar(); /* NOTE: not using getch(), so f,esc clears hls */
158 printf("\r/"); fflush(stdout
);
159 fgets(f
.h
, 3, stdin
);
160 if (f
.h
[0] != '\n' && f
.h
[1] != '\n') while(getchar()!='\n'); // note: when we read 1 byte, it is followed by CR, NUL. if we read two bytes (or more), it is only followed by NUL, since there is no space for the CR. TODO: cleanup
164 case CMD_UNDO
: undo_pop(f
.u
); break;
165 case CMD_INVAL
: visbell(); break;
166 case CMD_NEW
: return GAME_NEW
;
167 case CMD_AGAIN
: goto restart
;
168 case CMD_QUIT
: return GAME_QUIT
;
170 printf (KEYHELP
"\nPress any key to continue.");
183 // card games helper functions {{{
184 #define get_suit(card) \
185 ((card-1) % NUM_SUITS)
186 #define get_rank(card) \
187 ((card-1) / NUM_SUITS)
188 #define get_color(card) \
189 ((get_suit(card) ^ get_suit(card)>>1) & 1)
191 int find_top(card_t
* pile
) {
193 for(i
=PILE_SIZE
-1; i
>=0 && !pile
[i
]; i
--);
196 int first_movable(card_t
* pile
) {
197 /* NOTE: in FREECELL this does not take max_move into account! */
199 for (;pile
[i
] && !is_movable(pile
, i
); i
++);
202 int turn_over(card_t
* pile
) {
203 int top
= find_top(pile
);
209 int check_won(void) {
210 for (int pile
= 0; pile
< NUM_DECKS
*NUM_SUITS
; pile
++)
211 if (f
.f
[pile
][NUM_RANKS
-1] == NO_CARD
) return 0;
215 int rank_next (card_t a
, card_t b
) {
216 return get_rank(a
) == get_rank(b
)-1;
218 int color_ok (card_t a
, card_t b
) {
219 #if defined KLONDIKE || defined FREECELL
220 /* color opposite? */
221 return (get_color(a
) != get_color(b
));
224 return (get_suit(a
) == get_suit(b
));
227 int is_consecutive (card_t
* pile
, int pos
) {
228 if (pos
+1 >= PILE_SIZE
) return 1; /* card is last */
229 if (pile
[pos
+1] == NO_CARD
) return 1; /* card is first */
231 /* ranks consecutive? */
232 if (!rank_next(pile
[pos
+1], pile
[pos
])) return 0;
234 if (!color_ok(pile
[pos
+1], pile
[pos
])) return 0;
239 int is_movable(card_t
* pile
, int n
) {
241 return(pile
[n
] > NO_CARD
); /*non-movable cards don't exist in klondike*/
242 #elif defined SPIDER || defined FREECELL
243 int top
= find_top(pile
);
244 for (int i
= top
; i
>= 0; i
--) {
245 if (pile
[i
] <= NO_CARD
) return 0; /*no card or card face down?*/
246 if (!is_consecutive(pile
, i
)) return 0;
247 if (i
== n
) return 1; /* card reached, must be movable */
253 int hls(card_t card
, char* hi
) {
254 /* checks if a card matches a highlight search. a hilight search might be a rank, a suit, a color or both. */
255 // TODO: now we use rankletters in keyboard input and here. that's ugly.
256 int ok
= 0; /* prevent an invalid highlight from matching everything */
260 case 'a': case 'A': if (get_rank(card
)!=RANK_A
) return 0; ok
++; break;
262 case 'x': case 'X': if (get_rank(card
)!=RANK_X
) return 0; ok
++; break;
263 case 'j': case 'J': if (get_rank(card
)!=RANK_J
) return 0; ok
++; break;
264 case 'q': case 'Q': if (get_rank(card
)!=RANK_Q
) return 0; ok
++; break;
265 case 'k': case 'K': if (get_rank(card
)!=RANK_K
) return 0; ok
++; break;
268 case 'c': case 'C': if (get_suit(card
)!=CLUBS
) return 0; ok
++; break;
269 case 'd': case 'D': if (get_suit(card
)!=DIAMONDS
)return 0;ok
++; break;
270 case 'h': case 'H': if (get_suit(card
)!=HEARTS
) return 0; ok
++; break;
271 case 's': case 'S': if (get_suit(card
)!=SPADES
) return 0; ok
++; break;
274 case 'r': case 'R': if (get_color(card
)!=RED
) return 0; ok
++; break;
275 case 'b': case 'B': if (get_color(card
)!=BLK
) return 0; ok
++; break;
279 if (*hi
< '1' || *hi
> '9') continue;
280 if (get_rank(card
) != *hi
- '1') return 0;
289 // takeable actions {{{
291 card_t
stack_take(void) { /*NOTE: assert(f.w >= 0) */
292 card_t card
= f
.s
[f
.w
];
293 /* move stack one over, so there are no gaps in it: */
294 for (int i
= f
.w
; i
< f
.z
-1; i
++)
297 f
.w
--; /* make previous card visible again */
300 int t2f(int from
, int to
, int opt
) { /* tableu to foundation */
301 (void) to
; (void) opt
; /* don't need */
302 int top_from
= find_top(f
.t
[from
]);
303 to
= get_suit(f
.t
[from
][top_from
]);
304 int top_to
= find_top(f
.f
[to
]);
305 if ((top_to
< 0 && get_rank(f
.t
[from
][top_from
]) == RANK_A
)
306 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.t
[from
][top_from
]))) {
307 f
.f
[to
][top_to
+1] = f
.t
[from
][top_from
];
308 f
.t
[from
][top_from
] = NO_CARD
;
309 undo_push(from
, FOUNDATION
, to
,
310 turn_over(f
.t
[from
]));
311 if (check_won()) return WON
;
315 int w2f(int from
, int to
, int opt
) { /* waste to foundation */
316 (void) from
; (void) to
; (void) opt
; /* don't need */
317 if (f
.w
< 0) return ERR
;
318 to
= get_suit(f
.s
[f
.w
]);
319 int top_to
= find_top(f
.f
[to
]);
320 if ((top_to
< 0 && get_rank(f
.s
[f
.w
]) == RANK_A
)
321 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
], f
.s
[f
.w
]))) {
322 undo_push(WASTE
, FOUNDATION
, f
.w
| to
<<16, 0);//ugly encoding :|
323 f
.f
[to
][top_to
+1] = stack_take();
324 if (check_won()) return WON
;
329 int s2w(int from
, int to
, int opt
) { /* stock to waste */
330 (void) from
; (void) to
; (void) opt
; /* don't need */
331 if (f
.z
== 0) return ERR
;
333 if (f
.w
== f
.z
) f
.w
= -1;
336 int w2s(int from
, int to
, int opt
) { /* waste to stock (undo stock to waste) */
337 (void) from
; (void) to
; (void) opt
; /* don't need */
338 if (f
.z
== 0) return ERR
;
340 if (f
.w
< -1) f
.w
= f
.z
-1;
343 int f2t(int from
, int to
, int opt
) { /* foundation to tableu */
344 (void) from
; /* don't need */
345 int top_to
= find_top(f
.t
[to
]);
347 int top_from
= find_top(f
.f
[from
]);
349 if (color_ok(f
.t
[to
][top_to
], f
.f
[from
][top_from
])
350 && (rank_next(f
.f
[from
][top_from
], f
.t
[to
][top_to
]))) {
351 f
.t
[to
][top_to
+1] = f
.f
[from
][top_from
];
352 f
.f
[from
][top_from
] = NO_CARD
;
353 undo_push(FOUNDATION
, to
, from
, 0);
357 int w2t(int from
, int to
, int opt
) { /* waste to tableu */
358 (void) from
; (void) opt
; /* don't need */
359 if (f
.w
< 0) return ERR
;
360 int top_to
= find_top(f
.t
[to
]);
361 if ((color_ok(f
.t
[to
][top_to
], f
.s
[f
.w
])
362 && (rank_next(f
.s
[f
.w
], f
.t
[to
][top_to
])))
363 || (top_to
< 0 && get_rank(f
.s
[f
.w
]) == RANK_K
)) {
364 undo_push(WASTE
, to
, f
.w
, 0);
365 f
.t
[to
][top_to
+1] = stack_take();
369 int t2t(int from
, int to
, int opt
) { /* tableu to tableu */
370 (void) opt
; /* don't need */
371 int top_to
= find_top(f
.t
[to
]);
372 int top_from
= find_top(f
.t
[from
]);
373 for (int i
= top_from
; i
>=0; i
--) {
374 if ((color_ok(f
.t
[to
][top_to
], f
.t
[from
][i
])
375 && (rank_next(f
.t
[from
][i
], f
.t
[to
][top_to
]))
376 && f
.t
[from
][i
] > NO_CARD
) /* card face up? */
377 || (top_to
< 0 && get_rank(f
.t
[from
][i
]) == RANK_K
)) {
378 /* move cards [i..top_from] to their destination */
380 for (;i
<= top_from
; i
++) {
382 f
.t
[to
][top_to
] = f
.t
[from
][i
];
383 f
.t
[from
][i
] = NO_CARD
;
386 undo_push(from
, to
, count
,
387 turn_over(f
.t
[from
]));
391 return ERR
; /* no such move possible */
394 int remove_if_complete (int pileno
) { //cleanup!
395 card_t
* pile
= f
.t
[pileno
];
396 /* test if K...A complete; move to foundation if so */
397 int top_from
= find_top(pile
);
398 if (get_rank(pile
[top_from
]) != RANK_A
) return 0;
399 for (int i
= top_from
; i
>=0; i
--) {
400 if (!is_consecutive (pile
, i
)) return 0;
401 if (i
+RANK_K
== top_from
/* if ace to king: remove it */
402 && get_rank(pile
[top_from
-RANK_K
]) == RANK_K
) {
403 for(int i
=top_from
, j
=0; i
>top_from
-NUM_RANKS
; i
--,j
++){
404 f
.f
[f
.w
][j
] = pile
[i
];
407 undo_push(pileno
, FOUNDATION
, f
.w
,
416 int t2t(int from
, int to
, int opt
) { //in dire need of cleanup
417 int top_from
= find_top(f
.t
[from
]);
418 int top_to
= find_top(f
.t
[to
]);
419 int empty_to
= (top_to
< 0)? opt
: -1; /* empty pile? */
421 for (int i
= top_from
; i
>= 0; i
--) {
422 if (!is_consecutive(f
.t
[from
], i
)) break;
424 /* is consecutive OR to empty pile and rank ok? */
425 if (rank_next(f
.t
[from
][i
], f
.t
[to
][top_to
])
426 || (empty_to
>= RANK_A
&& get_rank(f
.t
[from
][i
]) == empty_to
)) {
428 for (;i
<= top_from
; i
++) {
430 f
.t
[to
][top_to
] = f
.t
[from
][i
];
431 f
.t
[from
][i
] = NO_CARD
;
434 undo_push(from
, to
, count
,
435 turn_over(f
.t
[from
]));
436 remove_if_complete(to
);
437 if (check_won()) return WON
;
442 return ERR
; /* no such move possible */
444 int s2t(int from
, int to
, int opt
) {
445 (void) from
; (void) to
; (void) opt
; /* don't need */
446 if (f
.z
<= 0) return ERR
; /* stack out of cards */
447 for (int pile
= 0; pile
< NUM_PILES
; pile
++)
448 if (f
.t
[pile
][0]==NO_CARD
) return ERR
; /*no piles may be empty*/
450 undo_push(STOCK
, TABLEU
, 1, 0); /* NOTE: before remove_if_complete()! */
451 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
452 f
.t
[pile
][find_top(f
.t
[pile
])+1] = f
.s
[--f
.z
];
453 remove_if_complete(pile
);
454 if (check_won()) return WON
;
458 int t2f(int from
, int to
, int opt
) {
459 (void) to
; (void) opt
; /* don't need */
460 /* manually retrigger remove_if_complete() (e.g. after undo_pop) */
461 return remove_if_complete(from
)?OK
:ERR
;
463 #elif defined FREECELL
464 int max_move(int from
, int to
) {
465 /* returns the maximum number of cards that can be moved */
466 /* see also: https://boardgames.stackexchange.com/a/45157/26498 */
467 int free_tabs
= 0, free_cells
= 0;
468 for (int i
= 0; i
< NUM_PILES
; i
++) free_tabs
+= f
.t
[i
][0] == NO_CARD
;
469 for (int i
= 0; i
< NUM_CELLS
; i
++) free_cells
+= f
.s
[i
] == NO_CARD
;
471 /* don't count the tableau we are moving to: */
472 if (to
>= 0 && f
.t
[to
][0] == NO_CARD
) free_tabs
--;
474 /* theoretic maximum is limited by the number of cards on the pile */
475 int max_theory
= (1<<free_tabs
) * (free_cells
+ 1);
476 int max_effective
= 1 + find_top(f
.t
[from
]) - first_movable(f
.t
[from
]);
477 return max_effective
< max_theory
? max_effective
: max_theory
;
479 //TODO FREECELL: auto move to tableu after each move (not all cards possible, only when it is the smallest rank still on the board)
480 int t2t(int from
, int to
, int opt
) {
481 int top_to
= find_top(f
.t
[to
]);
482 int top_from
= find_top(f
.t
[from
]);
483 int cards
= max_move(from
, to
);
484 if (top_to
< 0) { /* moving to empty pile? */
486 return ERR
; /* cannot execute move */
487 cards
= opt
; /* user wants to move n cards*/
490 for (int i
= top_from
; i
>=0; i
--) {
491 if (cards
-->0/*enough space and not more attempted than wanted*/
492 && ((top_to
>= 0 /* if destn. not empty: check rank/color */
493 && (color_ok(f
.t
[to
][top_to
], f
.t
[from
][i
])
494 && (rank_next(f
.t
[from
][i
], f
.t
[to
][top_to
]))))
495 || (top_to
< 0 && !cards
))) {/*if dest empty and right # cards*/
496 /* move cards [i..top_from] to their destination */
498 for (;i
<= top_from
; i
++) {
500 f
.t
[to
][top_to
] = f
.t
[from
][i
];
501 f
.t
[from
][i
] = NO_CARD
;
504 undo_push(from
, to
, count
, 0);
508 return ERR
; /* no such move possible */
510 int t2f(int from
, int to
, int opt
) { /* 1:1 copy from KLONDIKE */
511 (void) to
; (void) opt
; /* don't need */
512 int top_from
= find_top(f
.t
[from
]);
513 to
= get_suit(f
.t
[from
][top_from
]);
514 int top_to
= find_top(f
.f
[to
]);
515 if ((top_to
< 0 && get_rank(f
.t
[from
][top_from
]) == RANK_A
)
516 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.t
[from
][top_from
]))) {
517 f
.f
[to
][top_to
+1] = f
.t
[from
][top_from
];
518 f
.t
[from
][top_from
] = NO_CARD
;
519 undo_push(from
, FOUNDATION
, to
, 0);
520 if (check_won()) return WON
;
524 int f2t(int from
, int to
, int opt
) {
525 (void) from
; /* don't need */
526 int top_to
= find_top(f
.t
[to
]);
528 int top_from
= find_top(f
.f
[from
]);
530 if (top_to
< 0 /* empty tableu? */
531 ||(color_ok(f
.t
[to
][top_to
], f
.f
[from
][top_from
])
532 && (rank_next(f
.f
[from
][top_from
], f
.t
[to
][top_to
])))) {
533 f
.t
[to
][top_to
+1] = f
.f
[from
][top_from
];
534 f
.f
[from
][top_from
] = NO_CARD
;
535 undo_push(FOUNDATION
, to
, from
, 0);
539 int t2c(int from
, int to
, int opt
) {
540 (void) to
; (void) opt
; /* don't need */
541 /* is a cell free? */
542 if (f
.w
== (1<<NUM_CELLS
)-1)
544 for (to
= 0; to
< NUM_CELLS
; to
++)
545 if (!(f
.w
>>to
&1)) break;
547 int top_from
= find_top(f
.t
[from
]);
548 f
.s
[to
] = f
.t
[from
][top_from
];
549 f
.t
[from
][top_from
] = NO_CARD
;
550 f
.w
|= 1<<to
; /* mark cell as occupied */
551 undo_push(from
, STOCK
, to
, 0);
555 int c2t(int from
, int to
, int opt
) {
556 (void) from
; /* don't need */
557 int top_to
= find_top(f
.t
[to
]);
560 if (top_to
< 0 /* empty tableu? */
561 ||(color_ok(f
.t
[to
][top_to
], f
.s
[from
])
562 && (rank_next(f
.s
[from
], f
.t
[to
][top_to
])))) {
563 f
.t
[to
][top_to
+1] = f
.s
[from
];
565 f
.w
&= ~(1<<from
); /* mark cell as free */
566 undo_push(STOCK
, to
, from
, 0);
571 int c2f(int from
, int to
, int opt
) {
572 (void) from
; (void) to
; /* don't need */
574 if (f
.s
[from
] == NO_CARD
) return ERR
;
575 to
= get_suit(f
.s
[from
]);
576 int top_to
= find_top(f
.f
[to
]);
577 if ((top_to
< 0 && get_rank(f
.s
[from
]) == RANK_A
)
578 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.s
[from
]))) {
579 f
.f
[to
][top_to
+1] = f
.s
[from
];
581 f
.w
&= ~(1<<from
); /* mark cell as free */
582 undo_push(STOCK
, FOUNDATION
, from
| to
<<16, 0);
583 if (check_won()) return WON
;
587 int f2c(int from
, int to
, int opt
) {
588 (void) from
; (void) to
; /* don't need */
589 /* is a cell free? */
590 if (f
.w
== (1<<NUM_CELLS
)-1)
592 for (to
= 0; to
< NUM_CELLS
; to
++)
593 if (!(f
.w
>>to
&1)) break;
596 int top_from
= find_top(f
.f
[from
]);
597 f
.s
[to
] = f
.f
[from
][top_from
];
598 f
.f
[from
][top_from
] = NO_CARD
;
599 f
.w
|= 1<<to
; /* mark cell as occupied */
600 undo_push(FOUNDATION
, STOCK
, from
| to
<<16, 0);
604 #define w2f c2f /* for join()'s "to foundation" */
607 //TODO: generalize prediction engine for CMD_HINT
609 #define would_complete(pile) 0
611 #define would_complete(pile) \
612 (get_rank(f.t[pile][r[pile].top]) == RANK_A \
613 && get_rank(f.t[to][bottom_to]) == RANK_K)
614 #elif defined FREECELL
615 #define would_complete(pile) 0
617 #define would_turn(pile) \
618 (f.t[pile][r[pile].pos-1] < 0)
619 #define would_empty(pile) \
623 int top_to
= find_top(f
.t
[to
]);
625 int bottom_to
= first_movable(f
.t
[to
]);
628 #if defined KLONDIKE || defined FREECELL
629 if (to
== WASTE
|| to
== STOCK
) return ERR
; /*why would you do that!?*/
631 if (to
== FOUNDATION
) {
633 for (int i
= 0; i
< NUM_PILES
+NUM_CELLS
; i
++)
634 switch (is_tableu(i
)?t2f(i
, FOUNDATION
, 0)
635 :w2f(STOCK
,FOUNDATION
,i
-NUM_PILES
)){
636 case WON
: return WON
;
637 case OK
: status
= OK
;
645 if (top_to
< 0) { /* move a king to empty pile: */
646 for (int i
= 0; i
<= TAB_MAX
; i
++) {
647 if (f
.t
[i
][0] < 0) /* i.e. would turn? */
648 if (t2t(i
, to
, 0) == OK
) return OK
;
650 return w2t(WASTE
, to
, 0);
652 #elif defined FREECELL
653 if (top_to
< 0) { /* move longest cascade to empty tableu: */ //TODO FREECELL:
656 for (int i
= 0; i
<= TAB_MAX
; i
++) {
657 int m
= max_move(i
, to
);
658 /*longest cascade that won't uncover another free pile*/
659 //TODO: don't rip apart cascades
660 if (m
>= length
&& m
<= find_top(f
.t
[i
]))
661 length
= m
, longest
= i
;
663 if (longest
< 0) return ERR
;
664 return t2t(longest
, to
, length
);
669 int ok
:1; /* card to move in pile? */
670 int above
; /* number of movable cards above */
671 int below
; /* number of cards below ours */
672 int pos
; /* where the card to move is in the pile */
673 int top
; /* find_top() */
674 } r
[NUM_PILES
] = {{0}};
675 int complete
= 0;/* SPIDER: true if any pile would complete a stack */
676 int turn
= 0; /* SPIDER: true if any pile would turn_over */
677 int empty
= 0; /* true if any pile would become empty */
679 /* 1. rate each pile: */
682 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
683 if (pile
== to
) continue;
684 int top
= find_top(f
.t
[pile
]);
685 int bottom
= first_movable(f
.t
[pile
]);
686 r
[pile
].pos
= bottom
; /* need for would_empty */
688 if (top
< 0) continue; /* no cards to move */
689 if (would_empty(pile
)) continue; /* doesn't help */
692 r
[pile
].above
= 0; /* always take as many as possible */
693 r
[pile
].below
= top
- bottom
;
695 complete
|= would_complete(pile
); /* never happens */
696 turn
|= would_turn(pile
);
697 empty
|= would_empty(pile
);
701 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
702 r
[pile
].top
= r
[pile
].pos
= find_top(f
.t
[pile
]);
703 /* backtrack until we find a compatible-to-'to'-pile card: */
705 int maxmove
= max_move(pile
, -1);
707 while (r
[pile
].pos
>= 0 && is_movable(f
.t
[pile
], r
[pile
].pos
)) {
708 int rankdiff
= get_rank(f
.t
[pile
][r
[pile
].pos
])
709 - get_rank(f
.t
[to
][top_to
]);
710 if (rankdiff
>= 0) break; /* past our card */
712 if (!maxmove
--) break; /* can't move this many cards */
714 if (rankdiff
== -1 && /* rank matches */
715 color_ok(f
.t
[pile
][r
[pile
].pos
], f
.t
[to
][top_to
])
718 complete
|= would_complete(pile
);
719 turn
|= would_turn(pile
);
720 empty
|= would_empty(pile
);
721 for (int i
= r
[pile
].pos
; i
>= 0; i
--)
722 if (is_movable(f
.t
[pile
], i
-1))
732 /* 2. find optimal pile: (optimized for spider) */
733 //todo: in spider, prefer longest piles if above==0 (faster completions)
735 for (int pile
= 0, above
= 99, below
= 99; pile
< NUM_PILES
; pile
++) {
736 if (!r
[pile
].ok
) continue;
737 /* don't bother if another pile would be better: prefer ... */
738 /* ... to complete a stack: */
739 if (!would_complete(pile
) && complete
) continue;
740 /* ... emptying piles: */
741 if (!would_empty(pile
) && empty
&& !complete
) continue;
742 /* ... to turn_over: */
743 if (!would_turn(pile
) && turn
&& !complete
&& !empty
) continue;
744 /* ... not to rip apart too many cards: */
745 if (r
[pile
].above
> above
) continue;
746 /* if tied, prefer ... */
747 if (r
[pile
].above
== above
748 /* ... larger pile if destination is empty */
749 && (top_to
< 0? r
[pile
].below
< below
750 /* ... shorter pile otherwise */
751 : r
[pile
].below
> below
))
755 above
= r
[pile
].above
;
756 below
= r
[pile
].below
;
759 /* 3. move cards over and return: */
761 /* prefer waste if it wouldn't turn_over: */
762 /* NOTE: does not attempt to take from froundation */
763 if (!empty
&& !turn
&& w2t(WASTE
, to
, 0) == OK
)
765 if (from
< 0) /* nothing found */
767 return t2t(from
, to
, 0);
769 if (from
< 0) /* nothing found */
771 int bottom
= first_movable(f
.t
[from
]);
772 return t2t(from
, to
, get_rank(f
.t
[from
][bottom
]));
773 #elif defined FREECELL
774 //TODO: if would rip apart, try freecells first (instead after)
775 if (from
< 0) /* no tableu move found */ {
776 /* try all free cells before giving up: */
777 for (int i
= 0; i
< NUM_CELLS
; i
++)
778 if (c2t(STOCK
, to
, i
) == OK
) return OK
;
781 return t2t(from
, to
, 0);
786 #undef would_complete
787 int nop(int from
, int to
, int opt
) { (void)from
;(void)to
;(void)opt
;return ERR
; }
790 // keyboard input handling {{{
791 // cursor functions{{{
793 void cursor_left (struct cursor
* cursor
) {
795 if (is_tableu(cursor
->pile
)) {
796 if (cursor
->pile
> 0) cursor
->pile
--;
798 } else { /* stock/waste/foundation*/
799 switch (cursor
->pile
) {
800 case WASTE
: cursor
->pile
= STOCK
; cursor
->opt
= 0; break;
802 if (cursor
->opt
<= 0)
803 cursor
->pile
= WASTE
;
809 void cursor_down (struct cursor
* cursor
) {
811 if (!is_tableu(cursor
->pile
)) {
812 switch (cursor
->pile
) {
813 case STOCK
: cursor
->pile
= TAB_1
; break;
814 case WASTE
: cursor
->pile
= TAB_2
; break;
816 cursor
->pile
= TAB_4
+ cursor
->opt
;
821 void cursor_up (struct cursor
* cursor
) {
823 if (is_tableu(cursor
->pile
)) {
824 switch (cursor
->pile
) { //ugly :|
825 case TAB_1
: cursor
->pile
= STOCK
; break;
826 case TAB_2
: cursor
->pile
= WASTE
; break;
827 case TAB_3
: cursor
->pile
= WASTE
; break;
828 case TAB_4
: case TAB_5
: case TAB_6
: case TAB_7
:
829 cursor
->opt
=cursor
->pile
-TAB_4
;
830 cursor
->pile
= FOUNDATION
;
835 void cursor_right (struct cursor
* cursor
) {
837 if (is_tableu(cursor
->pile
)) {
838 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
841 switch (cursor
->pile
) {
842 case STOCK
: cursor
->pile
= WASTE
; break;
843 case WASTE
: cursor
->pile
= FOUNDATION
;cursor
->opt
= 0; break;
845 if (cursor
->opt
< NUM_SUITS
-1)
851 /*NOTE: one can't highlight the stock due to me being too lazy to implement it*/
852 void cursor_left (struct cursor
* cursor
) {
854 if (cursor
->pile
> 0) cursor
->pile
--;
857 void cursor_down (struct cursor
* cursor
) {
859 int first
= first_movable(f
.t
[cursor
->pile
]);
860 int top
= find_top(f
.t
[cursor
->pile
]);
861 if (first
+ cursor
->opt
< top
)
864 void cursor_up (struct cursor
* cursor
) {
866 if (cursor
->opt
> 0) cursor
->opt
--;
868 void cursor_right (struct cursor
* cursor
) {
870 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
873 #elif defined FREECELL
874 void cursor_left (struct cursor
* cursor
) {
876 if (is_tableu(cursor
->pile
)) {
877 if (cursor
->pile
> 0) cursor
->pile
--;
879 } else { /* cells/foundation*/
880 switch (cursor
->pile
) {
886 if (cursor
->opt
<= 0) {
887 cursor
->pile
= STOCK
;
895 void cursor_down (struct cursor
* cursor
) {
897 if (is_tableu(cursor
->pile
)) {
898 if (cursor
->opt
< max_move(cursor
->pile
, -1)-1)
901 cursor
->pile
= cursor
->opt
+NUM_CELLS
*(cursor
->pile
==FOUNDATION
);
905 void cursor_up (struct cursor
* cursor
) {
907 if (is_tableu(cursor
->pile
)) {
908 if (cursor
->opt
> 0) {
911 switch (cursor
->pile
) {
912 case TAB_1
: case TAB_2
: case TAB_3
: case TAB_4
:
913 cursor
->opt
= cursor
->pile
; /*assumes TAB_1==0*/
914 cursor
->pile
= STOCK
;
916 case TAB_5
: case TAB_6
: case TAB_7
: case TAB_8
:
917 cursor
->opt
= cursor
->pile
- NUM_CELLS
;
918 cursor
->pile
= FOUNDATION
;
923 void cursor_right (struct cursor
* cursor
) {
925 if (is_tableu(cursor
->pile
)) {
926 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
929 switch (cursor
->pile
) {
931 if (cursor
->opt
< NUM_SUITS
-1) {
934 cursor
->pile
= FOUNDATION
;
938 if (cursor
->opt
< NUM_SUITS
-1)
944 void cursor_to (struct cursor
* cursor
, int pile
) {
949 int set_mouse(int pile
, int* main
, int* opt
) {
950 //TODO: this should set cursor.opt, so card selector choice dialog does not trigger!
952 if (pile
< 0) return 1;
955 if (pile
>= FOUNDATION
)//TODO: check upper bound!
957 *opt
= pile
- FOUNDATION
;
960 #elif defined FREECELL
961 if (pile
> TAB_MAX
) {
962 *main
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
963 *opt
= (pile
-STOCK
) % 4;
969 int get_cmd (int* from
, int* to
, int* opt
) {
971 unsigned char mouse
[6] = {0}; /* must clear [3]! */
972 struct cursor inactive
= {-1,-1};
973 static struct cursor active
= {0,0};
974 if (is_tableu(active
.pile
))
978 from_l
: print_table(&active
, &inactive
);
982 /* direct addressing: */
983 case '1': *from
= TAB_1
; break;
984 case '2': *from
= TAB_2
; break;
985 case '3': *from
= TAB_3
; break;
986 case '4': *from
= TAB_4
; break;
987 case '5': *from
= TAB_5
; break;
988 case '6': *from
= TAB_6
; break;
989 case '7': *from
= TAB_7
; break;
991 case '8': *from
= TAB_8
; break;
992 case '9': *from
= TAB_9
; break;
993 case '0': *from
= TAB_10
;break;
994 #elif defined FREECELL
995 case '8': *from
= TAB_8
; break;
996 case '9': *from
= STOCK
; break;
997 case '0': *from
= FOUNDATION
; break;
998 #elif defined KLONDIKE
999 case '9': *from
= WASTE
; break;
1000 case '0': *from
= FOUNDATION
; break;
1001 case '8': /* fallthrough */
1004 case '\n': *from
= STOCK
; break;
1006 /* cursor keys addressing: */
1008 case 'h': cursor_left (&active
); goto from_l
;
1010 case 'j': cursor_down (&active
); goto from_l
;
1012 case 'k': cursor_up (&active
); goto from_l
;
1014 case 'l': cursor_right(&active
); goto from_l
;
1016 case 'H': cursor_to(&active
,TAB_1
); goto from_l
; /* leftmost tableu */
1018 case 'L': cursor_to(&active
,TAB_MAX
);goto from_l
; /* rigthmost tableu */
1020 case 'M': cursor_to(&active
,TAB_MAX
/2); goto from_l
; /* center tableu */
1021 case ' ': /* continue with second cursor */
1022 *from
= active
.pile
;
1024 *opt
= active
.opt
; /* when FOUNDATION */
1028 /* mouse addressing: */
1029 case MOUSE_MIDDLE
: return CMD_NONE
;
1031 if (set_mouse(term2pile(mouse
), to
, opt
))
1035 if (set_mouse(term2pile(mouse
), from
, opt
))
1037 if (!is_tableu(*from
))
1038 inactive
.opt
= *opt
; /* prevents card selector dialog */
1043 fprintf (stderr
, ":");
1044 raw_mode(0); /* turn on echo */
1045 fgets (buf
, 256, stdin
);
1048 case 'q': return CMD_QUIT
;
1049 case 'n': return CMD_NEW
;
1050 case 'r': return CMD_AGAIN
;
1051 case 'h': return CMD_HELP
;
1052 default: return CMD_INVAL
;
1057 case 'K': /* fallthrough */
1058 case '?': return CMD_HINT
;
1059 case 'f': return CMD_FIND
;
1060 case '/': return CMD_SEARCH
;
1061 case 'u': return CMD_UNDO
;
1062 case 002: return CMD_NONE
; /* sent by SIGWINCH */
1063 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1064 default: return CMD_INVAL
;
1066 inactive
.pile
= *from
; /* for direct addressing highlighting */
1067 if (is_tableu(*from
) && f
.t
[*from
][0] == NO_CARD
) return CMD_INVAL
;
1068 //TODO: freecell: if from==stock && stock[x] == empty: return inval
1071 if (*from
== STOCK
) {
1078 to_l
: print_table(&active
, &inactive
);
1083 case 'h': cursor_left (&active
); goto to_l
;
1085 case 'j': cursor_down (&active
); goto to_l
;
1087 case 'k': cursor_up (&active
); goto to_l
;
1089 case 'l': cursor_right(&active
); goto to_l
;
1091 case 'H': cursor_to(&active
,TAB_1
); goto to_l
;
1093 case 'L': cursor_to(&active
,TAB_MAX
); goto to_l
;
1095 case 'M': cursor_to(&active
,TAB_MAX
/2); goto to_l
;
1096 case 'J': /* fallthrough; just join selected pile */
1099 break; /* continues with the foundation/empty tableu check */
1101 case MOUSE_RIGHT
: return CMD_NONE
;
1103 if (set_mouse(term2pile(mouse
), to
, opt
))
1106 case 'K': /* fallthrough */
1107 case '?': return CMD_HINT
;
1108 case 'f': return CMD_FIND
; // XXX: will cancel from-card
1109 case '/': return CMD_SEARCH
; //ditto.
1110 case 'u': return CMD_NONE
; /* cancel selection */
1111 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1113 if (t
< '0' || t
> '9') return CMD_INVAL
;
1117 #elif defined SPIDER
1119 #elif defined FREECELL
1129 /* direct addressing post-processing stage:
1130 because foundations/freecells share the same key (and you can't select
1131 partial piles) there are sometimes ambiguous situations where it isn't
1132 clear from which pile (or how many cards) to take. the code below will
1133 only ask the user if there are at least two possible moves and
1134 automatically choose otherwise. */
1136 /* if it was selected with a cursor, it's obvious: */
1137 if (inactive
.opt
>= 0) {
1138 if (is_tableu(*from
)) {
1139 /* NOTE: max_move same as in cursor_down() */
1140 *opt
= max_move(*from
, -1) - inactive
.opt
;
1142 *opt
= inactive
.opt
;
1144 /* moving from tableu to empty tableu? */
1145 } else if(is_tableu(*from
) && is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
){
1146 int top
= find_top(f
.t
[*from
]);
1147 int max
= max_move(*from
, *to
);
1149 if (top
< 0) return CMD_INVAL
;
1150 if (max
== 1) { /* only 1 movable? */
1151 return *opt
= 1, CMD_MOVE
;
1152 } else { /* only ask the user if it's unclear: */
1153 int bottom
= top
- (max
-1);
1154 printf ("\rup to ([a23456789xjqk] or space/return): ");
1157 case ' ': rank
= get_rank(f
.t
[*from
][top
]); break;
1158 case'\n': rank
= get_rank(f
.t
[*from
][bottom
]); break;
1159 case 'a': case 'A': rank
= RANK_A
; break;
1160 case '0': /* fallthrough */
1161 case 'x': case 'X': rank
= RANK_X
; break;
1162 case 'j': case 'J': rank
= RANK_J
; break;
1163 case 'q': case 'Q': rank
= RANK_Q
; break;
1164 case 'k': case 'K': rank
= RANK_K
; break;
1165 default: rank
-= '1';
1167 if (rank
< RANK_A
|| rank
> RANK_K
) return CMD_INVAL
;
1169 for (int i
= 0; max
--; i
++)
1170 if (get_rank(f
.t
[*from
][top
-i
]) == rank
)
1171 return *opt
= 1+i
, CMD_MOVE
;
1175 /* `opt` is the number of cards to move */
1176 /* moving between stock/foundation? */
1177 } else if (*from
== FOUNDATION
&& *to
== FOUNDATION
) {
1178 return CMD_INVAL
; /* nonsensical */
1179 } else if (*from
== FOUNDATION
&& *to
== STOCK
) {
1180 if (f
.w
== (1<<NUM_CELLS
)-1) return CMD_INVAL
; /*no free cells*/
1181 int ok_foundation
; /* find compatible (non-empty) foundations:*/
1182 int used_fs
=0; for (int i
= 0; i
< NUM_SUITS
; i
++)
1183 if (!!f
.f
[i
][0]) ok_foundation
= i
, used_fs
++;
1185 if (used_fs
== 0) return CMD_INVAL
; /* nowhere to take from */
1186 if (used_fs
== 1) { /* take from the only one */
1187 return *opt
= ok_foundation
, CMD_MOVE
;
1188 } else { /* ask user */
1189 printf ("take from (1-4): "); fflush (stdout
);
1190 *opt
= getch(NULL
) - '1';
1191 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1193 /* `opt` is the foundation index (0..3) */
1194 } else if (*from
== STOCK
) { /* cell -> foundation/tableu */
1195 if (!f
.w
) return CMD_INVAL
; /* no cell to take from */
1196 int ok_cell
; /* find compatible (non-empty) cells: */
1197 int tab
= is_tableu(*to
);
1198 int used_cs
=0; for (int i
= 0; i
< NUM_CELLS
; i
++) {
1199 card_t
* pile
= (tab
?f
.t
[*to
]:f
.f
[get_suit(f
.s
[i
])]);
1200 int top_to
= find_top(pile
);
1201 if (tab
? /* to tableu? */
1202 ((top_to
<0 && f
.s
[i
] > NO_CARD
)
1203 ||(top_to
>=0 && rank_next(f
.s
[i
], pile
[top_to
])
1204 && color_ok(f
.s
[i
], pile
[top_to
])))
1205 : /* to foundation? */
1206 ((top_to
<0 && get_rank(f
.s
[i
]) == RANK_A
)
1207 ||(top_to
>=0 && rank_next(pile
[top_to
],f
.s
[i
])))
1209 ok_cell
= i
, used_cs
++;
1212 if (used_cs
== 0) return CMD_INVAL
; /* nowhere to take from */
1213 if (used_cs
== 1) { /* take from the only one */
1214 return *opt
= ok_cell
, CMD_MOVE
;
1215 } else { /* ask user */
1216 printf ("take from (1-4): "); fflush (stdout
);
1217 *opt
= getch(NULL
) - '1';
1218 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1220 /* `opt` is the cell index (0..3) */
1223 //TODO: mouse-friendly "up to?" dialog
1224 #if defined KLONDIKE || defined FREECELL
1225 if (*from
== FOUNDATION
) {
1226 if (inactive
.opt
>= 0) {
1227 *opt
= inactive
.opt
;
1230 int top
= find_top(f
.t
[*to
]);
1231 if (top
< 0) return CMD_INVAL
;
1232 int color
= get_color(f
.t
[*to
][top
]);
1233 int choice_1
= 1-color
; /* selects piles of */
1234 int choice_2
= 2+color
; /* the opposite color */
1235 int top_c1
= find_top(f
.f
[choice_1
]);
1236 int top_c2
= find_top(f
.f
[choice_2
]);
1238 switch ((rank_next(f
.f
[choice_1
][top_c1
], f
.t
[*to
][top
])
1239 && top_c1
>= 0 ) << 0
1240 |(rank_next(f
.f
[choice_2
][top_c2
], f
.t
[*to
][top
])
1241 && top_c2
>= 0 ) << 1) {
1242 case ( 1<<0): *opt
= choice_1
; break; /* choice_1 only */
1243 case (1<<1 ): *opt
= choice_2
; break; /* choice_2 only */
1244 case (1<<1 | 1<<0): /* both, ask user which to pick from */
1245 printf ("take from (1-4): "); fflush (stdout
);
1246 *opt
= getch(NULL
) - '1';
1247 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1249 default: return CMD_INVAL
; /* none matched */
1251 /* `opt` is the foundation index (0..3) */
1253 #elif defined SPIDER
1254 /* moving to empty tableu? */
1255 if (is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
) {
1256 int bottom
= first_movable(f
.t
[*from
]);
1257 if (inactive
.opt
>= 0) { /*if from was cursor addressed: */
1258 *opt
= get_rank(f
.t
[*from
][bottom
+ inactive
.opt
]);
1261 int top
= find_top(f
.t
[*from
]);
1262 if (top
< 0) return CMD_INVAL
;
1263 if (top
>= 0 && !is_movable(f
.t
[*from
], top
-1)) {
1264 *opt
= get_rank(f
.t
[*from
][top
]);
1265 } else { /* only ask the user if it's unclear: */
1266 printf ("\rup to ([a23456789xjqk] or space/return): ");
1269 case ' ': *opt
= get_rank(f
.t
[*from
][top
]); break;
1270 case'\n': *opt
= get_rank(f
.t
[*from
][bottom
]); break;
1271 case 'a': case 'A': *opt
= RANK_A
; break;
1272 case '0': /* fallthrough */
1273 case 'x': case 'X': *opt
= RANK_X
; break;
1274 case 'j': case 'J': *opt
= RANK_J
; break;
1275 case 'q': case 'Q': *opt
= RANK_Q
; break;
1276 case 'k': case 'K': *opt
= RANK_K
; break;
1277 default: *opt
-= '1';
1279 if (*opt
< RANK_A
|| *opt
> RANK_K
) return CMD_INVAL
;
1281 /* `opt` is the rank of the highest card to move */
1287 int getctrlseq(unsigned char* buf
) {
1295 int offset
= 0x20; /* never sends control chars as data */
1296 while ((c
= getchar()) != EOF
) {
1300 case '\033': state
=ESC_SENT
; break;
1306 case '[': state
=CSI_SENT
; break;
1307 default: return KEY_INVAL
;
1312 case 'A': return KEY_UP
;
1313 case 'B': return KEY_DOWN
;
1314 case 'C': return KEY_RIGHT
;
1315 case 'D': return KEY_LEFT
;
1316 /*NOTE: home/end send ^[[x~ . no support for modifiers*/
1317 case 'H': return KEY_HOME
;
1318 case 'F': return KEY_END
;
1319 case '2': getchar(); return KEY_INS
;
1320 case '5': getchar(); return KEY_PGUP
;
1321 case '6': getchar(); return KEY_PGDN
;
1322 case 'M': state
=MOUSE_EVENT
; break;
1323 default: return KEY_INVAL
;
1327 if (buf
== NULL
) return KEY_INVAL
;
1328 buf
[0] = c
- offset
;
1329 buf
[1] = getchar() - offset
;
1330 buf
[2] = getchar() - offset
;
1338 int term2pile(unsigned char *mouse
) {
1339 int line
= (mouse
[2]-1);
1340 int column
= (mouse
[1]-1) / op
.s
->width
;
1342 if (line
< op
.s
->height
) { /* first line */
1345 case 0: return STOCK
;
1346 case 1: return WASTE
;
1347 case 2: return -1; /* spacer */
1348 case 3: return FOUNDATION
+0;
1349 case 4: return FOUNDATION
+1;
1350 case 5: return FOUNDATION
+2;
1351 case 6: return FOUNDATION
+3;
1353 #elif defined SPIDER
1354 if (column
< 3) return STOCK
;
1356 #elif defined FREECELL
1357 if (column
< NUM_SUITS
+ NUM_CELLS
) return STOCK
+column
;
1360 } else if (line
> op
.s
->height
) { /* tableu */
1361 if (column
<= TAB_MAX
) return column
;
1365 int wait_mouse_up(unsigned char* mouse
) {
1366 //TODO: mouse drag: start gets inactive, hovering gets active cursors
1367 struct cursor cur
= {-1,-1};
1369 /* note: if dragged [3]==1 and second position is in mouse[0,4,5] */
1371 /* display a cursor while mouse button is pushed: */
1372 int pile
= term2pile(mouse
);
1375 if (pile
>= FOUNDATION
) {
1376 cur
.pile
= FOUNDATION
;
1377 cur
.opt
= pile
-FOUNDATION
;
1379 #elif defined FREECELL
1380 if (pile
> TAB_MAX
) {
1381 cur
.pile
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
1382 cur
.opt
= (pile
-STOCK
) % 4;
1385 /* need to temporarily show the cursor, then revert to last state: */
1386 int old_show_cursor_hi
= op
.h
; //TODO: ARGH! that's awful!
1388 print_table(&cur
, NO_HI
); //TODO: should not overwrite inactive cursor!
1389 op
.h
= old_show_cursor_hi
;
1392 if (getctrlseq (mouse
+3) == MOUSE_ANY
) {
1393 /* ignore mouse wheel events: */
1394 if (mouse
[3] & 0x40) continue;
1396 else if((mouse
[3]&3) == 3) level
--; /* release event */
1397 else level
++; /* another button pressed */
1401 int success
= mouse
[1] == mouse
[4] && mouse
[2] == mouse
[5];
1408 int getch(unsigned char* buf
) {
1409 //TODO: if buf==NULL disable mouse input
1410 /* returns a character, EOF, or constant for an escape/control sequence - NOT
1411 compatible with the ncurses implementation of same name */
1413 if (buf
&& buf
[3]) {
1414 /* mouse was dragged; return 'ungetted' previous destination */
1415 action
= MOUSE_DRAG
;
1416 /* keep original [0], as [3] only contains release event */
1421 action
= getctrlseq(buf
);
1426 if (buf
[0] > 3) break; /* ignore wheel events */
1431 case 0: return MOUSE_LEFT
;
1432 case 1: return MOUSE_MIDDLE
;
1433 case 2: return MOUSE_RIGHT
;
1441 // shuffling and dealing {{{
1442 void deal(long seed
) {
1443 //TODO: clear hls/f.h
1444 f
= (const struct playfield
){0}; /* clear playfield */
1445 card_t deck
[DECK_SIZE
*NUM_DECKS
];
1446 int avail
= DECK_SIZE
*NUM_DECKS
;
1447 for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) deck
[i
] = (i
%DECK_SIZE
)+1;
1449 if (op
.m
!= NORMAL
) for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) {
1450 if (op
.m
== MEDIUM
) deck
[i
] = 1+((deck
[i
]-1) | 2);
1451 if (op
.m
== EASY
) deck
[i
] = 1+((deck
[i
]-1) | 2 | 1);
1452 /* the 1+ -1 dance gets rid of the offset created by NO_CARD */
1456 for (int i
= DECK_SIZE
*NUM_DECKS
-1; i
> 0; i
--) { /* fisher-yates */
1457 int j
= rand() % (i
+1);
1458 if (j
-i
) deck
[i
]^=deck
[j
],deck
[j
]^=deck
[i
],deck
[i
]^=deck
[j
];
1462 for (int i
= 0; i
< NUM_PILES
; i
++) {
1465 int count
= i
; /* pile n has n closed cards, then 1 open */
1466 #elif defined SPIDER
1468 int count
= i
<4?5:4; /* pile 1-4 have 5, 5-10 have 4 closed */
1469 #elif defined FREECELL
1471 int count
= i
<4?6:5;/*like spider, but cards are dealt face-up*/
1473 /* "SIGN": face down cards are negated */
1474 for (int j
= 0; j
< count
; j
++) f
.t
[i
][j
] = SIGN deck
[--avail
];
1475 f
.t
[i
][count
] = deck
[--avail
]; /* the face-up card */
1478 /* rest of the cards to the stock: */
1479 /* NOTE: assert(avail==50) for spider, assert(avail==0) for freecell */
1480 for (f
.z
= 0; avail
; f
.z
++) f
.s
[f
.z
] = deck
[--avail
];
1482 f
.w
= -1; /* @start: nothing on waste */
1483 #elif defined SPIDER
1484 f
.w
= 0; /* number of used foundations */
1485 #elif defined FREECELL
1486 f
.w
= 0; /* bitmask of used free cells */
1489 f
.u
= &undo_sentinel
;
1493 // screen drawing routines {{{
1494 void print_hi(int invert
, int grey_bg
, int bold
, int blink
, char* str
) {
1495 if (!op
.h
) invert
= 0; /* don't show invert if we used the mouse last */
1496 if (bold
&& op
.s
== &unicode_large_color
){ //awful hack for bold + faint
1497 int offset
= str
[3]==017?16:str
[4]==017?17:0;
1498 printf ("%s%s%s%s""%.*s%s%s""%s%s%s%s",
1499 "\033[1m", invert
?"\033[7m":"", grey_bg
?"\033[100m":"", blink
?"\033[5m":"",
1500 offset
, str
, bold
?"\033[1m":"", str
+offset
,
1501 blink
?"\033[25m":"", grey_bg
?"\033[49m":"", invert
?"\033[27m":"","\033[22m");
1504 printf ("%s%s%s%s%s%s%s%s%s",
1505 bold
?"\033[1m":"", invert
?"\033[7m":"", grey_bg
?"\033[100m":"", blink
?"\033[5m":"",
1507 blink
?"\033[25m":"", grey_bg
?"\033[49m":"", invert
?"\033[27m":"",bold
?"\033[22m":"");
1509 void print_table(const struct cursor
* active
, const struct cursor
* inactive
) {
1510 int do_blink
= 0; //XXX: remove
1511 printf("\033[2J\033[H"); /* clear screen, reset cursor */
1513 /* print stock, waste and foundation: */
1514 for (int line
= 0; line
< op
.s
->height
; line
++) {
1516 print_hi (active
->pile
== STOCK
, inactive
->pile
== STOCK
, 1, do_blink
, (
1517 (f
.w
< f
.z
-1)?op
.s
->facedown
1518 :op
.s
->placeholder
)[line
]);
1520 print_hi (active
->pile
== WASTE
, inactive
->pile
== WASTE
, 1, do_blink
, (
1521 /* NOTE: cast, because f.w sometimes is (short)-1 !? */
1522 ((short)f
.w
>= 0)?op
.s
->card
[f
.s
[f
.w
]]
1523 :op
.s
->placeholder
)[line
]);
1524 printf ("%s", op
.s
->card
[NO_CARD
][line
]); /* spacer */
1526 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1527 int card
= find_top(f
.f
[pile
]);
1528 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1529 inactive
->pile
==FOUNDATION
&& (
1530 /* cursor addr. || direct addr. */
1531 inactive
->opt
==pile
|| inactive
->opt
< 0
1532 ), !!f
.f
[pile
][0], do_blink
,
1533 (card
< 0)?op
.s
->foundation
[line
]
1534 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1539 #elif defined SPIDER
1540 int fdone
; for (fdone
= NUM_DECKS
*NUM_SUITS
; fdone
; fdone
--)
1541 if (f
.f
[fdone
-1][RANK_K
]) break; /*number of completed stacks*/
1542 int spacer_from
= f
.z
?(f
.z
/10-1) * op
.s
->halfwidth
[0] + op
.s
->width
:0;
1543 int spacer_to
= NUM_PILES
*op
.s
->width
-
1544 ((fdone
?(fdone
-1) * op
.s
->halfwidth
[1]:0)+op
.s
->width
);
1545 for (int line
= 0; line
< op
.s
->height
; line
++) {
1546 /* available stock: */
1547 for (int i
= f
.z
/10; i
; i
--) {
1548 if (i
==1) printf ("%s", op
.s
->facedown
[line
]);
1549 else printf ("%s", op
.s
->halfstack
[line
]);
1552 for (int i
= spacer_from
; i
< spacer_to
; i
++) printf (" ");
1553 /* foundation (overlapping): */
1554 for (int i
= NUM_DECKS
*NUM_SUITS
-1, half
= 0; i
>= 0; i
--) {
1555 int overlap
= half
? op
.s
->halfcard
[line
]: 0;
1556 if (f
.f
[i
][RANK_K
]) printf ("%.*s", op
.s
->halfwidth
[2],
1557 op
.s
->card
[f
.f
[i
][RANK_K
]][line
]+overlap
),
1563 #elif defined FREECELL
1564 /* print open cells, foundation: */
1565 for (int line
= 0; line
< op
.s
->height
; line
++) {
1566 for (int pile
= 0; pile
< NUM_CELLS
; pile
++)
1567 print_hi (active
->pile
==STOCK
&& active
->opt
==pile
,
1568 inactive
->pile
==STOCK
&& (
1569 /* cursor addr. || direct addr. */
1570 inactive
->opt
==pile
|| inactive
->opt
< 0
1571 ), !!f
.s
[pile
], do_blink
,
1572 ((f
.s
[pile
])?op
.s
->card
[f
.s
[pile
]]
1573 :op
.s
->placeholder
)[line
]);
1574 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1575 int card
= find_top(f
.f
[pile
]);
1576 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1577 inactive
->pile
==FOUNDATION
&& (
1578 /* cursor addr. || direct addr. */
1579 inactive
->opt
==pile
|| inactive
->opt
< 0
1580 ), !!f
.f
[pile
][0], do_blink
,
1581 (card
< 0)?op
.s
->foundation
[line
]
1582 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1589 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty))
1590 #define TOP_HI(c) 1 /* can't select partial stacks in KLONDIKE */
1591 #elif defined SPIDER || defined FREECELL
1592 int offset
[NUM_PILES
]={0}; /* first card to highlight */
1594 int bottom
[NUM_PILES
]; /* first movable card */
1595 for (int i
=0; i
<NUM_PILES
; i
++)
1596 bottom
[i
] = find_top(f
.t
[i
]) - max_move(i
,-1);
1598 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty) \
1599 && offset[pile] >= cursor->opt)
1600 #define TOP_HI(cursor) (cursor->pile == pile && movable \
1601 && offset[pile] == cursor->opt)
1603 /* print tableu piles: */
1604 int row
[NUM_PILES
] = {0};
1605 int line
[NUM_PILES
]= {0};
1606 int label
[NUM_PILES
]={0};
1608 int did_placeholders
= 0;
1611 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
1612 card_t card
= f
.t
[pile
][row
[pile
]];
1613 card_t next
= f
.t
[pile
][row
[pile
]+1];
1614 int movable
= is_movable(f
.t
[pile
], row
[pile
]);
1615 int do_blink
= hls(card
, f
.h
);
1617 if(row
[pile
] <= bottom
[pile
]) movable
= 0;
1619 int empty
= !card
&& row
[pile
] == 0;
1621 print_hi (DO_HI(active
), DO_HI(inactive
), movable
, do_blink
, (
1622 (!card
&& row
[pile
] == 0)?op
.s
->placeholder
1623 :(card
<0)?op
.s
->facedown
1627 int extreme_overlap
= ( 3 /* spacer, labels, status */
1628 + 2 * op
.s
->height
/* stock, top tableu card */
1629 + find_top(f
.t
[pile
]) * op
.s
->overlap
) >op
.w
[0];
1630 /* normal overlap: */
1631 if (++line
[pile
] >= (next
?op
.s
->overlap
:op
.s
->height
)
1632 /* extreme overlap on closed cards: */
1633 || (extreme_overlap
&&
1635 f
.t
[pile
][row
[pile
]] < 0 &&
1636 f
.t
[pile
][row
[pile
]+1] <0)
1637 /* extreme overlap on sequences: */
1638 || (extreme_overlap
&&
1639 !TOP_HI(active
) && /*always show top selected card*/
1640 line
[pile
] >= 1 && row
[pile
] > 0 &&
1641 f
.t
[pile
][row
[pile
]-1] > NO_CARD
&&
1642 is_consecutive (f
.t
[pile
], row
[pile
]) &&
1643 is_consecutive (f
.t
[pile
], row
[pile
]-1) &&
1644 f
.t
[pile
][row
[pile
]+1] != NO_CARD
)
1648 #if defined SPIDER || defined FREECELL
1649 if (movable
) offset
[pile
]++;
1652 /* tableu labels: */
1653 if(!card
&& !label
[pile
] && row
[pile
]>0&&line
[pile
]>0) {
1655 printf ("\b\b%d ", (pile
+1) % 10); //XXX: hack
1657 line_had_card
|= !!card
;
1658 did_placeholders
|= row
[pile
] > 0;
1661 } while (line_had_card
|| !did_placeholders
);
1664 void visbell (void) {
1666 printf ("\033[?5h"); fflush (stdout
);
1668 printf ("\033[?5l"); fflush (stdout
);
1670 void win_anim(void) {
1671 printf ("\033[?25l"); /* hide cursor */
1673 /* set cursor to random location */
1674 int row
= 1+rand()%(1+op
.w
[0]-op
.s
->height
);
1675 int col
= 1+rand()%(1+op
.w
[1]-op
.s
->width
);
1677 /* draw random card */
1678 int face
= 1 + rand() % 52;
1679 for (int l
= 0; l
< op
.s
->height
; l
++) {
1680 printf ("\033[%d;%dH", row
+l
, col
);
1681 printf ("%s", op
.s
->card
[face
][l
]);
1685 /* exit on keypress */
1686 struct pollfd p
= {STDIN_FILENO
, POLLIN
, 0};
1687 if (poll (&p
, 1, 80)) goto fin
;
1690 printf ("\033[?25h"); /* show cursor */
1696 void undo_push (int _f
, int t
, int n
, int o
) {
1697 struct undo
* new = malloc(sizeof(struct undo
));
1707 void undo_pop (struct undo
* u
) {
1708 if (u
== &undo_sentinel
) return;
1711 if (u
->f
== FOUNDATION
) {
1712 /* foundation -> tableu */
1713 int top_f
= find_top(f
.f
[u
->n
]);
1714 int top_t
= find_top(f
.t
[u
->t
]);
1715 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1716 f
.t
[u
->t
][top_t
] = NO_CARD
;
1717 } else if (u
->f
== WASTE
&& u
->t
== FOUNDATION
) {
1718 /* waste -> foundation */
1719 /* split u->n into wst and fnd: */
1720 int wst
= u
->n
& 0xffff;
1721 int fnd
= u
->n
>> 16;
1722 /* move stock cards one position up to make room: */
1723 for (int i
= f
.z
; i
>= wst
; i
--) f
.s
[i
+1] = f
.s
[i
];
1724 /* move one card from foundation to waste: */
1725 int top
= find_top(f
.f
[fnd
]);
1726 f
.s
[wst
] = f
.f
[fnd
][top
];
1727 f
.f
[fnd
][top
] = NO_CARD
;
1730 } else if (u
->f
== WASTE
) {
1731 /* waste -> tableu */
1732 /* move stock cards one position up to make room: */
1733 for (int i
= f
.z
-1; i
>= u
->n
; i
--) f
.s
[i
+1] = f
.s
[i
];
1734 /* move one card from tableu to waste: */
1735 int top
= find_top(f
.t
[u
->t
]);
1736 f
.s
[u
->n
] = f
.t
[u
->t
][top
];
1737 f
.t
[u
->t
][top
] = NO_CARD
;
1740 } else if (u
->t
== FOUNDATION
) {
1741 /* tableu -> foundation */
1742 int top_f
= find_top(f
.t
[u
->f
]);
1743 int top_t
= find_top(f
.f
[u
->n
]);
1744 /* close topcard if previous action caused turn_over(): */
1745 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1746 /* move one card from foundation to tableu: */
1747 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1748 f
.f
[u
->n
][top_t
] = NO_CARD
;
1750 /* tableu -> tableu */
1751 int top_f
= find_top(f
.t
[u
->f
]);
1752 int top_t
= find_top(f
.t
[u
->t
]);
1753 /* close topcard if previous action caused turn_over(): */
1754 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1755 /* move n cards from tableu[f] to tableu[t]: */
1756 for (int i
= 0; i
< u
->n
; i
++) {
1757 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1758 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1761 #elif defined SPIDER
1762 if (u
->f
== STOCK
) {
1763 /* stock -> tableu */
1764 /*remove a card from each pile and put it back onto the stock:*/
1765 for (int pile
= NUM_PILES
-1; pile
>= 0; pile
--) {
1766 int top
= find_top(f
.t
[pile
]);
1767 f
.s
[f
.z
++] = f
.t
[pile
][top
];
1768 f
.t
[pile
][top
] = NO_CARD
;
1770 } else if (u
->t
== FOUNDATION
) {
1771 /* tableu -> foundation */
1772 int top
= find_top(f
.t
[u
->f
]);
1773 /* close topcard if previous action caused turn_over(): */
1774 if (u
->o
) f
.t
[u
->f
][top
] *= -1;
1775 /* append cards from foundation to tableu */
1776 for (int i
= RANK_K
; i
>= RANK_A
; i
--) {
1777 f
.t
[u
->f
][++top
] = f
.f
[u
->n
][i
];
1778 f
.f
[u
->n
][i
] = NO_CARD
;
1780 f
.w
--; /* decrement complete-foundation-counter */
1783 /* tableu -> tableu */
1784 int top_f
= find_top(f
.t
[u
->f
]);
1785 int top_t
= find_top(f
.t
[u
->t
]);
1786 /* close topcard if previous action caused turn_over(): */
1787 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1788 /* move n cards from tableu[f] to tableu[t]: */
1789 for (int i
= 0; i
< u
->n
; i
++) {
1790 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1791 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1794 #elif defined FREECELL
1795 /*NOTE: if from and to are both stock/foundation, opt = from | to<<16 */
1796 if (u
->f
== STOCK
&& u
->t
== FOUNDATION
) {
1797 /* free cells -> foundation */
1798 /* split u->n into cll and fnd: */
1799 int cll
= u
->n
& 0xffff;
1800 int fnd
= u
->n
>> 16;
1801 /* move one card from foundation to free cell: */
1802 int top
= find_top(f
.f
[fnd
]);
1803 f
.s
[cll
] = f
.f
[fnd
][top
];
1804 f
.f
[fnd
][top
] = NO_CARD
;
1805 f
.w
|= 1<<cll
; /* mark cell as occupied */
1806 } else if (u
->f
== STOCK
) {
1807 /* free cells -> cascade */
1808 int top_t
= find_top(f
.t
[u
->t
]);
1809 f
.s
[u
->n
] = f
.t
[u
->t
][top_t
];
1810 f
.t
[u
->t
][top_t
] = NO_CARD
;
1811 f
.w
|= 1<<u
->n
; /* mark cell as occupied */
1812 } else if (u
->f
== FOUNDATION
&& u
->t
== STOCK
) {
1813 /* foundation -> free cells */
1814 /* split u->n into cll and fnd: */
1815 int cll
= u
->n
>> 16;
1816 int fnd
= u
->n
& 0xffff;
1817 /* move 1 card from free cell to foundation: */
1818 int top_f
= find_top(f
.f
[fnd
]);
1819 f
.f
[fnd
][top_f
+1] = f
.s
[cll
];
1821 f
.w
&= ~(1<<cll
); /* mark cell as free */
1822 } else if (u
->f
== FOUNDATION
) {
1823 /* foundation -> cascade */
1824 int top_f
= find_top(f
.f
[u
->n
]);
1825 int top_t
= find_top(f
.t
[u
->t
]);
1826 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1827 f
.t
[u
->t
][top_t
] = NO_CARD
;
1828 } else if (u
->t
== STOCK
) {
1829 /* cascade -> free cells */
1830 int top_f
= find_top(f
.t
[u
->f
]);
1831 f
.t
[u
->f
][top_f
+1] = f
.s
[u
->n
];
1832 f
.s
[u
->n
] = NO_CARD
;
1833 f
.w
&= ~(1<<u
->n
); /* mark cell as free */
1834 } else if (u
->t
== FOUNDATION
) {
1835 /* cascade -> foundation */
1836 int top_f
= find_top(f
.t
[u
->f
]);
1837 int top_t
= find_top(f
.f
[u
->n
]);
1838 /* move one card from foundation to cascade: */
1839 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1840 f
.f
[u
->n
][top_t
] = NO_CARD
;
1842 /* cascade -> cascade */
1843 int top_f
= find_top(f
.t
[u
->f
]);
1844 int top_t
= find_top(f
.t
[u
->t
]);
1845 /* move n cards from tableu[f] to tableu[t]: */
1846 for (int i
= 0; i
< u
->n
; i
++) {
1847 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1848 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1857 void free_undo (struct undo
* u
) {
1858 while (u
&& u
!= &undo_sentinel
) {
1866 // initialization stuff {{{
1867 void screen_setup (int enable
) {
1870 printf ("\033[s\033[?47h"); /* save cursor, alternate screen */
1871 printf ("\033[H\033[J"); /* reset cursor, clear screen */
1872 printf ("\033[?1000h"); /* enable mouse */
1874 printf ("\033[?1000l"); /* disable mouse */
1875 printf ("\033[?47l\033[u"); /* primary screen, restore cursor */
1880 void raw_mode(int enable
) {
1881 static struct termios saved_term_mode
;
1882 struct termios raw_term_mode
;
1885 if (saved_term_mode
.c_lflag
== 0)/*don't overwrite stored mode*/
1886 tcgetattr(STDIN_FILENO
, &saved_term_mode
);
1887 raw_term_mode
= saved_term_mode
;
1888 raw_term_mode
.c_lflag
&= ~(ICANON
| ECHO
);
1889 raw_term_mode
.c_cc
[VMIN
] = 1 ;
1890 raw_term_mode
.c_cc
[VTIME
] = 0;
1891 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw_term_mode
);
1893 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &saved_term_mode
);
1897 void signal_handler (int signum
) {
1902 signal(SIGTSTP
, SIG_DFL
); /* NOTE: assumes SysV semantics! */
1907 print_table(NO_HI
, NO_HI
);
1909 case SIGINT
: //TODO: don't exit; just warn like vim does
1912 ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &w
);
1918 void signal_setup(void) {
1919 struct sigaction saction
;
1921 saction
.sa_handler
= signal_handler
;
1922 sigemptyset(&saction
.sa_mask
);
1923 saction
.sa_flags
= 0;
1924 if (sigaction(SIGTSTP
, &saction
, NULL
) < 0) {
1928 if (sigaction(SIGCONT
, &saction
, NULL
) < 0) {
1932 if (sigaction(SIGINT
, &saction
, NULL
) < 0) {
1936 if (sigaction(SIGWINCH
, &saction
, NULL
) < 0) {
1937 perror ("SIGWINCH");
1943 //vim: foldmethod=marker