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" */
118 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
);
139 case ERR
: visbell(); break;
140 case WON
: return GAME_WON
;
146 case ERR
: visbell(); break;
147 case WON
: return GAME_WON
;
150 case CMD_HINT
: break;//TODO: show a possible (and sensible) move. if possible, involve active cursor
151 case CMD_UNDO
: undo_pop(f
.u
); break;
152 case CMD_INVAL
: visbell(); break;
153 case CMD_NEW
: return GAME_NEW
;
154 case CMD_AGAIN
: goto restart
;
155 case CMD_QUIT
: return GAME_QUIT
;
157 printf (KEYHELP
"\nPress any key to continue.");
170 // card games helper functions {{{
171 #define get_suit(card) \
172 ((card-1) % NUM_SUITS)
173 #define get_rank(card) \
174 ((card-1) / NUM_SUITS)
175 #define get_color(card) \
176 ((get_suit(card) ^ get_suit(card)>>1) & 1)
178 int find_top(card_t
* pile
) {
180 for(i
=PILE_SIZE
-1; i
>=0 && !pile
[i
]; i
--);
183 int first_movable(card_t
* pile
) {
184 /* NOTE: in FREECELL this does not take max_move into account! */
186 for (;pile
[i
] && !is_movable(pile
, i
); i
++);
189 int turn_over(card_t
* pile
) {
190 int top
= find_top(pile
);
196 int check_won(void) {
197 for (int pile
= 0; pile
< NUM_DECKS
*NUM_SUITS
; pile
++)
198 if (f
.f
[pile
][NUM_RANKS
-1] == NO_CARD
) return 0;
202 int rank_next (card_t a
, card_t b
) {
203 return get_rank(a
) == get_rank(b
)-1;
205 int color_ok (card_t a
, card_t b
) {
206 #if defined KLONDIKE || defined FREECELL
207 /* color opposite? */
208 return (get_color(a
) != get_color(b
));
211 return (get_suit(a
) == get_suit(b
));
214 int is_consecutive (card_t
* pile
, int pos
) {
215 if (pos
+1 >= PILE_SIZE
) return 1; /* card is last */
216 if (pile
[pos
+1] == NO_CARD
) return 1; /* card is first */
218 /* ranks consecutive? */
219 if (!rank_next(pile
[pos
+1], pile
[pos
])) return 0;
221 if (!color_ok(pile
[pos
+1], pile
[pos
])) return 0;
226 int is_movable(card_t
* pile
, int n
) {
228 return(pile
[n
] > NO_CARD
); /*non-movable cards don't exist in klondike*/
229 #elif defined SPIDER || defined FREECELL
230 int top
= find_top(pile
);
231 for (int i
= top
; i
>= 0; i
--) {
232 if (pile
[i
] <= NO_CARD
) return 0; /*no card or card face down?*/
233 if (!is_consecutive(pile
, i
)) return 0;
234 if (i
== n
) return 1; /* card reached, must be movable */
241 // takeable actions {{{
243 card_t
stack_take(void) { /*NOTE: assert(f.w >= 0) */
244 card_t card
= f
.s
[f
.w
];
245 /* move stack one over, so there are no gaps in it: */
246 for (int i
= f
.w
; i
< f
.z
-1; i
++)
249 f
.w
--; /* make previous card visible again */
252 int t2f(int from
, int to
, int opt
) { /* tableu to foundation */
253 (void) to
; (void) opt
; /* don't need */
254 int top_from
= find_top(f
.t
[from
]);
255 to
= get_suit(f
.t
[from
][top_from
]);
256 int top_to
= find_top(f
.f
[to
]);
257 if ((top_to
< 0 && get_rank(f
.t
[from
][top_from
]) == RANK_A
)
258 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.t
[from
][top_from
]))) {
259 f
.f
[to
][top_to
+1] = f
.t
[from
][top_from
];
260 f
.t
[from
][top_from
] = NO_CARD
;
261 undo_push(from
, FOUNDATION
, to
,
262 turn_over(f
.t
[from
]));
263 if (check_won()) return WON
;
267 int w2f(int from
, int to
, int opt
) { /* waste to foundation */
268 (void) from
; (void) to
; (void) opt
; /* don't need */
269 if (f
.w
< 0) return ERR
;
270 to
= get_suit(f
.s
[f
.w
]);
271 int top_to
= find_top(f
.f
[to
]);
272 if ((top_to
< 0 && get_rank(f
.s
[f
.w
]) == RANK_A
)
273 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
], f
.s
[f
.w
]))) {
274 undo_push(WASTE
, FOUNDATION
, f
.w
| to
<<16, 0);//ugly encoding :|
275 f
.f
[to
][top_to
+1] = stack_take();
276 if (check_won()) return WON
;
281 int s2w(int from
, int to
, int opt
) { /* stock to waste */
282 (void) from
; (void) to
; (void) opt
; /* don't need */
283 if (f
.z
== 0) return ERR
;
285 if (f
.w
== f
.z
) f
.w
= -1;
288 int w2s(int from
, int to
, int opt
) { /* waste to stock (undo stock to waste) */
289 (void) from
; (void) to
; (void) opt
; /* don't need */
290 if (f
.z
== 0) return ERR
;
292 if (f
.w
< -1) f
.w
= f
.z
-1;
295 int f2t(int from
, int to
, int opt
) { /* foundation to tableu */
296 (void) from
; /* don't need */
297 int top_to
= find_top(f
.t
[to
]);
299 int top_from
= find_top(f
.f
[from
]);
301 if ((get_color(f
.t
[to
][top_to
]) != get_color(f
.f
[from
][top_from
])) //TODO: color_ok()
302 && (rank_next(f
.f
[from
][top_from
], f
.t
[to
][top_to
]))) {
303 f
.t
[to
][top_to
+1] = f
.f
[from
][top_from
];
304 f
.f
[from
][top_from
] = NO_CARD
;
305 undo_push(FOUNDATION
, to
, from
, 0);
309 int w2t(int from
, int to
, int opt
) { /* waste to tableu */
310 (void) from
; (void) opt
; /* don't need */
311 if (f
.w
< 0) return ERR
;
312 int top_to
= find_top(f
.t
[to
]);
313 if (((get_color(f
.t
[to
][top_to
]) != get_color(f
.s
[f
.w
])) //TODO: color_ok()
314 && (rank_next(f
.s
[f
.w
], f
.t
[to
][top_to
])))
315 || (top_to
< 0 && get_rank(f
.s
[f
.w
]) == RANK_K
)) {
316 undo_push(WASTE
, to
, f
.w
, 0);
317 f
.t
[to
][top_to
+1] = stack_take();
321 int t2t(int from
, int to
, int opt
) { /* tableu to tableu */
322 (void) opt
; /* don't need */
323 int top_to
= find_top(f
.t
[to
]);
324 int top_from
= find_top(f
.t
[from
]);
325 for (int i
= top_from
; i
>=0; i
--) {
326 if (((get_color(f
.t
[to
][top_to
]) != get_color(f
.t
[from
][i
])) //TODO: color_ok()
327 && (rank_next(f
.t
[from
][i
], f
.t
[to
][top_to
]))
328 && f
.t
[from
][i
] > NO_CARD
) /* card face up? */
329 || (top_to
< 0 && get_rank(f
.t
[from
][i
]) == RANK_K
)) {
330 /* move cards [i..top_from] to their destination */
332 for (;i
<= top_from
; i
++) {
334 f
.t
[to
][top_to
] = f
.t
[from
][i
];
335 f
.t
[from
][i
] = NO_CARD
;
338 undo_push(from
, to
, count
,
339 turn_over(f
.t
[from
]));
343 return ERR
; /* no such move possible */
346 int remove_if_complete (int pileno
) { //cleanup!
347 card_t
* pile
= f
.t
[pileno
];
348 /* test if K...A complete; move to foundation if so */
349 int top_from
= find_top(pile
);
350 if (get_rank(pile
[top_from
]) != RANK_A
) return 0;
351 for (int i
= top_from
; i
>=0; i
--) {
352 if (!is_consecutive (pile
, i
)) return 0;
353 if (i
+RANK_K
== top_from
/* if ace to king: remove it */
354 && get_rank(pile
[top_from
-RANK_K
]) == RANK_K
) {
355 for(int i
=top_from
, j
=0; i
>top_from
-NUM_RANKS
; i
--,j
++){
356 f
.f
[f
.w
][j
] = pile
[i
];
359 undo_push(pileno
, FOUNDATION
, f
.w
,
368 int t2t(int from
, int to
, int opt
) { //in dire need of cleanup
369 int top_from
= find_top(f
.t
[from
]);
370 int top_to
= find_top(f
.t
[to
]);
371 int empty_to
= (top_to
< 0)? opt
: -1; /* empty pile? */
373 for (int i
= top_from
; i
>= 0; i
--) {
374 if (!is_consecutive(f
.t
[from
], i
)) break;
376 /* is consecutive OR to empty pile and rank ok? */
377 if (rank_next(f
.t
[from
][i
], f
.t
[to
][top_to
])
378 || (empty_to
>= RANK_A
&& get_rank(f
.t
[from
][i
]) == empty_to
)) {
380 for (;i
<= top_from
; i
++) {
382 f
.t
[to
][top_to
] = f
.t
[from
][i
];
383 f
.t
[from
][i
] = NO_CARD
;
386 undo_push(from
, to
, count
,
387 turn_over(f
.t
[from
]));
388 remove_if_complete(to
);
389 if (check_won()) return WON
;
394 return ERR
; /* no such move possible */
396 int s2t(int from
, int to
, int opt
) {
397 (void) from
; (void) to
; (void) opt
; /* don't need */
398 if (f
.z
<= 0) return ERR
; /* stack out of cards */
399 for (int pile
= 0; pile
< NUM_PILES
; pile
++)
400 if (f
.t
[pile
][0]==NO_CARD
) return ERR
; /*no piles may be empty*/
401 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
402 f
.t
[pile
][find_top(f
.t
[pile
])+1] = f
.s
[--f
.z
];
403 remove_if_complete(pile
);
404 if (check_won()) return WON
;
406 undo_push(STOCK
, TABLEU
, 1, 0);/*NOTE: puts 1 card on each tableu pile*/
409 int t2f(int from
, int to
, int opt
) {
410 (void) to
; (void) opt
; /* don't need */
411 /* manually retrigger remove_if_complete() (e.g. after undo_pop) */
412 return remove_if_complete(from
)?OK
:ERR
;
414 #elif defined FREECELL
415 int max_move(int from
, int to
) {
416 /* returns the maximum number of cards that can be moved */
417 /* see also: https://boardgames.stackexchange.com/a/45157/26498 */
418 int free_tabs
= 0, free_cells
= 0;
419 for (int i
= 0; i
< NUM_PILES
; i
++) free_tabs
+= f
.t
[i
][0] == NO_CARD
;
420 for (int i
= 0; i
< NUM_CELLS
; i
++) free_cells
+= f
.s
[i
] == NO_CARD
;
422 /* don't count the tableau we are moving to: */
423 if (to
>= 0 && f
.t
[to
][0] == NO_CARD
) free_tabs
--;
425 /* theoretic maximum is limited by the number of cards on the pile */
426 int max_theory
= (1<<free_tabs
) * (free_cells
+ 1);
427 int max_effective
= 1 + find_top(f
.t
[from
]) - first_movable(f
.t
[from
]);
428 return max_effective
< max_theory
? max_effective
: max_theory
;
430 //TODO FREECELL: auto move to tableu after each move (not all cards possible, only when it is the smallest rank still on the board)
431 int t2t(int from
, int to
, int opt
) {
432 int top_to
= find_top(f
.t
[to
]);
433 int top_from
= find_top(f
.t
[from
]);
434 int cards
= max_move(from
, to
);
435 if (top_to
< 0) { /* moving to empty pile? */
437 return ERR
; /* cannot execute move */
438 cards
= opt
; /* user wants to move n cards*/
441 for (int i
= top_from
; i
>=0; i
--) {
442 if (cards
-->0/*enough space and not more attempted than wanted*/
443 && ((top_to
>= 0 /* if destn. not empty: check rank/color */
444 && ((get_color(f
.t
[to
][top_to
]) != get_color(f
.t
[from
][i
])) //TODO: color_ok()
445 && (rank_next(f
.t
[from
][i
], f
.t
[to
][top_to
]))))
446 || (top_to
< 0 && !cards
))) {/*if dest empty and right # cards*/
447 /* move cards [i..top_from] to their destination */
449 for (;i
<= top_from
; i
++) {
451 f
.t
[to
][top_to
] = f
.t
[from
][i
];
452 f
.t
[from
][i
] = NO_CARD
;
455 undo_push(from
, to
, count
, 0);
459 return ERR
; /* no such move possible */
461 int t2f(int from
, int to
, int opt
) { /* 1:1 copy from KLONDIKE */
462 (void) to
; (void) opt
; /* don't need */
463 int top_from
= find_top(f
.t
[from
]);
464 to
= get_suit(f
.t
[from
][top_from
]);
465 int top_to
= find_top(f
.f
[to
]);
466 if ((top_to
< 0 && get_rank(f
.t
[from
][top_from
]) == RANK_A
)
467 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.t
[from
][top_from
]))) {
468 f
.f
[to
][top_to
+1] = f
.t
[from
][top_from
];
469 f
.t
[from
][top_from
] = NO_CARD
;
470 undo_push(from
, FOUNDATION
, to
, 0);
471 if (check_won()) return WON
;
475 int f2t(int from
, int to
, int opt
) {
476 (void) from
; /* don't need */
477 int top_to
= find_top(f
.t
[to
]);
479 int top_from
= find_top(f
.f
[from
]);
481 if (top_to
< 0 /* empty tableu? */
482 ||((get_color(f
.t
[to
][top_to
]) != get_color(f
.f
[from
][top_from
])) //TODO: color_ok()
483 && (rank_next(f
.f
[from
][top_from
], f
.t
[to
][top_to
])))) {
484 f
.t
[to
][top_to
+1] = f
.f
[from
][top_from
];
485 f
.f
[from
][top_from
] = NO_CARD
;
486 undo_push(FOUNDATION
, to
, from
, 0);
490 int t2c(int from
, int to
, int opt
) {
491 (void) to
; (void) opt
; /* don't need */
492 /* is a cell free? */
493 if (f
.w
== (1<<NUM_CELLS
)-1)
495 for (to
= 0; to
< NUM_CELLS
; to
++)
496 if (!(f
.w
>>to
&1)) break;
498 int top_from
= find_top(f
.t
[from
]);
499 f
.s
[to
] = f
.t
[from
][top_from
];
500 f
.t
[from
][top_from
] = NO_CARD
;
501 f
.w
|= 1<<to
; /* mark cell as occupied */
502 undo_push(from
, STOCK
, to
, 0);
506 int c2t(int from
, int to
, int opt
) {
507 (void) from
; /* don't need */
508 int top_to
= find_top(f
.t
[to
]);
511 if (top_to
< 0 /* empty tableu? */
512 ||((get_color(f
.t
[to
][top_to
]) != get_color(f
.s
[from
])) //TODO: color_ok()
513 && (rank_next(f
.s
[from
], f
.t
[to
][top_to
])))) {
514 f
.t
[to
][top_to
+1] = f
.s
[from
];
516 f
.w
&= ~(1<<from
); /* mark cell as free */
517 undo_push(STOCK
, to
, from
, 0);
522 int c2f(int from
, int to
, int opt
) {
523 (void) from
; (void) to
; /* don't need */
525 to
= get_suit(f
.s
[from
]);
526 int top_to
= find_top(f
.f
[to
]);
527 if ((top_to
< 0 && get_rank(f
.s
[from
]) == RANK_A
)
528 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.s
[from
]))) {
529 f
.f
[to
][top_to
+1] = f
.s
[from
];
531 f
.w
&= ~(1<<from
); /* mark cell as free */
532 undo_push(STOCK
, FOUNDATION
, from
| to
<<16, 0);
533 if (check_won()) return WON
;
537 int f2c(int from
, int to
, int opt
) {
538 (void) from
; (void) to
; /* don't need */
539 /* is a cell free? */
540 if (f
.w
== (1<<NUM_CELLS
)-1)
542 for (to
= 0; to
< NUM_CELLS
; to
++)
543 if (!(f
.w
>>to
&1)) break;
546 int top_from
= find_top(f
.f
[from
]);
547 f
.s
[to
] = f
.f
[from
][top_from
];
548 f
.f
[from
][top_from
] = NO_CARD
;
549 f
.w
|= 1<<to
; /* mark cell as occupied */
550 undo_push(FOUNDATION
, STOCK
, from
| to
<<16, 0);
556 //TODO: generalize prediction engine for CMD_HINT
558 #define would_complete(pile) 0
560 #define would_complete(pile) \
561 (get_rank(f.t[pile][r[pile].top]) == RANK_A \
562 && get_rank(f.t[to][bottom_to]) == RANK_K)
563 #elif defined FREECELL
564 #define would_complete(pile) 0
566 #define would_turn(pile) \
567 (f.t[pile][r[pile].pos-1] < 0)
568 #define would_empty(pile) \
572 //TODO FREECELL: join to empty tableu (longest cascade?)
573 int top_to
= find_top(f
.t
[to
]);
575 int bottom_to
= first_movable(f
.t
[to
]);
579 if (to
== WASTE
|| to
== STOCK
) return ERR
; /*why would you do that!?*/
581 if (to
== FOUNDATION
) {
583 for (int i
= 0; i
<= TAB_MAX
; i
++)
584 switch ((i
?t2f
:w2f
)(i
-1, FOUNDATION
, 0)) {
585 case WON
: return WON
;
586 case OK
: status
= OK
;
592 if (top_to
< 0) { /* move a king to empty pile: */
593 for (int i
= 0; i
< TAB_MAX
; i
++) {
594 if (f
.t
[i
][0] < 0) /* i.e. would turn? */
595 if (t2t(i
, to
, 0) == OK
) return OK
;
597 return w2t(WASTE
, to
, 0);
602 int ok
:1; /* card to move in pile? */
603 int above
; /* number of movable cards above */
604 int below
; /* number of cards below ours */
605 int pos
; /* where the card to move is in the pile */
606 int top
; /* find_top() */
607 } r
[NUM_PILES
] = {{0}};
608 int complete
= 0;/* SPIDER: true if any pile would complete a stack */
609 int turn
= 0; /* SPIDER: true if any pile would turn_over */
610 int empty
= 0; /* true if any pile would become empty */
612 /* 1. rate each pile: */
615 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
616 if (pile
== to
) continue;
617 int top
= find_top(f
.t
[pile
]);
618 int bottom
= first_movable(f
.t
[pile
]);
619 r
[pile
].pos
= bottom
; /* need for would_empty */
621 if (top
< 0) continue; /* no cards to move */
622 if (would_empty(pile
)) continue; /* doesn't help */
625 r
[pile
].above
= 0; /* always take as many as possible */
626 r
[pile
].below
= top
- bottom
;
628 complete
|= would_complete(pile
); /* never happens */
629 turn
|= would_turn(pile
);
630 empty
|= would_empty(pile
);
634 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
635 r
[pile
].top
= r
[pile
].pos
= find_top(f
.t
[pile
]);
636 /* backtrack until we find a compatible-to-'to'-pile card: */
638 int maxmove
= max_move(pile
, -1);
640 while (r
[pile
].pos
>= 0 && is_movable(f
.t
[pile
], r
[pile
].pos
)) {
641 int rankdiff
= get_rank(f
.t
[pile
][r
[pile
].pos
])
642 - get_rank(f
.t
[to
][top_to
]);
643 if (rankdiff
>= 0) break; /* past our card */
645 if (!maxmove
--) break; /* can't move this many cards */
647 if (rankdiff
== -1 && /* rank matches */
648 color_ok(f
.t
[pile
][r
[pile
].pos
], f
.t
[to
][top_to
])
651 complete
|= would_complete(pile
);
652 turn
|= would_turn(pile
);
653 empty
|= would_empty(pile
);
654 for (int i
= r
[pile
].pos
; i
>= 0; i
--)
655 if (is_movable(f
.t
[pile
], i
-1))
665 /* 2. find optimal pile: (optimized for spider) */
666 //todo: in spider, prefer longest piles if above==0 (faster completions)
668 for (int pile
= 0, above
= 99, below
= 99; pile
< NUM_PILES
; pile
++) {
669 if (!r
[pile
].ok
) continue;
670 /* don't bother if another pile would be better: prefer ... */
671 /* ... to complete a stack: */
672 if (!would_complete(pile
) && complete
) continue;
673 /* ... emptying piles: */
674 if (!would_empty(pile
) && empty
&& !complete
) continue;
675 /* ... to turn_over: */
676 if (!would_turn(pile
) && turn
&& !complete
&& !empty
) continue;
677 /* ... not to rip apart too many cards: */
678 if (r
[pile
].above
> above
) continue;
679 /* if tied, prefer ... */
680 if (r
[pile
].above
== above
681 /* ... larger pile if destination is empty */
682 && (top_to
< 0? r
[pile
].below
< below
683 /* ... shorter pile otherwise */
684 : r
[pile
].below
> below
))
688 above
= r
[pile
].above
;
689 below
= r
[pile
].below
;
692 /* 3. move cards over and return: */
694 /* prefer waste if it wouldn't turn_over: */
695 /* NOTE: does not attempt to take from froundation */
696 if (!empty
&& !turn
&& w2t(WASTE
, to
, 0) == OK
)
698 if (from
< 0) /* nothing found */
700 return t2t(from
, to
, 0);
702 if (from
< 0) /* nothing found */
704 int bottom
= first_movable(f
.t
[from
]);
705 return t2t(from
, to
, get_rank(f
.t
[from
][bottom
]));
706 #elif defined FREECELL
707 if (from
< 0) /* no tableu move found */ {
708 /* try all free cells before giving up: */
709 for (int i
= 0; i
< NUM_CELLS
; i
++)
710 if (c2t(STOCK
, to
, i
) == OK
) return OK
;
713 return t2t(from
, to
, 0);
718 #undef would_complete
719 int nop(int from
, int to
, int opt
) { (void)from
;(void)to
;(void)opt
;return ERR
; }
722 // keyboard input handling {{{
723 // cursor functions{{{
725 void cursor_left (struct cursor
* cursor
) {
727 if (is_tableu(cursor
->pile
)) {
728 if (cursor
->pile
> 0) cursor
->pile
--;
730 } else { /* stock/waste/foundation*/
731 switch (cursor
->pile
) {
732 case WASTE
: cursor
->pile
= STOCK
; cursor
->opt
= 0; break;
734 if (cursor
->opt
<= 0)
735 cursor
->pile
= WASTE
;
741 void cursor_down (struct cursor
* cursor
) {
743 if (!is_tableu(cursor
->pile
)) {
744 switch (cursor
->pile
) {
745 case STOCK
: cursor
->pile
= TAB_1
; break;
746 case WASTE
: cursor
->pile
= TAB_2
; break;
748 cursor
->pile
= TAB_4
+ cursor
->opt
;
753 void cursor_up (struct cursor
* cursor
) {
755 if (is_tableu(cursor
->pile
)) {
756 switch (cursor
->pile
) { //ugly :|
757 case TAB_1
: cursor
->pile
= STOCK
; break;
758 case TAB_2
: cursor
->pile
= WASTE
; break;
759 case TAB_3
: cursor
->pile
= WASTE
; break;
760 case TAB_4
: case TAB_5
: case TAB_6
: case TAB_7
:
761 cursor
->opt
=cursor
->pile
-TAB_4
;
762 cursor
->pile
= FOUNDATION
;
767 void cursor_right (struct cursor
* cursor
) {
769 if (is_tableu(cursor
->pile
)) {
770 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
773 switch (cursor
->pile
) {
774 case STOCK
: cursor
->pile
= WASTE
; break;
775 case WASTE
: cursor
->pile
= FOUNDATION
;cursor
->opt
= 0; break;
777 if (cursor
->opt
< NUM_SUITS
-1)
783 /*NOTE: one can't highlight the stock due to me being too lazy to implement it*/
784 void cursor_left (struct cursor
* cursor
) {
786 if (cursor
->pile
> 0) cursor
->pile
--;
789 void cursor_down (struct cursor
* cursor
) {
791 int first
= first_movable(f
.t
[cursor
->pile
]);
792 int top
= find_top(f
.t
[cursor
->pile
]);
793 if (first
+ cursor
->opt
< top
)
796 void cursor_up (struct cursor
* cursor
) {
798 if (cursor
->opt
> 0) cursor
->opt
--;
800 void cursor_right (struct cursor
* cursor
) {
802 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
805 #elif defined FREECELL
806 void cursor_left (struct cursor
* cursor
) {
808 if (is_tableu(cursor
->pile
)) {
809 if (cursor
->pile
> 0) cursor
->pile
--;
811 } else { /* cells/foundation*/
812 switch (cursor
->pile
) {
818 if (cursor
->opt
<= 0) {
819 cursor
->pile
= STOCK
;
827 void cursor_down (struct cursor
* cursor
) {
829 if (is_tableu(cursor
->pile
)) {
830 if (cursor
->opt
< max_move(cursor
->pile
, -1)-1)
833 cursor
->pile
= cursor
->opt
+NUM_CELLS
*(cursor
->pile
==FOUNDATION
);
837 void cursor_up (struct cursor
* cursor
) {
839 if (is_tableu(cursor
->pile
)) {
840 if (cursor
->opt
> 0) {
843 switch (cursor
->pile
) {
844 case TAB_1
: case TAB_2
: case TAB_3
: case TAB_4
:
845 cursor
->opt
= cursor
->pile
; /*assumes TAB_1==0*/
846 cursor
->pile
= STOCK
;
848 case TAB_5
: case TAB_6
: case TAB_7
: case TAB_8
:
849 cursor
->opt
= cursor
->pile
- NUM_CELLS
;
850 cursor
->pile
= FOUNDATION
;
855 void cursor_right (struct cursor
* cursor
) {
857 if (is_tableu(cursor
->pile
)) {
858 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
861 switch (cursor
->pile
) {
863 if (cursor
->opt
< NUM_SUITS
-1) {
866 cursor
->pile
= FOUNDATION
;
870 if (cursor
->opt
< NUM_SUITS
-1)
876 void cursor_to (struct cursor
* cursor
, int pile
) {
881 int set_mouse(int pile
, int* main
, int* opt
) {
882 //TODO: this should set cursor.opt, so card selector choice dialog does not trigger!
884 if (pile
< 0) return 1;
887 if (pile
>= FOUNDATION
)//TODO: check upper bound!
889 *opt
= pile
- FOUNDATION
;
892 #elif defined FREECELL
893 if (pile
> TAB_MAX
) {
894 *main
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
895 *opt
= (pile
-STOCK
) % 4;
901 int get_cmd (int* from
, int* to
, int* opt
) {
903 unsigned char mouse
[6] = {0}; /* must clear [3]! */
904 struct cursor inactive
= {-1,-1};
905 static struct cursor active
= {0,0};
906 if (is_tableu(active
.pile
))
910 from_l
: print_table(&active
, &inactive
);
914 /* direct addressing: */
915 case '1': *from
= TAB_1
; break;
916 case '2': *from
= TAB_2
; break;
917 case '3': *from
= TAB_3
; break;
918 case '4': *from
= TAB_4
; break;
919 case '5': *from
= TAB_5
; break;
920 case '6': *from
= TAB_6
; break;
921 case '7': *from
= TAB_7
; break;
923 case '8': *from
= TAB_8
; break;
924 case '9': *from
= TAB_9
; break;
925 case '0': *from
= TAB_10
;break;
926 #elif defined FREECELL
927 case '8': *from
= TAB_8
; break;
928 case '9': *from
= STOCK
; break;
929 case '0': *from
= FOUNDATION
; break;
930 #elif defined KLONDIKE
931 case '9': *from
= WASTE
; break;
932 case '0': *from
= FOUNDATION
; break;
933 case '8': /* fallthrough */
936 case '\n': *from
= STOCK
; break;
938 /* cursor keys addressing: */
940 case 'h': cursor_left (&active
); goto from_l
;
942 case 'j': cursor_down (&active
); goto from_l
;
944 case 'k': cursor_up (&active
); goto from_l
;
946 case 'l': cursor_right(&active
); goto from_l
;
948 case 'H': cursor_to(&active
,TAB_1
); goto from_l
; /* leftmost tableu */
950 case 'L': cursor_to(&active
,TAB_MAX
);goto from_l
; /* rigthmost tableu */
952 case 'M': cursor_to(&active
,TAB_MAX
/2); goto from_l
; /* center tableu */
953 case ' ': /* continue with second cursor */
956 *opt
= active
.opt
; /* when FOUNDATION */
960 /* mouse addressing: */
961 case MOUSE_MIDDLE
: return CMD_NONE
;
963 if (set_mouse(term2pile(mouse
), to
, opt
))
967 if (set_mouse(term2pile(mouse
), from
, opt
))
969 if (!is_tableu(*from
))
970 inactive
.opt
= *opt
; /* prevents card selector dialog */
975 fprintf (stderr
, ":");
976 raw_mode(0); /* turn on echo */
977 fgets (buf
, 256, stdin
);
980 case 'q': return CMD_QUIT
;
981 case 'n': return CMD_NEW
;
982 case 'r': return CMD_AGAIN
;
983 case 'h': return CMD_HELP
;
984 default: return CMD_INVAL
;
990 if (*to
== FOUNDATION
) return CMD_JOIN
;
992 if (*to
> TAB_MAX
) return CMD_INVAL
;
994 case 'K': /* fallthrough */
995 case '?': return CMD_HINT
;
996 case 'u': return CMD_UNDO
;
997 case 002: return CMD_NONE
; /* sent by SIGWINCH */
998 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
999 default: return CMD_INVAL
;
1001 inactive
.pile
= *from
; /* for direct addressing highlighting */
1002 if (is_tableu(*from
) && f
.t
[*from
][0] == NO_CARD
) return CMD_INVAL
;
1005 if (*from
== STOCK
) {
1012 to_l
: print_table(&active
, &inactive
);
1017 case 'h': cursor_left (&active
); goto to_l
;
1019 case 'j': cursor_down (&active
); goto to_l
;
1021 case 'k': cursor_up (&active
); goto to_l
;
1023 case 'l': cursor_right(&active
); goto to_l
;
1025 case 'H': cursor_to(&active
,TAB_1
); goto to_l
;
1027 case 'L': cursor_to(&active
,TAB_MAX
); goto to_l
;
1029 case 'M': cursor_to(&active
,TAB_MAX
/2); goto to_l
;
1030 case 'J': /* fallthrough; just join selected pile */
1033 break; /* continues with the foundation/empty tableu check */
1035 case MOUSE_RIGHT
: return CMD_NONE
;
1037 if (set_mouse(term2pile(mouse
), to
, opt
))
1040 //TODO: set opt if to field is empty; suppress "up do" dialog from below
1041 if (is_tableu(*to) && f.t[*to][0] == NO_CARD) {
1042 int top = find_top(f.t[*from]);
1043 if (top < 0) return CMD_INVAL;
1044 if (top >= 0 && !is_movable(f.t[*from], top-1)) {
1045 *opt = get_rank(f.t[*from][top]);
1052 case 'K': /* fallthrough */
1053 case '?': return CMD_HINT
;
1054 case 'u': return CMD_NONE
; /* cancel selection */
1055 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1057 if (t
< '0' || t
> '9') return CMD_INVAL
;
1061 #elif defined SPIDER
1063 #elif defined FREECELL
1073 /* direct addressing post-processing stage:
1074 because foundations/freecells share the same key (and you can't select
1075 partial piles) there are sometimes ambiguous situations where it isn't
1076 clear from which pile (or how many cards) to take. the code below will
1077 only ask the user if there are at least two possible moves and
1078 automatically choose otherwise. */
1080 /* if it was selected with a cursor, it's obvious: */
1081 if (inactive
.opt
>= 0) {
1082 if (is_tableu(*from
)) {
1083 /* NOTE: max_move same as in cursor_down() */
1084 *opt
= max_move(*from
, -1) - inactive
.opt
;
1086 *opt
= inactive
.opt
;
1088 /* moving from tableu to empty tableu? */
1089 } else if(is_tableu(*from
) && is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
){
1090 int top
= find_top(f
.t
[*from
]);
1091 int max
= max_move(*from
, *to
);
1093 if (top
< 0) return CMD_INVAL
;
1094 if (max
== 1) { /* only 1 movable? */
1095 return *opt
= 1, CMD_MOVE
;
1096 } else { /* only ask the user if it's unclear: */
1097 int bottom
= top
- (max
-1);
1098 printf ("\rup to ([a23456789xjqk] or space/return): ");
1101 case ' ': rank
= get_rank(f
.t
[*from
][top
]); break;
1102 case'\n': rank
= get_rank(f
.t
[*from
][bottom
]); break;
1103 case 'a': case 'A': rank
= RANK_A
; break;
1104 case '0': /* fallthrough */
1105 case 'x': case 'X': rank
= RANK_X
; break;
1106 case 'j': case 'J': rank
= RANK_J
; break;
1107 case 'q': case 'Q': rank
= RANK_Q
; break;
1108 case 'k': case 'K': rank
= RANK_K
; break;
1109 default: rank
-= '1';
1111 if (rank
< RANK_A
|| rank
> RANK_K
) return CMD_INVAL
;
1113 for (int i
= 0; max
--; i
++)
1114 if (get_rank(f
.t
[*from
][top
-i
]) == rank
)
1115 return *opt
= 1+i
, CMD_MOVE
;
1119 /* `opt` is the number of cards to move */
1120 /* moving between stock/foundation? */
1121 } else if (*from
== FOUNDATION
&& *to
== FOUNDATION
) {
1122 return CMD_INVAL
; /* nonsensical */
1123 } else if (*from
== FOUNDATION
&& *to
== STOCK
) {
1124 if (f
.w
== (1<<NUM_CELLS
)-1) return CMD_INVAL
; /*no free cells*/
1125 int ok_foundation
; /* find compatible (non-empty) foundations:*/
1126 int used_fs
=0; for (int i
= 0; i
< NUM_SUITS
; i
++)
1127 if (!!f
.f
[i
][0]) ok_foundation
= i
, used_fs
++;
1129 if (used_fs
== 0) return CMD_INVAL
; /* nowhere to take from */
1130 if (used_fs
== 1) { /* take from the only one */
1131 return *opt
= ok_foundation
, CMD_MOVE
;
1132 } else { /* ask user */
1133 printf ("take from (1-4): "); fflush (stdout
);
1134 *opt
= getch(NULL
) - '1';
1135 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1137 /* `opt` is the foundation index (0..3) */
1138 } else if (*from
== STOCK
) { /* cell -> foundation/tableu */
1139 if (!f
.w
) return CMD_INVAL
; /* no cell to take from */
1140 int ok_cell
; /* find compatible (non-empty) cells: */
1141 int tab
= is_tableu(*to
);
1142 int used_cs
=0; for (int i
= 0; i
< NUM_CELLS
; i
++) {
1143 card_t
* pile
= (tab
?f
.t
[*to
]:f
.f
[get_suit(f
.s
[i
])]);
1144 int top_to
= find_top(pile
);
1145 if (tab
? /* to tableu? */
1147 ||(top_to
>=0 && rank_next(f
.s
[i
], pile
[top_to
])
1148 && color_ok(f
.s
[i
], pile
[top_to
])))
1149 : /* to foundation? */
1150 ((top_to
<0 && get_rank(f
.s
[i
]) == RANK_A
)
1151 ||(top_to
>=0 && rank_next(pile
[top_to
],f
.s
[i
])))
1153 ok_cell
= i
, used_cs
++;
1156 if (used_cs
== 0) return CMD_INVAL
; /* nowhere to take from */
1157 if (used_cs
== 1) { /* take from the only one */
1158 return *opt
= ok_cell
, CMD_MOVE
;
1159 } else { /* ask user */
1160 printf ("take from (1-4): "); fflush (stdout
);
1161 *opt
= getch(NULL
) - '1';
1162 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1164 /* `opt` is the cell index (0..3) */
1167 #if defined KLONDIKE || defined FREECELL
1168 if (*from
== FOUNDATION
) {
1169 if (inactive
.opt
>= 0) {
1170 *opt
= inactive
.opt
;
1173 int top
= find_top(f
.t
[*to
]);
1174 if (top
< 0) return CMD_INVAL
;
1175 int color
= get_color(f
.t
[*to
][top
]);
1176 int choice_1
= 1-color
; /* selects piles of */
1177 int choice_2
= 2+color
; /* the opposite color */
1178 int top_c1
= find_top(f
.f
[choice_1
]);
1179 int top_c2
= find_top(f
.f
[choice_2
]);
1181 switch ((rank_next(f
.f
[choice_1
][top_c1
], f
.t
[*to
][top
])
1182 && top_c1
>= 0 ) << 0
1183 |(rank_next(f
.f
[choice_2
][top_c2
], f
.t
[*to
][top
])
1184 && top_c2
>= 0 ) << 1) {
1185 case ( 1<<0): *opt
= choice_1
; break; /* choice_1 only */
1186 case (1<<1 ): *opt
= choice_2
; break; /* choice_2 only */
1187 case (1<<1 | 1<<0): /* both, ask user which to pick from */
1188 printf ("take from (1-4): "); fflush (stdout
);
1189 *opt
= getch(NULL
) - '1';
1190 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1192 default: return CMD_INVAL
; /* none matched */
1194 /* `opt` is the foundation index (0..3) */
1196 #elif defined SPIDER
1197 /* moving to empty tableu? */
1198 if (is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
) {
1199 int bottom
= first_movable(f
.t
[*from
]);
1200 if (inactive
.opt
>= 0) { /*if from was cursor addressed: */
1201 *opt
= get_rank(f
.t
[*from
][bottom
+ inactive
.opt
]);
1204 int top
= find_top(f
.t
[*from
]);
1205 if (top
< 0) return CMD_INVAL
;
1206 if (top
>= 0 && !is_movable(f
.t
[*from
], top
-1)) {
1207 *opt
= get_rank(f
.t
[*from
][top
]);
1208 } else { /* only ask the user if it's unclear: */
1209 printf ("\rup to ([a23456789xjqk] or space/return): ");
1212 case ' ': *opt
= get_rank(f
.t
[*from
][top
]); break;
1213 case'\n': *opt
= get_rank(f
.t
[*from
][bottom
]); break;
1214 case 'a': case 'A': *opt
= RANK_A
; break;
1215 case '0': /* fallthrough */
1216 case 'x': case 'X': *opt
= RANK_X
; break;
1217 case 'j': case 'J': *opt
= RANK_J
; break;
1218 case 'q': case 'Q': *opt
= RANK_Q
; break;
1219 case 'k': case 'K': *opt
= RANK_K
; break;
1220 default: *opt
-= '1';
1222 if (*opt
< RANK_A
|| *opt
> RANK_K
) return CMD_INVAL
;
1224 /* `opt` is the rank of the highest card to move */
1230 int getctrlseq(unsigned char* buf
) {
1238 int offset
= 0x20; /* never sends control chars as data */
1239 while ((c
= getchar()) != EOF
) {
1243 case '\033': state
=ESC_SENT
; break;
1249 case '[': state
=CSI_SENT
; break;
1250 default: return KEY_INVAL
;
1255 case 'A': return KEY_UP
;
1256 case 'B': return KEY_DOWN
;
1257 case 'C': return KEY_RIGHT
;
1258 case 'D': return KEY_LEFT
;
1259 /*NOTE: home/end send ^[[x~ . no support for modifiers*/
1260 case 'H': return KEY_HOME
;
1261 case 'F': return KEY_END
;
1262 case '2': getchar(); return KEY_INS
;
1263 case '5': getchar(); return KEY_PGUP
;
1264 case '6': getchar(); return KEY_PGDN
;
1265 case 'M': state
=MOUSE_EVENT
; break;
1266 default: return KEY_INVAL
;
1270 if (buf
== NULL
) return KEY_INVAL
;
1271 buf
[0] = c
- offset
;
1272 buf
[1] = getchar() - offset
;
1273 buf
[2] = getchar() - offset
;
1281 int term2pile(unsigned char *mouse
) {
1282 int line
= (mouse
[2]-1);
1283 int column
= (mouse
[1]-1) / op
.s
->width
;
1285 if (line
< op
.s
->height
) { /* first line */
1288 case 0: return STOCK
;
1289 case 1: return WASTE
;
1290 case 2: return -1; /* spacer */
1291 case 3: return FOUNDATION
+0;
1292 case 4: return FOUNDATION
+1;
1293 case 5: return FOUNDATION
+2;
1294 case 6: return FOUNDATION
+3;
1296 #elif defined SPIDER
1297 if (column
< 3) return STOCK
;
1299 #elif defined FREECELL
1300 if (column
< NUM_SUITS
+ NUM_CELLS
) return STOCK
+column
;
1303 } else if (line
> op
.s
->height
) { /* tableu */
1304 if (column
<= TAB_MAX
) return column
;
1308 int wait_mouse_up(unsigned char* mouse
) {
1309 //TODO: mouse drag: start gets inactive, hovering gets active cursors
1310 struct cursor cur
= {-1,-1};
1312 /* note: if dragged [3]==1 and second position is in mouse[0,4,5] */
1314 /* display a cursor while mouse button is pushed: */
1315 int pile
= term2pile(mouse
);
1318 if (pile
>= FOUNDATION
) {
1319 cur
.pile
= FOUNDATION
;
1320 cur
.opt
= pile
-FOUNDATION
;
1322 #elif defined FREECELL
1323 if (pile
> TAB_MAX
) {
1324 cur
.pile
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
1325 cur
.opt
= (pile
-STOCK
) % 4;
1328 /* need to temporarily show the cursor, then revert to last state: */
1329 int old_show_cursor_hi
= op
.h
; //TODO: ARGH! that's awful!
1331 print_table(&cur
, NO_HI
); //TODO: should not overwrite inactive cursor!
1332 op
.h
= old_show_cursor_hi
;
1335 if (getctrlseq (mouse
+3) == MOUSE_ANY
) {
1336 /* ignore mouse wheel events: */
1337 if (mouse
[3] & 0x40) continue;
1339 else if((mouse
[3]&3) == 3) level
--; /* release event */
1340 else level
++; /* another button pressed */
1344 int success
= mouse
[1] == mouse
[4] && mouse
[2] == mouse
[5];
1351 int getch(unsigned char* buf
) {
1352 //TODO: if buf==NULL disable mouse input
1353 /* returns a character, EOF, or constant for an escape/control sequence - NOT
1354 compatible with the ncurses implementation of same name */
1356 if (buf
&& buf
[3]) {
1357 /* mouse was dragged; return 'ungetted' previous destination */
1358 action
= MOUSE_DRAG
;
1359 /* keep original [0], as [3] only contains release event */
1364 action
= getctrlseq(buf
);
1369 if (buf
[0] > 3) break; /* ignore wheel events */
1374 case 0: return MOUSE_LEFT
;
1375 case 1: return MOUSE_MIDDLE
;
1376 case 2: return MOUSE_RIGHT
;
1384 // shuffling and dealing {{{
1385 void deal(long seed
) {
1386 f
= (const struct playfield
){0}; /* clear playfield */
1387 card_t deck
[DECK_SIZE
*NUM_DECKS
];
1388 int avail
= DECK_SIZE
*NUM_DECKS
;
1389 for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) deck
[i
] = (i
%DECK_SIZE
)+1;
1391 if (op
.m
!= NORMAL
) for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) {
1392 if (op
.m
== MEDIUM
) deck
[i
] = 1+((deck
[i
]-1) | 2);
1393 if (op
.m
== EASY
) deck
[i
] = 1+((deck
[i
]-1) | 2 | 1);
1394 /* the 1+ -1 dance gets rid of the offset created by NO_CARD */
1398 for (int i
= DECK_SIZE
*NUM_DECKS
-1; i
> 0; i
--) { /* fisher-yates */
1399 int j
= rand() % (i
+1);
1400 if (j
-i
) deck
[i
]^=deck
[j
],deck
[j
]^=deck
[i
],deck
[i
]^=deck
[j
];
1404 for (int i
= 0; i
< NUM_PILES
; i
++) {
1407 int count
= i
; /* pile n has n closed cards, then 1 open */
1408 #elif defined SPIDER
1410 int count
= i
<4?5:4; /* pile 1-4 have 5, 5-10 have 4 closed */
1411 #elif defined FREECELL
1413 int count
= i
<4?6:5;/*like spider, but cards are dealt face-up*/
1415 /* "SIGN": face down cards are negated */
1416 for (int j
= 0; j
< count
; j
++) f
.t
[i
][j
] = SIGN deck
[--avail
];
1417 f
.t
[i
][count
] = deck
[--avail
]; /* the face-up card */
1420 /* rest of the cards to the stock: */
1421 /* NOTE: assert(avail==50) for spider, assert(avail==0) for freecell */
1422 for (f
.z
= 0; avail
; f
.z
++) f
.s
[f
.z
] = deck
[--avail
];
1424 f
.w
= -1; /* @start: nothing on waste */
1425 #elif defined SPIDER
1426 f
.w
= 0; /* number of used foundations */
1427 #elif defined FREECELL
1428 f
.w
= 0; /* bitmask of used free cells */
1431 f
.u
= &undo_sentinel
;
1435 // screen drawing routines {{{
1436 void print_hi(int invert
, int grey_bg
, int bold
, char* str
) {
1437 if (!op
.h
) invert
= 0; /* don't show invert if we used the mouse last */
1438 if (bold
&& op
.s
== &unicode_large_color
){ //awful hack for bold + faint
1439 int offset
= str
[3]==017?16:str
[4]==017?17:0;
1440 printf ("%s%s%s""%.*s%s%s""%s%s%s",
1441 "\033[1m", invert
?"\033[7m":"", grey_bg
?"\033[100m":"",
1442 offset
, str
, bold
?"\033[1m":"", str
+offset
,
1443 grey_bg
?"\033[49m":"", invert
?"\033[27m":"","\033[22m");
1446 printf ("%s%s%s%s%s%s%s",
1447 bold
?"\033[1m":"", invert
?"\033[7m":"", grey_bg
?"\033[100m":"",
1449 grey_bg
?"\033[49m":"", invert
?"\033[27m":"",bold
?"\033[22m":"");
1451 void print_table(const struct cursor
* active
, const struct cursor
* inactive
) {
1452 printf("\033[2J\033[H"); /* clear screen, reset cursor */
1454 /* print stock, waste and foundation: */
1455 for (int line
= 0; line
< op
.s
->height
; line
++) {
1457 print_hi (active
->pile
== STOCK
, inactive
->pile
== STOCK
, 1, (
1458 (f
.w
< f
.z
-1)?op
.s
->facedown
1459 :op
.s
->placeholder
)[line
]);
1461 print_hi (active
->pile
== WASTE
, inactive
->pile
== WASTE
, 1, (
1462 /* NOTE: cast, because f.w sometimes is (short)-1 !? */
1463 ((short)f
.w
>= 0)?op
.s
->card
[f
.s
[f
.w
]]
1464 :op
.s
->placeholder
)[line
]);
1465 printf ("%s", op
.s
->card
[NO_CARD
][line
]); /* spacer */
1467 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1468 int card
= find_top(f
.f
[pile
]);
1469 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1470 inactive
->pile
==FOUNDATION
&& (
1471 /* cursor addr. || direct addr. */
1472 inactive
->opt
==pile
|| inactive
->opt
< 0
1474 (card
< 0)?op
.s
->foundation
[line
]
1475 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1480 #elif defined SPIDER
1481 int fdone
; for (fdone
= NUM_DECKS
*NUM_SUITS
; fdone
; fdone
--)
1482 if (f
.f
[fdone
-1][RANK_K
]) break; /*number of completed stacks*/
1483 int spacer_from
= f
.z
?(f
.z
/10-1) * op
.s
->halfwidth
[0] + op
.s
->width
:0;
1484 int spacer_to
= NUM_PILES
*op
.s
->width
-
1485 ((fdone
?(fdone
-1) * op
.s
->halfwidth
[1]:0)+op
.s
->width
);
1486 for (int line
= 0; line
< op
.s
->height
; line
++) {
1487 /* available stock: */
1488 for (int i
= f
.z
/10; i
; i
--) {
1489 if (i
==1) printf ("%s", op
.s
->facedown
[line
]);
1490 else printf ("%s", op
.s
->halfstack
[line
]);
1493 for (int i
= spacer_from
; i
< spacer_to
; i
++) printf (" ");
1494 /* foundation (overlapping): */
1495 for (int i
= NUM_DECKS
*NUM_SUITS
-1, half
= 0; i
>= 0; i
--) {
1496 int overlap
= half
? op
.s
->halfcard
[line
]: 0;
1497 if (f
.f
[i
][RANK_K
]) printf ("%.*s", op
.s
->halfwidth
[2],
1498 op
.s
->card
[f
.f
[i
][RANK_K
]][line
]+overlap
),
1504 #elif defined FREECELL
1505 /* print open cells, foundation: */
1506 for (int line
= 0; line
< op
.s
->height
; line
++) {
1507 for (int pile
= 0; pile
< NUM_CELLS
; pile
++)
1508 print_hi (active
->pile
==STOCK
&& active
->opt
==pile
,
1509 inactive
->pile
==STOCK
&& (
1510 /* cursor addr. || direct addr. */
1511 inactive
->opt
==pile
|| inactive
->opt
< 0
1513 ((f
.s
[pile
])?op
.s
->card
[f
.s
[pile
]]
1514 :op
.s
->placeholder
)[line
]);
1515 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1516 int card
= find_top(f
.f
[pile
]);
1517 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1518 inactive
->pile
==FOUNDATION
&& (
1519 /* cursor addr. || direct addr. */
1520 inactive
->opt
==pile
|| inactive
->opt
< 0
1522 (card
< 0)?op
.s
->foundation
[line
]
1523 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1530 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty))
1531 #define TOP_HI(c) 1 /* can't select partial stacks in KLONDIKE */
1532 #elif defined SPIDER || defined FREECELL
1533 int offset
[NUM_PILES
]={0}; /* first card to highlight */
1535 int bottom
[NUM_PILES
]; /* first movable card */
1536 for (int i
=0; i
<NUM_PILES
; i
++)
1537 bottom
[i
] = find_top(f
.t
[i
]) - max_move(i
,-1);
1539 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty) \
1540 && offset[pile] >= cursor->opt)
1541 #define TOP_HI(cursor) (cursor->pile == pile && movable \
1542 && offset[pile] == cursor->opt)
1544 /* print tableu piles: */
1545 int row
[NUM_PILES
] = {0};
1546 int line
[NUM_PILES
]= {0};
1547 int label
[NUM_PILES
]={0};
1549 int did_placeholders
= 0;
1552 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
1553 card_t card
= f
.t
[pile
][row
[pile
]];
1554 card_t next
= f
.t
[pile
][row
[pile
]+1];
1555 int movable
= is_movable(f
.t
[pile
], row
[pile
]);
1557 if(row
[pile
] <= bottom
[pile
]) movable
= 0;
1559 int empty
= !card
&& row
[pile
] == 0;
1561 print_hi (DO_HI(active
), DO_HI(inactive
), movable
, (
1562 (!card
&& row
[pile
] == 0)?op
.s
->placeholder
1563 :(card
<0)?op
.s
->facedown
1567 int extreme_overlap
= ( 3 /* spacer, labels, status */
1568 + 2 * op
.s
->height
/* stock, top tableu card */
1569 + find_top(f
.t
[pile
]) * op
.s
->overlap
) >op
.w
[0];
1570 /* normal overlap: */
1571 if (++line
[pile
] >= (next
?op
.s
->overlap
:op
.s
->height
)
1572 /* extreme overlap on closed cards: */
1573 || (extreme_overlap
&&
1575 f
.t
[pile
][row
[pile
]] < 0 &&
1576 f
.t
[pile
][row
[pile
]+1] <0)
1577 /* extreme overlap on sequences: */
1578 || (extreme_overlap
&&
1579 !TOP_HI(active
) && /*always show top selected card*/
1580 line
[pile
] >= 1 && row
[pile
] > 0 &&
1581 f
.t
[pile
][row
[pile
]-1] > NO_CARD
&&
1582 is_consecutive (f
.t
[pile
], row
[pile
]) &&
1583 is_consecutive (f
.t
[pile
], row
[pile
]-1) &&
1584 f
.t
[pile
][row
[pile
]+1] != NO_CARD
)
1588 #if defined SPIDER || defined FREECELL
1589 if (movable
) offset
[pile
]++;
1592 /* tableu labels: */
1593 if(!card
&& !label
[pile
] && row
[pile
]>0&&line
[pile
]>0) {
1595 printf ("\b\b%d ", (pile
+1) % 10); //XXX: hack
1597 line_had_card
|= !!card
;
1598 did_placeholders
|= row
[pile
] > 0;
1601 } while (line_had_card
|| !did_placeholders
);
1604 void visbell (void) {
1606 printf ("\033[?5h"); fflush (stdout
);
1608 printf ("\033[?5l"); fflush (stdout
);
1610 void win_anim(void) {
1611 printf ("\033[?25l"); /* hide cursor */
1613 /* set cursor to random location */
1614 int row
= 1+rand()%(1+op
.w
[0]-op
.s
->height
);
1615 int col
= 1+rand()%(1+op
.w
[1]-op
.s
->width
);
1617 /* draw random card */
1618 int face
= 1 + rand() % 52;
1619 for (int l
= 0; l
< op
.s
->height
; l
++) {
1620 printf ("\033[%d;%dH", row
+l
, col
);
1621 printf ("%s", op
.s
->card
[face
][l
]);
1625 /* exit on keypress */
1626 struct pollfd p
= {STDIN_FILENO
, POLLIN
, 0};
1627 if (poll (&p
, 1, 80)) goto fin
;
1630 printf ("\033[?25h"); /* show cursor */
1636 void undo_push (int _f
, int t
, int n
, int o
) {
1637 struct undo
* new = malloc(sizeof(struct undo
));
1647 void undo_pop (struct undo
* u
) {
1648 if (u
== &undo_sentinel
) return;
1651 if (u
->f
== FOUNDATION
) {
1652 /* foundation -> tableu */
1653 int top_f
= find_top(f
.f
[u
->n
]);
1654 int top_t
= find_top(f
.t
[u
->t
]);
1655 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1656 f
.t
[u
->t
][top_t
] = NO_CARD
;
1657 } else if (u
->f
== WASTE
&& u
->t
== FOUNDATION
) {
1658 /* waste -> foundation */
1659 /* split u->n into wst and fnd: */
1660 int wst
= u
->n
& 0xffff;
1661 int fnd
= u
->n
>> 16;
1662 /* move stock cards one position up to make room: */
1663 for (int i
= f
.z
; i
>= wst
; i
--) f
.s
[i
+1] = f
.s
[i
];
1664 /* move one card from foundation to waste: */
1665 int top
= find_top(f
.f
[fnd
]);
1666 f
.s
[wst
] = f
.f
[fnd
][top
];
1667 f
.f
[fnd
][top
] = NO_CARD
;
1670 } else if (u
->f
== WASTE
) {
1671 /* waste -> tableu */
1672 /* move stock cards one position up to make room: */
1673 for (int i
= f
.z
-1; i
>= u
->n
; i
--) f
.s
[i
+1] = f
.s
[i
];
1674 /* move one card from tableu to waste: */
1675 int top
= find_top(f
.t
[u
->t
]);
1676 f
.s
[u
->n
] = f
.t
[u
->t
][top
];
1677 f
.t
[u
->t
][top
] = NO_CARD
;
1680 } else if (u
->t
== FOUNDATION
) {
1681 /* tableu -> foundation */
1682 int top_f
= find_top(f
.t
[u
->f
]);
1683 int top_t
= find_top(f
.f
[u
->n
]);
1684 /* close topcard if previous action caused turn_over(): */
1685 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1686 /* move one card from foundation to tableu: */
1687 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1688 f
.f
[u
->n
][top_t
] = NO_CARD
;
1690 /* tableu -> tableu */
1691 int top_f
= find_top(f
.t
[u
->f
]);
1692 int top_t
= find_top(f
.t
[u
->t
]);
1693 /* close topcard if previous action caused turn_over(): */
1694 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1695 /* move n cards from tableu[f] to tableu[t]: */
1696 for (int i
= 0; i
< u
->n
; i
++) {
1697 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1698 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1701 #elif defined SPIDER
1702 if (u
->f
== STOCK
) {
1703 /* stock -> tableu */
1704 /*remove a card from each pile and put it back onto the stock:*/
1705 for (int pile
= NUM_PILES
-1; pile
>= 0; pile
--) {
1706 int top
= find_top(f
.t
[pile
]);
1707 f
.s
[f
.z
++] = f
.t
[pile
][top
];
1708 f
.t
[pile
][top
] = NO_CARD
;
1710 } else if (u
->t
== FOUNDATION
) {
1711 /* tableu -> foundation */
1712 int top
= find_top(f
.t
[u
->f
]);
1713 /* close topcard if previous action caused turn_over(): */
1714 if (u
->o
) f
.t
[u
->f
][top
] *= -1;
1715 /* append cards from foundation to tableu */
1716 for (int i
= RANK_K
; i
>= RANK_A
; i
--) {
1717 f
.t
[u
->f
][++top
] = f
.f
[u
->n
][i
];
1718 f
.f
[u
->n
][i
] = NO_CARD
;
1720 f
.w
--; /* decrement complete-foundation-counter */
1723 /* tableu -> tableu */
1724 int top_f
= find_top(f
.t
[u
->f
]);
1725 int top_t
= find_top(f
.t
[u
->t
]);
1726 /* close topcard if previous action caused turn_over(): */
1727 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1728 /* move n cards from tableu[f] to tableu[t]: */
1729 for (int i
= 0; i
< u
->n
; i
++) {
1730 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1731 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1734 #elif defined FREECELL
1735 /*NOTE: if from and to are both stock/foundation, opt = from | to<<16 */
1736 if (u
->f
== STOCK
&& u
->t
== FOUNDATION
) {
1737 /* free cells -> foundation */
1738 /* split u->n into cll and fnd: */
1739 int cll
= u
->n
& 0xffff;
1740 int fnd
= u
->n
>> 16;
1741 /* move one card from foundation to free cell: */
1742 int top
= find_top(f
.f
[fnd
]);
1743 f
.s
[cll
] = f
.f
[fnd
][top
];
1744 f
.f
[fnd
][top
] = NO_CARD
;
1745 f
.w
|= 1<<cll
; /* mark cell as occupied */
1746 } else if (u
->f
== STOCK
) {
1747 /* free cells -> cascade */
1748 int top_t
= find_top(f
.t
[u
->t
]);
1749 f
.s
[u
->n
] = f
.t
[u
->t
][top_t
];
1750 f
.t
[u
->t
][top_t
] = NO_CARD
;
1751 f
.w
|= 1<<u
->n
; /* mark cell as occupied */
1752 } else if (u
->f
== FOUNDATION
&& u
->t
== STOCK
) {
1753 /* foundation -> free cells */
1754 /* split u->n into cll and fnd: */
1755 int cll
= u
->n
>> 16;
1756 int fnd
= u
->n
& 0xffff;
1757 /* move 1 card from free cell to foundation: */
1758 int top_f
= find_top(f
.f
[fnd
]);
1759 f
.f
[fnd
][top_f
+1] = f
.s
[cll
];
1761 f
.w
&= ~(1<<cll
); /* mark cell as free */
1762 } else if (u
->f
== FOUNDATION
) {
1763 /* foundation -> cascade */
1764 int top_f
= find_top(f
.f
[u
->n
]);
1765 int top_t
= find_top(f
.t
[u
->t
]);
1766 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1767 f
.t
[u
->t
][top_t
] = NO_CARD
;
1768 } else if (u
->t
== STOCK
) {
1769 /* cascade -> free cells */
1770 int top_f
= find_top(f
.t
[u
->f
]);
1771 f
.t
[u
->f
][top_f
+1] = f
.s
[u
->n
];
1772 f
.s
[u
->n
] = NO_CARD
;
1773 f
.w
&= ~(1<<u
->n
); /* mark cell as free */
1774 } else if (u
->t
== FOUNDATION
) {
1775 /* cascade -> foundation */
1776 int top_f
= find_top(f
.t
[u
->f
]);
1777 int top_t
= find_top(f
.f
[u
->n
]);
1778 /* move one card from foundation to cascade: */
1779 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1780 f
.f
[u
->n
][top_t
] = NO_CARD
;
1782 /* cascade -> cascade */
1783 int top_f
= find_top(f
.t
[u
->f
]);
1784 int top_t
= find_top(f
.t
[u
->t
]);
1785 /* move n cards from tableu[f] to tableu[t]: */
1786 for (int i
= 0; i
< u
->n
; i
++) {
1787 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1788 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1797 void free_undo (struct undo
* u
) {
1798 while (u
&& u
!= &undo_sentinel
) {
1806 // initialization stuff {{{
1807 void screen_setup (int enable
) {
1810 printf ("\033[s\033[?47h"); /* save cursor, alternate screen */
1811 printf ("\033[H\033[J"); /* reset cursor, clear screen */
1812 printf ("\033[?1000h"); /* enable mouse */
1814 printf ("\033[?1000l"); /* disable mouse */
1815 printf ("\033[?47l\033[u"); /* primary screen, restore cursor */
1820 void raw_mode(int enable
) {
1821 static struct termios saved_term_mode
;
1822 struct termios raw_term_mode
;
1825 if (saved_term_mode
.c_lflag
== 0)/*don't overwrite stored mode*/
1826 tcgetattr(STDIN_FILENO
, &saved_term_mode
);
1827 raw_term_mode
= saved_term_mode
;
1828 raw_term_mode
.c_lflag
&= ~(ICANON
| ECHO
);
1829 raw_term_mode
.c_cc
[VMIN
] = 1 ;
1830 raw_term_mode
.c_cc
[VTIME
] = 0;
1831 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw_term_mode
);
1833 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &saved_term_mode
);
1837 void signal_handler (int signum
) {
1842 signal(SIGTSTP
, SIG_DFL
); /* NOTE: assumes SysV semantics! */
1847 print_table(NO_HI
, NO_HI
);
1849 case SIGINT
: //TODO: don't exit; just warn like vim does
1852 ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &w
);
1858 void signal_setup(void) {
1859 struct sigaction saction
;
1861 saction
.sa_handler
= signal_handler
;
1862 sigemptyset(&saction
.sa_mask
);
1863 saction
.sa_flags
= 0;
1864 if (sigaction(SIGTSTP
, &saction
, NULL
) < 0) {
1868 if (sigaction(SIGCONT
, &saction
, NULL
) < 0) {
1872 if (sigaction(SIGINT
, &saction
, NULL
) < 0) {
1876 if (sigaction(SIGWINCH
, &saction
, NULL
) < 0) {
1877 perror ("SIGWINCH");
1883 //vim: foldmethod=marker