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 (color_ok(f
.t
[to
][top_to
], f
.f
[from
][top_from
])
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 ((color_ok(f
.t
[to
][top_to
], f
.s
[f
.w
])
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 ((color_ok(f
.t
[to
][top_to
], f
.t
[from
][i
])
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*/
402 undo_push(STOCK
, TABLEU
, 1, 0); /* NOTE: before remove_if_complete()! */
403 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
404 f
.t
[pile
][find_top(f
.t
[pile
])+1] = f
.s
[--f
.z
];
405 remove_if_complete(pile
);
406 if (check_won()) return WON
;
410 int t2f(int from
, int to
, int opt
) {
411 (void) to
; (void) opt
; /* don't need */
412 /* manually retrigger remove_if_complete() (e.g. after undo_pop) */
413 return remove_if_complete(from
)?OK
:ERR
;
415 #elif defined FREECELL
416 int max_move(int from
, int to
) {
417 /* returns the maximum number of cards that can be moved */
418 /* see also: https://boardgames.stackexchange.com/a/45157/26498 */
419 int free_tabs
= 0, free_cells
= 0;
420 for (int i
= 0; i
< NUM_PILES
; i
++) free_tabs
+= f
.t
[i
][0] == NO_CARD
;
421 for (int i
= 0; i
< NUM_CELLS
; i
++) free_cells
+= f
.s
[i
] == NO_CARD
;
423 /* don't count the tableau we are moving to: */
424 if (to
>= 0 && f
.t
[to
][0] == NO_CARD
) free_tabs
--;
426 /* theoretic maximum is limited by the number of cards on the pile */
427 int max_theory
= (1<<free_tabs
) * (free_cells
+ 1);
428 int max_effective
= 1 + find_top(f
.t
[from
]) - first_movable(f
.t
[from
]);
429 return max_effective
< max_theory
? max_effective
: max_theory
;
431 //TODO FREECELL: auto move to tableu after each move (not all cards possible, only when it is the smallest rank still on the board)
432 int t2t(int from
, int to
, int opt
) {
433 int top_to
= find_top(f
.t
[to
]);
434 int top_from
= find_top(f
.t
[from
]);
435 int cards
= max_move(from
, to
);
436 if (top_to
< 0) { /* moving to empty pile? */
438 return ERR
; /* cannot execute move */
439 cards
= opt
; /* user wants to move n cards*/
442 for (int i
= top_from
; i
>=0; i
--) {
443 if (cards
-->0/*enough space and not more attempted than wanted*/
444 && ((top_to
>= 0 /* if destn. not empty: check rank/color */
445 && (color_ok(f
.t
[to
][top_to
], f
.t
[from
][i
])
446 && (rank_next(f
.t
[from
][i
], f
.t
[to
][top_to
]))))
447 || (top_to
< 0 && !cards
))) {/*if dest empty and right # cards*/
448 /* move cards [i..top_from] to their destination */
450 for (;i
<= top_from
; i
++) {
452 f
.t
[to
][top_to
] = f
.t
[from
][i
];
453 f
.t
[from
][i
] = NO_CARD
;
456 undo_push(from
, to
, count
, 0);
460 return ERR
; /* no such move possible */
462 int t2f(int from
, int to
, int opt
) { /* 1:1 copy from KLONDIKE */
463 (void) to
; (void) opt
; /* don't need */
464 int top_from
= find_top(f
.t
[from
]);
465 to
= get_suit(f
.t
[from
][top_from
]);
466 int top_to
= find_top(f
.f
[to
]);
467 if ((top_to
< 0 && get_rank(f
.t
[from
][top_from
]) == RANK_A
)
468 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.t
[from
][top_from
]))) {
469 f
.f
[to
][top_to
+1] = f
.t
[from
][top_from
];
470 f
.t
[from
][top_from
] = NO_CARD
;
471 undo_push(from
, FOUNDATION
, to
, 0);
472 if (check_won()) return WON
;
476 int f2t(int from
, int to
, int opt
) {
477 (void) from
; /* don't need */
478 int top_to
= find_top(f
.t
[to
]);
480 int top_from
= find_top(f
.f
[from
]);
482 if (top_to
< 0 /* empty tableu? */
483 ||(color_ok(f
.t
[to
][top_to
], f
.f
[from
][top_from
])
484 && (rank_next(f
.f
[from
][top_from
], f
.t
[to
][top_to
])))) {
485 f
.t
[to
][top_to
+1] = f
.f
[from
][top_from
];
486 f
.f
[from
][top_from
] = NO_CARD
;
487 undo_push(FOUNDATION
, to
, from
, 0);
491 int t2c(int from
, int to
, int opt
) {
492 (void) to
; (void) opt
; /* don't need */
493 /* is a cell free? */
494 if (f
.w
== (1<<NUM_CELLS
)-1)
496 for (to
= 0; to
< NUM_CELLS
; to
++)
497 if (!(f
.w
>>to
&1)) break;
499 int top_from
= find_top(f
.t
[from
]);
500 f
.s
[to
] = f
.t
[from
][top_from
];
501 f
.t
[from
][top_from
] = NO_CARD
;
502 f
.w
|= 1<<to
; /* mark cell as occupied */
503 undo_push(from
, STOCK
, to
, 0);
507 int c2t(int from
, int to
, int opt
) {
508 (void) from
; /* don't need */
509 int top_to
= find_top(f
.t
[to
]);
512 if (top_to
< 0 /* empty tableu? */
513 ||(color_ok(f
.t
[to
][top_to
], f
.s
[from
])
514 && (rank_next(f
.s
[from
], f
.t
[to
][top_to
])))) {
515 f
.t
[to
][top_to
+1] = f
.s
[from
];
517 f
.w
&= ~(1<<from
); /* mark cell as free */
518 undo_push(STOCK
, to
, from
, 0);
523 int c2f(int from
, int to
, int opt
) {
524 (void) from
; (void) to
; /* don't need */
526 to
= get_suit(f
.s
[from
]);
527 int top_to
= find_top(f
.f
[to
]);
528 if ((top_to
< 0 && get_rank(f
.s
[from
]) == RANK_A
)
529 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.s
[from
]))) {
530 f
.f
[to
][top_to
+1] = f
.s
[from
];
532 f
.w
&= ~(1<<from
); /* mark cell as free */
533 undo_push(STOCK
, FOUNDATION
, from
| to
<<16, 0);
534 if (check_won()) return WON
;
538 int f2c(int from
, int to
, int opt
) {
539 (void) from
; (void) to
; /* don't need */
540 /* is a cell free? */
541 if (f
.w
== (1<<NUM_CELLS
)-1)
543 for (to
= 0; to
< NUM_CELLS
; to
++)
544 if (!(f
.w
>>to
&1)) break;
547 int top_from
= find_top(f
.f
[from
]);
548 f
.s
[to
] = f
.f
[from
][top_from
];
549 f
.f
[from
][top_from
] = NO_CARD
;
550 f
.w
|= 1<<to
; /* mark cell as occupied */
551 undo_push(FOUNDATION
, STOCK
, from
| to
<<16, 0);
555 #define w2f nop /* for join()'s "to foundation" */
556 #define w2t nop /* ditto. */
559 //TODO: generalize prediction engine for CMD_HINT
561 #define would_complete(pile) 0
563 #define would_complete(pile) \
564 (get_rank(f.t[pile][r[pile].top]) == RANK_A \
565 && get_rank(f.t[to][bottom_to]) == RANK_K)
566 #elif defined FREECELL
567 #define would_complete(pile) 0
569 #define would_turn(pile) \
570 (f.t[pile][r[pile].pos-1] < 0)
571 #define would_empty(pile) \
575 int top_to
= find_top(f
.t
[to
]);
577 int bottom_to
= first_movable(f
.t
[to
]);
580 #if defined KLONDIKE || defined FREECELL
581 if (to
== WASTE
|| to
== STOCK
) return ERR
; /*why would you do that!?*/
583 if (to
== FOUNDATION
) {
585 for (int i
= 0; i
<= TAB_MAX
+1; i
++)
586 switch ((i
?t2f
:w2f
)(i
-1, FOUNDATION
, 0)) {
587 case WON
: return WON
;
588 case OK
: status
= OK
;
596 if (top_to
< 0) { /* move a king to empty pile: */
597 for (int i
= 0; i
<= TAB_MAX
; i
++) {
598 if (f
.t
[i
][0] < 0) /* i.e. would turn? */
599 if (t2t(i
, to
, 0) == OK
) return OK
;
601 return w2t(WASTE
, to
, 0);
603 #elif defined FREECELL
604 if (top_to
< 0) { /* move longest cascade to empty tableu: */ //TODO FREECELL:
607 for (int i
= 0; i
<= TAB_MAX
; i
++) {
608 int m
= max_move(i
, to
);
609 /*longest cascade that won't uncover another free pile*/
610 //TODO: don't rip apart cascades
611 if (m
>= length
&& m
<= find_top(f
.t
[i
]))
612 length
= m
, longest
= i
;
614 if (longest
< 0) return ERR
;
615 return t2t(longest
, to
, length
);
620 int ok
:1; /* card to move in pile? */
621 int above
; /* number of movable cards above */
622 int below
; /* number of cards below ours */
623 int pos
; /* where the card to move is in the pile */
624 int top
; /* find_top() */
625 } r
[NUM_PILES
] = {{0}};
626 int complete
= 0;/* SPIDER: true if any pile would complete a stack */
627 int turn
= 0; /* SPIDER: true if any pile would turn_over */
628 int empty
= 0; /* true if any pile would become empty */
630 /* 1. rate each pile: */
633 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
634 if (pile
== to
) continue;
635 int top
= find_top(f
.t
[pile
]);
636 int bottom
= first_movable(f
.t
[pile
]);
637 r
[pile
].pos
= bottom
; /* need for would_empty */
639 if (top
< 0) continue; /* no cards to move */
640 if (would_empty(pile
)) continue; /* doesn't help */
643 r
[pile
].above
= 0; /* always take as many as possible */
644 r
[pile
].below
= top
- bottom
;
646 complete
|= would_complete(pile
); /* never happens */
647 turn
|= would_turn(pile
);
648 empty
|= would_empty(pile
);
652 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
653 r
[pile
].top
= r
[pile
].pos
= find_top(f
.t
[pile
]);
654 /* backtrack until we find a compatible-to-'to'-pile card: */
656 int maxmove
= max_move(pile
, -1);
658 while (r
[pile
].pos
>= 0 && is_movable(f
.t
[pile
], r
[pile
].pos
)) {
659 int rankdiff
= get_rank(f
.t
[pile
][r
[pile
].pos
])
660 - get_rank(f
.t
[to
][top_to
]);
661 if (rankdiff
>= 0) break; /* past our card */
663 if (!maxmove
--) break; /* can't move this many cards */
665 if (rankdiff
== -1 && /* rank matches */
666 color_ok(f
.t
[pile
][r
[pile
].pos
], f
.t
[to
][top_to
])
669 complete
|= would_complete(pile
);
670 turn
|= would_turn(pile
);
671 empty
|= would_empty(pile
);
672 for (int i
= r
[pile
].pos
; i
>= 0; i
--)
673 if (is_movable(f
.t
[pile
], i
-1))
683 /* 2. find optimal pile: (optimized for spider) */
684 //todo: in spider, prefer longest piles if above==0 (faster completions)
686 for (int pile
= 0, above
= 99, below
= 99; pile
< NUM_PILES
; pile
++) {
687 if (!r
[pile
].ok
) continue;
688 /* don't bother if another pile would be better: prefer ... */
689 /* ... to complete a stack: */
690 if (!would_complete(pile
) && complete
) continue;
691 /* ... emptying piles: */
692 if (!would_empty(pile
) && empty
&& !complete
) continue;
693 /* ... to turn_over: */
694 if (!would_turn(pile
) && turn
&& !complete
&& !empty
) continue;
695 /* ... not to rip apart too many cards: */
696 if (r
[pile
].above
> above
) continue;
697 /* if tied, prefer ... */
698 if (r
[pile
].above
== above
699 /* ... larger pile if destination is empty */
700 && (top_to
< 0? r
[pile
].below
< below
701 /* ... shorter pile otherwise */
702 : r
[pile
].below
> below
))
706 above
= r
[pile
].above
;
707 below
= r
[pile
].below
;
710 /* 3. move cards over and return: */
712 /* prefer waste if it wouldn't turn_over: */
713 /* NOTE: does not attempt to take from froundation */
714 if (!empty
&& !turn
&& w2t(WASTE
, to
, 0) == OK
)
716 if (from
< 0) /* nothing found */
718 return t2t(from
, to
, 0);
720 if (from
< 0) /* nothing found */
722 int bottom
= first_movable(f
.t
[from
]);
723 return t2t(from
, to
, get_rank(f
.t
[from
][bottom
]));
724 #elif defined FREECELL
725 //TODO: if would rip apart, try freecells first (instead after)
726 if (from
< 0) /* no tableu move found */ {
727 /* try all free cells before giving up: */
728 for (int i
= 0; i
< NUM_CELLS
; i
++)
729 if (c2t(STOCK
, to
, i
) == OK
) return OK
;
732 return t2t(from
, to
, 0);
737 #undef would_complete
738 int nop(int from
, int to
, int opt
) { (void)from
;(void)to
;(void)opt
;return ERR
; }
741 // keyboard input handling {{{
742 // cursor functions{{{
744 void cursor_left (struct cursor
* cursor
) {
746 if (is_tableu(cursor
->pile
)) {
747 if (cursor
->pile
> 0) cursor
->pile
--;
749 } else { /* stock/waste/foundation*/
750 switch (cursor
->pile
) {
751 case WASTE
: cursor
->pile
= STOCK
; cursor
->opt
= 0; break;
753 if (cursor
->opt
<= 0)
754 cursor
->pile
= WASTE
;
760 void cursor_down (struct cursor
* cursor
) {
762 if (!is_tableu(cursor
->pile
)) {
763 switch (cursor
->pile
) {
764 case STOCK
: cursor
->pile
= TAB_1
; break;
765 case WASTE
: cursor
->pile
= TAB_2
; break;
767 cursor
->pile
= TAB_4
+ cursor
->opt
;
772 void cursor_up (struct cursor
* cursor
) {
774 if (is_tableu(cursor
->pile
)) {
775 switch (cursor
->pile
) { //ugly :|
776 case TAB_1
: cursor
->pile
= STOCK
; break;
777 case TAB_2
: cursor
->pile
= WASTE
; break;
778 case TAB_3
: cursor
->pile
= WASTE
; break;
779 case TAB_4
: case TAB_5
: case TAB_6
: case TAB_7
:
780 cursor
->opt
=cursor
->pile
-TAB_4
;
781 cursor
->pile
= FOUNDATION
;
786 void cursor_right (struct cursor
* cursor
) {
788 if (is_tableu(cursor
->pile
)) {
789 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
792 switch (cursor
->pile
) {
793 case STOCK
: cursor
->pile
= WASTE
; break;
794 case WASTE
: cursor
->pile
= FOUNDATION
;cursor
->opt
= 0; break;
796 if (cursor
->opt
< NUM_SUITS
-1)
802 /*NOTE: one can't highlight the stock due to me being too lazy to implement it*/
803 void cursor_left (struct cursor
* cursor
) {
805 if (cursor
->pile
> 0) cursor
->pile
--;
808 void cursor_down (struct cursor
* cursor
) {
810 int first
= first_movable(f
.t
[cursor
->pile
]);
811 int top
= find_top(f
.t
[cursor
->pile
]);
812 if (first
+ cursor
->opt
< top
)
815 void cursor_up (struct cursor
* cursor
) {
817 if (cursor
->opt
> 0) cursor
->opt
--;
819 void cursor_right (struct cursor
* cursor
) {
821 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
824 #elif defined FREECELL
825 void cursor_left (struct cursor
* cursor
) {
827 if (is_tableu(cursor
->pile
)) {
828 if (cursor
->pile
> 0) cursor
->pile
--;
830 } else { /* cells/foundation*/
831 switch (cursor
->pile
) {
837 if (cursor
->opt
<= 0) {
838 cursor
->pile
= STOCK
;
846 void cursor_down (struct cursor
* cursor
) {
848 if (is_tableu(cursor
->pile
)) {
849 if (cursor
->opt
< max_move(cursor
->pile
, -1)-1)
852 cursor
->pile
= cursor
->opt
+NUM_CELLS
*(cursor
->pile
==FOUNDATION
);
856 void cursor_up (struct cursor
* cursor
) {
858 if (is_tableu(cursor
->pile
)) {
859 if (cursor
->opt
> 0) {
862 switch (cursor
->pile
) {
863 case TAB_1
: case TAB_2
: case TAB_3
: case TAB_4
:
864 cursor
->opt
= cursor
->pile
; /*assumes TAB_1==0*/
865 cursor
->pile
= STOCK
;
867 case TAB_5
: case TAB_6
: case TAB_7
: case TAB_8
:
868 cursor
->opt
= cursor
->pile
- NUM_CELLS
;
869 cursor
->pile
= FOUNDATION
;
874 void cursor_right (struct cursor
* cursor
) {
876 if (is_tableu(cursor
->pile
)) {
877 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
880 switch (cursor
->pile
) {
882 if (cursor
->opt
< NUM_SUITS
-1) {
885 cursor
->pile
= FOUNDATION
;
889 if (cursor
->opt
< NUM_SUITS
-1)
895 void cursor_to (struct cursor
* cursor
, int pile
) {
900 int set_mouse(int pile
, int* main
, int* opt
) {
901 //TODO: this should set cursor.opt, so card selector choice dialog does not trigger!
903 if (pile
< 0) return 1;
906 if (pile
>= FOUNDATION
)//TODO: check upper bound!
908 *opt
= pile
- FOUNDATION
;
911 #elif defined FREECELL
912 if (pile
> TAB_MAX
) {
913 *main
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
914 *opt
= (pile
-STOCK
) % 4;
920 int get_cmd (int* from
, int* to
, int* opt
) {
922 unsigned char mouse
[6] = {0}; /* must clear [3]! */
923 struct cursor inactive
= {-1,-1};
924 static struct cursor active
= {0,0};
925 if (is_tableu(active
.pile
))
929 from_l
: print_table(&active
, &inactive
);
933 /* direct addressing: */
934 case '1': *from
= TAB_1
; break;
935 case '2': *from
= TAB_2
; break;
936 case '3': *from
= TAB_3
; break;
937 case '4': *from
= TAB_4
; break;
938 case '5': *from
= TAB_5
; break;
939 case '6': *from
= TAB_6
; break;
940 case '7': *from
= TAB_7
; break;
942 case '8': *from
= TAB_8
; break;
943 case '9': *from
= TAB_9
; break;
944 case '0': *from
= TAB_10
;break;
945 #elif defined FREECELL
946 case '8': *from
= TAB_8
; break;
947 case '9': *from
= STOCK
; break;
948 case '0': *from
= FOUNDATION
; break;
949 #elif defined KLONDIKE
950 case '9': *from
= WASTE
; break;
951 case '0': *from
= FOUNDATION
; break;
952 case '8': /* fallthrough */
955 case '\n': *from
= STOCK
; break;
957 /* cursor keys addressing: */
959 case 'h': cursor_left (&active
); goto from_l
;
961 case 'j': cursor_down (&active
); goto from_l
;
963 case 'k': cursor_up (&active
); goto from_l
;
965 case 'l': cursor_right(&active
); goto from_l
;
967 case 'H': cursor_to(&active
,TAB_1
); goto from_l
; /* leftmost tableu */
969 case 'L': cursor_to(&active
,TAB_MAX
);goto from_l
; /* rigthmost tableu */
971 case 'M': cursor_to(&active
,TAB_MAX
/2); goto from_l
; /* center tableu */
972 case ' ': /* continue with second cursor */
975 *opt
= active
.opt
; /* when FOUNDATION */
979 /* mouse addressing: */
980 case MOUSE_MIDDLE
: return CMD_NONE
;
982 if (set_mouse(term2pile(mouse
), to
, opt
))
986 if (set_mouse(term2pile(mouse
), from
, opt
))
988 if (!is_tableu(*from
))
989 inactive
.opt
= *opt
; /* prevents card selector dialog */
994 fprintf (stderr
, ":");
995 raw_mode(0); /* turn on echo */
996 fgets (buf
, 256, stdin
);
999 case 'q': return CMD_QUIT
;
1000 case 'n': return CMD_NEW
;
1001 case 'r': return CMD_AGAIN
;
1002 case 'h': return CMD_HELP
;
1003 default: return CMD_INVAL
;
1008 case 'K': /* fallthrough */
1009 case '?': return CMD_HINT
;
1010 case 'u': return CMD_UNDO
;
1011 case 002: return CMD_NONE
; /* sent by SIGWINCH */
1012 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1013 default: return CMD_INVAL
;
1015 inactive
.pile
= *from
; /* for direct addressing highlighting */
1016 if (is_tableu(*from
) && f
.t
[*from
][0] == NO_CARD
) return CMD_INVAL
;
1019 if (*from
== STOCK
) {
1026 to_l
: print_table(&active
, &inactive
);
1031 case 'h': cursor_left (&active
); goto to_l
;
1033 case 'j': cursor_down (&active
); goto to_l
;
1035 case 'k': cursor_up (&active
); goto to_l
;
1037 case 'l': cursor_right(&active
); goto to_l
;
1039 case 'H': cursor_to(&active
,TAB_1
); goto to_l
;
1041 case 'L': cursor_to(&active
,TAB_MAX
); goto to_l
;
1043 case 'M': cursor_to(&active
,TAB_MAX
/2); goto to_l
;
1044 case 'J': /* fallthrough; just join selected pile */
1047 break; /* continues with the foundation/empty tableu check */
1049 case MOUSE_RIGHT
: return CMD_NONE
;
1051 if (set_mouse(term2pile(mouse
), to
, opt
))
1054 case 'K': /* fallthrough */
1055 case '?': return CMD_HINT
;
1056 case 'u': return CMD_NONE
; /* cancel selection */
1057 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1059 if (t
< '0' || t
> '9') return CMD_INVAL
;
1063 #elif defined SPIDER
1065 #elif defined FREECELL
1075 /* direct addressing post-processing stage:
1076 because foundations/freecells share the same key (and you can't select
1077 partial piles) there are sometimes ambiguous situations where it isn't
1078 clear from which pile (or how many cards) to take. the code below will
1079 only ask the user if there are at least two possible moves and
1080 automatically choose otherwise. */
1082 /* if it was selected with a cursor, it's obvious: */
1083 if (inactive
.opt
>= 0) {
1084 if (is_tableu(*from
)) {
1085 /* NOTE: max_move same as in cursor_down() */
1086 *opt
= max_move(*from
, -1) - inactive
.opt
;
1088 *opt
= inactive
.opt
;
1090 /* moving from tableu to empty tableu? */
1091 } else if(is_tableu(*from
) && is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
){
1092 int top
= find_top(f
.t
[*from
]);
1093 int max
= max_move(*from
, *to
);
1095 if (top
< 0) return CMD_INVAL
;
1096 if (max
== 1) { /* only 1 movable? */
1097 return *opt
= 1, CMD_MOVE
;
1098 } else { /* only ask the user if it's unclear: */
1099 int bottom
= top
- (max
-1);
1100 printf ("\rup to ([a23456789xjqk] or space/return): ");
1103 case ' ': rank
= get_rank(f
.t
[*from
][top
]); break;
1104 case'\n': rank
= get_rank(f
.t
[*from
][bottom
]); break;
1105 case 'a': case 'A': rank
= RANK_A
; break;
1106 case '0': /* fallthrough */
1107 case 'x': case 'X': rank
= RANK_X
; break;
1108 case 'j': case 'J': rank
= RANK_J
; break;
1109 case 'q': case 'Q': rank
= RANK_Q
; break;
1110 case 'k': case 'K': rank
= RANK_K
; break;
1111 default: rank
-= '1';
1113 if (rank
< RANK_A
|| rank
> RANK_K
) return CMD_INVAL
;
1115 for (int i
= 0; max
--; i
++)
1116 if (get_rank(f
.t
[*from
][top
-i
]) == rank
)
1117 return *opt
= 1+i
, CMD_MOVE
;
1121 /* `opt` is the number of cards to move */
1122 /* moving between stock/foundation? */
1123 } else if (*from
== FOUNDATION
&& *to
== FOUNDATION
) {
1124 return CMD_INVAL
; /* nonsensical */
1125 } else if (*from
== FOUNDATION
&& *to
== STOCK
) {
1126 if (f
.w
== (1<<NUM_CELLS
)-1) return CMD_INVAL
; /*no free cells*/
1127 int ok_foundation
; /* find compatible (non-empty) foundations:*/
1128 int used_fs
=0; for (int i
= 0; i
< NUM_SUITS
; i
++)
1129 if (!!f
.f
[i
][0]) ok_foundation
= i
, used_fs
++;
1131 if (used_fs
== 0) return CMD_INVAL
; /* nowhere to take from */
1132 if (used_fs
== 1) { /* take from the only one */
1133 return *opt
= ok_foundation
, CMD_MOVE
;
1134 } else { /* ask user */
1135 printf ("take from (1-4): "); fflush (stdout
);
1136 *opt
= getch(NULL
) - '1';
1137 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1139 /* `opt` is the foundation index (0..3) */
1140 } else if (*from
== STOCK
) { /* cell -> foundation/tableu */
1141 if (!f
.w
) return CMD_INVAL
; /* no cell to take from */
1142 int ok_cell
; /* find compatible (non-empty) cells: */
1143 int tab
= is_tableu(*to
);
1144 int used_cs
=0; for (int i
= 0; i
< NUM_CELLS
; i
++) {
1145 card_t
* pile
= (tab
?f
.t
[*to
]:f
.f
[get_suit(f
.s
[i
])]);
1146 int top_to
= find_top(pile
);
1147 if (tab
? /* to tableu? */
1149 ||(top_to
>=0 && rank_next(f
.s
[i
], pile
[top_to
])
1150 && color_ok(f
.s
[i
], pile
[top_to
])))
1151 : /* to foundation? */
1152 ((top_to
<0 && get_rank(f
.s
[i
]) == RANK_A
)
1153 ||(top_to
>=0 && rank_next(pile
[top_to
],f
.s
[i
])))
1155 ok_cell
= i
, used_cs
++;
1158 if (used_cs
== 0) return CMD_INVAL
; /* nowhere to take from */
1159 if (used_cs
== 1) { /* take from the only one */
1160 return *opt
= ok_cell
, CMD_MOVE
;
1161 } else { /* ask user */
1162 printf ("take from (1-4): "); fflush (stdout
);
1163 *opt
= getch(NULL
) - '1';
1164 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1166 /* `opt` is the cell index (0..3) */
1169 //TODO: mouse-friendly "up to?" dialog
1170 #if defined KLONDIKE || defined FREECELL
1171 if (*from
== FOUNDATION
) {
1172 if (inactive
.opt
>= 0) {
1173 *opt
= inactive
.opt
;
1176 int top
= find_top(f
.t
[*to
]);
1177 if (top
< 0) return CMD_INVAL
;
1178 int color
= get_color(f
.t
[*to
][top
]);
1179 int choice_1
= 1-color
; /* selects piles of */
1180 int choice_2
= 2+color
; /* the opposite color */
1181 int top_c1
= find_top(f
.f
[choice_1
]);
1182 int top_c2
= find_top(f
.f
[choice_2
]);
1184 switch ((rank_next(f
.f
[choice_1
][top_c1
], f
.t
[*to
][top
])
1185 && top_c1
>= 0 ) << 0
1186 |(rank_next(f
.f
[choice_2
][top_c2
], f
.t
[*to
][top
])
1187 && top_c2
>= 0 ) << 1) {
1188 case ( 1<<0): *opt
= choice_1
; break; /* choice_1 only */
1189 case (1<<1 ): *opt
= choice_2
; break; /* choice_2 only */
1190 case (1<<1 | 1<<0): /* both, ask user which to pick from */
1191 printf ("take from (1-4): "); fflush (stdout
);
1192 *opt
= getch(NULL
) - '1';
1193 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1195 default: return CMD_INVAL
; /* none matched */
1197 /* `opt` is the foundation index (0..3) */
1199 #elif defined SPIDER
1200 /* moving to empty tableu? */
1201 if (is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
) {
1202 int bottom
= first_movable(f
.t
[*from
]);
1203 if (inactive
.opt
>= 0) { /*if from was cursor addressed: */
1204 *opt
= get_rank(f
.t
[*from
][bottom
+ inactive
.opt
]);
1207 int top
= find_top(f
.t
[*from
]);
1208 if (top
< 0) return CMD_INVAL
;
1209 if (top
>= 0 && !is_movable(f
.t
[*from
], top
-1)) {
1210 *opt
= get_rank(f
.t
[*from
][top
]);
1211 } else { /* only ask the user if it's unclear: */
1212 printf ("\rup to ([a23456789xjqk] or space/return): ");
1215 case ' ': *opt
= get_rank(f
.t
[*from
][top
]); break;
1216 case'\n': *opt
= get_rank(f
.t
[*from
][bottom
]); break;
1217 case 'a': case 'A': *opt
= RANK_A
; break;
1218 case '0': /* fallthrough */
1219 case 'x': case 'X': *opt
= RANK_X
; break;
1220 case 'j': case 'J': *opt
= RANK_J
; break;
1221 case 'q': case 'Q': *opt
= RANK_Q
; break;
1222 case 'k': case 'K': *opt
= RANK_K
; break;
1223 default: *opt
-= '1';
1225 if (*opt
< RANK_A
|| *opt
> RANK_K
) return CMD_INVAL
;
1227 /* `opt` is the rank of the highest card to move */
1233 int getctrlseq(unsigned char* buf
) {
1241 int offset
= 0x20; /* never sends control chars as data */
1242 while ((c
= getchar()) != EOF
) {
1246 case '\033': state
=ESC_SENT
; break;
1252 case '[': state
=CSI_SENT
; break;
1253 default: return KEY_INVAL
;
1258 case 'A': return KEY_UP
;
1259 case 'B': return KEY_DOWN
;
1260 case 'C': return KEY_RIGHT
;
1261 case 'D': return KEY_LEFT
;
1262 /*NOTE: home/end send ^[[x~ . no support for modifiers*/
1263 case 'H': return KEY_HOME
;
1264 case 'F': return KEY_END
;
1265 case '2': getchar(); return KEY_INS
;
1266 case '5': getchar(); return KEY_PGUP
;
1267 case '6': getchar(); return KEY_PGDN
;
1268 case 'M': state
=MOUSE_EVENT
; break;
1269 default: return KEY_INVAL
;
1273 if (buf
== NULL
) return KEY_INVAL
;
1274 buf
[0] = c
- offset
;
1275 buf
[1] = getchar() - offset
;
1276 buf
[2] = getchar() - offset
;
1284 int term2pile(unsigned char *mouse
) {
1285 int line
= (mouse
[2]-1);
1286 int column
= (mouse
[1]-1) / op
.s
->width
;
1288 if (line
< op
.s
->height
) { /* first line */
1291 case 0: return STOCK
;
1292 case 1: return WASTE
;
1293 case 2: return -1; /* spacer */
1294 case 3: return FOUNDATION
+0;
1295 case 4: return FOUNDATION
+1;
1296 case 5: return FOUNDATION
+2;
1297 case 6: return FOUNDATION
+3;
1299 #elif defined SPIDER
1300 if (column
< 3) return STOCK
;
1302 #elif defined FREECELL
1303 if (column
< NUM_SUITS
+ NUM_CELLS
) return STOCK
+column
;
1306 } else if (line
> op
.s
->height
) { /* tableu */
1307 if (column
<= TAB_MAX
) return column
;
1311 int wait_mouse_up(unsigned char* mouse
) {
1312 //TODO: mouse drag: start gets inactive, hovering gets active cursors
1313 struct cursor cur
= {-1,-1};
1315 /* note: if dragged [3]==1 and second position is in mouse[0,4,5] */
1317 /* display a cursor while mouse button is pushed: */
1318 int pile
= term2pile(mouse
);
1321 if (pile
>= FOUNDATION
) {
1322 cur
.pile
= FOUNDATION
;
1323 cur
.opt
= pile
-FOUNDATION
;
1325 #elif defined FREECELL
1326 if (pile
> TAB_MAX
) {
1327 cur
.pile
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
1328 cur
.opt
= (pile
-STOCK
) % 4;
1331 /* need to temporarily show the cursor, then revert to last state: */
1332 int old_show_cursor_hi
= op
.h
; //TODO: ARGH! that's awful!
1334 print_table(&cur
, NO_HI
); //TODO: should not overwrite inactive cursor!
1335 op
.h
= old_show_cursor_hi
;
1338 if (getctrlseq (mouse
+3) == MOUSE_ANY
) {
1339 /* ignore mouse wheel events: */
1340 if (mouse
[3] & 0x40) continue;
1342 else if((mouse
[3]&3) == 3) level
--; /* release event */
1343 else level
++; /* another button pressed */
1347 int success
= mouse
[1] == mouse
[4] && mouse
[2] == mouse
[5];
1354 int getch(unsigned char* buf
) {
1355 //TODO: if buf==NULL disable mouse input
1356 /* returns a character, EOF, or constant for an escape/control sequence - NOT
1357 compatible with the ncurses implementation of same name */
1359 if (buf
&& buf
[3]) {
1360 /* mouse was dragged; return 'ungetted' previous destination */
1361 action
= MOUSE_DRAG
;
1362 /* keep original [0], as [3] only contains release event */
1367 action
= getctrlseq(buf
);
1372 if (buf
[0] > 3) break; /* ignore wheel events */
1377 case 0: return MOUSE_LEFT
;
1378 case 1: return MOUSE_MIDDLE
;
1379 case 2: return MOUSE_RIGHT
;
1387 // shuffling and dealing {{{
1388 void deal(long seed
) {
1389 f
= (const struct playfield
){0}; /* clear playfield */
1390 card_t deck
[DECK_SIZE
*NUM_DECKS
];
1391 int avail
= DECK_SIZE
*NUM_DECKS
;
1392 for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) deck
[i
] = (i
%DECK_SIZE
)+1;
1394 if (op
.m
!= NORMAL
) for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) {
1395 if (op
.m
== MEDIUM
) deck
[i
] = 1+((deck
[i
]-1) | 2);
1396 if (op
.m
== EASY
) deck
[i
] = 1+((deck
[i
]-1) | 2 | 1);
1397 /* the 1+ -1 dance gets rid of the offset created by NO_CARD */
1401 for (int i
= DECK_SIZE
*NUM_DECKS
-1; i
> 0; i
--) { /* fisher-yates */
1402 int j
= rand() % (i
+1);
1403 if (j
-i
) deck
[i
]^=deck
[j
],deck
[j
]^=deck
[i
],deck
[i
]^=deck
[j
];
1407 for (int i
= 0; i
< NUM_PILES
; i
++) {
1410 int count
= i
; /* pile n has n closed cards, then 1 open */
1411 #elif defined SPIDER
1413 int count
= i
<4?5:4; /* pile 1-4 have 5, 5-10 have 4 closed */
1414 #elif defined FREECELL
1416 int count
= i
<4?6:5;/*like spider, but cards are dealt face-up*/
1418 /* "SIGN": face down cards are negated */
1419 for (int j
= 0; j
< count
; j
++) f
.t
[i
][j
] = SIGN deck
[--avail
];
1420 f
.t
[i
][count
] = deck
[--avail
]; /* the face-up card */
1423 /* rest of the cards to the stock: */
1424 /* NOTE: assert(avail==50) for spider, assert(avail==0) for freecell */
1425 for (f
.z
= 0; avail
; f
.z
++) f
.s
[f
.z
] = deck
[--avail
];
1427 f
.w
= -1; /* @start: nothing on waste */
1428 #elif defined SPIDER
1429 f
.w
= 0; /* number of used foundations */
1430 #elif defined FREECELL
1431 f
.w
= 0; /* bitmask of used free cells */
1434 f
.u
= &undo_sentinel
;
1438 // screen drawing routines {{{
1439 void print_hi(int invert
, int grey_bg
, int bold
, char* str
) {
1440 if (!op
.h
) invert
= 0; /* don't show invert if we used the mouse last */
1441 if (bold
&& op
.s
== &unicode_large_color
){ //awful hack for bold + faint
1442 int offset
= str
[3]==017?16:str
[4]==017?17:0;
1443 printf ("%s%s%s""%.*s%s%s""%s%s%s",
1444 "\033[1m", invert
?"\033[7m":"", grey_bg
?"\033[100m":"",
1445 offset
, str
, bold
?"\033[1m":"", str
+offset
,
1446 grey_bg
?"\033[49m":"", invert
?"\033[27m":"","\033[22m");
1449 printf ("%s%s%s%s%s%s%s",
1450 bold
?"\033[1m":"", invert
?"\033[7m":"", grey_bg
?"\033[100m":"",
1452 grey_bg
?"\033[49m":"", invert
?"\033[27m":"",bold
?"\033[22m":"");
1454 void print_table(const struct cursor
* active
, const struct cursor
* inactive
) {
1455 printf("\033[2J\033[H"); /* clear screen, reset cursor */
1457 /* print stock, waste and foundation: */
1458 for (int line
= 0; line
< op
.s
->height
; line
++) {
1460 print_hi (active
->pile
== STOCK
, inactive
->pile
== STOCK
, 1, (
1461 (f
.w
< f
.z
-1)?op
.s
->facedown
1462 :op
.s
->placeholder
)[line
]);
1464 print_hi (active
->pile
== WASTE
, inactive
->pile
== WASTE
, 1, (
1465 /* NOTE: cast, because f.w sometimes is (short)-1 !? */
1466 ((short)f
.w
>= 0)?op
.s
->card
[f
.s
[f
.w
]]
1467 :op
.s
->placeholder
)[line
]);
1468 printf ("%s", op
.s
->card
[NO_CARD
][line
]); /* spacer */
1470 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1471 int card
= find_top(f
.f
[pile
]);
1472 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1473 inactive
->pile
==FOUNDATION
&& (
1474 /* cursor addr. || direct addr. */
1475 inactive
->opt
==pile
|| inactive
->opt
< 0
1477 (card
< 0)?op
.s
->foundation
[line
]
1478 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1483 #elif defined SPIDER
1484 int fdone
; for (fdone
= NUM_DECKS
*NUM_SUITS
; fdone
; fdone
--)
1485 if (f
.f
[fdone
-1][RANK_K
]) break; /*number of completed stacks*/
1486 int spacer_from
= f
.z
?(f
.z
/10-1) * op
.s
->halfwidth
[0] + op
.s
->width
:0;
1487 int spacer_to
= NUM_PILES
*op
.s
->width
-
1488 ((fdone
?(fdone
-1) * op
.s
->halfwidth
[1]:0)+op
.s
->width
);
1489 for (int line
= 0; line
< op
.s
->height
; line
++) {
1490 /* available stock: */
1491 for (int i
= f
.z
/10; i
; i
--) {
1492 if (i
==1) printf ("%s", op
.s
->facedown
[line
]);
1493 else printf ("%s", op
.s
->halfstack
[line
]);
1496 for (int i
= spacer_from
; i
< spacer_to
; i
++) printf (" ");
1497 /* foundation (overlapping): */
1498 for (int i
= NUM_DECKS
*NUM_SUITS
-1, half
= 0; i
>= 0; i
--) {
1499 int overlap
= half
? op
.s
->halfcard
[line
]: 0;
1500 if (f
.f
[i
][RANK_K
]) printf ("%.*s", op
.s
->halfwidth
[2],
1501 op
.s
->card
[f
.f
[i
][RANK_K
]][line
]+overlap
),
1507 #elif defined FREECELL
1508 /* print open cells, foundation: */
1509 for (int line
= 0; line
< op
.s
->height
; line
++) {
1510 for (int pile
= 0; pile
< NUM_CELLS
; pile
++)
1511 print_hi (active
->pile
==STOCK
&& active
->opt
==pile
,
1512 inactive
->pile
==STOCK
&& (
1513 /* cursor addr. || direct addr. */
1514 inactive
->opt
==pile
|| inactive
->opt
< 0
1516 ((f
.s
[pile
])?op
.s
->card
[f
.s
[pile
]]
1517 :op
.s
->placeholder
)[line
]);
1518 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1519 int card
= find_top(f
.f
[pile
]);
1520 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1521 inactive
->pile
==FOUNDATION
&& (
1522 /* cursor addr. || direct addr. */
1523 inactive
->opt
==pile
|| inactive
->opt
< 0
1525 (card
< 0)?op
.s
->foundation
[line
]
1526 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1533 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty))
1534 #define TOP_HI(c) 1 /* can't select partial stacks in KLONDIKE */
1535 #elif defined SPIDER || defined FREECELL
1536 int offset
[NUM_PILES
]={0}; /* first card to highlight */
1538 int bottom
[NUM_PILES
]; /* first movable card */
1539 for (int i
=0; i
<NUM_PILES
; i
++)
1540 bottom
[i
] = find_top(f
.t
[i
]) - max_move(i
,-1);
1542 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty) \
1543 && offset[pile] >= cursor->opt)
1544 #define TOP_HI(cursor) (cursor->pile == pile && movable \
1545 && offset[pile] == cursor->opt)
1547 /* print tableu piles: */
1548 int row
[NUM_PILES
] = {0};
1549 int line
[NUM_PILES
]= {0};
1550 int label
[NUM_PILES
]={0};
1552 int did_placeholders
= 0;
1555 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
1556 card_t card
= f
.t
[pile
][row
[pile
]];
1557 card_t next
= f
.t
[pile
][row
[pile
]+1];
1558 int movable
= is_movable(f
.t
[pile
], row
[pile
]);
1560 if(row
[pile
] <= bottom
[pile
]) movable
= 0;
1562 int empty
= !card
&& row
[pile
] == 0;
1564 print_hi (DO_HI(active
), DO_HI(inactive
), movable
, (
1565 (!card
&& row
[pile
] == 0)?op
.s
->placeholder
1566 :(card
<0)?op
.s
->facedown
1570 int extreme_overlap
= ( 3 /* spacer, labels, status */
1571 + 2 * op
.s
->height
/* stock, top tableu card */
1572 + find_top(f
.t
[pile
]) * op
.s
->overlap
) >op
.w
[0];
1573 /* normal overlap: */
1574 if (++line
[pile
] >= (next
?op
.s
->overlap
:op
.s
->height
)
1575 /* extreme overlap on closed cards: */
1576 || (extreme_overlap
&&
1578 f
.t
[pile
][row
[pile
]] < 0 &&
1579 f
.t
[pile
][row
[pile
]+1] <0)
1580 /* extreme overlap on sequences: */
1581 || (extreme_overlap
&&
1582 !TOP_HI(active
) && /*always show top selected card*/
1583 line
[pile
] >= 1 && row
[pile
] > 0 &&
1584 f
.t
[pile
][row
[pile
]-1] > NO_CARD
&&
1585 is_consecutive (f
.t
[pile
], row
[pile
]) &&
1586 is_consecutive (f
.t
[pile
], row
[pile
]-1) &&
1587 f
.t
[pile
][row
[pile
]+1] != NO_CARD
)
1591 #if defined SPIDER || defined FREECELL
1592 if (movable
) offset
[pile
]++;
1595 /* tableu labels: */
1596 if(!card
&& !label
[pile
] && row
[pile
]>0&&line
[pile
]>0) {
1598 printf ("\b\b%d ", (pile
+1) % 10); //XXX: hack
1600 line_had_card
|= !!card
;
1601 did_placeholders
|= row
[pile
] > 0;
1604 } while (line_had_card
|| !did_placeholders
);
1607 void visbell (void) {
1609 printf ("\033[?5h"); fflush (stdout
);
1611 printf ("\033[?5l"); fflush (stdout
);
1613 void win_anim(void) {
1614 printf ("\033[?25l"); /* hide cursor */
1616 /* set cursor to random location */
1617 int row
= 1+rand()%(1+op
.w
[0]-op
.s
->height
);
1618 int col
= 1+rand()%(1+op
.w
[1]-op
.s
->width
);
1620 /* draw random card */
1621 int face
= 1 + rand() % 52;
1622 for (int l
= 0; l
< op
.s
->height
; l
++) {
1623 printf ("\033[%d;%dH", row
+l
, col
);
1624 printf ("%s", op
.s
->card
[face
][l
]);
1628 /* exit on keypress */
1629 struct pollfd p
= {STDIN_FILENO
, POLLIN
, 0};
1630 if (poll (&p
, 1, 80)) goto fin
;
1633 printf ("\033[?25h"); /* show cursor */
1639 void undo_push (int _f
, int t
, int n
, int o
) {
1640 struct undo
* new = malloc(sizeof(struct undo
));
1650 void undo_pop (struct undo
* u
) {
1651 if (u
== &undo_sentinel
) return;
1654 if (u
->f
== FOUNDATION
) {
1655 /* foundation -> tableu */
1656 int top_f
= find_top(f
.f
[u
->n
]);
1657 int top_t
= find_top(f
.t
[u
->t
]);
1658 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1659 f
.t
[u
->t
][top_t
] = NO_CARD
;
1660 } else if (u
->f
== WASTE
&& u
->t
== FOUNDATION
) {
1661 /* waste -> foundation */
1662 /* split u->n into wst and fnd: */
1663 int wst
= u
->n
& 0xffff;
1664 int fnd
= u
->n
>> 16;
1665 /* move stock cards one position up to make room: */
1666 for (int i
= f
.z
; i
>= wst
; i
--) f
.s
[i
+1] = f
.s
[i
];
1667 /* move one card from foundation to waste: */
1668 int top
= find_top(f
.f
[fnd
]);
1669 f
.s
[wst
] = f
.f
[fnd
][top
];
1670 f
.f
[fnd
][top
] = NO_CARD
;
1673 } else if (u
->f
== WASTE
) {
1674 /* waste -> tableu */
1675 /* move stock cards one position up to make room: */
1676 for (int i
= f
.z
-1; i
>= u
->n
; i
--) f
.s
[i
+1] = f
.s
[i
];
1677 /* move one card from tableu to waste: */
1678 int top
= find_top(f
.t
[u
->t
]);
1679 f
.s
[u
->n
] = f
.t
[u
->t
][top
];
1680 f
.t
[u
->t
][top
] = NO_CARD
;
1683 } else if (u
->t
== FOUNDATION
) {
1684 /* tableu -> foundation */
1685 int top_f
= find_top(f
.t
[u
->f
]);
1686 int top_t
= find_top(f
.f
[u
->n
]);
1687 /* close topcard if previous action caused turn_over(): */
1688 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1689 /* move one card from foundation to tableu: */
1690 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1691 f
.f
[u
->n
][top_t
] = NO_CARD
;
1693 /* tableu -> tableu */
1694 int top_f
= find_top(f
.t
[u
->f
]);
1695 int top_t
= find_top(f
.t
[u
->t
]);
1696 /* close topcard if previous action caused turn_over(): */
1697 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1698 /* move n cards from tableu[f] to tableu[t]: */
1699 for (int i
= 0; i
< u
->n
; i
++) {
1700 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1701 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1704 #elif defined SPIDER
1705 if (u
->f
== STOCK
) {
1706 /* stock -> tableu */
1707 /*remove a card from each pile and put it back onto the stock:*/
1708 for (int pile
= NUM_PILES
-1; pile
>= 0; pile
--) {
1709 int top
= find_top(f
.t
[pile
]);
1710 f
.s
[f
.z
++] = f
.t
[pile
][top
];
1711 f
.t
[pile
][top
] = NO_CARD
;
1713 } else if (u
->t
== FOUNDATION
) {
1714 /* tableu -> foundation */
1715 int top
= find_top(f
.t
[u
->f
]);
1716 /* close topcard if previous action caused turn_over(): */
1717 if (u
->o
) f
.t
[u
->f
][top
] *= -1;
1718 /* append cards from foundation to tableu */
1719 for (int i
= RANK_K
; i
>= RANK_A
; i
--) {
1720 f
.t
[u
->f
][++top
] = f
.f
[u
->n
][i
];
1721 f
.f
[u
->n
][i
] = NO_CARD
;
1723 f
.w
--; /* decrement complete-foundation-counter */
1726 /* tableu -> tableu */
1727 int top_f
= find_top(f
.t
[u
->f
]);
1728 int top_t
= find_top(f
.t
[u
->t
]);
1729 /* close topcard if previous action caused turn_over(): */
1730 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1731 /* move n cards from tableu[f] to tableu[t]: */
1732 for (int i
= 0; i
< u
->n
; i
++) {
1733 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1734 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1737 #elif defined FREECELL
1738 /*NOTE: if from and to are both stock/foundation, opt = from | to<<16 */
1739 if (u
->f
== STOCK
&& u
->t
== FOUNDATION
) {
1740 /* free cells -> foundation */
1741 /* split u->n into cll and fnd: */
1742 int cll
= u
->n
& 0xffff;
1743 int fnd
= u
->n
>> 16;
1744 /* move one card from foundation to free cell: */
1745 int top
= find_top(f
.f
[fnd
]);
1746 f
.s
[cll
] = f
.f
[fnd
][top
];
1747 f
.f
[fnd
][top
] = NO_CARD
;
1748 f
.w
|= 1<<cll
; /* mark cell as occupied */
1749 } else if (u
->f
== STOCK
) {
1750 /* free cells -> cascade */
1751 int top_t
= find_top(f
.t
[u
->t
]);
1752 f
.s
[u
->n
] = f
.t
[u
->t
][top_t
];
1753 f
.t
[u
->t
][top_t
] = NO_CARD
;
1754 f
.w
|= 1<<u
->n
; /* mark cell as occupied */
1755 } else if (u
->f
== FOUNDATION
&& u
->t
== STOCK
) {
1756 /* foundation -> free cells */
1757 /* split u->n into cll and fnd: */
1758 int cll
= u
->n
>> 16;
1759 int fnd
= u
->n
& 0xffff;
1760 /* move 1 card from free cell to foundation: */
1761 int top_f
= find_top(f
.f
[fnd
]);
1762 f
.f
[fnd
][top_f
+1] = f
.s
[cll
];
1764 f
.w
&= ~(1<<cll
); /* mark cell as free */
1765 } else if (u
->f
== FOUNDATION
) {
1766 /* foundation -> cascade */
1767 int top_f
= find_top(f
.f
[u
->n
]);
1768 int top_t
= find_top(f
.t
[u
->t
]);
1769 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1770 f
.t
[u
->t
][top_t
] = NO_CARD
;
1771 } else if (u
->t
== STOCK
) {
1772 /* cascade -> free cells */
1773 int top_f
= find_top(f
.t
[u
->f
]);
1774 f
.t
[u
->f
][top_f
+1] = f
.s
[u
->n
];
1775 f
.s
[u
->n
] = NO_CARD
;
1776 f
.w
&= ~(1<<u
->n
); /* mark cell as free */
1777 } else if (u
->t
== FOUNDATION
) {
1778 /* cascade -> foundation */
1779 int top_f
= find_top(f
.t
[u
->f
]);
1780 int top_t
= find_top(f
.f
[u
->n
]);
1781 /* move one card from foundation to cascade: */
1782 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1783 f
.f
[u
->n
][top_t
] = NO_CARD
;
1785 /* cascade -> cascade */
1786 int top_f
= find_top(f
.t
[u
->f
]);
1787 int top_t
= find_top(f
.t
[u
->t
]);
1788 /* move n cards from tableu[f] to tableu[t]: */
1789 for (int i
= 0; i
< u
->n
; i
++) {
1790 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1791 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1800 void free_undo (struct undo
* u
) {
1801 while (u
&& u
!= &undo_sentinel
) {
1809 // initialization stuff {{{
1810 void screen_setup (int enable
) {
1813 printf ("\033[s\033[?47h"); /* save cursor, alternate screen */
1814 printf ("\033[H\033[J"); /* reset cursor, clear screen */
1815 printf ("\033[?1000h"); /* enable mouse */
1817 printf ("\033[?1000l"); /* disable mouse */
1818 printf ("\033[?47l\033[u"); /* primary screen, restore cursor */
1823 void raw_mode(int enable
) {
1824 static struct termios saved_term_mode
;
1825 struct termios raw_term_mode
;
1828 if (saved_term_mode
.c_lflag
== 0)/*don't overwrite stored mode*/
1829 tcgetattr(STDIN_FILENO
, &saved_term_mode
);
1830 raw_term_mode
= saved_term_mode
;
1831 raw_term_mode
.c_lflag
&= ~(ICANON
| ECHO
);
1832 raw_term_mode
.c_cc
[VMIN
] = 1 ;
1833 raw_term_mode
.c_cc
[VTIME
] = 0;
1834 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw_term_mode
);
1836 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &saved_term_mode
);
1840 void signal_handler (int signum
) {
1845 signal(SIGTSTP
, SIG_DFL
); /* NOTE: assumes SysV semantics! */
1850 print_table(NO_HI
, NO_HI
);
1852 case SIGINT
: //TODO: don't exit; just warn like vim does
1855 ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &w
);
1861 void signal_setup(void) {
1862 struct sigaction saction
;
1864 saction
.sa_handler
= signal_handler
;
1865 sigemptyset(&saction
.sa_mask
);
1866 saction
.sa_flags
= 0;
1867 if (sigaction(SIGTSTP
, &saction
, NULL
) < 0) {
1871 if (sigaction(SIGCONT
, &saction
, NULL
) < 0) {
1875 if (sigaction(SIGINT
, &saction
, NULL
) < 0) {
1879 if (sigaction(SIGWINCH
, &saction
, NULL
) < 0) {
1880 perror ("SIGWINCH");
1886 //vim: foldmethod=marker