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 static char last_successful_action
[2] = {0,0}; //TODO: dot implementation should be in main game loop (CMD_AGAIN)
975 if (is_tableu(active
.pile
))
979 from_l
: print_table(&active
, &inactive
);
983 /* direct addressing: */
984 case '1': *from
= TAB_1
; break;
985 case '2': *from
= TAB_2
; break;
986 case '3': *from
= TAB_3
; break;
987 case '4': *from
= TAB_4
; break;
988 case '5': *from
= TAB_5
; break;
989 case '6': *from
= TAB_6
; break;
990 case '7': *from
= TAB_7
; break;
992 case '8': *from
= TAB_8
; break;
993 case '9': *from
= TAB_9
; break;
994 case '0': *from
= TAB_10
;break;
995 #elif defined FREECELL
996 case '8': *from
= TAB_8
; break;
997 case '9': *from
= STOCK
; break;
998 case '0': *from
= FOUNDATION
; break;
999 #elif defined KLONDIKE
1000 case '9': *from
= WASTE
; break;
1001 case '0': *from
= FOUNDATION
; break;
1002 case '8': /* fallthrough */
1005 case '\n': *from
= STOCK
; break;
1007 /* cursor keys addressing: */
1009 case 'h': cursor_left (&active
); goto from_l
;
1011 case 'j': cursor_down (&active
); goto from_l
;
1013 case 'k': cursor_up (&active
); goto from_l
;
1015 case 'l': cursor_right(&active
); goto from_l
;
1017 case 'H': cursor_to(&active
,TAB_1
); goto from_l
; /* leftmost tableu */
1019 case 'L': cursor_to(&active
,TAB_MAX
);goto from_l
; /* rigthmost tableu */
1021 case 'M': cursor_to(&active
,TAB_MAX
/2); goto from_l
; /* center tableu */
1022 case ' ': /* continue with second cursor */
1023 *from
= active
.pile
;
1025 *opt
= active
.opt
; /* when FOUNDATION */
1029 /* mouse addressing: */
1030 case MOUSE_MIDDLE
: return CMD_NONE
;
1032 if (set_mouse(term2pile(mouse
), to
, opt
))
1036 if (set_mouse(term2pile(mouse
), from
, opt
))
1038 if (!is_tableu(*from
))
1039 inactive
.opt
= *opt
; /* prevents card selector dialog */
1043 ungetc(last_successful_action
[1], stdin
);
1044 ungetc(last_successful_action
[0], stdin
); //XXX: 2nd ungetc() not portable!
1048 fprintf (stderr
, ":");
1049 raw_mode(0); /* turn on echo */
1050 fgets (buf
, 256, stdin
);
1053 case 'q': return CMD_QUIT
;
1054 case 'n': return CMD_NEW
;
1055 case 'r': return CMD_AGAIN
;
1056 case 'h': return CMD_HELP
;
1057 default: return CMD_INVAL
;
1062 case 'K': /* fallthrough */
1063 case '?': return CMD_HINT
;
1064 case 'f': return CMD_FIND
;
1065 case '/': return CMD_SEARCH
;
1066 case 'u': return CMD_UNDO
;
1067 case 002: return CMD_NONE
; /* sent by SIGWINCH */
1068 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1069 default: return CMD_INVAL
;
1071 inactive
.pile
= *from
; /* for direct addressing highlighting */
1072 if (is_tableu(*from
) && f
.t
[*from
][0] == NO_CARD
) return CMD_INVAL
;
1073 //TODO: freecell: if from==stock && stock[x] == empty: return inval
1076 if (*from
== STOCK
) {
1083 to_l
: print_table(&active
, &inactive
);
1088 case 'h': cursor_left (&active
); goto to_l
;
1090 case 'j': cursor_down (&active
); goto to_l
;
1092 case 'k': cursor_up (&active
); goto to_l
;
1094 case 'l': cursor_right(&active
); goto to_l
;
1096 case 'H': cursor_to(&active
,TAB_1
); goto to_l
;
1098 case 'L': cursor_to(&active
,TAB_MAX
); goto to_l
;
1100 case 'M': cursor_to(&active
,TAB_MAX
/2); goto to_l
;
1101 case 'J': /* fallthrough; just join selected pile */
1104 break; /* continues with the foundation/empty tableu check */
1106 case MOUSE_RIGHT
: return CMD_NONE
;
1108 if (set_mouse(term2pile(mouse
), to
, opt
))
1111 case 'K': /* fallthrough */
1112 case '?': return CMD_HINT
;
1113 case 'f': return CMD_FIND
; // XXX: will cancel from-card
1114 case '/': return CMD_SEARCH
; //ditto.
1115 case 'u': return CMD_NONE
; /* cancel selection */
1116 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1118 if (t
< '0' || t
> '9') return CMD_INVAL
;
1122 #elif defined SPIDER
1124 #elif defined FREECELL
1132 last_successful_action
[0] = _f
;
1133 last_successful_action
[1] = t
;
1136 /* direct addressing post-processing stage:
1137 because foundations/freecells share the same key (and you can't select
1138 partial piles) there are sometimes ambiguous situations where it isn't
1139 clear from which pile (or how many cards) to take. the code below will
1140 only ask the user if there are at least two possible moves and
1141 automatically choose otherwise. */
1143 /* if it was selected with a cursor, it's obvious: */
1144 if (inactive
.opt
>= 0) {
1145 if (is_tableu(*from
)) {
1146 /* NOTE: max_move same as in cursor_down() */
1147 *opt
= max_move(*from
, -1) - inactive
.opt
;
1149 *opt
= inactive
.opt
;
1151 /* moving from tableu to empty tableu? */
1152 } else if(is_tableu(*from
) && is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
){
1153 int top
= find_top(f
.t
[*from
]);
1154 int max
= max_move(*from
, *to
);
1156 if (top
< 0) return CMD_INVAL
;
1157 if (max
== 1) { /* only 1 movable? */
1158 return *opt
= 1, CMD_MOVE
;
1159 } else { /* only ask the user if it's unclear: */
1160 int bottom
= top
- (max
-1);
1161 printf ("\rup to ([a23456789xjqk] or space/return): ");
1164 case ' ': rank
= get_rank(f
.t
[*from
][top
]); break;
1165 case'\n': rank
= get_rank(f
.t
[*from
][bottom
]); break;
1166 case 'a': case 'A': rank
= RANK_A
; break;
1167 case '0': /* fallthrough */
1168 case 'x': case 'X': rank
= RANK_X
; break;
1169 case 'j': case 'J': rank
= RANK_J
; break;
1170 case 'q': case 'Q': rank
= RANK_Q
; break;
1171 case 'k': case 'K': rank
= RANK_K
; break;
1172 default: rank
-= '1';
1174 if (rank
< RANK_A
|| rank
> RANK_K
) return CMD_INVAL
;
1176 for (int i
= 0; max
--; i
++)
1177 if (get_rank(f
.t
[*from
][top
-i
]) == rank
)
1178 return *opt
= 1+i
, CMD_MOVE
;
1182 /* `opt` is the number of cards to move */
1183 /* moving between stock/foundation? */
1184 } else if (*from
== FOUNDATION
&& *to
== FOUNDATION
) {
1185 return CMD_INVAL
; /* nonsensical */
1186 } else if (*from
== FOUNDATION
&& *to
== STOCK
) {
1187 if (f
.w
== (1<<NUM_CELLS
)-1) return CMD_INVAL
; /*no free cells*/
1188 int ok_foundation
; /* find compatible (non-empty) foundations:*/
1189 int used_fs
=0; for (int i
= 0; i
< NUM_SUITS
; i
++)
1190 if (!!f
.f
[i
][0]) ok_foundation
= i
, used_fs
++;
1192 if (used_fs
== 0) return CMD_INVAL
; /* nowhere to take from */
1193 if (used_fs
== 1) { /* take from the only one */
1194 return *opt
= ok_foundation
, CMD_MOVE
;
1195 } else { /* ask user */
1196 printf ("take from (1-4): "); fflush (stdout
);
1197 *opt
= getch(NULL
) - '1';
1198 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1200 /* `opt` is the foundation index (0..3) */
1201 } else if (*from
== STOCK
) { /* cell -> foundation/tableu */
1202 if (!f
.w
) return CMD_INVAL
; /* no cell to take from */
1203 int ok_cell
; /* find compatible (non-empty) cells: */
1204 int tab
= is_tableu(*to
);
1205 int used_cs
=0; for (int i
= 0; i
< NUM_CELLS
; i
++) {
1206 card_t
* pile
= (tab
?f
.t
[*to
]:f
.f
[get_suit(f
.s
[i
])]);
1207 int top_to
= find_top(pile
);
1208 if (tab
? /* to tableu? */
1209 ((top_to
<0 && f
.s
[i
] > NO_CARD
)
1210 ||(top_to
>=0 && rank_next(f
.s
[i
], pile
[top_to
])
1211 && color_ok(f
.s
[i
], pile
[top_to
])))
1212 : /* to foundation? */
1213 ((top_to
<0 && get_rank(f
.s
[i
]) == RANK_A
)
1214 ||(top_to
>=0 && rank_next(pile
[top_to
],f
.s
[i
])))
1216 ok_cell
= i
, used_cs
++;
1219 if (used_cs
== 0) return CMD_INVAL
; /* nowhere to take from */
1220 if (used_cs
== 1) { /* take from the only one */
1221 return *opt
= ok_cell
, CMD_MOVE
;
1222 } else { /* ask user */
1223 printf ("take from (1-4): "); fflush (stdout
);
1224 *opt
= getch(NULL
) - '1';
1225 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1227 /* `opt` is the cell index (0..3) */
1230 //TODO: mouse-friendly "up to?" dialog
1231 #if defined KLONDIKE || defined FREECELL
1232 if (*from
== FOUNDATION
) {
1233 if (inactive
.opt
>= 0) {
1234 *opt
= inactive
.opt
;
1237 int top
= find_top(f
.t
[*to
]);
1238 if (top
< 0) return CMD_INVAL
;
1239 int color
= get_color(f
.t
[*to
][top
]);
1240 int choice_1
= 1-color
; /* selects piles of */
1241 int choice_2
= 2+color
; /* the opposite color */
1242 int top_c1
= find_top(f
.f
[choice_1
]);
1243 int top_c2
= find_top(f
.f
[choice_2
]);
1245 switch ((rank_next(f
.f
[choice_1
][top_c1
], f
.t
[*to
][top
])
1246 && top_c1
>= 0 ) << 0
1247 |(rank_next(f
.f
[choice_2
][top_c2
], f
.t
[*to
][top
])
1248 && top_c2
>= 0 ) << 1) {
1249 case ( 1<<0): *opt
= choice_1
; break; /* choice_1 only */
1250 case (1<<1 ): *opt
= choice_2
; break; /* choice_2 only */
1251 case (1<<1 | 1<<0): /* both, ask user which to pick from */
1252 printf ("take from (1-4): "); fflush (stdout
);
1253 *opt
= getch(NULL
) - '1';
1254 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1256 default: return CMD_INVAL
; /* none matched */
1258 /* `opt` is the foundation index (0..3) */
1260 #elif defined SPIDER
1261 /* moving to empty tableu? */
1262 if (is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
) {
1263 int bottom
= first_movable(f
.t
[*from
]);
1264 if (inactive
.opt
>= 0) { /*if from was cursor addressed: */
1265 *opt
= get_rank(f
.t
[*from
][bottom
+ inactive
.opt
]);
1268 int top
= find_top(f
.t
[*from
]);
1269 if (top
< 0) return CMD_INVAL
;
1270 if (top
>= 0 && !is_movable(f
.t
[*from
], top
-1)) {
1271 *opt
= get_rank(f
.t
[*from
][top
]);
1272 } else { /* only ask the user if it's unclear: */
1273 printf ("\rup to ([a23456789xjqk] or space/return): ");
1276 case ' ': *opt
= get_rank(f
.t
[*from
][top
]); break;
1277 case'\n': *opt
= get_rank(f
.t
[*from
][bottom
]); break;
1278 case 'a': case 'A': *opt
= RANK_A
; break;
1279 case '0': /* fallthrough */
1280 case 'x': case 'X': *opt
= RANK_X
; break;
1281 case 'j': case 'J': *opt
= RANK_J
; break;
1282 case 'q': case 'Q': *opt
= RANK_Q
; break;
1283 case 'k': case 'K': *opt
= RANK_K
; break;
1284 default: *opt
-= '1';
1286 if (*opt
< RANK_A
|| *opt
> RANK_K
) return CMD_INVAL
;
1288 /* `opt` is the rank of the highest card to move */
1294 int getctrlseq(unsigned char* buf
) {
1302 int offset
= 0x20; /* never sends control chars as data */
1303 while ((c
= getchar()) != EOF
) {
1307 case '\033': state
=ESC_SENT
; break;
1313 case '[': state
=CSI_SENT
; break;
1314 default: return KEY_INVAL
;
1319 case 'A': return KEY_UP
;
1320 case 'B': return KEY_DOWN
;
1321 case 'C': return KEY_RIGHT
;
1322 case 'D': return KEY_LEFT
;
1323 /*NOTE: home/end send ^[[x~ . no support for modifiers*/
1324 case 'H': return KEY_HOME
;
1325 case 'F': return KEY_END
;
1326 case '2': getchar(); return KEY_INS
;
1327 case '5': getchar(); return KEY_PGUP
;
1328 case '6': getchar(); return KEY_PGDN
;
1329 case 'M': state
=MOUSE_EVENT
; break;
1330 default: return KEY_INVAL
;
1334 if (buf
== NULL
) return KEY_INVAL
;
1335 buf
[0] = c
- offset
;
1336 buf
[1] = getchar() - offset
;
1337 buf
[2] = getchar() - offset
;
1345 int term2pile(unsigned char *mouse
) {
1346 int line
= (mouse
[2]-1);
1347 int column
= (mouse
[1]-1) / op
.s
->width
;
1349 if (line
< op
.s
->height
) { /* first line */
1352 case 0: return STOCK
;
1353 case 1: return WASTE
;
1354 case 2: return -1; /* spacer */
1355 case 3: return FOUNDATION
+0;
1356 case 4: return FOUNDATION
+1;
1357 case 5: return FOUNDATION
+2;
1358 case 6: return FOUNDATION
+3;
1360 #elif defined SPIDER
1361 if (column
< 3) return STOCK
;
1363 #elif defined FREECELL
1364 if (column
< NUM_SUITS
+ NUM_CELLS
) return STOCK
+column
;
1367 } else if (line
> op
.s
->height
) { /* tableu */
1368 if (column
<= TAB_MAX
) return column
;
1372 int wait_mouse_up(unsigned char* mouse
) {
1373 //TODO: mouse drag: start gets inactive, hovering gets active cursors
1374 struct cursor cur
= {-1,-1};
1376 /* note: if dragged [3]==1 and second position is in mouse[0,4,5] */
1378 /* display a cursor while mouse button is pushed: */
1379 int pile
= term2pile(mouse
);
1382 if (pile
>= FOUNDATION
) {
1383 cur
.pile
= FOUNDATION
;
1384 cur
.opt
= pile
-FOUNDATION
;
1386 #elif defined FREECELL
1387 if (pile
> TAB_MAX
) {
1388 cur
.pile
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
1389 cur
.opt
= (pile
-STOCK
) % 4;
1392 /* need to temporarily show the cursor, then revert to last state: */
1393 int old_show_cursor_hi
= op
.h
; //TODO: ARGH! that's awful!
1395 print_table(&cur
, NO_HI
); //TODO: should not overwrite inactive cursor!
1396 op
.h
= old_show_cursor_hi
;
1399 if (getctrlseq (mouse
+3) == MOUSE_ANY
) {
1400 /* ignore mouse wheel events: */
1401 if (mouse
[3] & 0x40) continue;
1403 else if((mouse
[3]&3) == 3) level
--; /* release event */
1404 else level
++; /* another button pressed */
1408 int success
= mouse
[1] == mouse
[4] && mouse
[2] == mouse
[5];
1415 int getch(unsigned char* buf
) {
1416 //TODO: if buf==NULL disable mouse input
1417 /* returns a character, EOF, or constant for an escape/control sequence - NOT
1418 compatible with the ncurses implementation of same name */
1420 if (buf
&& buf
[3]) {
1421 /* mouse was dragged; return 'ungetted' previous destination */
1422 action
= MOUSE_DRAG
;
1423 /* keep original [0], as [3] only contains release event */
1428 action
= getctrlseq(buf
);
1433 if (buf
[0] > 3) break; /* ignore wheel events */
1438 case 0: return MOUSE_LEFT
;
1439 case 1: return MOUSE_MIDDLE
;
1440 case 2: return MOUSE_RIGHT
;
1448 // shuffling and dealing {{{
1449 void deal(long seed
) {
1450 //TODO: clear hls/f.h
1451 f
= (const struct playfield
){0}; /* clear playfield */
1452 card_t deck
[DECK_SIZE
*NUM_DECKS
];
1453 int avail
= DECK_SIZE
*NUM_DECKS
;
1454 for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) deck
[i
] = (i
%DECK_SIZE
)+1;
1456 if (op
.m
!= NORMAL
) for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) {
1457 if (op
.m
== MEDIUM
) deck
[i
] = 1+((deck
[i
]-1) | 2);
1458 if (op
.m
== EASY
) deck
[i
] = 1+((deck
[i
]-1) | 2 | 1);
1459 /* the 1+ -1 dance gets rid of the offset created by NO_CARD */
1463 for (int i
= DECK_SIZE
*NUM_DECKS
-1; i
> 0; i
--) { /* fisher-yates */
1464 int j
= rand() % (i
+1);
1465 if (j
-i
) deck
[i
]^=deck
[j
],deck
[j
]^=deck
[i
],deck
[i
]^=deck
[j
];
1469 for (int i
= 0; i
< NUM_PILES
; i
++) {
1472 int count
= i
; /* pile n has n closed cards, then 1 open */
1473 #elif defined SPIDER
1475 int count
= i
<4?5:4; /* pile 1-4 have 5, 5-10 have 4 closed */
1476 #elif defined FREECELL
1478 int count
= i
<4?6:5;/*like spider, but cards are dealt face-up*/
1480 /* "SIGN": face down cards are negated */
1481 for (int j
= 0; j
< count
; j
++) f
.t
[i
][j
] = SIGN deck
[--avail
];
1482 f
.t
[i
][count
] = deck
[--avail
]; /* the face-up card */
1485 /* rest of the cards to the stock: */
1486 /* NOTE: assert(avail==50) for spider, assert(avail==0) for freecell */
1487 for (f
.z
= 0; avail
; f
.z
++) f
.s
[f
.z
] = deck
[--avail
];
1489 f
.w
= -1; /* @start: nothing on waste */
1490 #elif defined SPIDER
1491 f
.w
= 0; /* number of used foundations */
1492 #elif defined FREECELL
1493 f
.w
= 0; /* bitmask of used free cells */
1496 f
.u
= &undo_sentinel
;
1500 // screen drawing routines {{{
1501 void print_hi(int invert
, int grey_bg
, int bold
, int blink
, char* str
) {
1502 if (!op
.h
) invert
= 0; /* don't show invert if we used the mouse last */
1503 if (bold
&& op
.s
== &unicode_large_color
){ //awful hack for bold + faint
1504 int offset
= str
[3]==017?16:str
[4]==017?17:0;
1505 printf ("%s%s%s%s""%.*s%s%s""%s%s%s%s",
1506 "\033[1m", invert
?"\033[7m":"", grey_bg
?"\033[100m":"", blink
?"\033[5m":"",
1507 offset
, str
, bold
?"\033[1m":"", str
+offset
,
1508 blink
?"\033[25m":"", grey_bg
?"\033[49m":"", invert
?"\033[27m":"","\033[22m");
1511 printf ("%s%s%s%s%s%s%s%s%s",
1512 bold
?"\033[1m":"", invert
?"\033[7m":"", grey_bg
?"\033[100m":"", blink
?"\033[5m":"",
1514 blink
?"\033[25m":"", grey_bg
?"\033[49m":"", invert
?"\033[27m":"",bold
?"\033[22m":"");
1516 void print_table(const struct cursor
* active
, const struct cursor
* inactive
) {
1517 int do_blink
= 0; //XXX: remove
1518 printf("\033[2J\033[H"); /* clear screen, reset cursor */
1520 /* print stock, waste and foundation: */
1521 for (int line
= 0; line
< op
.s
->height
; line
++) {
1523 print_hi (active
->pile
== STOCK
, inactive
->pile
== STOCK
, 1, do_blink
, (
1524 (f
.w
< f
.z
-1)?op
.s
->facedown
1525 :op
.s
->placeholder
)[line
]);
1527 print_hi (active
->pile
== WASTE
, inactive
->pile
== WASTE
, 1, do_blink
, (
1528 /* NOTE: cast, because f.w sometimes is (short)-1 !? */
1529 ((short)f
.w
>= 0)?op
.s
->card
[f
.s
[f
.w
]]
1530 :op
.s
->placeholder
)[line
]);
1531 printf ("%s", op
.s
->card
[NO_CARD
][line
]); /* spacer */
1533 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1534 int card
= find_top(f
.f
[pile
]);
1535 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1536 inactive
->pile
==FOUNDATION
&& (
1537 /* cursor addr. || direct addr. */
1538 inactive
->opt
==pile
|| inactive
->opt
< 0
1539 ), !!f
.f
[pile
][0], do_blink
,
1540 (card
< 0)?op
.s
->foundation
[line
]
1541 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1546 #elif defined SPIDER
1547 int fdone
; for (fdone
= NUM_DECKS
*NUM_SUITS
; fdone
; fdone
--)
1548 if (f
.f
[fdone
-1][RANK_K
]) break; /*number of completed stacks*/
1549 int spacer_from
= f
.z
?(f
.z
/10-1) * op
.s
->halfwidth
[0] + op
.s
->width
:0;
1550 int spacer_to
= NUM_PILES
*op
.s
->width
-
1551 ((fdone
?(fdone
-1) * op
.s
->halfwidth
[1]:0)+op
.s
->width
);
1552 for (int line
= 0; line
< op
.s
->height
; line
++) {
1553 /* available stock: */
1554 for (int i
= f
.z
/10; i
; i
--) {
1555 if (i
==1) printf ("%s", op
.s
->facedown
[line
]);
1556 else printf ("%s", op
.s
->halfstack
[line
]);
1559 for (int i
= spacer_from
; i
< spacer_to
; i
++) printf (" ");
1560 /* foundation (overlapping): */
1561 for (int i
= NUM_DECKS
*NUM_SUITS
-1, half
= 0; i
>= 0; i
--) {
1562 int overlap
= half
? op
.s
->halfcard
[line
]: 0;
1563 if (f
.f
[i
][RANK_K
]) printf ("%.*s", op
.s
->halfwidth
[2],
1564 op
.s
->card
[f
.f
[i
][RANK_K
]][line
]+overlap
),
1570 #elif defined FREECELL
1571 /* print open cells, foundation: */
1572 for (int line
= 0; line
< op
.s
->height
; line
++) {
1573 for (int pile
= 0; pile
< NUM_CELLS
; pile
++)
1574 print_hi (active
->pile
==STOCK
&& active
->opt
==pile
,
1575 inactive
->pile
==STOCK
&& (
1576 /* cursor addr. || direct addr. */
1577 inactive
->opt
==pile
|| inactive
->opt
< 0
1578 ), !!f
.s
[pile
], do_blink
,
1579 ((f
.s
[pile
])?op
.s
->card
[f
.s
[pile
]]
1580 :op
.s
->placeholder
)[line
]);
1581 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1582 int card
= find_top(f
.f
[pile
]);
1583 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1584 inactive
->pile
==FOUNDATION
&& (
1585 /* cursor addr. || direct addr. */
1586 inactive
->opt
==pile
|| inactive
->opt
< 0
1587 ), !!f
.f
[pile
][0], do_blink
,
1588 (card
< 0)?op
.s
->foundation
[line
]
1589 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1596 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty))
1597 #define TOP_HI(c) 1 /* can't select partial stacks in KLONDIKE */
1598 #elif defined SPIDER || defined FREECELL
1599 int offset
[NUM_PILES
]={0}; /* first card to highlight */
1601 int bottom
[NUM_PILES
]; /* first movable card */
1602 for (int i
=0; i
<NUM_PILES
; i
++)
1603 bottom
[i
] = find_top(f
.t
[i
]) - max_move(i
,-1);
1605 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty) \
1606 && offset[pile] >= cursor->opt)
1607 #define TOP_HI(cursor) (cursor->pile == pile && movable \
1608 && offset[pile] == cursor->opt)
1610 /* print tableu piles: */
1611 int row
[NUM_PILES
] = {0};
1612 int line
[NUM_PILES
]= {0};
1613 int label
[NUM_PILES
]={0};
1615 int did_placeholders
= 0;
1618 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
1619 card_t card
= f
.t
[pile
][row
[pile
]];
1620 card_t next
= f
.t
[pile
][row
[pile
]+1];
1621 int movable
= is_movable(f
.t
[pile
], row
[pile
]);
1622 int do_blink
= hls(card
, f
.h
);
1624 if(row
[pile
] <= bottom
[pile
]) movable
= 0;
1626 int empty
= !card
&& row
[pile
] == 0;
1628 print_hi (DO_HI(active
), DO_HI(inactive
), movable
, do_blink
, (
1629 (!card
&& row
[pile
] == 0)?op
.s
->placeholder
1630 :(card
<0)?op
.s
->facedown
1634 int extreme_overlap
= ( 3 /* spacer, labels, status */
1635 + 2 * op
.s
->height
/* stock, top tableu card */
1636 + find_top(f
.t
[pile
]) * op
.s
->overlap
) >op
.w
[0];
1637 /* normal overlap: */
1638 if (++line
[pile
] >= (next
?op
.s
->overlap
:op
.s
->height
)
1639 /* extreme overlap on closed cards: */
1640 || (extreme_overlap
&&
1642 f
.t
[pile
][row
[pile
]] < 0 &&
1643 f
.t
[pile
][row
[pile
]+1] <0)
1644 /* extreme overlap on sequences: */
1645 || (extreme_overlap
&&
1646 !TOP_HI(active
) && /*always show top selected card*/
1647 line
[pile
] >= 1 && row
[pile
] > 0 &&
1648 f
.t
[pile
][row
[pile
]-1] > NO_CARD
&&
1649 is_consecutive (f
.t
[pile
], row
[pile
]) &&
1650 is_consecutive (f
.t
[pile
], row
[pile
]-1) &&
1651 f
.t
[pile
][row
[pile
]+1] != NO_CARD
)
1655 #if defined SPIDER || defined FREECELL
1656 if (movable
) offset
[pile
]++;
1659 /* tableu labels: */
1660 if(!card
&& !label
[pile
] && row
[pile
]>0&&line
[pile
]>0) {
1662 printf ("\b\b%d ", (pile
+1) % 10); //XXX: hack
1664 line_had_card
|= !!card
;
1665 did_placeholders
|= row
[pile
] > 0;
1668 } while (line_had_card
|| !did_placeholders
);
1671 void visbell (void) {
1673 printf ("\033[?5h"); fflush (stdout
);
1675 printf ("\033[?5l"); fflush (stdout
);
1677 void win_anim(void) {
1678 printf ("\033[?25l"); /* hide cursor */
1680 /* set cursor to random location */
1681 int row
= 1+rand()%(1+op
.w
[0]-op
.s
->height
);
1682 int col
= 1+rand()%(1+op
.w
[1]-op
.s
->width
);
1684 /* draw random card */
1685 int face
= 1 + rand() % 52;
1686 for (int l
= 0; l
< op
.s
->height
; l
++) {
1687 printf ("\033[%d;%dH", row
+l
, col
);
1688 printf ("%s", op
.s
->card
[face
][l
]);
1692 /* exit on keypress */
1693 struct pollfd p
= {STDIN_FILENO
, POLLIN
, 0};
1694 if (poll (&p
, 1, 80)) goto fin
;
1697 printf ("\033[?25h"); /* show cursor */
1703 void undo_push (int _f
, int t
, int n
, int o
) {
1704 struct undo
* new = malloc(sizeof(struct undo
));
1714 void undo_pop (struct undo
* u
) {
1715 if (u
== &undo_sentinel
) return;
1718 if (u
->f
== FOUNDATION
) {
1719 /* foundation -> tableu */
1720 int top_f
= find_top(f
.f
[u
->n
]);
1721 int top_t
= find_top(f
.t
[u
->t
]);
1722 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1723 f
.t
[u
->t
][top_t
] = NO_CARD
;
1724 } else if (u
->f
== WASTE
&& u
->t
== FOUNDATION
) {
1725 /* waste -> foundation */
1726 /* split u->n into wst and fnd: */
1727 int wst
= u
->n
& 0xffff;
1728 int fnd
= u
->n
>> 16;
1729 /* move stock cards one position up to make room: */
1730 for (int i
= f
.z
; i
>= wst
; i
--) f
.s
[i
+1] = f
.s
[i
];
1731 /* move one card from foundation to waste: */
1732 int top
= find_top(f
.f
[fnd
]);
1733 f
.s
[wst
] = f
.f
[fnd
][top
];
1734 f
.f
[fnd
][top
] = NO_CARD
;
1737 } else if (u
->f
== WASTE
) {
1738 /* waste -> tableu */
1739 /* move stock cards one position up to make room: */
1740 for (int i
= f
.z
-1; i
>= u
->n
; i
--) f
.s
[i
+1] = f
.s
[i
];
1741 /* move one card from tableu to waste: */
1742 int top
= find_top(f
.t
[u
->t
]);
1743 f
.s
[u
->n
] = f
.t
[u
->t
][top
];
1744 f
.t
[u
->t
][top
] = NO_CARD
;
1747 } else if (u
->t
== FOUNDATION
) {
1748 /* tableu -> foundation */
1749 int top_f
= find_top(f
.t
[u
->f
]);
1750 int top_t
= find_top(f
.f
[u
->n
]);
1751 /* close topcard if previous action caused turn_over(): */
1752 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1753 /* move one card from foundation to tableu: */
1754 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1755 f
.f
[u
->n
][top_t
] = NO_CARD
;
1757 /* tableu -> tableu */
1758 int top_f
= find_top(f
.t
[u
->f
]);
1759 int top_t
= find_top(f
.t
[u
->t
]);
1760 /* close topcard if previous action caused turn_over(): */
1761 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1762 /* move n cards from tableu[f] to tableu[t]: */
1763 for (int i
= 0; i
< u
->n
; i
++) {
1764 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1765 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1768 #elif defined SPIDER
1769 if (u
->f
== STOCK
) {
1770 /* stock -> tableu */
1771 /*remove a card from each pile and put it back onto the stock:*/
1772 for (int pile
= NUM_PILES
-1; pile
>= 0; pile
--) {
1773 int top
= find_top(f
.t
[pile
]);
1774 f
.s
[f
.z
++] = f
.t
[pile
][top
];
1775 f
.t
[pile
][top
] = NO_CARD
;
1777 } else if (u
->t
== FOUNDATION
) {
1778 /* tableu -> foundation */
1779 int top
= find_top(f
.t
[u
->f
]);
1780 /* close topcard if previous action caused turn_over(): */
1781 if (u
->o
) f
.t
[u
->f
][top
] *= -1;
1782 /* append cards from foundation to tableu */
1783 for (int i
= RANK_K
; i
>= RANK_A
; i
--) {
1784 f
.t
[u
->f
][++top
] = f
.f
[u
->n
][i
];
1785 f
.f
[u
->n
][i
] = NO_CARD
;
1787 f
.w
--; /* decrement complete-foundation-counter */
1790 /* tableu -> tableu */
1791 int top_f
= find_top(f
.t
[u
->f
]);
1792 int top_t
= find_top(f
.t
[u
->t
]);
1793 /* close topcard if previous action caused turn_over(): */
1794 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1795 /* move n cards from tableu[f] to tableu[t]: */
1796 for (int i
= 0; i
< u
->n
; i
++) {
1797 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1798 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1801 #elif defined FREECELL
1802 /*NOTE: if from and to are both stock/foundation, opt = from | to<<16 */
1803 if (u
->f
== STOCK
&& u
->t
== FOUNDATION
) {
1804 /* free cells -> foundation */
1805 /* split u->n into cll and fnd: */
1806 int cll
= u
->n
& 0xffff;
1807 int fnd
= u
->n
>> 16;
1808 /* move one card from foundation to free cell: */
1809 int top
= find_top(f
.f
[fnd
]);
1810 f
.s
[cll
] = f
.f
[fnd
][top
];
1811 f
.f
[fnd
][top
] = NO_CARD
;
1812 f
.w
|= 1<<cll
; /* mark cell as occupied */
1813 } else if (u
->f
== STOCK
) {
1814 /* free cells -> cascade */
1815 int top_t
= find_top(f
.t
[u
->t
]);
1816 f
.s
[u
->n
] = f
.t
[u
->t
][top_t
];
1817 f
.t
[u
->t
][top_t
] = NO_CARD
;
1818 f
.w
|= 1<<u
->n
; /* mark cell as occupied */
1819 } else if (u
->f
== FOUNDATION
&& u
->t
== STOCK
) {
1820 /* foundation -> free cells */
1821 /* split u->n into cll and fnd: */
1822 int cll
= u
->n
>> 16;
1823 int fnd
= u
->n
& 0xffff;
1824 /* move 1 card from free cell to foundation: */
1825 int top_f
= find_top(f
.f
[fnd
]);
1826 f
.f
[fnd
][top_f
+1] = f
.s
[cll
];
1828 f
.w
&= ~(1<<cll
); /* mark cell as free */
1829 } else if (u
->f
== FOUNDATION
) {
1830 /* foundation -> cascade */
1831 int top_f
= find_top(f
.f
[u
->n
]);
1832 int top_t
= find_top(f
.t
[u
->t
]);
1833 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1834 f
.t
[u
->t
][top_t
] = NO_CARD
;
1835 } else if (u
->t
== STOCK
) {
1836 /* cascade -> free cells */
1837 int top_f
= find_top(f
.t
[u
->f
]);
1838 f
.t
[u
->f
][top_f
+1] = f
.s
[u
->n
];
1839 f
.s
[u
->n
] = NO_CARD
;
1840 f
.w
&= ~(1<<u
->n
); /* mark cell as free */
1841 } else if (u
->t
== FOUNDATION
) {
1842 /* cascade -> foundation */
1843 int top_f
= find_top(f
.t
[u
->f
]);
1844 int top_t
= find_top(f
.f
[u
->n
]);
1845 /* move one card from foundation to cascade: */
1846 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1847 f
.f
[u
->n
][top_t
] = NO_CARD
;
1849 /* cascade -> cascade */
1850 int top_f
= find_top(f
.t
[u
->f
]);
1851 int top_t
= find_top(f
.t
[u
->t
]);
1852 /* move n cards from tableu[f] to tableu[t]: */
1853 for (int i
= 0; i
< u
->n
; i
++) {
1854 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1855 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1864 void free_undo (struct undo
* u
) {
1865 while (u
&& u
!= &undo_sentinel
) {
1873 // initialization stuff {{{
1874 void screen_setup (int enable
) {
1877 printf ("\033[s\033[?47h"); /* save cursor, alternate screen */
1878 printf ("\033[H\033[J"); /* reset cursor, clear screen */
1879 printf ("\033[?1000h"); /* enable mouse */
1881 printf ("\033[?1000l"); /* disable mouse */
1882 printf ("\033[?47l\033[u"); /* primary screen, restore cursor */
1887 void raw_mode(int enable
) {
1888 static struct termios saved_term_mode
;
1889 struct termios raw_term_mode
;
1892 if (saved_term_mode
.c_lflag
== 0)/*don't overwrite stored mode*/
1893 tcgetattr(STDIN_FILENO
, &saved_term_mode
);
1894 raw_term_mode
= saved_term_mode
;
1895 raw_term_mode
.c_lflag
&= ~(ICANON
| ECHO
);
1896 raw_term_mode
.c_cc
[VMIN
] = 1 ;
1897 raw_term_mode
.c_cc
[VTIME
] = 0;
1898 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw_term_mode
);
1900 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &saved_term_mode
);
1904 void signal_handler (int signum
) {
1909 signal(SIGTSTP
, SIG_DFL
); /* NOTE: assumes SysV semantics! */
1914 print_table(NO_HI
, NO_HI
);
1916 case SIGINT
: //TODO: don't exit; just warn like vim does
1919 ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &w
);
1925 void signal_setup(void) {
1926 struct sigaction saction
;
1928 saction
.sa_handler
= signal_handler
;
1929 sigemptyset(&saction
.sa_mask
);
1930 saction
.sa_flags
= 0;
1931 if (sigaction(SIGTSTP
, &saction
, NULL
) < 0) {
1935 if (sigaction(SIGCONT
, &saction
, NULL
) < 0) {
1939 if (sigaction(SIGINT
, &saction
, NULL
) < 0) {
1943 if (sigaction(SIGWINCH
, &saction
, NULL
) < 0) {
1944 perror ("SIGWINCH");
1950 //vim: foldmethod=marker