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
152 case CMD_UNDO
: undo_pop(f
.u
); break;
153 case CMD_INVAL
: visbell(); break;
154 case CMD_NEW
: return GAME_NEW
;
155 case CMD_AGAIN
: goto restart
;
156 case CMD_QUIT
: return GAME_QUIT
;
158 printf (KEYHELP
"\nPress any key to continue.");
171 // card games helper functions {{{
172 #define get_suit(card) \
173 ((card-1) % NUM_SUITS)
174 #define get_rank(card) \
175 ((card-1) / NUM_SUITS)
176 #define get_color(card) \
177 ((get_suit(card) ^ get_suit(card)>>1) & 1)
179 int find_top(card_t
* pile
) {
181 for(i
=PILE_SIZE
-1; i
>=0 && !pile
[i
]; i
--);
184 int first_movable(card_t
* pile
) {
185 /* NOTE: in FREECELL this does not take max_move into account! */
187 for (;pile
[i
] && !is_movable(pile
, i
); i
++);
190 int turn_over(card_t
* pile
) {
191 int top
= find_top(pile
);
197 int check_won(void) {
198 for (int pile
= 0; pile
< NUM_DECKS
*NUM_SUITS
; pile
++)
199 if (f
.f
[pile
][NUM_RANKS
-1] == NO_CARD
) return 0;
203 int rank_next (card_t a
, card_t b
) {
204 return get_rank(a
) == get_rank(b
)-1;
206 int color_ok (card_t a
, card_t b
) {
207 #if defined KLONDIKE || defined FREECELL
208 /* color opposite? */
209 return (get_color(a
) != get_color(b
));
212 return (get_suit(a
) == get_suit(b
));
215 int is_consecutive (card_t
* pile
, int pos
) {
216 if (pos
+1 >= PILE_SIZE
) return 1; /* card is last */
217 if (pile
[pos
+1] == NO_CARD
) return 1; /* card is first */
219 /* ranks consecutive? */
220 if (!rank_next(pile
[pos
+1], pile
[pos
])) return 0;
222 if (!color_ok(pile
[pos
+1], pile
[pos
])) return 0;
227 int is_movable(card_t
* pile
, int n
) {
229 return(pile
[n
] > NO_CARD
); /*non-movable cards don't exist in klondike*/
230 #elif defined SPIDER || defined FREECELL
231 int top
= find_top(pile
);
232 for (int i
= top
; i
>= 0; i
--) {
233 if (pile
[i
] <= NO_CARD
) return 0; /*no card or card face down?*/
234 if (!is_consecutive(pile
, i
)) return 0;
235 if (i
== n
) return 1; /* card reached, must be movable */
242 // takeable actions {{{
244 card_t
stack_take(void) { /*NOTE: assert(f.w >= 0) */
245 card_t card
= f
.s
[f
.w
];
246 /* move stack one over, so there are no gaps in it: */
247 for (int i
= f
.w
; i
< f
.z
-1; i
++)
250 f
.w
--; /* make previous card visible again */
253 int t2f(int from
, int to
, int opt
) { /* tableu to foundation */
254 (void) to
; (void) opt
; /* don't need */
255 int top_from
= find_top(f
.t
[from
]);
256 to
= get_suit(f
.t
[from
][top_from
]);
257 int top_to
= find_top(f
.f
[to
]);
258 if ((top_to
< 0 && get_rank(f
.t
[from
][top_from
]) == RANK_A
)
259 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.t
[from
][top_from
]))) {
260 f
.f
[to
][top_to
+1] = f
.t
[from
][top_from
];
261 f
.t
[from
][top_from
] = NO_CARD
;
262 undo_push(from
, FOUNDATION
, to
,
263 turn_over(f
.t
[from
]));
264 if (check_won()) return WON
;
268 int w2f(int from
, int to
, int opt
) { /* waste to foundation */
269 (void) from
; (void) to
; (void) opt
; /* don't need */
270 if (f
.w
< 0) return ERR
;
271 to
= get_suit(f
.s
[f
.w
]);
272 int top_to
= find_top(f
.f
[to
]);
273 if ((top_to
< 0 && get_rank(f
.s
[f
.w
]) == RANK_A
)
274 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
], f
.s
[f
.w
]))) {
275 undo_push(WASTE
, FOUNDATION
, f
.w
| to
<<16, 0);//ugly encoding :|
276 f
.f
[to
][top_to
+1] = stack_take();
277 if (check_won()) return WON
;
282 int s2w(int from
, int to
, int opt
) { /* stock to waste */
283 (void) from
; (void) to
; (void) opt
; /* don't need */
284 if (f
.z
== 0) return ERR
;
286 if (f
.w
== f
.z
) f
.w
= -1;
289 int w2s(int from
, int to
, int opt
) { /* waste to stock (undo stock to waste) */
290 (void) from
; (void) to
; (void) opt
; /* don't need */
291 if (f
.z
== 0) return ERR
;
293 if (f
.w
< -1) f
.w
= f
.z
-1;
296 int f2t(int from
, int to
, int opt
) { /* foundation to tableu */
297 (void) from
; /* don't need */
298 int top_to
= find_top(f
.t
[to
]);
300 int top_from
= find_top(f
.f
[from
]);
302 if (color_ok(f
.t
[to
][top_to
], f
.f
[from
][top_from
])
303 && (rank_next(f
.f
[from
][top_from
], f
.t
[to
][top_to
]))) {
304 f
.t
[to
][top_to
+1] = f
.f
[from
][top_from
];
305 f
.f
[from
][top_from
] = NO_CARD
;
306 undo_push(FOUNDATION
, to
, from
, 0);
310 int w2t(int from
, int to
, int opt
) { /* waste to tableu */
311 (void) from
; (void) opt
; /* don't need */
312 if (f
.w
< 0) return ERR
;
313 int top_to
= find_top(f
.t
[to
]);
314 if ((color_ok(f
.t
[to
][top_to
], f
.s
[f
.w
])
315 && (rank_next(f
.s
[f
.w
], f
.t
[to
][top_to
])))
316 || (top_to
< 0 && get_rank(f
.s
[f
.w
]) == RANK_K
)) {
317 undo_push(WASTE
, to
, f
.w
, 0);
318 f
.t
[to
][top_to
+1] = stack_take();
322 int t2t(int from
, int to
, int opt
) { /* tableu to tableu */
323 (void) opt
; /* don't need */
324 int top_to
= find_top(f
.t
[to
]);
325 int top_from
= find_top(f
.t
[from
]);
326 for (int i
= top_from
; i
>=0; i
--) {
327 if ((color_ok(f
.t
[to
][top_to
], f
.t
[from
][i
])
328 && (rank_next(f
.t
[from
][i
], f
.t
[to
][top_to
]))
329 && f
.t
[from
][i
] > NO_CARD
) /* card face up? */
330 || (top_to
< 0 && get_rank(f
.t
[from
][i
]) == RANK_K
)) {
331 /* move cards [i..top_from] to their destination */
333 for (;i
<= top_from
; i
++) {
335 f
.t
[to
][top_to
] = f
.t
[from
][i
];
336 f
.t
[from
][i
] = NO_CARD
;
339 undo_push(from
, to
, count
,
340 turn_over(f
.t
[from
]));
344 return ERR
; /* no such move possible */
347 int remove_if_complete (int pileno
) { //cleanup!
348 card_t
* pile
= f
.t
[pileno
];
349 /* test if K...A complete; move to foundation if so */
350 int top_from
= find_top(pile
);
351 if (get_rank(pile
[top_from
]) != RANK_A
) return 0;
352 for (int i
= top_from
; i
>=0; i
--) {
353 if (!is_consecutive (pile
, i
)) return 0;
354 if (i
+RANK_K
== top_from
/* if ace to king: remove it */
355 && get_rank(pile
[top_from
-RANK_K
]) == RANK_K
) {
356 for(int i
=top_from
, j
=0; i
>top_from
-NUM_RANKS
; i
--,j
++){
357 f
.f
[f
.w
][j
] = pile
[i
];
360 undo_push(pileno
, FOUNDATION
, f
.w
,
369 int t2t(int from
, int to
, int opt
) { //in dire need of cleanup
370 int top_from
= find_top(f
.t
[from
]);
371 int top_to
= find_top(f
.t
[to
]);
372 int empty_to
= (top_to
< 0)? opt
: -1; /* empty pile? */
374 for (int i
= top_from
; i
>= 0; i
--) {
375 if (!is_consecutive(f
.t
[from
], i
)) break;
377 /* is consecutive OR to empty pile and rank ok? */
378 if (rank_next(f
.t
[from
][i
], f
.t
[to
][top_to
])
379 || (empty_to
>= RANK_A
&& get_rank(f
.t
[from
][i
]) == empty_to
)) {
381 for (;i
<= top_from
; i
++) {
383 f
.t
[to
][top_to
] = f
.t
[from
][i
];
384 f
.t
[from
][i
] = NO_CARD
;
387 undo_push(from
, to
, count
,
388 turn_over(f
.t
[from
]));
389 remove_if_complete(to
);
390 if (check_won()) return WON
;
395 return ERR
; /* no such move possible */
397 int s2t(int from
, int to
, int opt
) {
398 (void) from
; (void) to
; (void) opt
; /* don't need */
399 if (f
.z
<= 0) return ERR
; /* stack out of cards */
400 for (int pile
= 0; pile
< NUM_PILES
; pile
++)
401 if (f
.t
[pile
][0]==NO_CARD
) return ERR
; /*no piles may be empty*/
403 undo_push(STOCK
, TABLEU
, 1, 0); /* NOTE: before remove_if_complete()! */
404 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
405 f
.t
[pile
][find_top(f
.t
[pile
])+1] = f
.s
[--f
.z
];
406 remove_if_complete(pile
);
407 if (check_won()) return WON
;
411 int t2f(int from
, int to
, int opt
) {
412 (void) to
; (void) opt
; /* don't need */
413 /* manually retrigger remove_if_complete() (e.g. after undo_pop) */
414 return remove_if_complete(from
)?OK
:ERR
;
416 #elif defined FREECELL
417 int max_move(int from
, int to
) {
418 /* returns the maximum number of cards that can be moved */
419 /* see also: https://boardgames.stackexchange.com/a/45157/26498 */
420 int free_tabs
= 0, free_cells
= 0;
421 for (int i
= 0; i
< NUM_PILES
; i
++) free_tabs
+= f
.t
[i
][0] == NO_CARD
;
422 for (int i
= 0; i
< NUM_CELLS
; i
++) free_cells
+= f
.s
[i
] == NO_CARD
;
424 /* don't count the tableau we are moving to: */
425 if (to
>= 0 && f
.t
[to
][0] == NO_CARD
) free_tabs
--;
427 /* theoretic maximum is limited by the number of cards on the pile */
428 int max_theory
= (1<<free_tabs
) * (free_cells
+ 1);
429 int max_effective
= 1 + find_top(f
.t
[from
]) - first_movable(f
.t
[from
]);
430 return max_effective
< max_theory
? max_effective
: max_theory
;
432 //TODO FREECELL: auto move to tableu after each move (not all cards possible, only when it is the smallest rank still on the board)
433 int t2t(int from
, int to
, int opt
) {
434 int top_to
= find_top(f
.t
[to
]);
435 int top_from
= find_top(f
.t
[from
]);
436 int cards
= max_move(from
, to
);
437 if (top_to
< 0) { /* moving to empty pile? */
439 return ERR
; /* cannot execute move */
440 cards
= opt
; /* user wants to move n cards*/
443 for (int i
= top_from
; i
>=0; i
--) {
444 if (cards
-->0/*enough space and not more attempted than wanted*/
445 && ((top_to
>= 0 /* if destn. not empty: check rank/color */
446 && (color_ok(f
.t
[to
][top_to
], f
.t
[from
][i
])
447 && (rank_next(f
.t
[from
][i
], f
.t
[to
][top_to
]))))
448 || (top_to
< 0 && !cards
))) {/*if dest empty and right # cards*/
449 /* move cards [i..top_from] to their destination */
451 for (;i
<= top_from
; i
++) {
453 f
.t
[to
][top_to
] = f
.t
[from
][i
];
454 f
.t
[from
][i
] = NO_CARD
;
457 undo_push(from
, to
, count
, 0);
461 return ERR
; /* no such move possible */
463 int t2f(int from
, int to
, int opt
) { /* 1:1 copy from KLONDIKE */
464 (void) to
; (void) opt
; /* don't need */
465 int top_from
= find_top(f
.t
[from
]);
466 to
= get_suit(f
.t
[from
][top_from
]);
467 int top_to
= find_top(f
.f
[to
]);
468 if ((top_to
< 0 && get_rank(f
.t
[from
][top_from
]) == RANK_A
)
469 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.t
[from
][top_from
]))) {
470 f
.f
[to
][top_to
+1] = f
.t
[from
][top_from
];
471 f
.t
[from
][top_from
] = NO_CARD
;
472 undo_push(from
, FOUNDATION
, to
, 0);
473 if (check_won()) return WON
;
477 int f2t(int from
, int to
, int opt
) {
478 (void) from
; /* don't need */
479 int top_to
= find_top(f
.t
[to
]);
481 int top_from
= find_top(f
.f
[from
]);
483 if (top_to
< 0 /* empty tableu? */
484 ||(color_ok(f
.t
[to
][top_to
], f
.f
[from
][top_from
])
485 && (rank_next(f
.f
[from
][top_from
], f
.t
[to
][top_to
])))) {
486 f
.t
[to
][top_to
+1] = f
.f
[from
][top_from
];
487 f
.f
[from
][top_from
] = NO_CARD
;
488 undo_push(FOUNDATION
, to
, from
, 0);
492 int t2c(int from
, int to
, int opt
) {
493 (void) to
; (void) opt
; /* don't need */
494 /* is a cell free? */
495 if (f
.w
== (1<<NUM_CELLS
)-1)
497 for (to
= 0; to
< NUM_CELLS
; to
++)
498 if (!(f
.w
>>to
&1)) break;
500 int top_from
= find_top(f
.t
[from
]);
501 f
.s
[to
] = f
.t
[from
][top_from
];
502 f
.t
[from
][top_from
] = NO_CARD
;
503 f
.w
|= 1<<to
; /* mark cell as occupied */
504 undo_push(from
, STOCK
, to
, 0);
508 int c2t(int from
, int to
, int opt
) {
509 (void) from
; /* don't need */
510 int top_to
= find_top(f
.t
[to
]);
513 if (top_to
< 0 /* empty tableu? */
514 ||(color_ok(f
.t
[to
][top_to
], f
.s
[from
])
515 && (rank_next(f
.s
[from
], f
.t
[to
][top_to
])))) {
516 f
.t
[to
][top_to
+1] = f
.s
[from
];
518 f
.w
&= ~(1<<from
); /* mark cell as free */
519 undo_push(STOCK
, to
, from
, 0);
524 int c2f(int from
, int to
, int opt
) {
525 (void) from
; (void) to
; /* don't need */
527 if (f
.s
[from
] == NO_CARD
) return ERR
;
528 to
= get_suit(f
.s
[from
]);
529 int top_to
= find_top(f
.f
[to
]);
530 if ((top_to
< 0 && get_rank(f
.s
[from
]) == RANK_A
)
531 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.s
[from
]))) {
532 f
.f
[to
][top_to
+1] = f
.s
[from
];
534 f
.w
&= ~(1<<from
); /* mark cell as free */
535 undo_push(STOCK
, FOUNDATION
, from
| to
<<16, 0);
536 if (check_won()) return WON
;
540 int f2c(int from
, int to
, int opt
) {
541 (void) from
; (void) to
; /* don't need */
542 /* is a cell free? */
543 if (f
.w
== (1<<NUM_CELLS
)-1)
545 for (to
= 0; to
< NUM_CELLS
; to
++)
546 if (!(f
.w
>>to
&1)) break;
549 int top_from
= find_top(f
.f
[from
]);
550 f
.s
[to
] = f
.f
[from
][top_from
];
551 f
.f
[from
][top_from
] = NO_CARD
;
552 f
.w
|= 1<<to
; /* mark cell as occupied */
553 undo_push(FOUNDATION
, STOCK
, from
| to
<<16, 0);
557 #define w2f c2f /* for join()'s "to foundation" */
560 //TODO: generalize prediction engine for CMD_HINT
562 #define would_complete(pile) 0
564 #define would_complete(pile) \
565 (get_rank(f.t[pile][r[pile].top]) == RANK_A \
566 && get_rank(f.t[to][bottom_to]) == RANK_K)
567 #elif defined FREECELL
568 #define would_complete(pile) 0
570 #define would_turn(pile) \
571 (f.t[pile][r[pile].pos-1] < 0)
572 #define would_empty(pile) \
576 int top_to
= find_top(f
.t
[to
]);
578 int bottom_to
= first_movable(f
.t
[to
]);
581 #if defined KLONDIKE || defined FREECELL
582 if (to
== WASTE
|| to
== STOCK
) return ERR
; /*why would you do that!?*/
584 if (to
== FOUNDATION
) {
586 for (int i
= 0; i
< NUM_PILES
+NUM_CELLS
; i
++)
587 switch (is_tableu(i
)?t2f(i
, FOUNDATION
, 0)
588 :w2f(STOCK
,FOUNDATION
,i
-NUM_PILES
)){
589 case WON
: return WON
;
590 case OK
: status
= OK
;
598 if (top_to
< 0) { /* move a king to empty pile: */
599 for (int i
= 0; i
<= TAB_MAX
; i
++) {
600 if (f
.t
[i
][0] < 0) /* i.e. would turn? */
601 if (t2t(i
, to
, 0) == OK
) return OK
;
603 return w2t(WASTE
, to
, 0);
605 #elif defined FREECELL
606 if (top_to
< 0) { /* move longest cascade to empty tableu: */ //TODO FREECELL:
609 for (int i
= 0; i
<= TAB_MAX
; i
++) {
610 int m
= max_move(i
, to
);
611 /*longest cascade that won't uncover another free pile*/
612 //TODO: don't rip apart cascades
613 if (m
>= length
&& m
<= find_top(f
.t
[i
]))
614 length
= m
, longest
= i
;
616 if (longest
< 0) return ERR
;
617 return t2t(longest
, to
, length
);
622 int ok
:1; /* card to move in pile? */
623 int above
; /* number of movable cards above */
624 int below
; /* number of cards below ours */
625 int pos
; /* where the card to move is in the pile */
626 int top
; /* find_top() */
627 } r
[NUM_PILES
] = {{0}};
628 int complete
= 0;/* SPIDER: true if any pile would complete a stack */
629 int turn
= 0; /* SPIDER: true if any pile would turn_over */
630 int empty
= 0; /* true if any pile would become empty */
632 /* 1. rate each pile: */
635 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
636 if (pile
== to
) continue;
637 int top
= find_top(f
.t
[pile
]);
638 int bottom
= first_movable(f
.t
[pile
]);
639 r
[pile
].pos
= bottom
; /* need for would_empty */
641 if (top
< 0) continue; /* no cards to move */
642 if (would_empty(pile
)) continue; /* doesn't help */
645 r
[pile
].above
= 0; /* always take as many as possible */
646 r
[pile
].below
= top
- bottom
;
648 complete
|= would_complete(pile
); /* never happens */
649 turn
|= would_turn(pile
);
650 empty
|= would_empty(pile
);
654 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
655 r
[pile
].top
= r
[pile
].pos
= find_top(f
.t
[pile
]);
656 /* backtrack until we find a compatible-to-'to'-pile card: */
658 int maxmove
= max_move(pile
, -1);
660 while (r
[pile
].pos
>= 0 && is_movable(f
.t
[pile
], r
[pile
].pos
)) {
661 int rankdiff
= get_rank(f
.t
[pile
][r
[pile
].pos
])
662 - get_rank(f
.t
[to
][top_to
]);
663 if (rankdiff
>= 0) break; /* past our card */
665 if (!maxmove
--) break; /* can't move this many cards */
667 if (rankdiff
== -1 && /* rank matches */
668 color_ok(f
.t
[pile
][r
[pile
].pos
], f
.t
[to
][top_to
])
671 complete
|= would_complete(pile
);
672 turn
|= would_turn(pile
);
673 empty
|= would_empty(pile
);
674 for (int i
= r
[pile
].pos
; i
>= 0; i
--)
675 if (is_movable(f
.t
[pile
], i
-1))
685 /* 2. find optimal pile: (optimized for spider) */
686 //todo: in spider, prefer longest piles if above==0 (faster completions)
688 for (int pile
= 0, above
= 99, below
= 99; pile
< NUM_PILES
; pile
++) {
689 if (!r
[pile
].ok
) continue;
690 /* don't bother if another pile would be better: prefer ... */
691 /* ... to complete a stack: */
692 if (!would_complete(pile
) && complete
) continue;
693 /* ... emptying piles: */
694 if (!would_empty(pile
) && empty
&& !complete
) continue;
695 /* ... to turn_over: */
696 if (!would_turn(pile
) && turn
&& !complete
&& !empty
) continue;
697 /* ... not to rip apart too many cards: */
698 if (r
[pile
].above
> above
) continue;
699 /* if tied, prefer ... */
700 if (r
[pile
].above
== above
701 /* ... larger pile if destination is empty */
702 && (top_to
< 0? r
[pile
].below
< below
703 /* ... shorter pile otherwise */
704 : r
[pile
].below
> below
))
708 above
= r
[pile
].above
;
709 below
= r
[pile
].below
;
712 /* 3. move cards over and return: */
714 /* prefer waste if it wouldn't turn_over: */
715 /* NOTE: does not attempt to take from froundation */
716 if (!empty
&& !turn
&& w2t(WASTE
, to
, 0) == OK
)
718 if (from
< 0) /* nothing found */
720 return t2t(from
, to
, 0);
722 if (from
< 0) /* nothing found */
724 int bottom
= first_movable(f
.t
[from
]);
725 return t2t(from
, to
, get_rank(f
.t
[from
][bottom
]));
726 #elif defined FREECELL
727 //TODO: if would rip apart, try freecells first (instead after)
728 if (from
< 0) /* no tableu move found */ {
729 /* try all free cells before giving up: */
730 for (int i
= 0; i
< NUM_CELLS
; i
++)
731 if (c2t(STOCK
, to
, i
) == OK
) return OK
;
734 return t2t(from
, to
, 0);
739 #undef would_complete
740 int nop(int from
, int to
, int opt
) { (void)from
;(void)to
;(void)opt
;return ERR
; }
743 // keyboard input handling {{{
744 // cursor functions{{{
746 void cursor_left (struct cursor
* cursor
) {
748 if (is_tableu(cursor
->pile
)) {
749 if (cursor
->pile
> 0) cursor
->pile
--;
751 } else { /* stock/waste/foundation*/
752 switch (cursor
->pile
) {
753 case WASTE
: cursor
->pile
= STOCK
; cursor
->opt
= 0; break;
755 if (cursor
->opt
<= 0)
756 cursor
->pile
= WASTE
;
762 void cursor_down (struct cursor
* cursor
) {
764 if (!is_tableu(cursor
->pile
)) {
765 switch (cursor
->pile
) {
766 case STOCK
: cursor
->pile
= TAB_1
; break;
767 case WASTE
: cursor
->pile
= TAB_2
; break;
769 cursor
->pile
= TAB_4
+ cursor
->opt
;
774 void cursor_up (struct cursor
* cursor
) {
776 if (is_tableu(cursor
->pile
)) {
777 switch (cursor
->pile
) { //ugly :|
778 case TAB_1
: cursor
->pile
= STOCK
; break;
779 case TAB_2
: cursor
->pile
= WASTE
; break;
780 case TAB_3
: cursor
->pile
= WASTE
; break;
781 case TAB_4
: case TAB_5
: case TAB_6
: case TAB_7
:
782 cursor
->opt
=cursor
->pile
-TAB_4
;
783 cursor
->pile
= FOUNDATION
;
788 void cursor_right (struct cursor
* cursor
) {
790 if (is_tableu(cursor
->pile
)) {
791 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
794 switch (cursor
->pile
) {
795 case STOCK
: cursor
->pile
= WASTE
; break;
796 case WASTE
: cursor
->pile
= FOUNDATION
;cursor
->opt
= 0; break;
798 if (cursor
->opt
< NUM_SUITS
-1)
804 /*NOTE: one can't highlight the stock due to me being too lazy to implement it*/
805 void cursor_left (struct cursor
* cursor
) {
807 if (cursor
->pile
> 0) cursor
->pile
--;
810 void cursor_down (struct cursor
* cursor
) {
812 int first
= first_movable(f
.t
[cursor
->pile
]);
813 int top
= find_top(f
.t
[cursor
->pile
]);
814 if (first
+ cursor
->opt
< top
)
817 void cursor_up (struct cursor
* cursor
) {
819 if (cursor
->opt
> 0) cursor
->opt
--;
821 void cursor_right (struct cursor
* cursor
) {
823 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
826 #elif defined FREECELL
827 void cursor_left (struct cursor
* cursor
) {
829 if (is_tableu(cursor
->pile
)) {
830 if (cursor
->pile
> 0) cursor
->pile
--;
832 } else { /* cells/foundation*/
833 switch (cursor
->pile
) {
839 if (cursor
->opt
<= 0) {
840 cursor
->pile
= STOCK
;
848 void cursor_down (struct cursor
* cursor
) {
850 if (is_tableu(cursor
->pile
)) {
851 if (cursor
->opt
< max_move(cursor
->pile
, -1)-1)
854 cursor
->pile
= cursor
->opt
+NUM_CELLS
*(cursor
->pile
==FOUNDATION
);
858 void cursor_up (struct cursor
* cursor
) {
860 if (is_tableu(cursor
->pile
)) {
861 if (cursor
->opt
> 0) {
864 switch (cursor
->pile
) {
865 case TAB_1
: case TAB_2
: case TAB_3
: case TAB_4
:
866 cursor
->opt
= cursor
->pile
; /*assumes TAB_1==0*/
867 cursor
->pile
= STOCK
;
869 case TAB_5
: case TAB_6
: case TAB_7
: case TAB_8
:
870 cursor
->opt
= cursor
->pile
- NUM_CELLS
;
871 cursor
->pile
= FOUNDATION
;
876 void cursor_right (struct cursor
* cursor
) {
878 if (is_tableu(cursor
->pile
)) {
879 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
882 switch (cursor
->pile
) {
884 if (cursor
->opt
< NUM_SUITS
-1) {
887 cursor
->pile
= FOUNDATION
;
891 if (cursor
->opt
< NUM_SUITS
-1)
897 void cursor_to (struct cursor
* cursor
, int pile
) {
902 int set_mouse(int pile
, int* main
, int* opt
) {
903 //TODO: this should set cursor.opt, so card selector choice dialog does not trigger!
905 if (pile
< 0) return 1;
908 if (pile
>= FOUNDATION
)//TODO: check upper bound!
910 *opt
= pile
- FOUNDATION
;
913 #elif defined FREECELL
914 if (pile
> TAB_MAX
) {
915 *main
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
916 *opt
= (pile
-STOCK
) % 4;
922 int get_cmd (int* from
, int* to
, int* opt
) {
924 unsigned char mouse
[6] = {0}; /* must clear [3]! */
925 struct cursor inactive
= {-1,-1};
926 static struct cursor active
= {0,0};
927 if (is_tableu(active
.pile
))
931 from_l
: print_table(&active
, &inactive
);
935 /* direct addressing: */
936 case '1': *from
= TAB_1
; break;
937 case '2': *from
= TAB_2
; break;
938 case '3': *from
= TAB_3
; break;
939 case '4': *from
= TAB_4
; break;
940 case '5': *from
= TAB_5
; break;
941 case '6': *from
= TAB_6
; break;
942 case '7': *from
= TAB_7
; break;
944 case '8': *from
= TAB_8
; break;
945 case '9': *from
= TAB_9
; break;
946 case '0': *from
= TAB_10
;break;
947 #elif defined FREECELL
948 case '8': *from
= TAB_8
; break;
949 case '9': *from
= STOCK
; break;
950 case '0': *from
= FOUNDATION
; break;
951 #elif defined KLONDIKE
952 case '9': *from
= WASTE
; break;
953 case '0': *from
= FOUNDATION
; break;
954 case '8': /* fallthrough */
957 case '\n': *from
= STOCK
; break;
959 /* cursor keys addressing: */
961 case 'h': cursor_left (&active
); goto from_l
;
963 case 'j': cursor_down (&active
); goto from_l
;
965 case 'k': cursor_up (&active
); goto from_l
;
967 case 'l': cursor_right(&active
); goto from_l
;
969 case 'H': cursor_to(&active
,TAB_1
); goto from_l
; /* leftmost tableu */
971 case 'L': cursor_to(&active
,TAB_MAX
);goto from_l
; /* rigthmost tableu */
973 case 'M': cursor_to(&active
,TAB_MAX
/2); goto from_l
; /* center tableu */
974 case ' ': /* continue with second cursor */
977 *opt
= active
.opt
; /* when FOUNDATION */
981 /* mouse addressing: */
982 case MOUSE_MIDDLE
: return CMD_NONE
;
984 if (set_mouse(term2pile(mouse
), to
, opt
))
988 if (set_mouse(term2pile(mouse
), from
, opt
))
990 if (!is_tableu(*from
))
991 inactive
.opt
= *opt
; /* prevents card selector dialog */
996 fprintf (stderr
, ":");
997 raw_mode(0); /* turn on echo */
998 fgets (buf
, 256, stdin
);
1001 case 'q': return CMD_QUIT
;
1002 case 'n': return CMD_NEW
;
1003 case 'r': return CMD_AGAIN
;
1004 case 'h': return CMD_HELP
;
1005 default: return CMD_INVAL
;
1010 case 'K': /* fallthrough */
1011 case '?': return CMD_HINT
;
1012 case 'u': return CMD_UNDO
;
1013 case 002: return CMD_NONE
; /* sent by SIGWINCH */
1014 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1015 default: return CMD_INVAL
;
1017 inactive
.pile
= *from
; /* for direct addressing highlighting */
1018 if (is_tableu(*from
) && f
.t
[*from
][0] == NO_CARD
) return CMD_INVAL
;
1019 //TODO: freecell: if from==stock && stock[x] == empty: return inval
1022 if (*from
== STOCK
) {
1029 to_l
: print_table(&active
, &inactive
);
1034 case 'h': cursor_left (&active
); goto to_l
;
1036 case 'j': cursor_down (&active
); goto to_l
;
1038 case 'k': cursor_up (&active
); goto to_l
;
1040 case 'l': cursor_right(&active
); goto to_l
;
1042 case 'H': cursor_to(&active
,TAB_1
); goto to_l
;
1044 case 'L': cursor_to(&active
,TAB_MAX
); goto to_l
;
1046 case 'M': cursor_to(&active
,TAB_MAX
/2); goto to_l
;
1047 case 'J': /* fallthrough; just join selected pile */
1050 break; /* continues with the foundation/empty tableu check */
1052 case MOUSE_RIGHT
: return CMD_NONE
;
1054 if (set_mouse(term2pile(mouse
), to
, opt
))
1057 case 'K': /* fallthrough */
1058 case '?': return CMD_HINT
;
1059 case 'u': return CMD_NONE
; /* cancel selection */
1060 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1062 if (t
< '0' || t
> '9') return CMD_INVAL
;
1066 #elif defined SPIDER
1068 #elif defined FREECELL
1078 /* direct addressing post-processing stage:
1079 because foundations/freecells share the same key (and you can't select
1080 partial piles) there are sometimes ambiguous situations where it isn't
1081 clear from which pile (or how many cards) to take. the code below will
1082 only ask the user if there are at least two possible moves and
1083 automatically choose otherwise. */
1085 /* if it was selected with a cursor, it's obvious: */
1086 if (inactive
.opt
>= 0) {
1087 if (is_tableu(*from
)) {
1088 /* NOTE: max_move same as in cursor_down() */
1089 *opt
= max_move(*from
, -1) - inactive
.opt
;
1091 *opt
= inactive
.opt
;
1093 /* moving from tableu to empty tableu? */
1094 } else if(is_tableu(*from
) && is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
){
1095 int top
= find_top(f
.t
[*from
]);
1096 int max
= max_move(*from
, *to
);
1098 if (top
< 0) return CMD_INVAL
;
1099 if (max
== 1) { /* only 1 movable? */
1100 return *opt
= 1, CMD_MOVE
;
1101 } else { /* only ask the user if it's unclear: */
1102 int bottom
= top
- (max
-1);
1103 printf ("\rup to ([a23456789xjqk] or space/return): ");
1106 case ' ': rank
= get_rank(f
.t
[*from
][top
]); break;
1107 case'\n': rank
= get_rank(f
.t
[*from
][bottom
]); break;
1108 case 'a': case 'A': rank
= RANK_A
; break;
1109 case '0': /* fallthrough */
1110 case 'x': case 'X': rank
= RANK_X
; break;
1111 case 'j': case 'J': rank
= RANK_J
; break;
1112 case 'q': case 'Q': rank
= RANK_Q
; break;
1113 case 'k': case 'K': rank
= RANK_K
; break;
1114 default: rank
-= '1';
1116 if (rank
< RANK_A
|| rank
> RANK_K
) return CMD_INVAL
;
1118 for (int i
= 0; max
--; i
++)
1119 if (get_rank(f
.t
[*from
][top
-i
]) == rank
)
1120 return *opt
= 1+i
, CMD_MOVE
;
1124 /* `opt` is the number of cards to move */
1125 /* moving between stock/foundation? */
1126 } else if (*from
== FOUNDATION
&& *to
== FOUNDATION
) {
1127 return CMD_INVAL
; /* nonsensical */
1128 } else if (*from
== FOUNDATION
&& *to
== STOCK
) {
1129 if (f
.w
== (1<<NUM_CELLS
)-1) return CMD_INVAL
; /*no free cells*/
1130 int ok_foundation
; /* find compatible (non-empty) foundations:*/
1131 int used_fs
=0; for (int i
= 0; i
< NUM_SUITS
; i
++)
1132 if (!!f
.f
[i
][0]) ok_foundation
= i
, used_fs
++;
1134 if (used_fs
== 0) return CMD_INVAL
; /* nowhere to take from */
1135 if (used_fs
== 1) { /* take from the only one */
1136 return *opt
= ok_foundation
, CMD_MOVE
;
1137 } else { /* ask user */
1138 printf ("take from (1-4): "); fflush (stdout
);
1139 *opt
= getch(NULL
) - '1';
1140 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1142 /* `opt` is the foundation index (0..3) */
1143 } else if (*from
== STOCK
) { /* cell -> foundation/tableu */
1144 if (!f
.w
) return CMD_INVAL
; /* no cell to take from */
1145 int ok_cell
; /* find compatible (non-empty) cells: */
1146 int tab
= is_tableu(*to
);
1147 int used_cs
=0; for (int i
= 0; i
< NUM_CELLS
; i
++) {
1148 card_t
* pile
= (tab
?f
.t
[*to
]:f
.f
[get_suit(f
.s
[i
])]);
1149 int top_to
= find_top(pile
);
1150 if (tab
? /* to tableu? */
1151 ((top_to
<0 && f
.s
[i
] > NO_CARD
)
1152 ||(top_to
>=0 && rank_next(f
.s
[i
], pile
[top_to
])
1153 && color_ok(f
.s
[i
], pile
[top_to
])))
1154 : /* to foundation? */
1155 ((top_to
<0 && get_rank(f
.s
[i
]) == RANK_A
)
1156 ||(top_to
>=0 && rank_next(pile
[top_to
],f
.s
[i
])))
1158 ok_cell
= i
, used_cs
++;
1161 if (used_cs
== 0) return CMD_INVAL
; /* nowhere to take from */
1162 if (used_cs
== 1) { /* take from the only one */
1163 return *opt
= ok_cell
, CMD_MOVE
;
1164 } else { /* ask user */
1165 printf ("take from (1-4): "); fflush (stdout
);
1166 *opt
= getch(NULL
) - '1';
1167 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1169 /* `opt` is the cell index (0..3) */
1172 //TODO: mouse-friendly "up to?" dialog
1173 #if defined KLONDIKE || defined FREECELL
1174 if (*from
== FOUNDATION
) {
1175 if (inactive
.opt
>= 0) {
1176 *opt
= inactive
.opt
;
1179 int top
= find_top(f
.t
[*to
]);
1180 if (top
< 0) return CMD_INVAL
;
1181 int color
= get_color(f
.t
[*to
][top
]);
1182 int choice_1
= 1-color
; /* selects piles of */
1183 int choice_2
= 2+color
; /* the opposite color */
1184 int top_c1
= find_top(f
.f
[choice_1
]);
1185 int top_c2
= find_top(f
.f
[choice_2
]);
1187 switch ((rank_next(f
.f
[choice_1
][top_c1
], f
.t
[*to
][top
])
1188 && top_c1
>= 0 ) << 0
1189 |(rank_next(f
.f
[choice_2
][top_c2
], f
.t
[*to
][top
])
1190 && top_c2
>= 0 ) << 1) {
1191 case ( 1<<0): *opt
= choice_1
; break; /* choice_1 only */
1192 case (1<<1 ): *opt
= choice_2
; break; /* choice_2 only */
1193 case (1<<1 | 1<<0): /* both, ask user which to pick from */
1194 printf ("take from (1-4): "); fflush (stdout
);
1195 *opt
= getch(NULL
) - '1';
1196 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1198 default: return CMD_INVAL
; /* none matched */
1200 /* `opt` is the foundation index (0..3) */
1202 #elif defined SPIDER
1203 /* moving to empty tableu? */
1204 if (is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
) {
1205 int bottom
= first_movable(f
.t
[*from
]);
1206 if (inactive
.opt
>= 0) { /*if from was cursor addressed: */
1207 *opt
= get_rank(f
.t
[*from
][bottom
+ inactive
.opt
]);
1210 int top
= find_top(f
.t
[*from
]);
1211 if (top
< 0) return CMD_INVAL
;
1212 if (top
>= 0 && !is_movable(f
.t
[*from
], top
-1)) {
1213 *opt
= get_rank(f
.t
[*from
][top
]);
1214 } else { /* only ask the user if it's unclear: */
1215 printf ("\rup to ([a23456789xjqk] or space/return): ");
1218 case ' ': *opt
= get_rank(f
.t
[*from
][top
]); break;
1219 case'\n': *opt
= get_rank(f
.t
[*from
][bottom
]); break;
1220 case 'a': case 'A': *opt
= RANK_A
; break;
1221 case '0': /* fallthrough */
1222 case 'x': case 'X': *opt
= RANK_X
; break;
1223 case 'j': case 'J': *opt
= RANK_J
; break;
1224 case 'q': case 'Q': *opt
= RANK_Q
; break;
1225 case 'k': case 'K': *opt
= RANK_K
; break;
1226 default: *opt
-= '1';
1228 if (*opt
< RANK_A
|| *opt
> RANK_K
) return CMD_INVAL
;
1230 /* `opt` is the rank of the highest card to move */
1236 int getctrlseq(unsigned char* buf
) {
1244 int offset
= 0x20; /* never sends control chars as data */
1245 while ((c
= getchar()) != EOF
) {
1249 case '\033': state
=ESC_SENT
; break;
1255 case '[': state
=CSI_SENT
; break;
1256 default: return KEY_INVAL
;
1261 case 'A': return KEY_UP
;
1262 case 'B': return KEY_DOWN
;
1263 case 'C': return KEY_RIGHT
;
1264 case 'D': return KEY_LEFT
;
1265 /*NOTE: home/end send ^[[x~ . no support for modifiers*/
1266 case 'H': return KEY_HOME
;
1267 case 'F': return KEY_END
;
1268 case '2': getchar(); return KEY_INS
;
1269 case '5': getchar(); return KEY_PGUP
;
1270 case '6': getchar(); return KEY_PGDN
;
1271 case 'M': state
=MOUSE_EVENT
; break;
1272 default: return KEY_INVAL
;
1276 if (buf
== NULL
) return KEY_INVAL
;
1277 buf
[0] = c
- offset
;
1278 buf
[1] = getchar() - offset
;
1279 buf
[2] = getchar() - offset
;
1287 int term2pile(unsigned char *mouse
) {
1288 int line
= (mouse
[2]-1);
1289 int column
= (mouse
[1]-1) / op
.s
->width
;
1291 if (line
< op
.s
->height
) { /* first line */
1294 case 0: return STOCK
;
1295 case 1: return WASTE
;
1296 case 2: return -1; /* spacer */
1297 case 3: return FOUNDATION
+0;
1298 case 4: return FOUNDATION
+1;
1299 case 5: return FOUNDATION
+2;
1300 case 6: return FOUNDATION
+3;
1302 #elif defined SPIDER
1303 if (column
< 3) return STOCK
;
1305 #elif defined FREECELL
1306 if (column
< NUM_SUITS
+ NUM_CELLS
) return STOCK
+column
;
1309 } else if (line
> op
.s
->height
) { /* tableu */
1310 if (column
<= TAB_MAX
) return column
;
1314 int wait_mouse_up(unsigned char* mouse
) {
1315 //TODO: mouse drag: start gets inactive, hovering gets active cursors
1316 struct cursor cur
= {-1,-1};
1318 /* note: if dragged [3]==1 and second position is in mouse[0,4,5] */
1320 /* display a cursor while mouse button is pushed: */
1321 int pile
= term2pile(mouse
);
1324 if (pile
>= FOUNDATION
) {
1325 cur
.pile
= FOUNDATION
;
1326 cur
.opt
= pile
-FOUNDATION
;
1328 #elif defined FREECELL
1329 if (pile
> TAB_MAX
) {
1330 cur
.pile
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
1331 cur
.opt
= (pile
-STOCK
) % 4;
1334 /* need to temporarily show the cursor, then revert to last state: */
1335 int old_show_cursor_hi
= op
.h
; //TODO: ARGH! that's awful!
1337 print_table(&cur
, NO_HI
); //TODO: should not overwrite inactive cursor!
1338 op
.h
= old_show_cursor_hi
;
1341 if (getctrlseq (mouse
+3) == MOUSE_ANY
) {
1342 /* ignore mouse wheel events: */
1343 if (mouse
[3] & 0x40) continue;
1345 else if((mouse
[3]&3) == 3) level
--; /* release event */
1346 else level
++; /* another button pressed */
1350 int success
= mouse
[1] == mouse
[4] && mouse
[2] == mouse
[5];
1357 int getch(unsigned char* buf
) {
1358 //TODO: if buf==NULL disable mouse input
1359 /* returns a character, EOF, or constant for an escape/control sequence - NOT
1360 compatible with the ncurses implementation of same name */
1362 if (buf
&& buf
[3]) {
1363 /* mouse was dragged; return 'ungetted' previous destination */
1364 action
= MOUSE_DRAG
;
1365 /* keep original [0], as [3] only contains release event */
1370 action
= getctrlseq(buf
);
1375 if (buf
[0] > 3) break; /* ignore wheel events */
1380 case 0: return MOUSE_LEFT
;
1381 case 1: return MOUSE_MIDDLE
;
1382 case 2: return MOUSE_RIGHT
;
1390 // shuffling and dealing {{{
1391 void deal(long seed
) {
1392 f
= (const struct playfield
){0}; /* clear playfield */
1393 card_t deck
[DECK_SIZE
*NUM_DECKS
];
1394 int avail
= DECK_SIZE
*NUM_DECKS
;
1395 for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) deck
[i
] = (i
%DECK_SIZE
)+1;
1397 if (op
.m
!= NORMAL
) for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) {
1398 if (op
.m
== MEDIUM
) deck
[i
] = 1+((deck
[i
]-1) | 2);
1399 if (op
.m
== EASY
) deck
[i
] = 1+((deck
[i
]-1) | 2 | 1);
1400 /* the 1+ -1 dance gets rid of the offset created by NO_CARD */
1404 for (int i
= DECK_SIZE
*NUM_DECKS
-1; i
> 0; i
--) { /* fisher-yates */
1405 int j
= rand() % (i
+1);
1406 if (j
-i
) deck
[i
]^=deck
[j
],deck
[j
]^=deck
[i
],deck
[i
]^=deck
[j
];
1410 for (int i
= 0; i
< NUM_PILES
; i
++) {
1413 int count
= i
; /* pile n has n closed cards, then 1 open */
1414 #elif defined SPIDER
1416 int count
= i
<4?5:4; /* pile 1-4 have 5, 5-10 have 4 closed */
1417 #elif defined FREECELL
1419 int count
= i
<4?6:5;/*like spider, but cards are dealt face-up*/
1421 /* "SIGN": face down cards are negated */
1422 for (int j
= 0; j
< count
; j
++) f
.t
[i
][j
] = SIGN deck
[--avail
];
1423 f
.t
[i
][count
] = deck
[--avail
]; /* the face-up card */
1426 /* rest of the cards to the stock: */
1427 /* NOTE: assert(avail==50) for spider, assert(avail==0) for freecell */
1428 for (f
.z
= 0; avail
; f
.z
++) f
.s
[f
.z
] = deck
[--avail
];
1430 f
.w
= -1; /* @start: nothing on waste */
1431 #elif defined SPIDER
1432 f
.w
= 0; /* number of used foundations */
1433 #elif defined FREECELL
1434 f
.w
= 0; /* bitmask of used free cells */
1437 f
.u
= &undo_sentinel
;
1441 // screen drawing routines {{{
1442 void print_hi(int invert
, int grey_bg
, int bold
, char* str
) {
1443 if (!op
.h
) invert
= 0; /* don't show invert if we used the mouse last */
1444 if (bold
&& op
.s
== &unicode_large_color
){ //awful hack for bold + faint
1445 int offset
= str
[3]==017?16:str
[4]==017?17:0;
1446 printf ("%s%s%s""%.*s%s%s""%s%s%s",
1447 "\033[1m", invert
?"\033[7m":"", grey_bg
?"\033[100m":"",
1448 offset
, str
, bold
?"\033[1m":"", str
+offset
,
1449 grey_bg
?"\033[49m":"", invert
?"\033[27m":"","\033[22m");
1452 printf ("%s%s%s%s%s%s%s",
1453 bold
?"\033[1m":"", invert
?"\033[7m":"", grey_bg
?"\033[100m":"",
1455 grey_bg
?"\033[49m":"", invert
?"\033[27m":"",bold
?"\033[22m":"");
1457 void print_table(const struct cursor
* active
, const struct cursor
* inactive
) {
1458 printf("\033[2J\033[H"); /* clear screen, reset cursor */
1460 /* print stock, waste and foundation: */
1461 for (int line
= 0; line
< op
.s
->height
; line
++) {
1463 print_hi (active
->pile
== STOCK
, inactive
->pile
== STOCK
, 1, (
1464 (f
.w
< f
.z
-1)?op
.s
->facedown
1465 :op
.s
->placeholder
)[line
]);
1467 print_hi (active
->pile
== WASTE
, inactive
->pile
== WASTE
, 1, (
1468 /* NOTE: cast, because f.w sometimes is (short)-1 !? */
1469 ((short)f
.w
>= 0)?op
.s
->card
[f
.s
[f
.w
]]
1470 :op
.s
->placeholder
)[line
]);
1471 printf ("%s", op
.s
->card
[NO_CARD
][line
]); /* spacer */
1473 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1474 int card
= find_top(f
.f
[pile
]);
1475 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1476 inactive
->pile
==FOUNDATION
&& (
1477 /* cursor addr. || direct addr. */
1478 inactive
->opt
==pile
|| inactive
->opt
< 0
1480 (card
< 0)?op
.s
->foundation
[line
]
1481 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1486 #elif defined SPIDER
1487 int fdone
; for (fdone
= NUM_DECKS
*NUM_SUITS
; fdone
; fdone
--)
1488 if (f
.f
[fdone
-1][RANK_K
]) break; /*number of completed stacks*/
1489 int spacer_from
= f
.z
?(f
.z
/10-1) * op
.s
->halfwidth
[0] + op
.s
->width
:0;
1490 int spacer_to
= NUM_PILES
*op
.s
->width
-
1491 ((fdone
?(fdone
-1) * op
.s
->halfwidth
[1]:0)+op
.s
->width
);
1492 for (int line
= 0; line
< op
.s
->height
; line
++) {
1493 /* available stock: */
1494 for (int i
= f
.z
/10; i
; i
--) {
1495 if (i
==1) printf ("%s", op
.s
->facedown
[line
]);
1496 else printf ("%s", op
.s
->halfstack
[line
]);
1499 for (int i
= spacer_from
; i
< spacer_to
; i
++) printf (" ");
1500 /* foundation (overlapping): */
1501 for (int i
= NUM_DECKS
*NUM_SUITS
-1, half
= 0; i
>= 0; i
--) {
1502 int overlap
= half
? op
.s
->halfcard
[line
]: 0;
1503 if (f
.f
[i
][RANK_K
]) printf ("%.*s", op
.s
->halfwidth
[2],
1504 op
.s
->card
[f
.f
[i
][RANK_K
]][line
]+overlap
),
1510 #elif defined FREECELL
1511 /* print open cells, foundation: */
1512 for (int line
= 0; line
< op
.s
->height
; line
++) {
1513 for (int pile
= 0; pile
< NUM_CELLS
; pile
++)
1514 print_hi (active
->pile
==STOCK
&& active
->opt
==pile
,
1515 inactive
->pile
==STOCK
&& (
1516 /* cursor addr. || direct addr. */
1517 inactive
->opt
==pile
|| inactive
->opt
< 0
1519 ((f
.s
[pile
])?op
.s
->card
[f
.s
[pile
]]
1520 :op
.s
->placeholder
)[line
]);
1521 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1522 int card
= find_top(f
.f
[pile
]);
1523 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1524 inactive
->pile
==FOUNDATION
&& (
1525 /* cursor addr. || direct addr. */
1526 inactive
->opt
==pile
|| inactive
->opt
< 0
1528 (card
< 0)?op
.s
->foundation
[line
]
1529 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1536 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty))
1537 #define TOP_HI(c) 1 /* can't select partial stacks in KLONDIKE */
1538 #elif defined SPIDER || defined FREECELL
1539 int offset
[NUM_PILES
]={0}; /* first card to highlight */
1541 int bottom
[NUM_PILES
]; /* first movable card */
1542 for (int i
=0; i
<NUM_PILES
; i
++)
1543 bottom
[i
] = find_top(f
.t
[i
]) - max_move(i
,-1);
1545 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty) \
1546 && offset[pile] >= cursor->opt)
1547 #define TOP_HI(cursor) (cursor->pile == pile && movable \
1548 && offset[pile] == cursor->opt)
1550 /* print tableu piles: */
1551 int row
[NUM_PILES
] = {0};
1552 int line
[NUM_PILES
]= {0};
1553 int label
[NUM_PILES
]={0};
1555 int did_placeholders
= 0;
1558 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
1559 card_t card
= f
.t
[pile
][row
[pile
]];
1560 card_t next
= f
.t
[pile
][row
[pile
]+1];
1561 int movable
= is_movable(f
.t
[pile
], row
[pile
]);
1563 if(row
[pile
] <= bottom
[pile
]) movable
= 0;
1565 int empty
= !card
&& row
[pile
] == 0;
1567 print_hi (DO_HI(active
), DO_HI(inactive
), movable
, (
1568 (!card
&& row
[pile
] == 0)?op
.s
->placeholder
1569 :(card
<0)?op
.s
->facedown
1573 int extreme_overlap
= ( 3 /* spacer, labels, status */
1574 + 2 * op
.s
->height
/* stock, top tableu card */
1575 + find_top(f
.t
[pile
]) * op
.s
->overlap
) >op
.w
[0];
1576 /* normal overlap: */
1577 if (++line
[pile
] >= (next
?op
.s
->overlap
:op
.s
->height
)
1578 /* extreme overlap on closed cards: */
1579 || (extreme_overlap
&&
1581 f
.t
[pile
][row
[pile
]] < 0 &&
1582 f
.t
[pile
][row
[pile
]+1] <0)
1583 /* extreme overlap on sequences: */
1584 || (extreme_overlap
&&
1585 !TOP_HI(active
) && /*always show top selected card*/
1586 line
[pile
] >= 1 && row
[pile
] > 0 &&
1587 f
.t
[pile
][row
[pile
]-1] > NO_CARD
&&
1588 is_consecutive (f
.t
[pile
], row
[pile
]) &&
1589 is_consecutive (f
.t
[pile
], row
[pile
]-1) &&
1590 f
.t
[pile
][row
[pile
]+1] != NO_CARD
)
1594 #if defined SPIDER || defined FREECELL
1595 if (movable
) offset
[pile
]++;
1598 /* tableu labels: */
1599 if(!card
&& !label
[pile
] && row
[pile
]>0&&line
[pile
]>0) {
1601 printf ("\b\b%d ", (pile
+1) % 10); //XXX: hack
1603 line_had_card
|= !!card
;
1604 did_placeholders
|= row
[pile
] > 0;
1607 } while (line_had_card
|| !did_placeholders
);
1610 void visbell (void) {
1612 printf ("\033[?5h"); fflush (stdout
);
1614 printf ("\033[?5l"); fflush (stdout
);
1616 void win_anim(void) {
1617 printf ("\033[?25l"); /* hide cursor */
1619 /* set cursor to random location */
1620 int row
= 1+rand()%(1+op
.w
[0]-op
.s
->height
);
1621 int col
= 1+rand()%(1+op
.w
[1]-op
.s
->width
);
1623 /* draw random card */
1624 int face
= 1 + rand() % 52;
1625 for (int l
= 0; l
< op
.s
->height
; l
++) {
1626 printf ("\033[%d;%dH", row
+l
, col
);
1627 printf ("%s", op
.s
->card
[face
][l
]);
1631 /* exit on keypress */
1632 struct pollfd p
= {STDIN_FILENO
, POLLIN
, 0};
1633 if (poll (&p
, 1, 80)) goto fin
;
1636 printf ("\033[?25h"); /* show cursor */
1642 void undo_push (int _f
, int t
, int n
, int o
) {
1643 struct undo
* new = malloc(sizeof(struct undo
));
1653 void undo_pop (struct undo
* u
) {
1654 if (u
== &undo_sentinel
) return;
1657 if (u
->f
== FOUNDATION
) {
1658 /* foundation -> tableu */
1659 int top_f
= find_top(f
.f
[u
->n
]);
1660 int top_t
= find_top(f
.t
[u
->t
]);
1661 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1662 f
.t
[u
->t
][top_t
] = NO_CARD
;
1663 } else if (u
->f
== WASTE
&& u
->t
== FOUNDATION
) {
1664 /* waste -> foundation */
1665 /* split u->n into wst and fnd: */
1666 int wst
= u
->n
& 0xffff;
1667 int fnd
= u
->n
>> 16;
1668 /* move stock cards one position up to make room: */
1669 for (int i
= f
.z
; i
>= wst
; i
--) f
.s
[i
+1] = f
.s
[i
];
1670 /* move one card from foundation to waste: */
1671 int top
= find_top(f
.f
[fnd
]);
1672 f
.s
[wst
] = f
.f
[fnd
][top
];
1673 f
.f
[fnd
][top
] = NO_CARD
;
1676 } else if (u
->f
== WASTE
) {
1677 /* waste -> tableu */
1678 /* move stock cards one position up to make room: */
1679 for (int i
= f
.z
-1; i
>= u
->n
; i
--) f
.s
[i
+1] = f
.s
[i
];
1680 /* move one card from tableu to waste: */
1681 int top
= find_top(f
.t
[u
->t
]);
1682 f
.s
[u
->n
] = f
.t
[u
->t
][top
];
1683 f
.t
[u
->t
][top
] = NO_CARD
;
1686 } else if (u
->t
== FOUNDATION
) {
1687 /* tableu -> foundation */
1688 int top_f
= find_top(f
.t
[u
->f
]);
1689 int top_t
= find_top(f
.f
[u
->n
]);
1690 /* close topcard if previous action caused turn_over(): */
1691 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1692 /* move one card from foundation to tableu: */
1693 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1694 f
.f
[u
->n
][top_t
] = NO_CARD
;
1696 /* tableu -> tableu */
1697 int top_f
= find_top(f
.t
[u
->f
]);
1698 int top_t
= find_top(f
.t
[u
->t
]);
1699 /* close topcard if previous action caused turn_over(): */
1700 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1701 /* move n cards from tableu[f] to tableu[t]: */
1702 for (int i
= 0; i
< u
->n
; i
++) {
1703 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1704 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1707 #elif defined SPIDER
1708 if (u
->f
== STOCK
) {
1709 /* stock -> tableu */
1710 /*remove a card from each pile and put it back onto the stock:*/
1711 for (int pile
= NUM_PILES
-1; pile
>= 0; pile
--) {
1712 int top
= find_top(f
.t
[pile
]);
1713 f
.s
[f
.z
++] = f
.t
[pile
][top
];
1714 f
.t
[pile
][top
] = NO_CARD
;
1716 } else if (u
->t
== FOUNDATION
) {
1717 /* tableu -> foundation */
1718 int top
= find_top(f
.t
[u
->f
]);
1719 /* close topcard if previous action caused turn_over(): */
1720 if (u
->o
) f
.t
[u
->f
][top
] *= -1;
1721 /* append cards from foundation to tableu */
1722 for (int i
= RANK_K
; i
>= RANK_A
; i
--) {
1723 f
.t
[u
->f
][++top
] = f
.f
[u
->n
][i
];
1724 f
.f
[u
->n
][i
] = NO_CARD
;
1726 f
.w
--; /* decrement complete-foundation-counter */
1729 /* tableu -> tableu */
1730 int top_f
= find_top(f
.t
[u
->f
]);
1731 int top_t
= find_top(f
.t
[u
->t
]);
1732 /* close topcard if previous action caused turn_over(): */
1733 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1734 /* move n cards from tableu[f] to tableu[t]: */
1735 for (int i
= 0; i
< u
->n
; i
++) {
1736 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1737 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1740 #elif defined FREECELL
1741 /*NOTE: if from and to are both stock/foundation, opt = from | to<<16 */
1742 if (u
->f
== STOCK
&& u
->t
== FOUNDATION
) {
1743 /* free cells -> foundation */
1744 /* split u->n into cll and fnd: */
1745 int cll
= u
->n
& 0xffff;
1746 int fnd
= u
->n
>> 16;
1747 /* move one card from foundation to free cell: */
1748 int top
= find_top(f
.f
[fnd
]);
1749 f
.s
[cll
] = f
.f
[fnd
][top
];
1750 f
.f
[fnd
][top
] = NO_CARD
;
1751 f
.w
|= 1<<cll
; /* mark cell as occupied */
1752 } else if (u
->f
== STOCK
) {
1753 /* free cells -> cascade */
1754 int top_t
= find_top(f
.t
[u
->t
]);
1755 f
.s
[u
->n
] = f
.t
[u
->t
][top_t
];
1756 f
.t
[u
->t
][top_t
] = NO_CARD
;
1757 f
.w
|= 1<<u
->n
; /* mark cell as occupied */
1758 } else if (u
->f
== FOUNDATION
&& u
->t
== STOCK
) {
1759 /* foundation -> free cells */
1760 /* split u->n into cll and fnd: */
1761 int cll
= u
->n
>> 16;
1762 int fnd
= u
->n
& 0xffff;
1763 /* move 1 card from free cell to foundation: */
1764 int top_f
= find_top(f
.f
[fnd
]);
1765 f
.f
[fnd
][top_f
+1] = f
.s
[cll
];
1767 f
.w
&= ~(1<<cll
); /* mark cell as free */
1768 } else if (u
->f
== FOUNDATION
) {
1769 /* foundation -> cascade */
1770 int top_f
= find_top(f
.f
[u
->n
]);
1771 int top_t
= find_top(f
.t
[u
->t
]);
1772 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1773 f
.t
[u
->t
][top_t
] = NO_CARD
;
1774 } else if (u
->t
== STOCK
) {
1775 /* cascade -> free cells */
1776 int top_f
= find_top(f
.t
[u
->f
]);
1777 f
.t
[u
->f
][top_f
+1] = f
.s
[u
->n
];
1778 f
.s
[u
->n
] = NO_CARD
;
1779 f
.w
&= ~(1<<u
->n
); /* mark cell as free */
1780 } else if (u
->t
== FOUNDATION
) {
1781 /* cascade -> foundation */
1782 int top_f
= find_top(f
.t
[u
->f
]);
1783 int top_t
= find_top(f
.f
[u
->n
]);
1784 /* move one card from foundation to cascade: */
1785 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1786 f
.f
[u
->n
][top_t
] = NO_CARD
;
1788 /* cascade -> cascade */
1789 int top_f
= find_top(f
.t
[u
->f
]);
1790 int top_t
= find_top(f
.t
[u
->t
]);
1791 /* move n cards from tableu[f] to tableu[t]: */
1792 for (int i
= 0; i
< u
->n
; i
++) {
1793 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1794 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1803 void free_undo (struct undo
* u
) {
1804 while (u
&& u
!= &undo_sentinel
) {
1812 // initialization stuff {{{
1813 void screen_setup (int enable
) {
1816 printf ("\033[s\033[?47h"); /* save cursor, alternate screen */
1817 printf ("\033[H\033[J"); /* reset cursor, clear screen */
1818 printf ("\033[?1000h"); /* enable mouse */
1820 printf ("\033[?1000l"); /* disable mouse */
1821 printf ("\033[?47l\033[u"); /* primary screen, restore cursor */
1826 void raw_mode(int enable
) {
1827 static struct termios saved_term_mode
;
1828 struct termios raw_term_mode
;
1831 if (saved_term_mode
.c_lflag
== 0)/*don't overwrite stored mode*/
1832 tcgetattr(STDIN_FILENO
, &saved_term_mode
);
1833 raw_term_mode
= saved_term_mode
;
1834 raw_term_mode
.c_lflag
&= ~(ICANON
| ECHO
);
1835 raw_term_mode
.c_cc
[VMIN
] = 1 ;
1836 raw_term_mode
.c_cc
[VTIME
] = 0;
1837 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw_term_mode
);
1839 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &saved_term_mode
);
1843 void signal_handler (int signum
) {
1848 signal(SIGTSTP
, SIG_DFL
); /* NOTE: assumes SysV semantics! */
1853 print_table(NO_HI
, NO_HI
);
1855 case SIGINT
: //TODO: don't exit; just warn like vim does
1858 ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &w
);
1864 void signal_setup(void) {
1865 struct sigaction saction
;
1867 saction
.sa_handler
= signal_handler
;
1868 sigemptyset(&saction
.sa_mask
);
1869 saction
.sa_flags
= 0;
1870 if (sigaction(SIGTSTP
, &saction
, NULL
) < 0) {
1874 if (sigaction(SIGCONT
, &saction
, NULL
) < 0) {
1878 if (sigaction(SIGINT
, &saction
, NULL
) < 0) {
1882 if (sigaction(SIGWINCH
, &saction
, NULL
) < 0) {
1883 perror ("SIGWINCH");
1889 //vim: foldmethod=marker