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;
278 #if defined KLONDIKE || defined FREECELL
279 case 'f': case 'F': { /* highlight cards that go on the foundation next */
280 card_t
* foundation
= f
.f
[get_suit(card
)];
281 int top
= find_top(foundation
);
282 if (foundation
[top
]) {
283 if (rank_next(foundation
[top
], card
) &&
284 get_suit(card
) == get_suit(foundation
[top
]))
287 if (get_rank(card
) == RANK_A
) return 1;
290 #endif // NOTE: makes no sense in SPIDER
294 if (*hi
< '1' || *hi
> '9') continue;
295 if (get_rank(card
) != *hi
- '1') return 0;
304 // takeable actions {{{
306 card_t
stack_take(void) { /*NOTE: assert(f.w >= 0) */
307 card_t card
= f
.s
[f
.w
];
308 /* move stack one over, so there are no gaps in it: */
309 for (int i
= f
.w
; i
< f
.z
-1; i
++)
312 f
.w
--; /* make previous card visible again */
315 int t2f(int from
, int to
, int opt
) { /* tableu to foundation */
316 (void) to
; (void) opt
; /* don't need */
317 int top_from
= find_top(f
.t
[from
]);
318 to
= get_suit(f
.t
[from
][top_from
]);
319 int top_to
= find_top(f
.f
[to
]);
320 if ((top_to
< 0 && get_rank(f
.t
[from
][top_from
]) == RANK_A
)
321 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.t
[from
][top_from
]))) {
322 f
.f
[to
][top_to
+1] = f
.t
[from
][top_from
];
323 f
.t
[from
][top_from
] = NO_CARD
;
324 undo_push(from
, FOUNDATION
, to
,
325 turn_over(f
.t
[from
]));
326 if (check_won()) return WON
;
330 int w2f(int from
, int to
, int opt
) { /* waste to foundation */
331 (void) from
; (void) to
; (void) opt
; /* don't need */
332 if (f
.w
< 0) return ERR
;
333 to
= get_suit(f
.s
[f
.w
]);
334 int top_to
= find_top(f
.f
[to
]);
335 if ((top_to
< 0 && get_rank(f
.s
[f
.w
]) == RANK_A
)
336 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
], f
.s
[f
.w
]))) {
337 undo_push(WASTE
, FOUNDATION
, f
.w
| to
<<16, 0);//ugly encoding :|
338 f
.f
[to
][top_to
+1] = stack_take();
339 if (check_won()) return WON
;
344 int s2w(int from
, int to
, int opt
) { /* stock to waste */
345 (void) from
; (void) to
; (void) opt
; /* don't need */
346 if (f
.z
== 0) return ERR
;
348 if (f
.w
== f
.z
) f
.w
= -1;
351 int w2s(int from
, int to
, int opt
) { /* waste to stock (undo stock to waste) */
352 (void) from
; (void) to
; (void) opt
; /* don't need */
353 if (f
.z
== 0) return ERR
;
355 if (f
.w
< -1) f
.w
= f
.z
-1;
358 int f2t(int from
, int to
, int opt
) { /* foundation to tableu */
359 (void) from
; /* don't need */
360 int top_to
= find_top(f
.t
[to
]);
362 int top_from
= find_top(f
.f
[from
]);
364 if (color_ok(f
.t
[to
][top_to
], f
.f
[from
][top_from
])
365 && (rank_next(f
.f
[from
][top_from
], f
.t
[to
][top_to
]))) {
366 f
.t
[to
][top_to
+1] = f
.f
[from
][top_from
];
367 f
.f
[from
][top_from
] = NO_CARD
;
368 undo_push(FOUNDATION
, to
, from
, 0);
372 int w2t(int from
, int to
, int opt
) { /* waste to tableu */
373 (void) from
; (void) opt
; /* don't need */
374 if (f
.w
< 0) return ERR
;
375 int top_to
= find_top(f
.t
[to
]);
376 if ((color_ok(f
.t
[to
][top_to
], f
.s
[f
.w
])
377 && (rank_next(f
.s
[f
.w
], f
.t
[to
][top_to
])))
378 || (top_to
< 0 && get_rank(f
.s
[f
.w
]) == RANK_K
)) {
379 undo_push(WASTE
, to
, f
.w
, 0);
380 f
.t
[to
][top_to
+1] = stack_take();
384 int t2t(int from
, int to
, int opt
) { /* tableu to tableu */
385 (void) opt
; /* don't need */
386 int top_to
= find_top(f
.t
[to
]);
387 int top_from
= find_top(f
.t
[from
]);
388 for (int i
= top_from
; i
>=0; i
--) {
389 if ((color_ok(f
.t
[to
][top_to
], f
.t
[from
][i
])
390 && (rank_next(f
.t
[from
][i
], f
.t
[to
][top_to
]))
391 && f
.t
[from
][i
] > NO_CARD
) /* card face up? */
392 || (top_to
< 0 && get_rank(f
.t
[from
][i
]) == RANK_K
)) {
393 /* move cards [i..top_from] to their destination */
395 for (;i
<= top_from
; i
++) {
397 f
.t
[to
][top_to
] = f
.t
[from
][i
];
398 f
.t
[from
][i
] = NO_CARD
;
401 undo_push(from
, to
, count
,
402 turn_over(f
.t
[from
]));
406 return ERR
; /* no such move possible */
409 int remove_if_complete (int pileno
) { //cleanup!
410 card_t
* pile
= f
.t
[pileno
];
411 /* test if K...A complete; move to foundation if so */
412 int top_from
= find_top(pile
);
413 if (get_rank(pile
[top_from
]) != RANK_A
) return 0;
414 for (int i
= top_from
; i
>=0; i
--) {
415 if (!is_consecutive (pile
, i
)) return 0;
416 if (i
+RANK_K
== top_from
/* if ace to king: remove it */
417 && get_rank(pile
[top_from
-RANK_K
]) == RANK_K
) {
418 for(int i
=top_from
, j
=0; i
>top_from
-NUM_RANKS
; i
--,j
++){
419 f
.f
[f
.w
][j
] = pile
[i
];
422 undo_push(pileno
, FOUNDATION
, f
.w
,
431 int t2t(int from
, int to
, int opt
) { //in dire need of cleanup
432 int top_from
= find_top(f
.t
[from
]);
433 int top_to
= find_top(f
.t
[to
]);
434 int empty_to
= (top_to
< 0)? opt
: -1; /* empty pile? */
436 for (int i
= top_from
; i
>= 0; i
--) {
437 if (!is_consecutive(f
.t
[from
], i
)) break;
439 /* is consecutive OR to empty pile and rank ok? */
440 if (rank_next(f
.t
[from
][i
], f
.t
[to
][top_to
])
441 || (empty_to
>= RANK_A
&& get_rank(f
.t
[from
][i
]) == empty_to
)) {
443 for (;i
<= top_from
; i
++) {
445 f
.t
[to
][top_to
] = f
.t
[from
][i
];
446 f
.t
[from
][i
] = NO_CARD
;
449 undo_push(from
, to
, count
,
450 turn_over(f
.t
[from
]));
451 remove_if_complete(to
);
452 if (check_won()) return WON
;
457 return ERR
; /* no such move possible */
459 int s2t(int from
, int to
, int opt
) {
460 (void) from
; (void) to
; (void) opt
; /* don't need */
461 if (f
.z
<= 0) return ERR
; /* stack out of cards */
462 for (int pile
= 0; pile
< NUM_PILES
; pile
++)
463 if (f
.t
[pile
][0]==NO_CARD
) return ERR
; /*no piles may be empty*/
465 undo_push(STOCK
, TABLEU
, 1, 0); /* NOTE: before remove_if_complete()! */
466 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
467 f
.t
[pile
][find_top(f
.t
[pile
])+1] = f
.s
[--f
.z
];
468 remove_if_complete(pile
);
469 if (check_won()) return WON
;
473 int t2f(int from
, int to
, int opt
) {
474 (void) to
; (void) opt
; /* don't need */
475 /* manually retrigger remove_if_complete() (e.g. after undo_pop) */
476 return remove_if_complete(from
)?OK
:ERR
;
478 #elif defined FREECELL
479 int max_move(int from
, int to
) {
480 /* returns the maximum number of cards that can be moved */
481 /* see also: https://boardgames.stackexchange.com/a/45157/26498 */
482 int free_tabs
= 0, free_cells
= 0;
483 for (int i
= 0; i
< NUM_PILES
; i
++) free_tabs
+= f
.t
[i
][0] == NO_CARD
;
484 for (int i
= 0; i
< NUM_CELLS
; i
++) free_cells
+= f
.s
[i
] == NO_CARD
;
486 /* don't count the tableau we are moving to: */
487 if (to
>= 0 && f
.t
[to
][0] == NO_CARD
) free_tabs
--;
489 /* theoretic maximum is limited by the number of cards on the pile */
490 int max_theory
= (1<<free_tabs
) * (free_cells
+ 1);
491 int max_effective
= 1 + find_top(f
.t
[from
]) - first_movable(f
.t
[from
]);
492 return max_effective
< max_theory
? max_effective
: max_theory
;
494 //TODO FREECELL: auto move to tableu after each move (not all cards possible, only when it is the smallest rank still on the board)
495 int t2t(int from
, int to
, int opt
) {
496 int top_to
= find_top(f
.t
[to
]);
497 int top_from
= find_top(f
.t
[from
]);
498 int cards
= max_move(from
, to
);
499 if (top_to
< 0) { /* moving to empty pile? */
501 return ERR
; /* cannot execute move */
502 cards
= opt
; /* user wants to move n cards*/
505 for (int i
= top_from
; i
>=0; i
--) {
506 if (cards
-->0/*enough space and not more attempted than wanted*/
507 && ((top_to
>= 0 /* if destn. not empty: check rank/color */
508 && (color_ok(f
.t
[to
][top_to
], f
.t
[from
][i
])
509 && (rank_next(f
.t
[from
][i
], f
.t
[to
][top_to
]))))
510 || (top_to
< 0 && !cards
))) {/*if dest empty and right # cards*/
511 /* move cards [i..top_from] to their destination */
513 for (;i
<= top_from
; i
++) {
515 f
.t
[to
][top_to
] = f
.t
[from
][i
];
516 f
.t
[from
][i
] = NO_CARD
;
519 undo_push(from
, to
, count
, 0);
523 return ERR
; /* no such move possible */
525 int t2f(int from
, int to
, int opt
) { /* 1:1 copy from KLONDIKE */
526 (void) to
; (void) opt
; /* don't need */
527 int top_from
= find_top(f
.t
[from
]);
528 to
= get_suit(f
.t
[from
][top_from
]);
529 int top_to
= find_top(f
.f
[to
]);
530 if ((top_to
< 0 && get_rank(f
.t
[from
][top_from
]) == RANK_A
)
531 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.t
[from
][top_from
]))) {
532 f
.f
[to
][top_to
+1] = f
.t
[from
][top_from
];
533 f
.t
[from
][top_from
] = NO_CARD
;
534 undo_push(from
, FOUNDATION
, to
, 0);
535 if (check_won()) return WON
;
539 int f2t(int from
, int to
, int opt
) {
540 (void) from
; /* don't need */
541 int top_to
= find_top(f
.t
[to
]);
543 int top_from
= find_top(f
.f
[from
]);
545 if (top_to
< 0 /* empty tableu? */
546 ||(color_ok(f
.t
[to
][top_to
], f
.f
[from
][top_from
])
547 && (rank_next(f
.f
[from
][top_from
], f
.t
[to
][top_to
])))) {
548 f
.t
[to
][top_to
+1] = f
.f
[from
][top_from
];
549 f
.f
[from
][top_from
] = NO_CARD
;
550 undo_push(FOUNDATION
, to
, from
, 0);
554 int t2c(int from
, int to
, int opt
) {
555 (void) to
; (void) opt
; /* don't need */
556 /* is a cell free? */
557 if (f
.w
== (1<<NUM_CELLS
)-1)
559 for (to
= 0; to
< NUM_CELLS
; to
++)
560 if (!(f
.w
>>to
&1)) break;
562 int top_from
= find_top(f
.t
[from
]);
563 f
.s
[to
] = f
.t
[from
][top_from
];
564 f
.t
[from
][top_from
] = NO_CARD
;
565 f
.w
|= 1<<to
; /* mark cell as occupied */
566 undo_push(from
, STOCK
, to
, 0);
570 int c2t(int from
, int to
, int opt
) {
571 (void) from
; /* don't need */
572 int top_to
= find_top(f
.t
[to
]);
575 if (top_to
< 0 /* empty tableu? */
576 ||(color_ok(f
.t
[to
][top_to
], f
.s
[from
])
577 && (rank_next(f
.s
[from
], f
.t
[to
][top_to
])))) {
578 f
.t
[to
][top_to
+1] = f
.s
[from
];
580 f
.w
&= ~(1<<from
); /* mark cell as free */
581 undo_push(STOCK
, to
, from
, 0);
586 int c2f(int from
, int to
, int opt
) {
587 (void) from
; (void) to
; /* don't need */
589 if (f
.s
[from
] == NO_CARD
) return ERR
;
590 to
= get_suit(f
.s
[from
]);
591 int top_to
= find_top(f
.f
[to
]);
592 if ((top_to
< 0 && get_rank(f
.s
[from
]) == RANK_A
)
593 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.s
[from
]))) {
594 f
.f
[to
][top_to
+1] = f
.s
[from
];
596 f
.w
&= ~(1<<from
); /* mark cell as free */
597 undo_push(STOCK
, FOUNDATION
, from
| to
<<16, 0);
598 if (check_won()) return WON
;
602 int f2c(int from
, int to
, int opt
) {
603 (void) from
; (void) to
; /* don't need */
604 /* is a cell free? */
605 if (f
.w
== (1<<NUM_CELLS
)-1)
607 for (to
= 0; to
< NUM_CELLS
; to
++)
608 if (!(f
.w
>>to
&1)) break;
611 int top_from
= find_top(f
.f
[from
]);
612 f
.s
[to
] = f
.f
[from
][top_from
];
613 f
.f
[from
][top_from
] = NO_CARD
;
614 f
.w
|= 1<<to
; /* mark cell as occupied */
615 undo_push(FOUNDATION
, STOCK
, from
| to
<<16, 0);
619 #define w2f c2f /* for join()'s "to foundation" */
622 //TODO: generalize prediction engine for CMD_HINT
624 #define would_complete(pile) 0
626 #define would_complete(pile) \
627 (get_rank(f.t[pile][r[pile].top]) == RANK_A \
628 && get_rank(f.t[to][bottom_to]) == RANK_K)
629 #elif defined FREECELL
630 #define would_complete(pile) 0
632 #define would_turn(pile) \
633 (f.t[pile][r[pile].pos-1] < 0)
634 #define would_empty(pile) \
638 int top_to
= find_top(f
.t
[to
]);
640 int bottom_to
= first_movable(f
.t
[to
]);
643 #if defined KLONDIKE || defined FREECELL
644 if (to
== WASTE
|| to
== STOCK
) return ERR
; /*why would you do that!?*/
646 if (to
== FOUNDATION
) {
648 for (int i
= 0; i
< NUM_PILES
+NUM_CELLS
; i
++)
649 switch (is_tableu(i
)?t2f(i
, FOUNDATION
, 0)
650 :w2f(STOCK
,FOUNDATION
,i
-NUM_PILES
)){
651 case WON
: return WON
;
652 case OK
: status
= OK
;
660 if (top_to
< 0) { /* move a king to empty pile: */
661 for (int i
= 0; i
<= TAB_MAX
; i
++) {
662 if (f
.t
[i
][0] < 0) /* i.e. would turn? */
663 if (t2t(i
, to
, 0) == OK
) return OK
;
665 return w2t(WASTE
, to
, 0);
667 #elif defined FREECELL
668 if (top_to
< 0) { /* move longest cascade to empty tableu: */ //TODO FREECELL:
671 for (int i
= 0; i
<= TAB_MAX
; i
++) {
672 int m
= max_move(i
, to
);
673 /*longest cascade that won't uncover another free pile*/
674 //TODO: don't rip apart cascades
675 if (m
>= length
&& m
<= find_top(f
.t
[i
]))
676 length
= m
, longest
= i
;
678 if (longest
< 0) return ERR
;
679 return t2t(longest
, to
, length
);
684 int ok
:1; /* card to move in pile? */
685 int above
; /* number of movable cards above */
686 int below
; /* number of cards below ours */
687 int pos
; /* where the card to move is in the pile */
688 int top
; /* find_top() */
689 } r
[NUM_PILES
] = {{0}};
690 int complete
= 0;/* SPIDER: true if any pile would complete a stack */
691 int turn
= 0; /* SPIDER: true if any pile would turn_over */
692 int empty
= 0; /* true if any pile would become empty */
694 /* 1. rate each pile: */
697 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
698 if (pile
== to
) continue;
699 int top
= find_top(f
.t
[pile
]);
700 int bottom
= first_movable(f
.t
[pile
]);
701 r
[pile
].pos
= bottom
; /* need for would_empty */
703 if (top
< 0) continue; /* no cards to move */
704 if (would_empty(pile
)) continue; /* doesn't help */
707 r
[pile
].above
= 0; /* always take as many as possible */
708 r
[pile
].below
= top
- bottom
;
710 complete
|= would_complete(pile
); /* never happens */
711 turn
|= would_turn(pile
);
712 empty
|= would_empty(pile
);
716 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
717 r
[pile
].top
= r
[pile
].pos
= find_top(f
.t
[pile
]);
718 /* backtrack until we find a compatible-to-'to'-pile card: */
720 int maxmove
= max_move(pile
, -1);
722 while (r
[pile
].pos
>= 0 && is_movable(f
.t
[pile
], r
[pile
].pos
)) {
723 int rankdiff
= get_rank(f
.t
[pile
][r
[pile
].pos
])
724 - get_rank(f
.t
[to
][top_to
]);
725 if (rankdiff
>= 0) break; /* past our card */
727 if (!maxmove
--) break; /* can't move this many cards */
729 if (rankdiff
== -1 && /* rank matches */
730 color_ok(f
.t
[pile
][r
[pile
].pos
], f
.t
[to
][top_to
])
733 complete
|= would_complete(pile
);
734 turn
|= would_turn(pile
);
735 empty
|= would_empty(pile
);
736 for (int i
= r
[pile
].pos
; i
>= 0; i
--)
737 if (is_movable(f
.t
[pile
], i
-1))
747 /* 2. find optimal pile: (optimized for spider) */
748 //todo: in spider, prefer longest piles if above==0 (faster completions)
750 for (int pile
= 0, above
= 99, below
= 99; pile
< NUM_PILES
; pile
++) {
751 if (!r
[pile
].ok
) continue;
752 /* don't bother if another pile would be better: prefer ... */
753 /* ... to complete a stack: */
754 if (!would_complete(pile
) && complete
) continue;
755 /* ... emptying piles: */
756 if (!would_empty(pile
) && empty
&& !complete
) continue;
757 /* ... to turn_over: */
758 if (!would_turn(pile
) && turn
&& !complete
&& !empty
) continue;
759 /* ... not to rip apart too many cards: */
760 if (r
[pile
].above
> above
) continue;
761 /* if tied, prefer ... */
762 if (r
[pile
].above
== above
763 /* ... larger pile if destination is empty */
764 && (top_to
< 0? r
[pile
].below
< below
765 /* ... shorter pile otherwise */
766 : r
[pile
].below
> below
))
770 above
= r
[pile
].above
;
771 below
= r
[pile
].below
;
774 /* 3. move cards over and return: */
776 /* prefer waste if it wouldn't turn_over: */
777 /* NOTE: does not attempt to take from froundation */
778 if (!empty
&& !turn
&& w2t(WASTE
, to
, 0) == OK
)
780 if (from
< 0) /* nothing found */
782 return t2t(from
, to
, 0);
784 if (from
< 0) /* nothing found */
786 int bottom
= first_movable(f
.t
[from
]);
787 return t2t(from
, to
, get_rank(f
.t
[from
][bottom
]));
788 #elif defined FREECELL
789 //TODO: if would rip apart, try freecells first (instead after)
790 if (from
< 0) /* no tableu move found */ {
791 /* try all free cells before giving up: */
792 for (int i
= 0; i
< NUM_CELLS
; i
++)
793 if (c2t(STOCK
, to
, i
) == OK
) return OK
;
796 return t2t(from
, to
, 0);
801 #undef would_complete
802 int nop(int from
, int to
, int opt
) { (void)from
;(void)to
;(void)opt
;return ERR
; }
805 // keyboard input handling {{{
806 // cursor functions{{{
808 void cursor_left (struct cursor
* cursor
) {
810 if (is_tableu(cursor
->pile
)) {
811 if (cursor
->pile
> 0) cursor
->pile
--;
813 } else { /* stock/waste/foundation*/
814 switch (cursor
->pile
) {
815 case WASTE
: cursor
->pile
= STOCK
; cursor
->opt
= 0; break;
817 if (cursor
->opt
<= 0)
818 cursor
->pile
= WASTE
;
824 void cursor_down (struct cursor
* cursor
) {
826 if (!is_tableu(cursor
->pile
)) {
827 switch (cursor
->pile
) {
828 case STOCK
: cursor
->pile
= TAB_1
; break;
829 case WASTE
: cursor
->pile
= TAB_2
; break;
831 cursor
->pile
= TAB_4
+ cursor
->opt
;
836 void cursor_up (struct cursor
* cursor
) {
838 if (is_tableu(cursor
->pile
)) {
839 switch (cursor
->pile
) { //ugly :|
840 case TAB_1
: cursor
->pile
= STOCK
; break;
841 case TAB_2
: cursor
->pile
= WASTE
; break;
842 case TAB_3
: cursor
->pile
= WASTE
; break;
843 case TAB_4
: case TAB_5
: case TAB_6
: case TAB_7
:
844 cursor
->opt
=cursor
->pile
-TAB_4
;
845 cursor
->pile
= FOUNDATION
;
850 void cursor_right (struct cursor
* cursor
) {
852 if (is_tableu(cursor
->pile
)) {
853 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
856 switch (cursor
->pile
) {
857 case STOCK
: cursor
->pile
= WASTE
; break;
858 case WASTE
: cursor
->pile
= FOUNDATION
;cursor
->opt
= 0; break;
860 if (cursor
->opt
< NUM_SUITS
-1)
866 /*NOTE: one can't highlight the stock due to me being too lazy to implement it*/
867 void cursor_left (struct cursor
* cursor
) {
869 if (cursor
->pile
> 0) cursor
->pile
--;
872 void cursor_down (struct cursor
* cursor
) {
874 int first
= first_movable(f
.t
[cursor
->pile
]);
875 int top
= find_top(f
.t
[cursor
->pile
]);
876 if (first
+ cursor
->opt
< top
)
879 void cursor_up (struct cursor
* cursor
) {
881 if (cursor
->opt
> 0) cursor
->opt
--;
883 void cursor_right (struct cursor
* cursor
) {
885 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
888 #elif defined FREECELL
889 void cursor_left (struct cursor
* cursor
) {
891 if (is_tableu(cursor
->pile
)) {
892 if (cursor
->pile
> 0) cursor
->pile
--;
894 } else { /* cells/foundation*/
895 switch (cursor
->pile
) {
901 if (cursor
->opt
<= 0) {
902 cursor
->pile
= STOCK
;
910 void cursor_down (struct cursor
* cursor
) {
912 if (is_tableu(cursor
->pile
)) {
913 if (cursor
->opt
< max_move(cursor
->pile
, -1)-1)
916 cursor
->pile
= cursor
->opt
+NUM_CELLS
*(cursor
->pile
==FOUNDATION
);
920 void cursor_up (struct cursor
* cursor
) {
922 if (is_tableu(cursor
->pile
)) {
923 if (cursor
->opt
> 0) {
926 switch (cursor
->pile
) {
927 case TAB_1
: case TAB_2
: case TAB_3
: case TAB_4
:
928 cursor
->opt
= cursor
->pile
; /*assumes TAB_1==0*/
929 cursor
->pile
= STOCK
;
931 case TAB_5
: case TAB_6
: case TAB_7
: case TAB_8
:
932 cursor
->opt
= cursor
->pile
- NUM_CELLS
;
933 cursor
->pile
= FOUNDATION
;
938 void cursor_right (struct cursor
* cursor
) {
940 if (is_tableu(cursor
->pile
)) {
941 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
944 switch (cursor
->pile
) {
946 if (cursor
->opt
< NUM_SUITS
-1) {
949 cursor
->pile
= FOUNDATION
;
953 if (cursor
->opt
< NUM_SUITS
-1)
959 void cursor_to (struct cursor
* cursor
, int pile
) {
964 int set_mouse(int pile
, int* main
, int* opt
) {
965 //TODO: this should set cursor.opt, so card selector choice dialog does not trigger!
967 if (pile
< 0) return 1;
970 if (pile
>= FOUNDATION
)//TODO: check upper bound!
972 *opt
= pile
- FOUNDATION
;
975 #elif defined FREECELL
976 if (pile
> TAB_MAX
) {
977 *main
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
978 *opt
= (pile
-STOCK
) % 4;
984 int get_cmd (int* from
, int* to
, int* opt
) {
986 unsigned char mouse
[6] = {0}; /* must clear [3]! */
987 struct cursor inactive
= {-1,-1};
988 static struct cursor active
= {0,0};
989 static char last_successful_action
[2] = {0,0}; //TODO: dot implementation should be in main game loop (CMD_AGAIN)
990 if (is_tableu(active
.pile
))
994 from_l
: print_table(&active
, &inactive
);
998 /* direct addressing: */
999 case '1': *from
= TAB_1
; break;
1000 case '2': *from
= TAB_2
; break;
1001 case '3': *from
= TAB_3
; break;
1002 case '4': *from
= TAB_4
; break;
1003 case '5': *from
= TAB_5
; break;
1004 case '6': *from
= TAB_6
; break;
1005 case '7': *from
= TAB_7
; break;
1007 case '8': *from
= TAB_8
; break;
1008 case '9': *from
= TAB_9
; break;
1009 case '0': *from
= TAB_10
;break;
1010 #elif defined FREECELL
1011 case '8': *from
= TAB_8
; break;
1012 case '9': *from
= STOCK
; break;
1013 case '0': *from
= FOUNDATION
; break;
1014 #elif defined KLONDIKE
1015 case '9': *from
= WASTE
; break;
1016 case '0': *from
= FOUNDATION
; break;
1017 case '8': /* fallthrough */
1020 case '\n': *from
= STOCK
; break;
1022 /* cursor keys addressing: */
1024 case 'h': cursor_left (&active
); goto from_l
;
1026 case 'j': cursor_down (&active
); goto from_l
;
1028 case 'k': cursor_up (&active
); goto from_l
;
1030 case 'l': cursor_right(&active
); goto from_l
;
1032 case 'H': cursor_to(&active
,TAB_1
); goto from_l
; /* leftmost tableu */
1034 case 'L': cursor_to(&active
,TAB_MAX
);goto from_l
; /* rigthmost tableu */
1036 case 'M': cursor_to(&active
,TAB_MAX
/2); goto from_l
; /* center tableu */
1037 case ' ': /* continue with second cursor */
1038 *from
= active
.pile
;
1040 *opt
= active
.opt
; /* when FOUNDATION */
1044 /* mouse addressing: */
1045 case MOUSE_MIDDLE
: return CMD_NONE
;
1047 if (set_mouse(term2pile(mouse
), to
, opt
))
1051 if (set_mouse(term2pile(mouse
), from
, opt
))
1053 if (!is_tableu(*from
))
1054 inactive
.opt
= *opt
; /* prevents card selector dialog */
1058 ungetc(last_successful_action
[1], stdin
);
1059 ungetc(last_successful_action
[0], stdin
); //XXX: 2nd ungetc() not portable!
1063 fprintf (stderr
, ":");
1064 raw_mode(0); /* turn on echo */
1065 fgets (buf
, 256, stdin
);
1068 case 'q': return CMD_QUIT
;
1069 case 'n': return CMD_NEW
;
1070 case 'r': return CMD_AGAIN
;
1071 case 'h': return CMD_HELP
;
1072 default: return CMD_INVAL
;
1077 case 'K': /* fallthrough */
1078 case '?': return CMD_HINT
;
1079 case 'f': return CMD_FIND
;
1080 case '/': return CMD_SEARCH
;
1081 case 'u': return CMD_UNDO
;
1082 case 002: return CMD_NONE
; /* sent by SIGWINCH */
1083 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1084 default: return CMD_INVAL
;
1086 inactive
.pile
= *from
; /* for direct addressing highlighting */
1087 if (is_tableu(*from
) && f
.t
[*from
][0] == NO_CARD
) return CMD_INVAL
;
1089 if (*from
== STOCK
&& f
.s
[active
.opt
] == NO_CARD
) return CMD_INVAL
;
1093 if (*from
== STOCK
) {
1100 to_l
: print_table(&active
, &inactive
);
1105 case 'h': cursor_left (&active
); goto to_l
;
1107 case 'j': cursor_down (&active
); goto to_l
;
1109 case 'k': cursor_up (&active
); goto to_l
;
1111 case 'l': cursor_right(&active
); goto to_l
;
1113 case 'H': cursor_to(&active
,TAB_1
); goto to_l
;
1115 case 'L': cursor_to(&active
,TAB_MAX
); goto to_l
;
1117 case 'M': cursor_to(&active
,TAB_MAX
/2); goto to_l
;
1118 case 'J': /* fallthrough; just join selected pile */
1121 break; /* continues with the foundation/empty tableu check */
1123 case MOUSE_RIGHT
: return CMD_NONE
;
1125 if (set_mouse(term2pile(mouse
), to
, opt
))
1128 case 'K': /* fallthrough */
1129 case '?': return CMD_HINT
;
1130 case 'f': return CMD_FIND
; // XXX: will cancel from-card
1131 case '/': return CMD_SEARCH
; //ditto.
1132 case 'u': return CMD_NONE
; /* cancel selection */
1133 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1135 if (t
< '0' || t
> '9') return CMD_INVAL
;
1139 #elif defined SPIDER
1141 #elif defined FREECELL
1149 last_successful_action
[0] = _f
;
1150 last_successful_action
[1] = t
;
1153 /* direct addressing post-processing stage:
1154 because foundations/freecells share the same key (and you can't select
1155 partial piles) there are sometimes ambiguous situations where it isn't
1156 clear from which pile (or how many cards) to take. the code below will
1157 only ask the user if there are at least two possible moves and
1158 automatically choose otherwise. */
1160 /* if it was selected with a cursor, it's obvious: */
1161 if (inactive
.opt
>= 0) {
1162 if (is_tableu(*from
)) {
1163 /* NOTE: max_move same as in cursor_down() */
1164 *opt
= max_move(*from
, -1) - inactive
.opt
;
1166 *opt
= inactive
.opt
;
1168 /* moving from tableu to empty tableu? */
1169 } else if(is_tableu(*from
) && is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
){
1170 int top
= find_top(f
.t
[*from
]);
1171 int max
= max_move(*from
, *to
);
1173 if (top
< 0) return CMD_INVAL
;
1174 if (max
== 1) { /* only 1 movable? */
1175 return *opt
= 1, CMD_MOVE
;
1176 } else { /* only ask the user if it's unclear: */
1177 int bottom
= top
- (max
-1);
1178 printf ("\rup to ([a23456789xjqk] or space/return): ");
1181 case ' ': rank
= get_rank(f
.t
[*from
][top
]); break;
1182 case'\n': rank
= get_rank(f
.t
[*from
][bottom
]); break;
1183 case 'a': case 'A': rank
= RANK_A
; break;
1184 case '0': /* fallthrough */
1185 case 'x': case 'X': rank
= RANK_X
; break;
1186 case 'j': case 'J': rank
= RANK_J
; break;
1187 case 'q': case 'Q': rank
= RANK_Q
; break;
1188 case 'k': case 'K': rank
= RANK_K
; break;
1189 default: rank
-= '1';
1191 if (rank
< RANK_A
|| rank
> RANK_K
) return CMD_INVAL
;
1193 for (int i
= 0; max
--; i
++)
1194 if (get_rank(f
.t
[*from
][top
-i
]) == rank
)
1195 return *opt
= 1+i
, CMD_MOVE
;
1199 /* `opt` is the number of cards to move */
1200 /* moving between stock/foundation? */
1201 } else if (*from
== FOUNDATION
&& *to
== FOUNDATION
) {
1202 return CMD_INVAL
; /* nonsensical */
1203 } else if (*from
== FOUNDATION
&& *to
== STOCK
) {
1204 if (f
.w
== (1<<NUM_CELLS
)-1) return CMD_INVAL
; /*no free cells*/
1205 int ok_foundation
; /* find compatible (non-empty) foundations:*/
1206 int used_fs
=0; for (int i
= 0; i
< NUM_SUITS
; i
++)
1207 if (!!f
.f
[i
][0]) ok_foundation
= i
, used_fs
++;
1209 if (used_fs
== 0) return CMD_INVAL
; /* nowhere to take from */
1210 if (used_fs
== 1) { /* take from the only one */
1211 return *opt
= ok_foundation
, CMD_MOVE
;
1212 } else { /* ask user */
1213 printf ("take from (1-4): "); fflush (stdout
);
1214 *opt
= getch(NULL
) - '1';
1215 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1217 /* `opt` is the foundation index (0..3) */
1218 } else if (*from
== STOCK
) { /* cell -> foundation/tableu */
1219 if (!f
.w
) return CMD_INVAL
; /* no cell to take from */
1220 int ok_cell
; /* find compatible (non-empty) cells: */
1221 int tab
= is_tableu(*to
);
1222 int used_cs
=0; for (int i
= 0; i
< NUM_CELLS
; i
++) {
1223 card_t
* pile
= (tab
?f
.t
[*to
]:f
.f
[get_suit(f
.s
[i
])]);
1224 int top_to
= find_top(pile
);
1225 if (tab
? /* to tableu? */
1226 ((top_to
<0 && f
.s
[i
] > NO_CARD
)
1227 ||(top_to
>=0 && rank_next(f
.s
[i
], pile
[top_to
])
1228 && color_ok(f
.s
[i
], pile
[top_to
])))
1229 : /* to foundation? */
1230 ((top_to
<0 && get_rank(f
.s
[i
]) == RANK_A
)
1231 ||(top_to
>=0 && rank_next(pile
[top_to
],f
.s
[i
])))
1233 ok_cell
= i
, used_cs
++;
1236 if (used_cs
== 0) return CMD_INVAL
; /* nowhere to take from */
1237 if (used_cs
== 1) { /* take from the only one */
1238 return *opt
= ok_cell
, CMD_MOVE
;
1239 } else { /* ask user */
1240 printf ("take from (1-4): "); fflush (stdout
);
1241 *opt
= getch(NULL
) - '1';
1242 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1244 /* `opt` is the cell index (0..3) */
1247 //TODO: mouse-friendly "up to?" dialog
1248 #if defined KLONDIKE || defined FREECELL
1249 if (*from
== FOUNDATION
) {
1250 if (inactive
.opt
>= 0) {
1251 *opt
= inactive
.opt
;
1254 int top
= find_top(f
.t
[*to
]);
1255 if (top
< 0) return CMD_INVAL
;
1256 int color
= get_color(f
.t
[*to
][top
]);
1257 int choice_1
= 1-color
; /* selects piles of */
1258 int choice_2
= 2+color
; /* the opposite color */
1259 int top_c1
= find_top(f
.f
[choice_1
]);
1260 int top_c2
= find_top(f
.f
[choice_2
]);
1262 switch ((rank_next(f
.f
[choice_1
][top_c1
], f
.t
[*to
][top
])
1263 && top_c1
>= 0 ) << 0
1264 |(rank_next(f
.f
[choice_2
][top_c2
], f
.t
[*to
][top
])
1265 && top_c2
>= 0 ) << 1) {
1266 case ( 1<<0): *opt
= choice_1
; break; /* choice_1 only */
1267 case (1<<1 ): *opt
= choice_2
; break; /* choice_2 only */
1268 case (1<<1 | 1<<0): /* both, ask user which to pick from */
1269 printf ("take from (1-4): "); fflush (stdout
);
1270 *opt
= getch(NULL
) - '1';
1271 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1273 default: return CMD_INVAL
; /* none matched */
1275 /* `opt` is the foundation index (0..3) */
1277 #elif defined SPIDER
1278 /* moving to empty tableu? */
1279 if (is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
) {
1280 int bottom
= first_movable(f
.t
[*from
]);
1281 if (inactive
.opt
>= 0) { /*if from was cursor addressed: */
1282 *opt
= get_rank(f
.t
[*from
][bottom
+ inactive
.opt
]);
1285 int top
= find_top(f
.t
[*from
]);
1286 if (top
< 0) return CMD_INVAL
;
1287 if (top
>= 0 && !is_movable(f
.t
[*from
], top
-1)) {
1288 *opt
= get_rank(f
.t
[*from
][top
]);
1289 } else { /* only ask the user if it's unclear: */
1290 printf ("\rup to ([a23456789xjqk] or space/return): ");
1293 case ' ': *opt
= get_rank(f
.t
[*from
][top
]); break;
1294 case'\n': *opt
= get_rank(f
.t
[*from
][bottom
]); break;
1295 case 'a': case 'A': *opt
= RANK_A
; break;
1296 case '0': /* fallthrough */
1297 case 'x': case 'X': *opt
= RANK_X
; break;
1298 case 'j': case 'J': *opt
= RANK_J
; break;
1299 case 'q': case 'Q': *opt
= RANK_Q
; break;
1300 case 'k': case 'K': *opt
= RANK_K
; break;
1301 default: *opt
-= '1';
1303 if (*opt
< RANK_A
|| *opt
> RANK_K
) return CMD_INVAL
;
1305 /* `opt` is the rank of the highest card to move */
1311 int getctrlseq(unsigned char* buf
) {
1319 int offset
= 0x20; /* never sends control chars as data */
1320 while ((c
= getchar()) != EOF
) {
1324 case '\033': state
=ESC_SENT
; break;
1330 case '[': state
=CSI_SENT
; break;
1331 default: return KEY_INVAL
;
1336 case 'A': return KEY_UP
;
1337 case 'B': return KEY_DOWN
;
1338 case 'C': return KEY_RIGHT
;
1339 case 'D': return KEY_LEFT
;
1340 /*NOTE: home/end send ^[[x~ . no support for modifiers*/
1341 case 'H': return KEY_HOME
;
1342 case 'F': return KEY_END
;
1343 case '2': getchar(); return KEY_INS
;
1344 case '5': getchar(); return KEY_PGUP
;
1345 case '6': getchar(); return KEY_PGDN
;
1346 case 'M': state
=MOUSE_EVENT
; break;
1347 default: return KEY_INVAL
;
1351 if (buf
== NULL
) return KEY_INVAL
;
1352 buf
[0] = c
- offset
;
1353 buf
[1] = getchar() - offset
;
1354 buf
[2] = getchar() - offset
;
1362 int term2pile(unsigned char *mouse
) {
1363 int line
= (mouse
[2]-1);
1364 int column
= (mouse
[1]-1) / op
.s
->width
;
1366 if (line
< op
.s
->height
) { /* first line */
1369 case 0: return STOCK
;
1370 case 1: return WASTE
;
1371 case 2: return -1; /* spacer */
1372 case 3: return FOUNDATION
+0;
1373 case 4: return FOUNDATION
+1;
1374 case 5: return FOUNDATION
+2;
1375 case 6: return FOUNDATION
+3;
1377 #elif defined SPIDER
1378 if (column
< 3) return STOCK
;
1380 #elif defined FREECELL
1381 if (column
< NUM_SUITS
+ NUM_CELLS
) return STOCK
+column
;
1384 } else if (line
> op
.s
->height
) { /* tableu */
1385 if (column
<= TAB_MAX
) return column
;
1389 int wait_mouse_up(unsigned char* mouse
) {
1390 //TODO: mouse drag: start gets inactive, hovering gets active cursors
1391 struct cursor cur
= {-1,-1};
1393 /* note: if dragged [3]==1 and second position is in mouse[0,4,5] */
1395 /* display a cursor while mouse button is pushed: */
1396 int pile
= term2pile(mouse
);
1399 if (pile
>= FOUNDATION
) {
1400 cur
.pile
= FOUNDATION
;
1401 cur
.opt
= pile
-FOUNDATION
;
1403 #elif defined FREECELL
1404 if (pile
> TAB_MAX
) {
1405 cur
.pile
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
1406 cur
.opt
= (pile
-STOCK
) % 4;
1409 /* need to temporarily show the cursor, then revert to last state: */
1410 int old_show_cursor_hi
= op
.h
; //TODO: ARGH! that's awful!
1412 print_table(&cur
, NO_HI
); //TODO: should not overwrite inactive cursor!
1413 op
.h
= old_show_cursor_hi
;
1416 if (getctrlseq (mouse
+3) == MOUSE_ANY
) {
1417 /* ignore mouse wheel events: */
1418 if (mouse
[3] & 0x40) continue;
1420 else if((mouse
[3]&3) == 3) level
--; /* release event */
1421 else level
++; /* another button pressed */
1425 int success
= mouse
[1] == mouse
[4] && mouse
[2] == mouse
[5];
1432 int getch(unsigned char* buf
) {
1433 //TODO: if buf==NULL disable mouse input
1434 /* returns a character, EOF, or constant for an escape/control sequence - NOT
1435 compatible with the ncurses implementation of same name */
1437 if (buf
&& buf
[3]) {
1438 /* mouse was dragged; return 'ungetted' previous destination */
1439 action
= MOUSE_DRAG
;
1440 /* keep original [0], as [3] only contains release event */
1445 action
= getctrlseq(buf
);
1450 if (buf
[0] > 3) break; /* ignore wheel events */
1455 case 0: return MOUSE_LEFT
;
1456 case 1: return MOUSE_MIDDLE
;
1457 case 2: return MOUSE_RIGHT
;
1465 // shuffling and dealing {{{
1466 void deal(long seed
) {
1467 //TODO: clear hls/f.h
1468 f
= (const struct playfield
){0}; /* clear playfield */
1469 card_t deck
[DECK_SIZE
*NUM_DECKS
];
1470 int avail
= DECK_SIZE
*NUM_DECKS
;
1471 for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) deck
[i
] = (i
%DECK_SIZE
)+1;
1473 if (op
.m
!= NORMAL
) for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) {
1474 if (op
.m
== MEDIUM
) deck
[i
] = 1+((deck
[i
]-1) | 2);
1475 if (op
.m
== EASY
) deck
[i
] = 1+((deck
[i
]-1) | 2 | 1);
1476 /* the 1+ -1 dance gets rid of the offset created by NO_CARD */
1480 for (int i
= DECK_SIZE
*NUM_DECKS
-1; i
> 0; i
--) { /* fisher-yates */
1481 int j
= rand() % (i
+1);
1482 if (j
-i
) deck
[i
]^=deck
[j
],deck
[j
]^=deck
[i
],deck
[i
]^=deck
[j
];
1486 for (int i
= 0; i
< NUM_PILES
; i
++) {
1489 int count
= i
; /* pile n has n closed cards, then 1 open */
1490 #elif defined SPIDER
1492 int count
= i
<4?5:4; /* pile 1-4 have 5, 5-10 have 4 closed */
1493 #elif defined FREECELL
1495 int count
= i
<4?6:5;/*like spider, but cards are dealt face-up*/
1497 /* "SIGN": face down cards are negated */
1498 for (int j
= 0; j
< count
; j
++) f
.t
[i
][j
] = SIGN deck
[--avail
];
1499 f
.t
[i
][count
] = deck
[--avail
]; /* the face-up card */
1502 /* rest of the cards to the stock: */
1503 /* NOTE: assert(avail==50) for spider, assert(avail==0) for freecell */
1504 for (f
.z
= 0; avail
; f
.z
++) f
.s
[f
.z
] = deck
[--avail
];
1506 f
.w
= -1; /* @start: nothing on waste */
1507 #elif defined SPIDER
1508 f
.w
= 0; /* number of used foundations */
1509 #elif defined FREECELL
1510 f
.w
= 0; /* bitmask of used free cells */
1513 f
.u
= &undo_sentinel
;
1517 // screen drawing routines {{{
1518 void print_hi(int invert
, int grey_bg
, int bold
, int blink
, char* str
) {
1519 if (!op
.h
) invert
= 0; /* don't show invert if we used the mouse last */
1520 if (bold
&& op
.s
== &unicode_large_color
){ //awful hack for bold + faint
1521 int offset
= str
[3]==017?16:str
[4]==017?17:0;
1522 printf ("%s%s%s%s""%.*s%s%s""%s%s%s%s",
1523 "\033[1m", invert
?"\033[7m":"", grey_bg
?"\033[100m":"", blink
?"\033[5m":"",
1524 offset
, str
, bold
?"\033[1m":"", str
+offset
,
1525 blink
?"\033[25m":"", grey_bg
?"\033[49m":"", invert
?"\033[27m":"","\033[22m");
1528 printf ("%s%s%s%s%s%s%s%s%s",
1529 bold
?"\033[1m":"", invert
?"\033[7m":"", grey_bg
?"\033[100m":"", blink
?"\033[5m":"",
1531 blink
?"\033[25m":"", grey_bg
?"\033[49m":"", invert
?"\033[27m":"",bold
?"\033[22m":"");
1533 void print_table(const struct cursor
* active
, const struct cursor
* inactive
) {
1534 printf("\033[2J\033[H"); /* clear screen, reset cursor */
1536 /* print stock, waste and foundation: */
1537 for (int line
= 0; line
< op
.s
->height
; line
++) {
1539 print_hi (active
->pile
== STOCK
, inactive
->pile
== STOCK
, 1, 0, (
1540 (f
.w
< f
.z
-1)?op
.s
->facedown
1541 :op
.s
->placeholder
)[line
]);
1543 int do_blink
= hls(f
.s
[f
.w
], f
.h
); //xxx: unnecessarily recalculating
1544 print_hi (active
->pile
== WASTE
, inactive
->pile
== WASTE
, 1, do_blink
, (
1545 /* NOTE: cast, because f.w sometimes is (short)-1 !? */
1546 ((short)f
.w
>= 0)?op
.s
->card
[f
.s
[f
.w
]]
1547 :op
.s
->placeholder
)[line
]);
1548 printf ("%s", op
.s
->card
[NO_CARD
][line
]); /* spacer */
1550 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1551 int card
= find_top(f
.f
[pile
]);
1552 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1553 inactive
->pile
==FOUNDATION
&& (
1554 /* cursor addr. || direct addr. */
1555 inactive
->opt
==pile
|| inactive
->opt
< 0
1556 ), !!f
.f
[pile
][0], 0,
1557 (card
< 0)?op
.s
->foundation
[line
]
1558 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1563 #elif defined SPIDER
1564 int fdone
; for (fdone
= NUM_DECKS
*NUM_SUITS
; fdone
; fdone
--)
1565 if (f
.f
[fdone
-1][RANK_K
]) break; /*number of completed stacks*/
1566 int spacer_from
= f
.z
?(f
.z
/10-1) * op
.s
->halfwidth
[0] + op
.s
->width
:0;
1567 int spacer_to
= NUM_PILES
*op
.s
->width
-
1568 ((fdone
?(fdone
-1) * op
.s
->halfwidth
[1]:0)+op
.s
->width
);
1569 for (int line
= 0; line
< op
.s
->height
; line
++) {
1570 /* available stock: */
1571 for (int i
= f
.z
/10; i
; i
--) {
1572 if (i
==1) printf ("%s", op
.s
->facedown
[line
]);
1573 else printf ("%s", op
.s
->halfstack
[line
]);
1576 for (int i
= spacer_from
; i
< spacer_to
; i
++) printf (" ");
1577 /* foundation (overlapping): */
1578 for (int i
= NUM_DECKS
*NUM_SUITS
-1, half
= 0; i
>= 0; i
--) {
1579 int overlap
= half
? op
.s
->halfcard
[line
]: 0;
1580 if (f
.f
[i
][RANK_K
]) printf ("%.*s", op
.s
->halfwidth
[2],
1581 op
.s
->card
[f
.f
[i
][RANK_K
]][line
]+overlap
),
1587 #elif defined FREECELL
1588 /* print open cells, foundation: */
1589 for (int line
= 0; line
< op
.s
->height
; line
++) {
1590 for (int pile
= 0; pile
< NUM_CELLS
; pile
++) {
1591 int do_blink
= hls(f
.s
[pile
], f
.h
);
1592 print_hi (active
->pile
==STOCK
&& active
->opt
==pile
,
1593 inactive
->pile
==STOCK
&& (
1594 /* cursor addr. || direct addr. */
1595 inactive
->opt
==pile
|| inactive
->opt
< 0
1596 ), !!f
.s
[pile
], do_blink
,
1597 ((f
.s
[pile
])?op
.s
->card
[f
.s
[pile
]]
1598 :op
.s
->placeholder
)[line
]);
1600 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1601 int card
= find_top(f
.f
[pile
]);
1602 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1603 inactive
->pile
==FOUNDATION
&& (
1604 /* cursor addr. || direct addr. */
1605 inactive
->opt
==pile
|| inactive
->opt
< 0
1606 ), !!f
.f
[pile
][0], 0,
1607 (card
< 0)?op
.s
->foundation
[line
]
1608 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1615 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty))
1616 #define TOP_HI(c) 1 /* can't select partial stacks in KLONDIKE */
1617 #elif defined SPIDER || defined FREECELL
1618 int offset
[NUM_PILES
]={0}; /* first card to highlight */
1620 int bottom
[NUM_PILES
]; /* first movable card */
1621 for (int i
=0; i
<NUM_PILES
; i
++)
1622 bottom
[i
] = find_top(f
.t
[i
]) - max_move(i
,-1);
1624 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty) \
1625 && offset[pile] >= cursor->opt)
1626 #define TOP_HI(cursor) (cursor->pile == pile && movable \
1627 && offset[pile] == cursor->opt)
1629 /* print tableu piles: */
1630 int row
[NUM_PILES
] = {0};
1631 int line
[NUM_PILES
]= {0};
1632 int label
[NUM_PILES
]={0};
1634 int did_placeholders
= 0;
1637 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
1638 card_t card
= f
.t
[pile
][row
[pile
]];
1639 card_t next
= f
.t
[pile
][row
[pile
]+1];
1640 int movable
= is_movable(f
.t
[pile
], row
[pile
]);
1641 int do_blink
= hls(card
, f
.h
);
1643 if(row
[pile
] <= bottom
[pile
]) movable
= 0;
1645 int empty
= !card
&& row
[pile
] == 0;
1647 print_hi (DO_HI(active
), DO_HI(inactive
), movable
, do_blink
, (
1648 (!card
&& row
[pile
] == 0)?op
.s
->placeholder
1649 :(card
<0)?op
.s
->facedown
1653 int extreme_overlap
= ( 3 /* spacer, labels, status */
1654 + 2 * op
.s
->height
/* stock, top tableu card */
1655 + find_top(f
.t
[pile
]) * op
.s
->overlap
) >op
.w
[0];
1656 /* normal overlap: */
1657 if (++line
[pile
] >= (next
?op
.s
->overlap
:op
.s
->height
)
1658 /* extreme overlap on closed cards: */
1659 || (extreme_overlap
&&
1661 f
.t
[pile
][row
[pile
]] < 0 &&
1662 f
.t
[pile
][row
[pile
]+1] <0)
1663 /* extreme overlap on sequences: */
1664 || (extreme_overlap
&&
1665 !TOP_HI(active
) && /*always show top selected card*/
1666 line
[pile
] >= 1 && row
[pile
] > 0 &&
1667 f
.t
[pile
][row
[pile
]-1] > NO_CARD
&&
1668 is_consecutive (f
.t
[pile
], row
[pile
]) &&
1669 is_consecutive (f
.t
[pile
], row
[pile
]-1) &&
1670 f
.t
[pile
][row
[pile
]+1] != NO_CARD
)
1674 #if defined SPIDER || defined FREECELL
1675 if (movable
) offset
[pile
]++;
1678 /* tableu labels: */
1679 if(!card
&& !label
[pile
] && row
[pile
]>0&&line
[pile
]>0) {
1681 printf ("\b\b%d ", (pile
+1) % 10); //XXX: hack
1683 line_had_card
|= !!card
;
1684 did_placeholders
|= row
[pile
] > 0;
1687 } while (line_had_card
|| !did_placeholders
);
1690 void visbell (void) {
1692 printf ("\033[?5h"); fflush (stdout
);
1694 printf ("\033[?5l"); fflush (stdout
);
1696 void win_anim(void) {
1697 printf ("\033[?25l"); /* hide cursor */
1699 /* set cursor to random location */
1700 int row
= 1+rand()%(1+op
.w
[0]-op
.s
->height
);
1701 int col
= 1+rand()%(1+op
.w
[1]-op
.s
->width
);
1703 /* draw random card */
1704 int face
= 1 + rand() % 52;
1705 for (int l
= 0; l
< op
.s
->height
; l
++) {
1706 printf ("\033[%d;%dH", row
+l
, col
);
1707 printf ("%s", op
.s
->card
[face
][l
]);
1711 /* exit on keypress */
1712 struct pollfd p
= {STDIN_FILENO
, POLLIN
, 0};
1713 if (poll (&p
, 1, 80)) goto fin
;
1716 printf ("\033[?25h"); /* show cursor */
1722 void undo_push (int _f
, int t
, int n
, int o
) {
1723 struct undo
* new = malloc(sizeof(struct undo
));
1733 void undo_pop (struct undo
* u
) {
1734 if (u
== &undo_sentinel
) return;
1737 if (u
->f
== FOUNDATION
) {
1738 /* foundation -> tableu */
1739 int top_f
= find_top(f
.f
[u
->n
]);
1740 int top_t
= find_top(f
.t
[u
->t
]);
1741 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1742 f
.t
[u
->t
][top_t
] = NO_CARD
;
1743 } else if (u
->f
== WASTE
&& u
->t
== FOUNDATION
) {
1744 /* waste -> foundation */
1745 /* split u->n into wst and fnd: */
1746 int wst
= u
->n
& 0xffff;
1747 int fnd
= u
->n
>> 16;
1748 /* move stock cards one position up to make room: */
1749 for (int i
= f
.z
; i
>= wst
; i
--) f
.s
[i
+1] = f
.s
[i
];
1750 /* move one card from foundation to waste: */
1751 int top
= find_top(f
.f
[fnd
]);
1752 f
.s
[wst
] = f
.f
[fnd
][top
];
1753 f
.f
[fnd
][top
] = NO_CARD
;
1756 } else if (u
->f
== WASTE
) {
1757 /* waste -> tableu */
1758 /* move stock cards one position up to make room: */
1759 for (int i
= f
.z
-1; i
>= u
->n
; i
--) f
.s
[i
+1] = f
.s
[i
];
1760 /* move one card from tableu to waste: */
1761 int top
= find_top(f
.t
[u
->t
]);
1762 f
.s
[u
->n
] = f
.t
[u
->t
][top
];
1763 f
.t
[u
->t
][top
] = NO_CARD
;
1766 } else if (u
->t
== FOUNDATION
) {
1767 /* tableu -> foundation */
1768 int top_f
= find_top(f
.t
[u
->f
]);
1769 int top_t
= find_top(f
.f
[u
->n
]);
1770 /* close topcard if previous action caused turn_over(): */
1771 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1772 /* move one card from foundation to tableu: */
1773 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1774 f
.f
[u
->n
][top_t
] = NO_CARD
;
1776 /* tableu -> tableu */
1777 int top_f
= find_top(f
.t
[u
->f
]);
1778 int top_t
= find_top(f
.t
[u
->t
]);
1779 /* close topcard if previous action caused turn_over(): */
1780 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1781 /* move n cards from tableu[f] to tableu[t]: */
1782 for (int i
= 0; i
< u
->n
; i
++) {
1783 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1784 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1787 #elif defined SPIDER
1788 if (u
->f
== STOCK
) {
1789 /* stock -> tableu */
1790 /*remove a card from each pile and put it back onto the stock:*/
1791 for (int pile
= NUM_PILES
-1; pile
>= 0; pile
--) {
1792 int top
= find_top(f
.t
[pile
]);
1793 f
.s
[f
.z
++] = f
.t
[pile
][top
];
1794 f
.t
[pile
][top
] = NO_CARD
;
1796 } else if (u
->t
== FOUNDATION
) {
1797 /* tableu -> foundation */
1798 int top
= find_top(f
.t
[u
->f
]);
1799 /* close topcard if previous action caused turn_over(): */
1800 if (u
->o
) f
.t
[u
->f
][top
] *= -1;
1801 /* append cards from foundation to tableu */
1802 for (int i
= RANK_K
; i
>= RANK_A
; i
--) {
1803 f
.t
[u
->f
][++top
] = f
.f
[u
->n
][i
];
1804 f
.f
[u
->n
][i
] = NO_CARD
;
1806 f
.w
--; /* decrement complete-foundation-counter */
1809 /* tableu -> tableu */
1810 int top_f
= find_top(f
.t
[u
->f
]);
1811 int top_t
= find_top(f
.t
[u
->t
]);
1812 /* close topcard if previous action caused turn_over(): */
1813 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1814 /* move n cards from tableu[f] to tableu[t]: */
1815 for (int i
= 0; i
< u
->n
; i
++) {
1816 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1817 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1820 #elif defined FREECELL
1821 /*NOTE: if from and to are both stock/foundation, opt = from | to<<16 */
1822 if (u
->f
== STOCK
&& u
->t
== FOUNDATION
) {
1823 /* free cells -> foundation */
1824 /* split u->n into cll and fnd: */
1825 int cll
= u
->n
& 0xffff;
1826 int fnd
= u
->n
>> 16;
1827 /* move one card from foundation to free cell: */
1828 int top
= find_top(f
.f
[fnd
]);
1829 f
.s
[cll
] = f
.f
[fnd
][top
];
1830 f
.f
[fnd
][top
] = NO_CARD
;
1831 f
.w
|= 1<<cll
; /* mark cell as occupied */
1832 } else if (u
->f
== STOCK
) {
1833 /* free cells -> cascade */
1834 int top_t
= find_top(f
.t
[u
->t
]);
1835 f
.s
[u
->n
] = f
.t
[u
->t
][top_t
];
1836 f
.t
[u
->t
][top_t
] = NO_CARD
;
1837 f
.w
|= 1<<u
->n
; /* mark cell as occupied */
1838 } else if (u
->f
== FOUNDATION
&& u
->t
== STOCK
) {
1839 /* foundation -> free cells */
1840 /* split u->n into cll and fnd: */
1841 int cll
= u
->n
>> 16;
1842 int fnd
= u
->n
& 0xffff;
1843 /* move 1 card from free cell to foundation: */
1844 int top_f
= find_top(f
.f
[fnd
]);
1845 f
.f
[fnd
][top_f
+1] = f
.s
[cll
];
1847 f
.w
&= ~(1<<cll
); /* mark cell as free */
1848 } else if (u
->f
== FOUNDATION
) {
1849 /* foundation -> cascade */
1850 int top_f
= find_top(f
.f
[u
->n
]);
1851 int top_t
= find_top(f
.t
[u
->t
]);
1852 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1853 f
.t
[u
->t
][top_t
] = NO_CARD
;
1854 } else if (u
->t
== STOCK
) {
1855 /* cascade -> free cells */
1856 int top_f
= find_top(f
.t
[u
->f
]);
1857 f
.t
[u
->f
][top_f
+1] = f
.s
[u
->n
];
1858 f
.s
[u
->n
] = NO_CARD
;
1859 f
.w
&= ~(1<<u
->n
); /* mark cell as free */
1860 } else if (u
->t
== FOUNDATION
) {
1861 /* cascade -> foundation */
1862 int top_f
= find_top(f
.t
[u
->f
]);
1863 int top_t
= find_top(f
.f
[u
->n
]);
1864 /* move one card from foundation to cascade: */
1865 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1866 f
.f
[u
->n
][top_t
] = NO_CARD
;
1868 /* cascade -> cascade */
1869 int top_f
= find_top(f
.t
[u
->f
]);
1870 int top_t
= find_top(f
.t
[u
->t
]);
1871 /* move n cards from tableu[f] to tableu[t]: */
1872 for (int i
= 0; i
< u
->n
; i
++) {
1873 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1874 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1883 void free_undo (struct undo
* u
) {
1884 while (u
&& u
!= &undo_sentinel
) {
1892 // initialization stuff {{{
1893 void screen_setup (int enable
) {
1896 printf ("\033[s\033[?47h"); /* save cursor, alternate screen */
1897 printf ("\033[H\033[J"); /* reset cursor, clear screen */
1898 printf ("\033[?1000h"); /* enable mouse */
1900 printf ("\033[?1000l"); /* disable mouse */
1901 printf ("\033[?47l\033[u"); /* primary screen, restore cursor */
1906 void raw_mode(int enable
) {
1907 static struct termios saved_term_mode
;
1908 struct termios raw_term_mode
;
1911 if (saved_term_mode
.c_lflag
== 0)/*don't overwrite stored mode*/
1912 tcgetattr(STDIN_FILENO
, &saved_term_mode
);
1913 raw_term_mode
= saved_term_mode
;
1914 raw_term_mode
.c_lflag
&= ~(ICANON
| ECHO
);
1915 raw_term_mode
.c_cc
[VMIN
] = 1 ;
1916 raw_term_mode
.c_cc
[VTIME
] = 0;
1917 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw_term_mode
);
1919 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &saved_term_mode
);
1923 void signal_handler (int signum
) {
1928 signal(SIGTSTP
, SIG_DFL
); /* NOTE: assumes SysV semantics! */
1933 print_table(NO_HI
, NO_HI
);
1935 case SIGINT
: //TODO: don't exit; just warn like vim does
1938 ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &w
);
1944 void signal_setup(void) {
1945 struct sigaction saction
;
1947 saction
.sa_handler
= signal_handler
;
1948 sigemptyset(&saction
.sa_mask
);
1949 saction
.sa_flags
= 0;
1950 if (sigaction(SIGTSTP
, &saction
, NULL
) < 0) {
1954 if (sigaction(SIGCONT
, &saction
, NULL
) < 0) {
1958 if (sigaction(SIGINT
, &saction
, NULL
) < 0) {
1962 if (sigaction(SIGWINCH
, &saction
, NULL
) < 0) {
1963 perror ("SIGWINCH");
1969 //vim: foldmethod=marker