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*/
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 && (color_ok(f
.t
[to
][top_to
], f
.t
[from
][i
])
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 ||(color_ok(f
.t
[to
][top_to
], f
.f
[from
][top_from
])
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 ||(color_ok(f
.t
[to
][top_to
], f
.s
[from
])
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);
554 #define w2f nop /* for join()'s "to foundation" */
555 #define w2t nop /* ditto. */
558 //TODO: generalize prediction engine for CMD_HINT
560 #define would_complete(pile) 0
562 #define would_complete(pile) \
563 (get_rank(f.t[pile][r[pile].top]) == RANK_A \
564 && get_rank(f.t[to][bottom_to]) == RANK_K)
565 #elif defined FREECELL
566 #define would_complete(pile) 0
568 #define would_turn(pile) \
569 (f.t[pile][r[pile].pos-1] < 0)
570 #define would_empty(pile) \
574 int top_to
= find_top(f
.t
[to
]);
576 int bottom_to
= first_movable(f
.t
[to
]);
579 #if defined KLONDIKE || defined FREECELL
580 if (to
== WASTE
|| to
== STOCK
) return ERR
; /*why would you do that!?*/
582 if (to
== FOUNDATION
) {
584 for (int i
= 0; i
<= TAB_MAX
; i
++)
585 switch ((i
?t2f
:w2f
)(i
-1, FOUNDATION
, 0)) {
586 case WON
: return WON
;
587 case OK
: status
= OK
;
595 if (top_to
< 0) { /* move a king to empty pile: */
596 for (int i
= 0; i
<= TAB_MAX
; i
++) {
597 if (f
.t
[i
][0] < 0) /* i.e. would turn? */
598 if (t2t(i
, to
, 0) == OK
) return OK
;
600 return w2t(WASTE
, to
, 0);
602 #elif defined FREECELL
603 if (top_to
< 0) { /* move longest cascade to empty tableu: */ //TODO FREECELL:
606 for (int i
= 0; i
<= TAB_MAX
; i
++) {
607 int m
= max_move(i
, to
);
608 /*longest cascade that won't uncover another free pile*/
609 //TODO: don't rip apart cascades
610 if (m
>= length
&& m
<= find_top(f
.t
[i
]))
611 length
= m
, longest
= i
;
613 if (longest
< 0) return ERR
;
614 return t2t(longest
, to
, length
);
619 int ok
:1; /* card to move in pile? */
620 int above
; /* number of movable cards above */
621 int below
; /* number of cards below ours */
622 int pos
; /* where the card to move is in the pile */
623 int top
; /* find_top() */
624 } r
[NUM_PILES
] = {{0}};
625 int complete
= 0;/* SPIDER: true if any pile would complete a stack */
626 int turn
= 0; /* SPIDER: true if any pile would turn_over */
627 int empty
= 0; /* true if any pile would become empty */
629 /* 1. rate each pile: */
632 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
633 if (pile
== to
) continue;
634 int top
= find_top(f
.t
[pile
]);
635 int bottom
= first_movable(f
.t
[pile
]);
636 r
[pile
].pos
= bottom
; /* need for would_empty */
638 if (top
< 0) continue; /* no cards to move */
639 if (would_empty(pile
)) continue; /* doesn't help */
642 r
[pile
].above
= 0; /* always take as many as possible */
643 r
[pile
].below
= top
- bottom
;
645 complete
|= would_complete(pile
); /* never happens */
646 turn
|= would_turn(pile
);
647 empty
|= would_empty(pile
);
651 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
652 r
[pile
].top
= r
[pile
].pos
= find_top(f
.t
[pile
]);
653 /* backtrack until we find a compatible-to-'to'-pile card: */
655 int maxmove
= max_move(pile
, -1);
657 while (r
[pile
].pos
>= 0 && is_movable(f
.t
[pile
], r
[pile
].pos
)) {
658 int rankdiff
= get_rank(f
.t
[pile
][r
[pile
].pos
])
659 - get_rank(f
.t
[to
][top_to
]);
660 if (rankdiff
>= 0) break; /* past our card */
662 if (!maxmove
--) break; /* can't move this many cards */
664 if (rankdiff
== -1 && /* rank matches */
665 color_ok(f
.t
[pile
][r
[pile
].pos
], f
.t
[to
][top_to
])
668 complete
|= would_complete(pile
);
669 turn
|= would_turn(pile
);
670 empty
|= would_empty(pile
);
671 for (int i
= r
[pile
].pos
; i
>= 0; i
--)
672 if (is_movable(f
.t
[pile
], i
-1))
682 /* 2. find optimal pile: (optimized for spider) */
683 //todo: in spider, prefer longest piles if above==0 (faster completions)
685 for (int pile
= 0, above
= 99, below
= 99; pile
< NUM_PILES
; pile
++) {
686 if (!r
[pile
].ok
) continue;
687 /* don't bother if another pile would be better: prefer ... */
688 /* ... to complete a stack: */
689 if (!would_complete(pile
) && complete
) continue;
690 /* ... emptying piles: */
691 if (!would_empty(pile
) && empty
&& !complete
) continue;
692 /* ... to turn_over: */
693 if (!would_turn(pile
) && turn
&& !complete
&& !empty
) continue;
694 /* ... not to rip apart too many cards: */
695 if (r
[pile
].above
> above
) continue;
696 /* if tied, prefer ... */
697 if (r
[pile
].above
== above
698 /* ... larger pile if destination is empty */
699 && (top_to
< 0? r
[pile
].below
< below
700 /* ... shorter pile otherwise */
701 : r
[pile
].below
> below
))
705 above
= r
[pile
].above
;
706 below
= r
[pile
].below
;
709 /* 3. move cards over and return: */
711 /* prefer waste if it wouldn't turn_over: */
712 /* NOTE: does not attempt to take from froundation */
713 if (!empty
&& !turn
&& w2t(WASTE
, to
, 0) == OK
)
715 if (from
< 0) /* nothing found */
717 return t2t(from
, to
, 0);
719 if (from
< 0) /* nothing found */
721 int bottom
= first_movable(f
.t
[from
]);
722 return t2t(from
, to
, get_rank(f
.t
[from
][bottom
]));
723 #elif defined FREECELL
724 if (from
< 0) /* no tableu move found */ {
725 /* try all free cells before giving up: */
726 for (int i
= 0; i
< NUM_CELLS
; i
++)
727 if (c2t(STOCK
, to
, i
) == OK
) return OK
;
730 return t2t(from
, to
, 0);
735 #undef would_complete
736 int nop(int from
, int to
, int opt
) { (void)from
;(void)to
;(void)opt
;return ERR
; }
739 // keyboard input handling {{{
740 // cursor functions{{{
742 void cursor_left (struct cursor
* cursor
) {
744 if (is_tableu(cursor
->pile
)) {
745 if (cursor
->pile
> 0) cursor
->pile
--;
747 } else { /* stock/waste/foundation*/
748 switch (cursor
->pile
) {
749 case WASTE
: cursor
->pile
= STOCK
; cursor
->opt
= 0; break;
751 if (cursor
->opt
<= 0)
752 cursor
->pile
= WASTE
;
758 void cursor_down (struct cursor
* cursor
) {
760 if (!is_tableu(cursor
->pile
)) {
761 switch (cursor
->pile
) {
762 case STOCK
: cursor
->pile
= TAB_1
; break;
763 case WASTE
: cursor
->pile
= TAB_2
; break;
765 cursor
->pile
= TAB_4
+ cursor
->opt
;
770 void cursor_up (struct cursor
* cursor
) {
772 if (is_tableu(cursor
->pile
)) {
773 switch (cursor
->pile
) { //ugly :|
774 case TAB_1
: cursor
->pile
= STOCK
; break;
775 case TAB_2
: cursor
->pile
= WASTE
; break;
776 case TAB_3
: cursor
->pile
= WASTE
; break;
777 case TAB_4
: case TAB_5
: case TAB_6
: case TAB_7
:
778 cursor
->opt
=cursor
->pile
-TAB_4
;
779 cursor
->pile
= FOUNDATION
;
784 void cursor_right (struct cursor
* cursor
) {
786 if (is_tableu(cursor
->pile
)) {
787 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
790 switch (cursor
->pile
) {
791 case STOCK
: cursor
->pile
= WASTE
; break;
792 case WASTE
: cursor
->pile
= FOUNDATION
;cursor
->opt
= 0; break;
794 if (cursor
->opt
< NUM_SUITS
-1)
800 /*NOTE: one can't highlight the stock due to me being too lazy to implement it*/
801 void cursor_left (struct cursor
* cursor
) {
803 if (cursor
->pile
> 0) cursor
->pile
--;
806 void cursor_down (struct cursor
* cursor
) {
808 int first
= first_movable(f
.t
[cursor
->pile
]);
809 int top
= find_top(f
.t
[cursor
->pile
]);
810 if (first
+ cursor
->opt
< top
)
813 void cursor_up (struct cursor
* cursor
) {
815 if (cursor
->opt
> 0) cursor
->opt
--;
817 void cursor_right (struct cursor
* cursor
) {
819 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
822 #elif defined FREECELL
823 void cursor_left (struct cursor
* cursor
) {
825 if (is_tableu(cursor
->pile
)) {
826 if (cursor
->pile
> 0) cursor
->pile
--;
828 } else { /* cells/foundation*/
829 switch (cursor
->pile
) {
835 if (cursor
->opt
<= 0) {
836 cursor
->pile
= STOCK
;
844 void cursor_down (struct cursor
* cursor
) {
846 if (is_tableu(cursor
->pile
)) {
847 if (cursor
->opt
< max_move(cursor
->pile
, -1)-1)
850 cursor
->pile
= cursor
->opt
+NUM_CELLS
*(cursor
->pile
==FOUNDATION
);
854 void cursor_up (struct cursor
* cursor
) {
856 if (is_tableu(cursor
->pile
)) {
857 if (cursor
->opt
> 0) {
860 switch (cursor
->pile
) {
861 case TAB_1
: case TAB_2
: case TAB_3
: case TAB_4
:
862 cursor
->opt
= cursor
->pile
; /*assumes TAB_1==0*/
863 cursor
->pile
= STOCK
;
865 case TAB_5
: case TAB_6
: case TAB_7
: case TAB_8
:
866 cursor
->opt
= cursor
->pile
- NUM_CELLS
;
867 cursor
->pile
= FOUNDATION
;
872 void cursor_right (struct cursor
* cursor
) {
874 if (is_tableu(cursor
->pile
)) {
875 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
878 switch (cursor
->pile
) {
880 if (cursor
->opt
< NUM_SUITS
-1) {
883 cursor
->pile
= FOUNDATION
;
887 if (cursor
->opt
< NUM_SUITS
-1)
893 void cursor_to (struct cursor
* cursor
, int pile
) {
898 int set_mouse(int pile
, int* main
, int* opt
) {
899 //TODO: this should set cursor.opt, so card selector choice dialog does not trigger!
901 if (pile
< 0) return 1;
904 if (pile
>= FOUNDATION
)//TODO: check upper bound!
906 *opt
= pile
- FOUNDATION
;
909 #elif defined FREECELL
910 if (pile
> TAB_MAX
) {
911 *main
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
912 *opt
= (pile
-STOCK
) % 4;
918 int get_cmd (int* from
, int* to
, int* opt
) {
920 unsigned char mouse
[6] = {0}; /* must clear [3]! */
921 struct cursor inactive
= {-1,-1};
922 static struct cursor active
= {0,0};
923 if (is_tableu(active
.pile
))
927 from_l
: print_table(&active
, &inactive
);
931 /* direct addressing: */
932 case '1': *from
= TAB_1
; break;
933 case '2': *from
= TAB_2
; break;
934 case '3': *from
= TAB_3
; break;
935 case '4': *from
= TAB_4
; break;
936 case '5': *from
= TAB_5
; break;
937 case '6': *from
= TAB_6
; break;
938 case '7': *from
= TAB_7
; break;
940 case '8': *from
= TAB_8
; break;
941 case '9': *from
= TAB_9
; break;
942 case '0': *from
= TAB_10
;break;
943 #elif defined FREECELL
944 case '8': *from
= TAB_8
; break;
945 case '9': *from
= STOCK
; break;
946 case '0': *from
= FOUNDATION
; break;
947 #elif defined KLONDIKE
948 case '9': *from
= WASTE
; break;
949 case '0': *from
= FOUNDATION
; break;
950 case '8': /* fallthrough */
953 case '\n': *from
= STOCK
; break;
955 /* cursor keys addressing: */
957 case 'h': cursor_left (&active
); goto from_l
;
959 case 'j': cursor_down (&active
); goto from_l
;
961 case 'k': cursor_up (&active
); goto from_l
;
963 case 'l': cursor_right(&active
); goto from_l
;
965 case 'H': cursor_to(&active
,TAB_1
); goto from_l
; /* leftmost tableu */
967 case 'L': cursor_to(&active
,TAB_MAX
);goto from_l
; /* rigthmost tableu */
969 case 'M': cursor_to(&active
,TAB_MAX
/2); goto from_l
; /* center tableu */
970 case ' ': /* continue with second cursor */
973 *opt
= active
.opt
; /* when FOUNDATION */
977 /* mouse addressing: */
978 case MOUSE_MIDDLE
: return CMD_NONE
;
980 if (set_mouse(term2pile(mouse
), to
, opt
))
984 if (set_mouse(term2pile(mouse
), from
, opt
))
986 if (!is_tableu(*from
))
987 inactive
.opt
= *opt
; /* prevents card selector dialog */
992 fprintf (stderr
, ":");
993 raw_mode(0); /* turn on echo */
994 fgets (buf
, 256, stdin
);
997 case 'q': return CMD_QUIT
;
998 case 'n': return CMD_NEW
;
999 case 'r': return CMD_AGAIN
;
1000 case 'h': return CMD_HELP
;
1001 default: return CMD_INVAL
;
1006 case 'K': /* fallthrough */
1007 case '?': return CMD_HINT
;
1008 case 'u': return CMD_UNDO
;
1009 case 002: return CMD_NONE
; /* sent by SIGWINCH */
1010 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1011 default: return CMD_INVAL
;
1013 inactive
.pile
= *from
; /* for direct addressing highlighting */
1014 if (is_tableu(*from
) && f
.t
[*from
][0] == NO_CARD
) return CMD_INVAL
;
1017 if (*from
== STOCK
) {
1024 to_l
: print_table(&active
, &inactive
);
1029 case 'h': cursor_left (&active
); goto to_l
;
1031 case 'j': cursor_down (&active
); goto to_l
;
1033 case 'k': cursor_up (&active
); goto to_l
;
1035 case 'l': cursor_right(&active
); goto to_l
;
1037 case 'H': cursor_to(&active
,TAB_1
); goto to_l
;
1039 case 'L': cursor_to(&active
,TAB_MAX
); goto to_l
;
1041 case 'M': cursor_to(&active
,TAB_MAX
/2); goto to_l
;
1042 case 'J': /* fallthrough; just join selected pile */
1045 break; /* continues with the foundation/empty tableu check */
1047 case MOUSE_RIGHT
: return CMD_NONE
;
1049 if (set_mouse(term2pile(mouse
), to
, opt
))
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 //TODO: mouse-friendly "up to?" dialog
1168 #if defined KLONDIKE || defined FREECELL
1169 if (*from
== FOUNDATION
) {
1170 if (inactive
.opt
>= 0) {
1171 *opt
= inactive
.opt
;
1174 int top
= find_top(f
.t
[*to
]);
1175 if (top
< 0) return CMD_INVAL
;
1176 int color
= get_color(f
.t
[*to
][top
]);
1177 int choice_1
= 1-color
; /* selects piles of */
1178 int choice_2
= 2+color
; /* the opposite color */
1179 int top_c1
= find_top(f
.f
[choice_1
]);
1180 int top_c2
= find_top(f
.f
[choice_2
]);
1182 switch ((rank_next(f
.f
[choice_1
][top_c1
], f
.t
[*to
][top
])
1183 && top_c1
>= 0 ) << 0
1184 |(rank_next(f
.f
[choice_2
][top_c2
], f
.t
[*to
][top
])
1185 && top_c2
>= 0 ) << 1) {
1186 case ( 1<<0): *opt
= choice_1
; break; /* choice_1 only */
1187 case (1<<1 ): *opt
= choice_2
; break; /* choice_2 only */
1188 case (1<<1 | 1<<0): /* both, ask user which to pick from */
1189 printf ("take from (1-4): "); fflush (stdout
);
1190 *opt
= getch(NULL
) - '1';
1191 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1193 default: return CMD_INVAL
; /* none matched */
1195 /* `opt` is the foundation index (0..3) */
1197 #elif defined SPIDER
1198 /* moving to empty tableu? */
1199 if (is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
) {
1200 int bottom
= first_movable(f
.t
[*from
]);
1201 if (inactive
.opt
>= 0) { /*if from was cursor addressed: */
1202 *opt
= get_rank(f
.t
[*from
][bottom
+ inactive
.opt
]);
1205 int top
= find_top(f
.t
[*from
]);
1206 if (top
< 0) return CMD_INVAL
;
1207 if (top
>= 0 && !is_movable(f
.t
[*from
], top
-1)) {
1208 *opt
= get_rank(f
.t
[*from
][top
]);
1209 } else { /* only ask the user if it's unclear: */
1210 printf ("\rup to ([a23456789xjqk] or space/return): ");
1213 case ' ': *opt
= get_rank(f
.t
[*from
][top
]); break;
1214 case'\n': *opt
= get_rank(f
.t
[*from
][bottom
]); break;
1215 case 'a': case 'A': *opt
= RANK_A
; break;
1216 case '0': /* fallthrough */
1217 case 'x': case 'X': *opt
= RANK_X
; break;
1218 case 'j': case 'J': *opt
= RANK_J
; break;
1219 case 'q': case 'Q': *opt
= RANK_Q
; break;
1220 case 'k': case 'K': *opt
= RANK_K
; break;
1221 default: *opt
-= '1';
1223 if (*opt
< RANK_A
|| *opt
> RANK_K
) return CMD_INVAL
;
1225 /* `opt` is the rank of the highest card to move */
1231 int getctrlseq(unsigned char* buf
) {
1239 int offset
= 0x20; /* never sends control chars as data */
1240 while ((c
= getchar()) != EOF
) {
1244 case '\033': state
=ESC_SENT
; break;
1250 case '[': state
=CSI_SENT
; break;
1251 default: return KEY_INVAL
;
1256 case 'A': return KEY_UP
;
1257 case 'B': return KEY_DOWN
;
1258 case 'C': return KEY_RIGHT
;
1259 case 'D': return KEY_LEFT
;
1260 /*NOTE: home/end send ^[[x~ . no support for modifiers*/
1261 case 'H': return KEY_HOME
;
1262 case 'F': return KEY_END
;
1263 case '2': getchar(); return KEY_INS
;
1264 case '5': getchar(); return KEY_PGUP
;
1265 case '6': getchar(); return KEY_PGDN
;
1266 case 'M': state
=MOUSE_EVENT
; break;
1267 default: return KEY_INVAL
;
1271 if (buf
== NULL
) return KEY_INVAL
;
1272 buf
[0] = c
- offset
;
1273 buf
[1] = getchar() - offset
;
1274 buf
[2] = getchar() - offset
;
1282 int term2pile(unsigned char *mouse
) {
1283 int line
= (mouse
[2]-1);
1284 int column
= (mouse
[1]-1) / op
.s
->width
;
1286 if (line
< op
.s
->height
) { /* first line */
1289 case 0: return STOCK
;
1290 case 1: return WASTE
;
1291 case 2: return -1; /* spacer */
1292 case 3: return FOUNDATION
+0;
1293 case 4: return FOUNDATION
+1;
1294 case 5: return FOUNDATION
+2;
1295 case 6: return FOUNDATION
+3;
1297 #elif defined SPIDER
1298 if (column
< 3) return STOCK
;
1300 #elif defined FREECELL
1301 if (column
< NUM_SUITS
+ NUM_CELLS
) return STOCK
+column
;
1304 } else if (line
> op
.s
->height
) { /* tableu */
1305 if (column
<= TAB_MAX
) return column
;
1309 int wait_mouse_up(unsigned char* mouse
) {
1310 //TODO: mouse drag: start gets inactive, hovering gets active cursors
1311 struct cursor cur
= {-1,-1};
1313 /* note: if dragged [3]==1 and second position is in mouse[0,4,5] */
1315 /* display a cursor while mouse button is pushed: */
1316 int pile
= term2pile(mouse
);
1319 if (pile
>= FOUNDATION
) {
1320 cur
.pile
= FOUNDATION
;
1321 cur
.opt
= pile
-FOUNDATION
;
1323 #elif defined FREECELL
1324 if (pile
> TAB_MAX
) {
1325 cur
.pile
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
1326 cur
.opt
= (pile
-STOCK
) % 4;
1329 /* need to temporarily show the cursor, then revert to last state: */
1330 int old_show_cursor_hi
= op
.h
; //TODO: ARGH! that's awful!
1332 print_table(&cur
, NO_HI
); //TODO: should not overwrite inactive cursor!
1333 op
.h
= old_show_cursor_hi
;
1336 if (getctrlseq (mouse
+3) == MOUSE_ANY
) {
1337 /* ignore mouse wheel events: */
1338 if (mouse
[3] & 0x40) continue;
1340 else if((mouse
[3]&3) == 3) level
--; /* release event */
1341 else level
++; /* another button pressed */
1345 int success
= mouse
[1] == mouse
[4] && mouse
[2] == mouse
[5];
1352 int getch(unsigned char* buf
) {
1353 //TODO: if buf==NULL disable mouse input
1354 /* returns a character, EOF, or constant for an escape/control sequence - NOT
1355 compatible with the ncurses implementation of same name */
1357 if (buf
&& buf
[3]) {
1358 /* mouse was dragged; return 'ungetted' previous destination */
1359 action
= MOUSE_DRAG
;
1360 /* keep original [0], as [3] only contains release event */
1365 action
= getctrlseq(buf
);
1370 if (buf
[0] > 3) break; /* ignore wheel events */
1375 case 0: return MOUSE_LEFT
;
1376 case 1: return MOUSE_MIDDLE
;
1377 case 2: return MOUSE_RIGHT
;
1385 // shuffling and dealing {{{
1386 void deal(long seed
) {
1387 f
= (const struct playfield
){0}; /* clear playfield */
1388 card_t deck
[DECK_SIZE
*NUM_DECKS
];
1389 int avail
= DECK_SIZE
*NUM_DECKS
;
1390 for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) deck
[i
] = (i
%DECK_SIZE
)+1;
1392 if (op
.m
!= NORMAL
) for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) {
1393 if (op
.m
== MEDIUM
) deck
[i
] = 1+((deck
[i
]-1) | 2);
1394 if (op
.m
== EASY
) deck
[i
] = 1+((deck
[i
]-1) | 2 | 1);
1395 /* the 1+ -1 dance gets rid of the offset created by NO_CARD */
1399 for (int i
= DECK_SIZE
*NUM_DECKS
-1; i
> 0; i
--) { /* fisher-yates */
1400 int j
= rand() % (i
+1);
1401 if (j
-i
) deck
[i
]^=deck
[j
],deck
[j
]^=deck
[i
],deck
[i
]^=deck
[j
];
1405 for (int i
= 0; i
< NUM_PILES
; i
++) {
1408 int count
= i
; /* pile n has n closed cards, then 1 open */
1409 #elif defined SPIDER
1411 int count
= i
<4?5:4; /* pile 1-4 have 5, 5-10 have 4 closed */
1412 #elif defined FREECELL
1414 int count
= i
<4?6:5;/*like spider, but cards are dealt face-up*/
1416 /* "SIGN": face down cards are negated */
1417 for (int j
= 0; j
< count
; j
++) f
.t
[i
][j
] = SIGN deck
[--avail
];
1418 f
.t
[i
][count
] = deck
[--avail
]; /* the face-up card */
1421 /* rest of the cards to the stock: */
1422 /* NOTE: assert(avail==50) for spider, assert(avail==0) for freecell */
1423 for (f
.z
= 0; avail
; f
.z
++) f
.s
[f
.z
] = deck
[--avail
];
1425 f
.w
= -1; /* @start: nothing on waste */
1426 #elif defined SPIDER
1427 f
.w
= 0; /* number of used foundations */
1428 #elif defined FREECELL
1429 f
.w
= 0; /* bitmask of used free cells */
1432 f
.u
= &undo_sentinel
;
1436 // screen drawing routines {{{
1437 void print_hi(int invert
, int grey_bg
, int bold
, char* str
) {
1438 if (!op
.h
) invert
= 0; /* don't show invert if we used the mouse last */
1439 if (bold
&& op
.s
== &unicode_large_color
){ //awful hack for bold + faint
1440 int offset
= str
[3]==017?16:str
[4]==017?17:0;
1441 printf ("%s%s%s""%.*s%s%s""%s%s%s",
1442 "\033[1m", invert
?"\033[7m":"", grey_bg
?"\033[100m":"",
1443 offset
, str
, bold
?"\033[1m":"", str
+offset
,
1444 grey_bg
?"\033[49m":"", invert
?"\033[27m":"","\033[22m");
1447 printf ("%s%s%s%s%s%s%s",
1448 bold
?"\033[1m":"", invert
?"\033[7m":"", grey_bg
?"\033[100m":"",
1450 grey_bg
?"\033[49m":"", invert
?"\033[27m":"",bold
?"\033[22m":"");
1452 void print_table(const struct cursor
* active
, const struct cursor
* inactive
) {
1453 printf("\033[2J\033[H"); /* clear screen, reset cursor */
1455 /* print stock, waste and foundation: */
1456 for (int line
= 0; line
< op
.s
->height
; line
++) {
1458 print_hi (active
->pile
== STOCK
, inactive
->pile
== STOCK
, 1, (
1459 (f
.w
< f
.z
-1)?op
.s
->facedown
1460 :op
.s
->placeholder
)[line
]);
1462 print_hi (active
->pile
== WASTE
, inactive
->pile
== WASTE
, 1, (
1463 /* NOTE: cast, because f.w sometimes is (short)-1 !? */
1464 ((short)f
.w
>= 0)?op
.s
->card
[f
.s
[f
.w
]]
1465 :op
.s
->placeholder
)[line
]);
1466 printf ("%s", op
.s
->card
[NO_CARD
][line
]); /* spacer */
1468 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1469 int card
= find_top(f
.f
[pile
]);
1470 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1471 inactive
->pile
==FOUNDATION
&& (
1472 /* cursor addr. || direct addr. */
1473 inactive
->opt
==pile
|| inactive
->opt
< 0
1475 (card
< 0)?op
.s
->foundation
[line
]
1476 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1481 #elif defined SPIDER
1482 int fdone
; for (fdone
= NUM_DECKS
*NUM_SUITS
; fdone
; fdone
--)
1483 if (f
.f
[fdone
-1][RANK_K
]) break; /*number of completed stacks*/
1484 int spacer_from
= f
.z
?(f
.z
/10-1) * op
.s
->halfwidth
[0] + op
.s
->width
:0;
1485 int spacer_to
= NUM_PILES
*op
.s
->width
-
1486 ((fdone
?(fdone
-1) * op
.s
->halfwidth
[1]:0)+op
.s
->width
);
1487 for (int line
= 0; line
< op
.s
->height
; line
++) {
1488 /* available stock: */
1489 for (int i
= f
.z
/10; i
; i
--) {
1490 if (i
==1) printf ("%s", op
.s
->facedown
[line
]);
1491 else printf ("%s", op
.s
->halfstack
[line
]);
1494 for (int i
= spacer_from
; i
< spacer_to
; i
++) printf (" ");
1495 /* foundation (overlapping): */
1496 for (int i
= NUM_DECKS
*NUM_SUITS
-1, half
= 0; i
>= 0; i
--) {
1497 int overlap
= half
? op
.s
->halfcard
[line
]: 0;
1498 if (f
.f
[i
][RANK_K
]) printf ("%.*s", op
.s
->halfwidth
[2],
1499 op
.s
->card
[f
.f
[i
][RANK_K
]][line
]+overlap
),
1505 #elif defined FREECELL
1506 /* print open cells, foundation: */
1507 for (int line
= 0; line
< op
.s
->height
; line
++) {
1508 for (int pile
= 0; pile
< NUM_CELLS
; pile
++)
1509 print_hi (active
->pile
==STOCK
&& active
->opt
==pile
,
1510 inactive
->pile
==STOCK
&& (
1511 /* cursor addr. || direct addr. */
1512 inactive
->opt
==pile
|| inactive
->opt
< 0
1514 ((f
.s
[pile
])?op
.s
->card
[f
.s
[pile
]]
1515 :op
.s
->placeholder
)[line
]);
1516 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1517 int card
= find_top(f
.f
[pile
]);
1518 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1519 inactive
->pile
==FOUNDATION
&& (
1520 /* cursor addr. || direct addr. */
1521 inactive
->opt
==pile
|| inactive
->opt
< 0
1523 (card
< 0)?op
.s
->foundation
[line
]
1524 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1531 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty))
1532 #define TOP_HI(c) 1 /* can't select partial stacks in KLONDIKE */
1533 #elif defined SPIDER || defined FREECELL
1534 int offset
[NUM_PILES
]={0}; /* first card to highlight */
1536 int bottom
[NUM_PILES
]; /* first movable card */
1537 for (int i
=0; i
<NUM_PILES
; i
++)
1538 bottom
[i
] = find_top(f
.t
[i
]) - max_move(i
,-1);
1540 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty) \
1541 && offset[pile] >= cursor->opt)
1542 #define TOP_HI(cursor) (cursor->pile == pile && movable \
1543 && offset[pile] == cursor->opt)
1545 /* print tableu piles: */
1546 int row
[NUM_PILES
] = {0};
1547 int line
[NUM_PILES
]= {0};
1548 int label
[NUM_PILES
]={0};
1550 int did_placeholders
= 0;
1553 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
1554 card_t card
= f
.t
[pile
][row
[pile
]];
1555 card_t next
= f
.t
[pile
][row
[pile
]+1];
1556 int movable
= is_movable(f
.t
[pile
], row
[pile
]);
1558 if(row
[pile
] <= bottom
[pile
]) movable
= 0;
1560 int empty
= !card
&& row
[pile
] == 0;
1562 print_hi (DO_HI(active
), DO_HI(inactive
), movable
, (
1563 (!card
&& row
[pile
] == 0)?op
.s
->placeholder
1564 :(card
<0)?op
.s
->facedown
1568 int extreme_overlap
= ( 3 /* spacer, labels, status */
1569 + 2 * op
.s
->height
/* stock, top tableu card */
1570 + find_top(f
.t
[pile
]) * op
.s
->overlap
) >op
.w
[0];
1571 /* normal overlap: */
1572 if (++line
[pile
] >= (next
?op
.s
->overlap
:op
.s
->height
)
1573 /* extreme overlap on closed cards: */
1574 || (extreme_overlap
&&
1576 f
.t
[pile
][row
[pile
]] < 0 &&
1577 f
.t
[pile
][row
[pile
]+1] <0)
1578 /* extreme overlap on sequences: */
1579 || (extreme_overlap
&&
1580 !TOP_HI(active
) && /*always show top selected card*/
1581 line
[pile
] >= 1 && row
[pile
] > 0 &&
1582 f
.t
[pile
][row
[pile
]-1] > NO_CARD
&&
1583 is_consecutive (f
.t
[pile
], row
[pile
]) &&
1584 is_consecutive (f
.t
[pile
], row
[pile
]-1) &&
1585 f
.t
[pile
][row
[pile
]+1] != NO_CARD
)
1589 #if defined SPIDER || defined FREECELL
1590 if (movable
) offset
[pile
]++;
1593 /* tableu labels: */
1594 if(!card
&& !label
[pile
] && row
[pile
]>0&&line
[pile
]>0) {
1596 printf ("\b\b%d ", (pile
+1) % 10); //XXX: hack
1598 line_had_card
|= !!card
;
1599 did_placeholders
|= row
[pile
] > 0;
1602 } while (line_had_card
|| !did_placeholders
);
1605 void visbell (void) {
1607 printf ("\033[?5h"); fflush (stdout
);
1609 printf ("\033[?5l"); fflush (stdout
);
1611 void win_anim(void) {
1612 printf ("\033[?25l"); /* hide cursor */
1614 /* set cursor to random location */
1615 int row
= 1+rand()%(1+op
.w
[0]-op
.s
->height
);
1616 int col
= 1+rand()%(1+op
.w
[1]-op
.s
->width
);
1618 /* draw random card */
1619 int face
= 1 + rand() % 52;
1620 for (int l
= 0; l
< op
.s
->height
; l
++) {
1621 printf ("\033[%d;%dH", row
+l
, col
);
1622 printf ("%s", op
.s
->card
[face
][l
]);
1626 /* exit on keypress */
1627 struct pollfd p
= {STDIN_FILENO
, POLLIN
, 0};
1628 if (poll (&p
, 1, 80)) goto fin
;
1631 printf ("\033[?25h"); /* show cursor */
1637 void undo_push (int _f
, int t
, int n
, int o
) {
1638 struct undo
* new = malloc(sizeof(struct undo
));
1648 void undo_pop (struct undo
* u
) {
1649 if (u
== &undo_sentinel
) return;
1652 if (u
->f
== FOUNDATION
) {
1653 /* foundation -> tableu */
1654 int top_f
= find_top(f
.f
[u
->n
]);
1655 int top_t
= find_top(f
.t
[u
->t
]);
1656 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1657 f
.t
[u
->t
][top_t
] = NO_CARD
;
1658 } else if (u
->f
== WASTE
&& u
->t
== FOUNDATION
) {
1659 /* waste -> foundation */
1660 /* split u->n into wst and fnd: */
1661 int wst
= u
->n
& 0xffff;
1662 int fnd
= u
->n
>> 16;
1663 /* move stock cards one position up to make room: */
1664 for (int i
= f
.z
; i
>= wst
; i
--) f
.s
[i
+1] = f
.s
[i
];
1665 /* move one card from foundation to waste: */
1666 int top
= find_top(f
.f
[fnd
]);
1667 f
.s
[wst
] = f
.f
[fnd
][top
];
1668 f
.f
[fnd
][top
] = NO_CARD
;
1671 } else if (u
->f
== WASTE
) {
1672 /* waste -> tableu */
1673 /* move stock cards one position up to make room: */
1674 for (int i
= f
.z
-1; i
>= u
->n
; i
--) f
.s
[i
+1] = f
.s
[i
];
1675 /* move one card from tableu to waste: */
1676 int top
= find_top(f
.t
[u
->t
]);
1677 f
.s
[u
->n
] = f
.t
[u
->t
][top
];
1678 f
.t
[u
->t
][top
] = NO_CARD
;
1681 } else if (u
->t
== FOUNDATION
) {
1682 /* tableu -> foundation */
1683 int top_f
= find_top(f
.t
[u
->f
]);
1684 int top_t
= find_top(f
.f
[u
->n
]);
1685 /* close topcard if previous action caused turn_over(): */
1686 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1687 /* move one card from foundation to tableu: */
1688 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1689 f
.f
[u
->n
][top_t
] = NO_CARD
;
1691 /* tableu -> tableu */
1692 int top_f
= find_top(f
.t
[u
->f
]);
1693 int top_t
= find_top(f
.t
[u
->t
]);
1694 /* close topcard if previous action caused turn_over(): */
1695 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1696 /* move n cards from tableu[f] to tableu[t]: */
1697 for (int i
= 0; i
< u
->n
; i
++) {
1698 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1699 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1702 #elif defined SPIDER
1703 if (u
->f
== STOCK
) {
1704 /* stock -> tableu */
1705 /*remove a card from each pile and put it back onto the stock:*/
1706 for (int pile
= NUM_PILES
-1; pile
>= 0; pile
--) {
1707 int top
= find_top(f
.t
[pile
]);
1708 f
.s
[f
.z
++] = f
.t
[pile
][top
];
1709 f
.t
[pile
][top
] = NO_CARD
;
1711 } else if (u
->t
== FOUNDATION
) {
1712 /* tableu -> foundation */
1713 int top
= find_top(f
.t
[u
->f
]);
1714 /* close topcard if previous action caused turn_over(): */
1715 if (u
->o
) f
.t
[u
->f
][top
] *= -1;
1716 /* append cards from foundation to tableu */
1717 for (int i
= RANK_K
; i
>= RANK_A
; i
--) {
1718 f
.t
[u
->f
][++top
] = f
.f
[u
->n
][i
];
1719 f
.f
[u
->n
][i
] = NO_CARD
;
1721 f
.w
--; /* decrement complete-foundation-counter */
1724 /* tableu -> tableu */
1725 int top_f
= find_top(f
.t
[u
->f
]);
1726 int top_t
= find_top(f
.t
[u
->t
]);
1727 /* close topcard if previous action caused turn_over(): */
1728 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1729 /* move n cards from tableu[f] to tableu[t]: */
1730 for (int i
= 0; i
< u
->n
; i
++) {
1731 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1732 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1735 #elif defined FREECELL
1736 /*NOTE: if from and to are both stock/foundation, opt = from | to<<16 */
1737 if (u
->f
== STOCK
&& u
->t
== FOUNDATION
) {
1738 /* free cells -> foundation */
1739 /* split u->n into cll and fnd: */
1740 int cll
= u
->n
& 0xffff;
1741 int fnd
= u
->n
>> 16;
1742 /* move one card from foundation to free cell: */
1743 int top
= find_top(f
.f
[fnd
]);
1744 f
.s
[cll
] = f
.f
[fnd
][top
];
1745 f
.f
[fnd
][top
] = NO_CARD
;
1746 f
.w
|= 1<<cll
; /* mark cell as occupied */
1747 } else if (u
->f
== STOCK
) {
1748 /* free cells -> cascade */
1749 int top_t
= find_top(f
.t
[u
->t
]);
1750 f
.s
[u
->n
] = f
.t
[u
->t
][top_t
];
1751 f
.t
[u
->t
][top_t
] = NO_CARD
;
1752 f
.w
|= 1<<u
->n
; /* mark cell as occupied */
1753 } else if (u
->f
== FOUNDATION
&& u
->t
== STOCK
) {
1754 /* foundation -> free cells */
1755 /* split u->n into cll and fnd: */
1756 int cll
= u
->n
>> 16;
1757 int fnd
= u
->n
& 0xffff;
1758 /* move 1 card from free cell to foundation: */
1759 int top_f
= find_top(f
.f
[fnd
]);
1760 f
.f
[fnd
][top_f
+1] = f
.s
[cll
];
1762 f
.w
&= ~(1<<cll
); /* mark cell as free */
1763 } else if (u
->f
== FOUNDATION
) {
1764 /* foundation -> cascade */
1765 int top_f
= find_top(f
.f
[u
->n
]);
1766 int top_t
= find_top(f
.t
[u
->t
]);
1767 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1768 f
.t
[u
->t
][top_t
] = NO_CARD
;
1769 } else if (u
->t
== STOCK
) {
1770 /* cascade -> free cells */
1771 int top_f
= find_top(f
.t
[u
->f
]);
1772 f
.t
[u
->f
][top_f
+1] = f
.s
[u
->n
];
1773 f
.s
[u
->n
] = NO_CARD
;
1774 f
.w
&= ~(1<<u
->n
); /* mark cell as free */
1775 } else if (u
->t
== FOUNDATION
) {
1776 /* cascade -> foundation */
1777 int top_f
= find_top(f
.t
[u
->f
]);
1778 int top_t
= find_top(f
.f
[u
->n
]);
1779 /* move one card from foundation to cascade: */
1780 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1781 f
.f
[u
->n
][top_t
] = NO_CARD
;
1783 /* cascade -> cascade */
1784 int top_f
= find_top(f
.t
[u
->f
]);
1785 int top_t
= find_top(f
.t
[u
->t
]);
1786 /* move n cards from tableu[f] to tableu[t]: */
1787 for (int i
= 0; i
< u
->n
; i
++) {
1788 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1789 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1798 void free_undo (struct undo
* u
) {
1799 while (u
&& u
!= &undo_sentinel
) {
1807 // initialization stuff {{{
1808 void screen_setup (int enable
) {
1811 printf ("\033[s\033[?47h"); /* save cursor, alternate screen */
1812 printf ("\033[H\033[J"); /* reset cursor, clear screen */
1813 printf ("\033[?1000h"); /* enable mouse */
1815 printf ("\033[?1000l"); /* disable mouse */
1816 printf ("\033[?47l\033[u"); /* primary screen, restore cursor */
1821 void raw_mode(int enable
) {
1822 static struct termios saved_term_mode
;
1823 struct termios raw_term_mode
;
1826 if (saved_term_mode
.c_lflag
== 0)/*don't overwrite stored mode*/
1827 tcgetattr(STDIN_FILENO
, &saved_term_mode
);
1828 raw_term_mode
= saved_term_mode
;
1829 raw_term_mode
.c_lflag
&= ~(ICANON
| ECHO
);
1830 raw_term_mode
.c_cc
[VMIN
] = 1 ;
1831 raw_term_mode
.c_cc
[VTIME
] = 0;
1832 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw_term_mode
);
1834 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &saved_term_mode
);
1838 void signal_handler (int signum
) {
1843 signal(SIGTSTP
, SIG_DFL
); /* NOTE: assumes SysV semantics! */
1848 print_table(NO_HI
, NO_HI
);
1850 case SIGINT
: //TODO: don't exit; just warn like vim does
1853 ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &w
);
1859 void signal_setup(void) {
1860 struct sigaction saction
;
1862 saction
.sa_handler
= signal_handler
;
1863 sigemptyset(&saction
.sa_mask
);
1864 saction
.sa_flags
= 0;
1865 if (sigaction(SIGTSTP
, &saction
, NULL
) < 0) {
1869 if (sigaction(SIGCONT
, &saction
, NULL
) < 0) {
1873 if (sigaction(SIGINT
, &saction
, NULL
) < 0) {
1877 if (sigaction(SIGWINCH
, &saction
, NULL
) < 0) {
1878 perror ("SIGWINCH");
1884 //vim: foldmethod=marker