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 if (f
.s
[from
] == NO_CARD
) return ERR
;
527 to
= get_suit(f
.s
[from
]);
528 int top_to
= find_top(f
.f
[to
]);
529 if ((top_to
< 0 && get_rank(f
.s
[from
]) == RANK_A
)
530 || (top_to
>= 0 && rank_next(f
.f
[to
][top_to
],f
.s
[from
]))) {
531 f
.f
[to
][top_to
+1] = f
.s
[from
];
533 f
.w
&= ~(1<<from
); /* mark cell as free */
534 undo_push(STOCK
, FOUNDATION
, from
| to
<<16, 0);
535 if (check_won()) return WON
;
539 int f2c(int from
, int to
, int opt
) {
540 (void) from
; (void) to
; /* don't need */
541 /* is a cell free? */
542 if (f
.w
== (1<<NUM_CELLS
)-1)
544 for (to
= 0; to
< NUM_CELLS
; to
++)
545 if (!(f
.w
>>to
&1)) break;
548 int top_from
= find_top(f
.f
[from
]);
549 f
.s
[to
] = f
.f
[from
][top_from
];
550 f
.f
[from
][top_from
] = NO_CARD
;
551 f
.w
|= 1<<to
; /* mark cell as occupied */
552 undo_push(FOUNDATION
, STOCK
, from
| to
<<16, 0);
556 #define w2f c2f /* for join()'s "to foundation" */
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
< NUM_PILES
+NUM_CELLS
; i
++)
586 switch (is_tableu(i
)?t2f(i
, FOUNDATION
, 0)
587 :w2f(STOCK
,FOUNDATION
,i
-NUM_PILES
)){
588 case WON
: return WON
;
589 case OK
: status
= OK
;
597 if (top_to
< 0) { /* move a king to empty pile: */
598 for (int i
= 0; i
<= TAB_MAX
; i
++) {
599 if (f
.t
[i
][0] < 0) /* i.e. would turn? */
600 if (t2t(i
, to
, 0) == OK
) return OK
;
602 return w2t(WASTE
, to
, 0);
604 #elif defined FREECELL
605 if (top_to
< 0) { /* move longest cascade to empty tableu: */ //TODO FREECELL:
608 for (int i
= 0; i
<= TAB_MAX
; i
++) {
609 int m
= max_move(i
, to
);
610 /*longest cascade that won't uncover another free pile*/
611 //TODO: don't rip apart cascades
612 if (m
>= length
&& m
<= find_top(f
.t
[i
]))
613 length
= m
, longest
= i
;
615 if (longest
< 0) return ERR
;
616 return t2t(longest
, to
, length
);
621 int ok
:1; /* card to move in pile? */
622 int above
; /* number of movable cards above */
623 int below
; /* number of cards below ours */
624 int pos
; /* where the card to move is in the pile */
625 int top
; /* find_top() */
626 } r
[NUM_PILES
] = {{0}};
627 int complete
= 0;/* SPIDER: true if any pile would complete a stack */
628 int turn
= 0; /* SPIDER: true if any pile would turn_over */
629 int empty
= 0; /* true if any pile would become empty */
631 /* 1. rate each pile: */
634 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
635 if (pile
== to
) continue;
636 int top
= find_top(f
.t
[pile
]);
637 int bottom
= first_movable(f
.t
[pile
]);
638 r
[pile
].pos
= bottom
; /* need for would_empty */
640 if (top
< 0) continue; /* no cards to move */
641 if (would_empty(pile
)) continue; /* doesn't help */
644 r
[pile
].above
= 0; /* always take as many as possible */
645 r
[pile
].below
= top
- bottom
;
647 complete
|= would_complete(pile
); /* never happens */
648 turn
|= would_turn(pile
);
649 empty
|= would_empty(pile
);
653 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
654 r
[pile
].top
= r
[pile
].pos
= find_top(f
.t
[pile
]);
655 /* backtrack until we find a compatible-to-'to'-pile card: */
657 int maxmove
= max_move(pile
, -1);
659 while (r
[pile
].pos
>= 0 && is_movable(f
.t
[pile
], r
[pile
].pos
)) {
660 int rankdiff
= get_rank(f
.t
[pile
][r
[pile
].pos
])
661 - get_rank(f
.t
[to
][top_to
]);
662 if (rankdiff
>= 0) break; /* past our card */
664 if (!maxmove
--) break; /* can't move this many cards */
666 if (rankdiff
== -1 && /* rank matches */
667 color_ok(f
.t
[pile
][r
[pile
].pos
], f
.t
[to
][top_to
])
670 complete
|= would_complete(pile
);
671 turn
|= would_turn(pile
);
672 empty
|= would_empty(pile
);
673 for (int i
= r
[pile
].pos
; i
>= 0; i
--)
674 if (is_movable(f
.t
[pile
], i
-1))
684 /* 2. find optimal pile: (optimized for spider) */
685 //todo: in spider, prefer longest piles if above==0 (faster completions)
687 for (int pile
= 0, above
= 99, below
= 99; pile
< NUM_PILES
; pile
++) {
688 if (!r
[pile
].ok
) continue;
689 /* don't bother if another pile would be better: prefer ... */
690 /* ... to complete a stack: */
691 if (!would_complete(pile
) && complete
) continue;
692 /* ... emptying piles: */
693 if (!would_empty(pile
) && empty
&& !complete
) continue;
694 /* ... to turn_over: */
695 if (!would_turn(pile
) && turn
&& !complete
&& !empty
) continue;
696 /* ... not to rip apart too many cards: */
697 if (r
[pile
].above
> above
) continue;
698 /* if tied, prefer ... */
699 if (r
[pile
].above
== above
700 /* ... larger pile if destination is empty */
701 && (top_to
< 0? r
[pile
].below
< below
702 /* ... shorter pile otherwise */
703 : r
[pile
].below
> below
))
707 above
= r
[pile
].above
;
708 below
= r
[pile
].below
;
711 /* 3. move cards over and return: */
713 /* prefer waste if it wouldn't turn_over: */
714 /* NOTE: does not attempt to take from froundation */
715 if (!empty
&& !turn
&& w2t(WASTE
, to
, 0) == OK
)
717 if (from
< 0) /* nothing found */
719 return t2t(from
, to
, 0);
721 if (from
< 0) /* nothing found */
723 int bottom
= first_movable(f
.t
[from
]);
724 return t2t(from
, to
, get_rank(f
.t
[from
][bottom
]));
725 #elif defined FREECELL
726 //TODO: if would rip apart, try freecells first (instead after)
727 if (from
< 0) /* no tableu move found */ {
728 /* try all free cells before giving up: */
729 for (int i
= 0; i
< NUM_CELLS
; i
++)
730 if (c2t(STOCK
, to
, i
) == OK
) return OK
;
733 return t2t(from
, to
, 0);
738 #undef would_complete
739 int nop(int from
, int to
, int opt
) { (void)from
;(void)to
;(void)opt
;return ERR
; }
742 // keyboard input handling {{{
743 // cursor functions{{{
745 void cursor_left (struct cursor
* cursor
) {
747 if (is_tableu(cursor
->pile
)) {
748 if (cursor
->pile
> 0) cursor
->pile
--;
750 } else { /* stock/waste/foundation*/
751 switch (cursor
->pile
) {
752 case WASTE
: cursor
->pile
= STOCK
; cursor
->opt
= 0; break;
754 if (cursor
->opt
<= 0)
755 cursor
->pile
= WASTE
;
761 void cursor_down (struct cursor
* cursor
) {
763 if (!is_tableu(cursor
->pile
)) {
764 switch (cursor
->pile
) {
765 case STOCK
: cursor
->pile
= TAB_1
; break;
766 case WASTE
: cursor
->pile
= TAB_2
; break;
768 cursor
->pile
= TAB_4
+ cursor
->opt
;
773 void cursor_up (struct cursor
* cursor
) {
775 if (is_tableu(cursor
->pile
)) {
776 switch (cursor
->pile
) { //ugly :|
777 case TAB_1
: cursor
->pile
= STOCK
; break;
778 case TAB_2
: cursor
->pile
= WASTE
; break;
779 case TAB_3
: cursor
->pile
= WASTE
; break;
780 case TAB_4
: case TAB_5
: case TAB_6
: case TAB_7
:
781 cursor
->opt
=cursor
->pile
-TAB_4
;
782 cursor
->pile
= FOUNDATION
;
787 void cursor_right (struct cursor
* cursor
) {
789 if (is_tableu(cursor
->pile
)) {
790 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
793 switch (cursor
->pile
) {
794 case STOCK
: cursor
->pile
= WASTE
; break;
795 case WASTE
: cursor
->pile
= FOUNDATION
;cursor
->opt
= 0; break;
797 if (cursor
->opt
< NUM_SUITS
-1)
803 /*NOTE: one can't highlight the stock due to me being too lazy to implement it*/
804 void cursor_left (struct cursor
* cursor
) {
806 if (cursor
->pile
> 0) cursor
->pile
--;
809 void cursor_down (struct cursor
* cursor
) {
811 int first
= first_movable(f
.t
[cursor
->pile
]);
812 int top
= find_top(f
.t
[cursor
->pile
]);
813 if (first
+ cursor
->opt
< top
)
816 void cursor_up (struct cursor
* cursor
) {
818 if (cursor
->opt
> 0) cursor
->opt
--;
820 void cursor_right (struct cursor
* cursor
) {
822 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
825 #elif defined FREECELL
826 void cursor_left (struct cursor
* cursor
) {
828 if (is_tableu(cursor
->pile
)) {
829 if (cursor
->pile
> 0) cursor
->pile
--;
831 } else { /* cells/foundation*/
832 switch (cursor
->pile
) {
838 if (cursor
->opt
<= 0) {
839 cursor
->pile
= STOCK
;
847 void cursor_down (struct cursor
* cursor
) {
849 if (is_tableu(cursor
->pile
)) {
850 if (cursor
->opt
< max_move(cursor
->pile
, -1)-1)
853 cursor
->pile
= cursor
->opt
+NUM_CELLS
*(cursor
->pile
==FOUNDATION
);
857 void cursor_up (struct cursor
* cursor
) {
859 if (is_tableu(cursor
->pile
)) {
860 if (cursor
->opt
> 0) {
863 switch (cursor
->pile
) {
864 case TAB_1
: case TAB_2
: case TAB_3
: case TAB_4
:
865 cursor
->opt
= cursor
->pile
; /*assumes TAB_1==0*/
866 cursor
->pile
= STOCK
;
868 case TAB_5
: case TAB_6
: case TAB_7
: case TAB_8
:
869 cursor
->opt
= cursor
->pile
- NUM_CELLS
;
870 cursor
->pile
= FOUNDATION
;
875 void cursor_right (struct cursor
* cursor
) {
877 if (is_tableu(cursor
->pile
)) {
878 if (cursor
->pile
< TAB_MAX
) cursor
->pile
++;
881 switch (cursor
->pile
) {
883 if (cursor
->opt
< NUM_SUITS
-1) {
886 cursor
->pile
= FOUNDATION
;
890 if (cursor
->opt
< NUM_SUITS
-1)
896 void cursor_to (struct cursor
* cursor
, int pile
) {
901 int set_mouse(int pile
, int* main
, int* opt
) {
902 //TODO: this should set cursor.opt, so card selector choice dialog does not trigger!
904 if (pile
< 0) return 1;
907 if (pile
>= FOUNDATION
)//TODO: check upper bound!
909 *opt
= pile
- FOUNDATION
;
912 #elif defined FREECELL
913 if (pile
> TAB_MAX
) {
914 *main
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
915 *opt
= (pile
-STOCK
) % 4;
921 int get_cmd (int* from
, int* to
, int* opt
) {
923 unsigned char mouse
[6] = {0}; /* must clear [3]! */
924 struct cursor inactive
= {-1,-1};
925 static struct cursor active
= {0,0};
926 if (is_tableu(active
.pile
))
930 from_l
: print_table(&active
, &inactive
);
934 /* direct addressing: */
935 case '1': *from
= TAB_1
; break;
936 case '2': *from
= TAB_2
; break;
937 case '3': *from
= TAB_3
; break;
938 case '4': *from
= TAB_4
; break;
939 case '5': *from
= TAB_5
; break;
940 case '6': *from
= TAB_6
; break;
941 case '7': *from
= TAB_7
; break;
943 case '8': *from
= TAB_8
; break;
944 case '9': *from
= TAB_9
; break;
945 case '0': *from
= TAB_10
;break;
946 #elif defined FREECELL
947 case '8': *from
= TAB_8
; break;
948 case '9': *from
= STOCK
; break;
949 case '0': *from
= FOUNDATION
; break;
950 #elif defined KLONDIKE
951 case '9': *from
= WASTE
; break;
952 case '0': *from
= FOUNDATION
; break;
953 case '8': /* fallthrough */
956 case '\n': *from
= STOCK
; break;
958 /* cursor keys addressing: */
960 case 'h': cursor_left (&active
); goto from_l
;
962 case 'j': cursor_down (&active
); goto from_l
;
964 case 'k': cursor_up (&active
); goto from_l
;
966 case 'l': cursor_right(&active
); goto from_l
;
968 case 'H': cursor_to(&active
,TAB_1
); goto from_l
; /* leftmost tableu */
970 case 'L': cursor_to(&active
,TAB_MAX
);goto from_l
; /* rigthmost tableu */
972 case 'M': cursor_to(&active
,TAB_MAX
/2); goto from_l
; /* center tableu */
973 case ' ': /* continue with second cursor */
976 *opt
= active
.opt
; /* when FOUNDATION */
980 /* mouse addressing: */
981 case MOUSE_MIDDLE
: return CMD_NONE
;
983 if (set_mouse(term2pile(mouse
), to
, opt
))
987 if (set_mouse(term2pile(mouse
), from
, opt
))
989 if (!is_tableu(*from
))
990 inactive
.opt
= *opt
; /* prevents card selector dialog */
995 fprintf (stderr
, ":");
996 raw_mode(0); /* turn on echo */
997 fgets (buf
, 256, stdin
);
1000 case 'q': return CMD_QUIT
;
1001 case 'n': return CMD_NEW
;
1002 case 'r': return CMD_AGAIN
;
1003 case 'h': return CMD_HELP
;
1004 default: return CMD_INVAL
;
1009 case 'K': /* fallthrough */
1010 case '?': return CMD_HINT
;
1011 case 'u': return CMD_UNDO
;
1012 case 002: return CMD_NONE
; /* sent by SIGWINCH */
1013 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1014 default: return CMD_INVAL
;
1016 inactive
.pile
= *from
; /* for direct addressing highlighting */
1017 if (is_tableu(*from
) && f
.t
[*from
][0] == NO_CARD
) return CMD_INVAL
;
1018 //TODO: freecell: if from==stock && stock[x] == empty: return inval
1021 if (*from
== STOCK
) {
1028 to_l
: print_table(&active
, &inactive
);
1033 case 'h': cursor_left (&active
); goto to_l
;
1035 case 'j': cursor_down (&active
); goto to_l
;
1037 case 'k': cursor_up (&active
); goto to_l
;
1039 case 'l': cursor_right(&active
); goto to_l
;
1041 case 'H': cursor_to(&active
,TAB_1
); goto to_l
;
1043 case 'L': cursor_to(&active
,TAB_MAX
); goto to_l
;
1045 case 'M': cursor_to(&active
,TAB_MAX
/2); goto to_l
;
1046 case 'J': /* fallthrough; just join selected pile */
1049 break; /* continues with the foundation/empty tableu check */
1051 case MOUSE_RIGHT
: return CMD_NONE
;
1053 if (set_mouse(term2pile(mouse
), to
, opt
))
1056 case 'K': /* fallthrough */
1057 case '?': return CMD_HINT
;
1058 case 'u': return CMD_NONE
; /* cancel selection */
1059 case EOF
: return CMD_NONE
; /* sent by SIGCONT */
1061 if (t
< '0' || t
> '9') return CMD_INVAL
;
1065 #elif defined SPIDER
1067 #elif defined FREECELL
1077 /* direct addressing post-processing stage:
1078 because foundations/freecells share the same key (and you can't select
1079 partial piles) there are sometimes ambiguous situations where it isn't
1080 clear from which pile (or how many cards) to take. the code below will
1081 only ask the user if there are at least two possible moves and
1082 automatically choose otherwise. */
1084 /* if it was selected with a cursor, it's obvious: */
1085 if (inactive
.opt
>= 0) {
1086 if (is_tableu(*from
)) {
1087 /* NOTE: max_move same as in cursor_down() */
1088 *opt
= max_move(*from
, -1) - inactive
.opt
;
1090 *opt
= inactive
.opt
;
1092 /* moving from tableu to empty tableu? */
1093 } else if(is_tableu(*from
) && is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
){
1094 int top
= find_top(f
.t
[*from
]);
1095 int max
= max_move(*from
, *to
);
1097 if (top
< 0) return CMD_INVAL
;
1098 if (max
== 1) { /* only 1 movable? */
1099 return *opt
= 1, CMD_MOVE
;
1100 } else { /* only ask the user if it's unclear: */
1101 int bottom
= top
- (max
-1);
1102 printf ("\rup to ([a23456789xjqk] or space/return): ");
1105 case ' ': rank
= get_rank(f
.t
[*from
][top
]); break;
1106 case'\n': rank
= get_rank(f
.t
[*from
][bottom
]); break;
1107 case 'a': case 'A': rank
= RANK_A
; break;
1108 case '0': /* fallthrough */
1109 case 'x': case 'X': rank
= RANK_X
; break;
1110 case 'j': case 'J': rank
= RANK_J
; break;
1111 case 'q': case 'Q': rank
= RANK_Q
; break;
1112 case 'k': case 'K': rank
= RANK_K
; break;
1113 default: rank
-= '1';
1115 if (rank
< RANK_A
|| rank
> RANK_K
) return CMD_INVAL
;
1117 for (int i
= 0; max
--; i
++)
1118 if (get_rank(f
.t
[*from
][top
-i
]) == rank
)
1119 return *opt
= 1+i
, CMD_MOVE
;
1123 /* `opt` is the number of cards to move */
1124 /* moving between stock/foundation? */
1125 } else if (*from
== FOUNDATION
&& *to
== FOUNDATION
) {
1126 return CMD_INVAL
; /* nonsensical */
1127 } else if (*from
== FOUNDATION
&& *to
== STOCK
) {
1128 if (f
.w
== (1<<NUM_CELLS
)-1) return CMD_INVAL
; /*no free cells*/
1129 int ok_foundation
; /* find compatible (non-empty) foundations:*/
1130 int used_fs
=0; for (int i
= 0; i
< NUM_SUITS
; i
++)
1131 if (!!f
.f
[i
][0]) ok_foundation
= i
, used_fs
++;
1133 if (used_fs
== 0) return CMD_INVAL
; /* nowhere to take from */
1134 if (used_fs
== 1) { /* take from the only one */
1135 return *opt
= ok_foundation
, CMD_MOVE
;
1136 } else { /* ask user */
1137 printf ("take from (1-4): "); fflush (stdout
);
1138 *opt
= getch(NULL
) - '1';
1139 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1141 /* `opt` is the foundation index (0..3) */
1142 } else if (*from
== STOCK
) { /* cell -> foundation/tableu */
1143 if (!f
.w
) return CMD_INVAL
; /* no cell to take from */
1144 int ok_cell
; /* find compatible (non-empty) cells: */
1145 int tab
= is_tableu(*to
);
1146 int used_cs
=0; for (int i
= 0; i
< NUM_CELLS
; i
++) {
1147 card_t
* pile
= (tab
?f
.t
[*to
]:f
.f
[get_suit(f
.s
[i
])]);
1148 int top_to
= find_top(pile
);
1149 if (tab
? /* to tableu? */
1151 ||(top_to
>=0 && rank_next(f
.s
[i
], pile
[top_to
])
1152 && color_ok(f
.s
[i
], pile
[top_to
])))
1153 : /* to foundation? */
1154 ((top_to
<0 && get_rank(f
.s
[i
]) == RANK_A
)
1155 ||(top_to
>=0 && rank_next(pile
[top_to
],f
.s
[i
])))
1157 ok_cell
= i
, used_cs
++;
1160 if (used_cs
== 0) return CMD_INVAL
; /* nowhere to take from */
1161 if (used_cs
== 1) { /* take from the only one */
1162 return *opt
= ok_cell
, CMD_MOVE
;
1163 } else { /* ask user */
1164 printf ("take from (1-4): "); fflush (stdout
);
1165 *opt
= getch(NULL
) - '1';
1166 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1168 /* `opt` is the cell index (0..3) */
1171 //TODO: mouse-friendly "up to?" dialog
1172 #if defined KLONDIKE || defined FREECELL
1173 if (*from
== FOUNDATION
) {
1174 if (inactive
.opt
>= 0) {
1175 *opt
= inactive
.opt
;
1178 int top
= find_top(f
.t
[*to
]);
1179 if (top
< 0) return CMD_INVAL
;
1180 int color
= get_color(f
.t
[*to
][top
]);
1181 int choice_1
= 1-color
; /* selects piles of */
1182 int choice_2
= 2+color
; /* the opposite color */
1183 int top_c1
= find_top(f
.f
[choice_1
]);
1184 int top_c2
= find_top(f
.f
[choice_2
]);
1186 switch ((rank_next(f
.f
[choice_1
][top_c1
], f
.t
[*to
][top
])
1187 && top_c1
>= 0 ) << 0
1188 |(rank_next(f
.f
[choice_2
][top_c2
], f
.t
[*to
][top
])
1189 && top_c2
>= 0 ) << 1) {
1190 case ( 1<<0): *opt
= choice_1
; break; /* choice_1 only */
1191 case (1<<1 ): *opt
= choice_2
; break; /* choice_2 only */
1192 case (1<<1 | 1<<0): /* both, ask user which to pick from */
1193 printf ("take from (1-4): "); fflush (stdout
);
1194 *opt
= getch(NULL
) - '1';
1195 if (*opt
< 0 || *opt
> 3) return CMD_INVAL
;
1197 default: return CMD_INVAL
; /* none matched */
1199 /* `opt` is the foundation index (0..3) */
1201 #elif defined SPIDER
1202 /* moving to empty tableu? */
1203 if (is_tableu(*to
) && f
.t
[*to
][0] == NO_CARD
) {
1204 int bottom
= first_movable(f
.t
[*from
]);
1205 if (inactive
.opt
>= 0) { /*if from was cursor addressed: */
1206 *opt
= get_rank(f
.t
[*from
][bottom
+ inactive
.opt
]);
1209 int top
= find_top(f
.t
[*from
]);
1210 if (top
< 0) return CMD_INVAL
;
1211 if (top
>= 0 && !is_movable(f
.t
[*from
], top
-1)) {
1212 *opt
= get_rank(f
.t
[*from
][top
]);
1213 } else { /* only ask the user if it's unclear: */
1214 printf ("\rup to ([a23456789xjqk] or space/return): ");
1217 case ' ': *opt
= get_rank(f
.t
[*from
][top
]); break;
1218 case'\n': *opt
= get_rank(f
.t
[*from
][bottom
]); break;
1219 case 'a': case 'A': *opt
= RANK_A
; break;
1220 case '0': /* fallthrough */
1221 case 'x': case 'X': *opt
= RANK_X
; break;
1222 case 'j': case 'J': *opt
= RANK_J
; break;
1223 case 'q': case 'Q': *opt
= RANK_Q
; break;
1224 case 'k': case 'K': *opt
= RANK_K
; break;
1225 default: *opt
-= '1';
1227 if (*opt
< RANK_A
|| *opt
> RANK_K
) return CMD_INVAL
;
1229 /* `opt` is the rank of the highest card to move */
1235 int getctrlseq(unsigned char* buf
) {
1243 int offset
= 0x20; /* never sends control chars as data */
1244 while ((c
= getchar()) != EOF
) {
1248 case '\033': state
=ESC_SENT
; break;
1254 case '[': state
=CSI_SENT
; break;
1255 default: return KEY_INVAL
;
1260 case 'A': return KEY_UP
;
1261 case 'B': return KEY_DOWN
;
1262 case 'C': return KEY_RIGHT
;
1263 case 'D': return KEY_LEFT
;
1264 /*NOTE: home/end send ^[[x~ . no support for modifiers*/
1265 case 'H': return KEY_HOME
;
1266 case 'F': return KEY_END
;
1267 case '2': getchar(); return KEY_INS
;
1268 case '5': getchar(); return KEY_PGUP
;
1269 case '6': getchar(); return KEY_PGDN
;
1270 case 'M': state
=MOUSE_EVENT
; break;
1271 default: return KEY_INVAL
;
1275 if (buf
== NULL
) return KEY_INVAL
;
1276 buf
[0] = c
- offset
;
1277 buf
[1] = getchar() - offset
;
1278 buf
[2] = getchar() - offset
;
1286 int term2pile(unsigned char *mouse
) {
1287 int line
= (mouse
[2]-1);
1288 int column
= (mouse
[1]-1) / op
.s
->width
;
1290 if (line
< op
.s
->height
) { /* first line */
1293 case 0: return STOCK
;
1294 case 1: return WASTE
;
1295 case 2: return -1; /* spacer */
1296 case 3: return FOUNDATION
+0;
1297 case 4: return FOUNDATION
+1;
1298 case 5: return FOUNDATION
+2;
1299 case 6: return FOUNDATION
+3;
1301 #elif defined SPIDER
1302 if (column
< 3) return STOCK
;
1304 #elif defined FREECELL
1305 if (column
< NUM_SUITS
+ NUM_CELLS
) return STOCK
+column
;
1308 } else if (line
> op
.s
->height
) { /* tableu */
1309 if (column
<= TAB_MAX
) return column
;
1313 int wait_mouse_up(unsigned char* mouse
) {
1314 //TODO: mouse drag: start gets inactive, hovering gets active cursors
1315 struct cursor cur
= {-1,-1};
1317 /* note: if dragged [3]==1 and second position is in mouse[0,4,5] */
1319 /* display a cursor while mouse button is pushed: */
1320 int pile
= term2pile(mouse
);
1323 if (pile
>= FOUNDATION
) {
1324 cur
.pile
= FOUNDATION
;
1325 cur
.opt
= pile
-FOUNDATION
;
1327 #elif defined FREECELL
1328 if (pile
> TAB_MAX
) {
1329 cur
.pile
= pile
-STOCK
< NUM_CELLS
? STOCK
: FOUNDATION
;
1330 cur
.opt
= (pile
-STOCK
) % 4;
1333 /* need to temporarily show the cursor, then revert to last state: */
1334 int old_show_cursor_hi
= op
.h
; //TODO: ARGH! that's awful!
1336 print_table(&cur
, NO_HI
); //TODO: should not overwrite inactive cursor!
1337 op
.h
= old_show_cursor_hi
;
1340 if (getctrlseq (mouse
+3) == MOUSE_ANY
) {
1341 /* ignore mouse wheel events: */
1342 if (mouse
[3] & 0x40) continue;
1344 else if((mouse
[3]&3) == 3) level
--; /* release event */
1345 else level
++; /* another button pressed */
1349 int success
= mouse
[1] == mouse
[4] && mouse
[2] == mouse
[5];
1356 int getch(unsigned char* buf
) {
1357 //TODO: if buf==NULL disable mouse input
1358 /* returns a character, EOF, or constant for an escape/control sequence - NOT
1359 compatible with the ncurses implementation of same name */
1361 if (buf
&& buf
[3]) {
1362 /* mouse was dragged; return 'ungetted' previous destination */
1363 action
= MOUSE_DRAG
;
1364 /* keep original [0], as [3] only contains release event */
1369 action
= getctrlseq(buf
);
1374 if (buf
[0] > 3) break; /* ignore wheel events */
1379 case 0: return MOUSE_LEFT
;
1380 case 1: return MOUSE_MIDDLE
;
1381 case 2: return MOUSE_RIGHT
;
1389 // shuffling and dealing {{{
1390 void deal(long seed
) {
1391 f
= (const struct playfield
){0}; /* clear playfield */
1392 card_t deck
[DECK_SIZE
*NUM_DECKS
];
1393 int avail
= DECK_SIZE
*NUM_DECKS
;
1394 for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) deck
[i
] = (i
%DECK_SIZE
)+1;
1396 if (op
.m
!= NORMAL
) for (int i
= 0; i
< DECK_SIZE
*NUM_DECKS
; i
++) {
1397 if (op
.m
== MEDIUM
) deck
[i
] = 1+((deck
[i
]-1) | 2);
1398 if (op
.m
== EASY
) deck
[i
] = 1+((deck
[i
]-1) | 2 | 1);
1399 /* the 1+ -1 dance gets rid of the offset created by NO_CARD */
1403 for (int i
= DECK_SIZE
*NUM_DECKS
-1; i
> 0; i
--) { /* fisher-yates */
1404 int j
= rand() % (i
+1);
1405 if (j
-i
) deck
[i
]^=deck
[j
],deck
[j
]^=deck
[i
],deck
[i
]^=deck
[j
];
1409 for (int i
= 0; i
< NUM_PILES
; i
++) {
1412 int count
= i
; /* pile n has n closed cards, then 1 open */
1413 #elif defined SPIDER
1415 int count
= i
<4?5:4; /* pile 1-4 have 5, 5-10 have 4 closed */
1416 #elif defined FREECELL
1418 int count
= i
<4?6:5;/*like spider, but cards are dealt face-up*/
1420 /* "SIGN": face down cards are negated */
1421 for (int j
= 0; j
< count
; j
++) f
.t
[i
][j
] = SIGN deck
[--avail
];
1422 f
.t
[i
][count
] = deck
[--avail
]; /* the face-up card */
1425 /* rest of the cards to the stock: */
1426 /* NOTE: assert(avail==50) for spider, assert(avail==0) for freecell */
1427 for (f
.z
= 0; avail
; f
.z
++) f
.s
[f
.z
] = deck
[--avail
];
1429 f
.w
= -1; /* @start: nothing on waste */
1430 #elif defined SPIDER
1431 f
.w
= 0; /* number of used foundations */
1432 #elif defined FREECELL
1433 f
.w
= 0; /* bitmask of used free cells */
1436 f
.u
= &undo_sentinel
;
1440 // screen drawing routines {{{
1441 void print_hi(int invert
, int grey_bg
, int bold
, char* str
) {
1442 if (!op
.h
) invert
= 0; /* don't show invert if we used the mouse last */
1443 if (bold
&& op
.s
== &unicode_large_color
){ //awful hack for bold + faint
1444 int offset
= str
[3]==017?16:str
[4]==017?17:0;
1445 printf ("%s%s%s""%.*s%s%s""%s%s%s",
1446 "\033[1m", invert
?"\033[7m":"", grey_bg
?"\033[100m":"",
1447 offset
, str
, bold
?"\033[1m":"", str
+offset
,
1448 grey_bg
?"\033[49m":"", invert
?"\033[27m":"","\033[22m");
1451 printf ("%s%s%s%s%s%s%s",
1452 bold
?"\033[1m":"", invert
?"\033[7m":"", grey_bg
?"\033[100m":"",
1454 grey_bg
?"\033[49m":"", invert
?"\033[27m":"",bold
?"\033[22m":"");
1456 void print_table(const struct cursor
* active
, const struct cursor
* inactive
) {
1457 printf("\033[2J\033[H"); /* clear screen, reset cursor */
1459 /* print stock, waste and foundation: */
1460 for (int line
= 0; line
< op
.s
->height
; line
++) {
1462 print_hi (active
->pile
== STOCK
, inactive
->pile
== STOCK
, 1, (
1463 (f
.w
< f
.z
-1)?op
.s
->facedown
1464 :op
.s
->placeholder
)[line
]);
1466 print_hi (active
->pile
== WASTE
, inactive
->pile
== WASTE
, 1, (
1467 /* NOTE: cast, because f.w sometimes is (short)-1 !? */
1468 ((short)f
.w
>= 0)?op
.s
->card
[f
.s
[f
.w
]]
1469 :op
.s
->placeholder
)[line
]);
1470 printf ("%s", op
.s
->card
[NO_CARD
][line
]); /* spacer */
1472 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1473 int card
= find_top(f
.f
[pile
]);
1474 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1475 inactive
->pile
==FOUNDATION
&& (
1476 /* cursor addr. || direct addr. */
1477 inactive
->opt
==pile
|| inactive
->opt
< 0
1479 (card
< 0)?op
.s
->foundation
[line
]
1480 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1485 #elif defined SPIDER
1486 int fdone
; for (fdone
= NUM_DECKS
*NUM_SUITS
; fdone
; fdone
--)
1487 if (f
.f
[fdone
-1][RANK_K
]) break; /*number of completed stacks*/
1488 int spacer_from
= f
.z
?(f
.z
/10-1) * op
.s
->halfwidth
[0] + op
.s
->width
:0;
1489 int spacer_to
= NUM_PILES
*op
.s
->width
-
1490 ((fdone
?(fdone
-1) * op
.s
->halfwidth
[1]:0)+op
.s
->width
);
1491 for (int line
= 0; line
< op
.s
->height
; line
++) {
1492 /* available stock: */
1493 for (int i
= f
.z
/10; i
; i
--) {
1494 if (i
==1) printf ("%s", op
.s
->facedown
[line
]);
1495 else printf ("%s", op
.s
->halfstack
[line
]);
1498 for (int i
= spacer_from
; i
< spacer_to
; i
++) printf (" ");
1499 /* foundation (overlapping): */
1500 for (int i
= NUM_DECKS
*NUM_SUITS
-1, half
= 0; i
>= 0; i
--) {
1501 int overlap
= half
? op
.s
->halfcard
[line
]: 0;
1502 if (f
.f
[i
][RANK_K
]) printf ("%.*s", op
.s
->halfwidth
[2],
1503 op
.s
->card
[f
.f
[i
][RANK_K
]][line
]+overlap
),
1509 #elif defined FREECELL
1510 /* print open cells, foundation: */
1511 for (int line
= 0; line
< op
.s
->height
; line
++) {
1512 for (int pile
= 0; pile
< NUM_CELLS
; pile
++)
1513 print_hi (active
->pile
==STOCK
&& active
->opt
==pile
,
1514 inactive
->pile
==STOCK
&& (
1515 /* cursor addr. || direct addr. */
1516 inactive
->opt
==pile
|| inactive
->opt
< 0
1518 ((f
.s
[pile
])?op
.s
->card
[f
.s
[pile
]]
1519 :op
.s
->placeholder
)[line
]);
1520 for (int pile
= 0; pile
< NUM_SUITS
; pile
++) {
1521 int card
= find_top(f
.f
[pile
]);
1522 print_hi (active
->pile
==FOUNDATION
&& active
->opt
==pile
,
1523 inactive
->pile
==FOUNDATION
&& (
1524 /* cursor addr. || direct addr. */
1525 inactive
->opt
==pile
|| inactive
->opt
< 0
1527 (card
< 0)?op
.s
->foundation
[line
]
1528 :op
.s
->card
[f
.f
[pile
][card
]][line
]);
1535 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty))
1536 #define TOP_HI(c) 1 /* can't select partial stacks in KLONDIKE */
1537 #elif defined SPIDER || defined FREECELL
1538 int offset
[NUM_PILES
]={0}; /* first card to highlight */
1540 int bottom
[NUM_PILES
]; /* first movable card */
1541 for (int i
=0; i
<NUM_PILES
; i
++)
1542 bottom
[i
] = find_top(f
.t
[i
]) - max_move(i
,-1);
1544 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty) \
1545 && offset[pile] >= cursor->opt)
1546 #define TOP_HI(cursor) (cursor->pile == pile && movable \
1547 && offset[pile] == cursor->opt)
1549 /* print tableu piles: */
1550 int row
[NUM_PILES
] = {0};
1551 int line
[NUM_PILES
]= {0};
1552 int label
[NUM_PILES
]={0};
1554 int did_placeholders
= 0;
1557 for (int pile
= 0; pile
< NUM_PILES
; pile
++) {
1558 card_t card
= f
.t
[pile
][row
[pile
]];
1559 card_t next
= f
.t
[pile
][row
[pile
]+1];
1560 int movable
= is_movable(f
.t
[pile
], row
[pile
]);
1562 if(row
[pile
] <= bottom
[pile
]) movable
= 0;
1564 int empty
= !card
&& row
[pile
] == 0;
1566 print_hi (DO_HI(active
), DO_HI(inactive
), movable
, (
1567 (!card
&& row
[pile
] == 0)?op
.s
->placeholder
1568 :(card
<0)?op
.s
->facedown
1572 int extreme_overlap
= ( 3 /* spacer, labels, status */
1573 + 2 * op
.s
->height
/* stock, top tableu card */
1574 + find_top(f
.t
[pile
]) * op
.s
->overlap
) >op
.w
[0];
1575 /* normal overlap: */
1576 if (++line
[pile
] >= (next
?op
.s
->overlap
:op
.s
->height
)
1577 /* extreme overlap on closed cards: */
1578 || (extreme_overlap
&&
1580 f
.t
[pile
][row
[pile
]] < 0 &&
1581 f
.t
[pile
][row
[pile
]+1] <0)
1582 /* extreme overlap on sequences: */
1583 || (extreme_overlap
&&
1584 !TOP_HI(active
) && /*always show top selected card*/
1585 line
[pile
] >= 1 && row
[pile
] > 0 &&
1586 f
.t
[pile
][row
[pile
]-1] > NO_CARD
&&
1587 is_consecutive (f
.t
[pile
], row
[pile
]) &&
1588 is_consecutive (f
.t
[pile
], row
[pile
]-1) &&
1589 f
.t
[pile
][row
[pile
]+1] != NO_CARD
)
1593 #if defined SPIDER || defined FREECELL
1594 if (movable
) offset
[pile
]++;
1597 /* tableu labels: */
1598 if(!card
&& !label
[pile
] && row
[pile
]>0&&line
[pile
]>0) {
1600 printf ("\b\b%d ", (pile
+1) % 10); //XXX: hack
1602 line_had_card
|= !!card
;
1603 did_placeholders
|= row
[pile
] > 0;
1606 } while (line_had_card
|| !did_placeholders
);
1609 void visbell (void) {
1611 printf ("\033[?5h"); fflush (stdout
);
1613 printf ("\033[?5l"); fflush (stdout
);
1615 void win_anim(void) {
1616 printf ("\033[?25l"); /* hide cursor */
1618 /* set cursor to random location */
1619 int row
= 1+rand()%(1+op
.w
[0]-op
.s
->height
);
1620 int col
= 1+rand()%(1+op
.w
[1]-op
.s
->width
);
1622 /* draw random card */
1623 int face
= 1 + rand() % 52;
1624 for (int l
= 0; l
< op
.s
->height
; l
++) {
1625 printf ("\033[%d;%dH", row
+l
, col
);
1626 printf ("%s", op
.s
->card
[face
][l
]);
1630 /* exit on keypress */
1631 struct pollfd p
= {STDIN_FILENO
, POLLIN
, 0};
1632 if (poll (&p
, 1, 80)) goto fin
;
1635 printf ("\033[?25h"); /* show cursor */
1641 void undo_push (int _f
, int t
, int n
, int o
) {
1642 struct undo
* new = malloc(sizeof(struct undo
));
1652 void undo_pop (struct undo
* u
) {
1653 if (u
== &undo_sentinel
) return;
1656 if (u
->f
== FOUNDATION
) {
1657 /* foundation -> tableu */
1658 int top_f
= find_top(f
.f
[u
->n
]);
1659 int top_t
= find_top(f
.t
[u
->t
]);
1660 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1661 f
.t
[u
->t
][top_t
] = NO_CARD
;
1662 } else if (u
->f
== WASTE
&& u
->t
== FOUNDATION
) {
1663 /* waste -> foundation */
1664 /* split u->n into wst and fnd: */
1665 int wst
= u
->n
& 0xffff;
1666 int fnd
= u
->n
>> 16;
1667 /* move stock cards one position up to make room: */
1668 for (int i
= f
.z
; i
>= wst
; i
--) f
.s
[i
+1] = f
.s
[i
];
1669 /* move one card from foundation to waste: */
1670 int top
= find_top(f
.f
[fnd
]);
1671 f
.s
[wst
] = f
.f
[fnd
][top
];
1672 f
.f
[fnd
][top
] = NO_CARD
;
1675 } else if (u
->f
== WASTE
) {
1676 /* waste -> tableu */
1677 /* move stock cards one position up to make room: */
1678 for (int i
= f
.z
-1; i
>= u
->n
; i
--) f
.s
[i
+1] = f
.s
[i
];
1679 /* move one card from tableu to waste: */
1680 int top
= find_top(f
.t
[u
->t
]);
1681 f
.s
[u
->n
] = f
.t
[u
->t
][top
];
1682 f
.t
[u
->t
][top
] = NO_CARD
;
1685 } else if (u
->t
== FOUNDATION
) {
1686 /* tableu -> foundation */
1687 int top_f
= find_top(f
.t
[u
->f
]);
1688 int top_t
= find_top(f
.f
[u
->n
]);
1689 /* close topcard if previous action caused turn_over(): */
1690 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1691 /* move one card from foundation to tableu: */
1692 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1693 f
.f
[u
->n
][top_t
] = NO_CARD
;
1695 /* tableu -> tableu */
1696 int top_f
= find_top(f
.t
[u
->f
]);
1697 int top_t
= find_top(f
.t
[u
->t
]);
1698 /* close topcard if previous action caused turn_over(): */
1699 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1700 /* move n cards from tableu[f] to tableu[t]: */
1701 for (int i
= 0; i
< u
->n
; i
++) {
1702 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1703 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1706 #elif defined SPIDER
1707 if (u
->f
== STOCK
) {
1708 /* stock -> tableu */
1709 /*remove a card from each pile and put it back onto the stock:*/
1710 for (int pile
= NUM_PILES
-1; pile
>= 0; pile
--) {
1711 int top
= find_top(f
.t
[pile
]);
1712 f
.s
[f
.z
++] = f
.t
[pile
][top
];
1713 f
.t
[pile
][top
] = NO_CARD
;
1715 } else if (u
->t
== FOUNDATION
) {
1716 /* tableu -> foundation */
1717 int top
= find_top(f
.t
[u
->f
]);
1718 /* close topcard if previous action caused turn_over(): */
1719 if (u
->o
) f
.t
[u
->f
][top
] *= -1;
1720 /* append cards from foundation to tableu */
1721 for (int i
= RANK_K
; i
>= RANK_A
; i
--) {
1722 f
.t
[u
->f
][++top
] = f
.f
[u
->n
][i
];
1723 f
.f
[u
->n
][i
] = NO_CARD
;
1725 f
.w
--; /* decrement complete-foundation-counter */
1728 /* tableu -> tableu */
1729 int top_f
= find_top(f
.t
[u
->f
]);
1730 int top_t
= find_top(f
.t
[u
->t
]);
1731 /* close topcard if previous action caused turn_over(): */
1732 if (u
->o
) f
.t
[u
->f
][top_f
] *= -1;
1733 /* move n cards from tableu[f] to tableu[t]: */
1734 for (int i
= 0; i
< u
->n
; i
++) {
1735 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1736 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1739 #elif defined FREECELL
1740 /*NOTE: if from and to are both stock/foundation, opt = from | to<<16 */
1741 if (u
->f
== STOCK
&& u
->t
== FOUNDATION
) {
1742 /* free cells -> foundation */
1743 /* split u->n into cll and fnd: */
1744 int cll
= u
->n
& 0xffff;
1745 int fnd
= u
->n
>> 16;
1746 /* move one card from foundation to free cell: */
1747 int top
= find_top(f
.f
[fnd
]);
1748 f
.s
[cll
] = f
.f
[fnd
][top
];
1749 f
.f
[fnd
][top
] = NO_CARD
;
1750 f
.w
|= 1<<cll
; /* mark cell as occupied */
1751 } else if (u
->f
== STOCK
) {
1752 /* free cells -> cascade */
1753 int top_t
= find_top(f
.t
[u
->t
]);
1754 f
.s
[u
->n
] = f
.t
[u
->t
][top_t
];
1755 f
.t
[u
->t
][top_t
] = NO_CARD
;
1756 f
.w
|= 1<<u
->n
; /* mark cell as occupied */
1757 } else if (u
->f
== FOUNDATION
&& u
->t
== STOCK
) {
1758 /* foundation -> free cells */
1759 /* split u->n into cll and fnd: */
1760 int cll
= u
->n
>> 16;
1761 int fnd
= u
->n
& 0xffff;
1762 /* move 1 card from free cell to foundation: */
1763 int top_f
= find_top(f
.f
[fnd
]);
1764 f
.f
[fnd
][top_f
+1] = f
.s
[cll
];
1766 f
.w
&= ~(1<<cll
); /* mark cell as free */
1767 } else if (u
->f
== FOUNDATION
) {
1768 /* foundation -> cascade */
1769 int top_f
= find_top(f
.f
[u
->n
]);
1770 int top_t
= find_top(f
.t
[u
->t
]);
1771 f
.f
[u
->n
][top_f
+1] = f
.t
[u
->t
][top_t
];
1772 f
.t
[u
->t
][top_t
] = NO_CARD
;
1773 } else if (u
->t
== STOCK
) {
1774 /* cascade -> free cells */
1775 int top_f
= find_top(f
.t
[u
->f
]);
1776 f
.t
[u
->f
][top_f
+1] = f
.s
[u
->n
];
1777 f
.s
[u
->n
] = NO_CARD
;
1778 f
.w
&= ~(1<<u
->n
); /* mark cell as free */
1779 } else if (u
->t
== FOUNDATION
) {
1780 /* cascade -> foundation */
1781 int top_f
= find_top(f
.t
[u
->f
]);
1782 int top_t
= find_top(f
.f
[u
->n
]);
1783 /* move one card from foundation to cascade: */
1784 f
.t
[u
->f
][top_f
+1] = f
.f
[u
->n
][top_t
];
1785 f
.f
[u
->n
][top_t
] = NO_CARD
;
1787 /* cascade -> cascade */
1788 int top_f
= find_top(f
.t
[u
->f
]);
1789 int top_t
= find_top(f
.t
[u
->t
]);
1790 /* move n cards from tableu[f] to tableu[t]: */
1791 for (int i
= 0; i
< u
->n
; i
++) {
1792 f
.t
[u
->f
][top_f
+u
->n
-i
] = f
.t
[u
->t
][top_t
-i
];
1793 f
.t
[u
->t
][top_t
-i
] = NO_CARD
;
1802 void free_undo (struct undo
* u
) {
1803 while (u
&& u
!= &undo_sentinel
) {
1811 // initialization stuff {{{
1812 void screen_setup (int enable
) {
1815 printf ("\033[s\033[?47h"); /* save cursor, alternate screen */
1816 printf ("\033[H\033[J"); /* reset cursor, clear screen */
1817 printf ("\033[?1000h"); /* enable mouse */
1819 printf ("\033[?1000l"); /* disable mouse */
1820 printf ("\033[?47l\033[u"); /* primary screen, restore cursor */
1825 void raw_mode(int enable
) {
1826 static struct termios saved_term_mode
;
1827 struct termios raw_term_mode
;
1830 if (saved_term_mode
.c_lflag
== 0)/*don't overwrite stored mode*/
1831 tcgetattr(STDIN_FILENO
, &saved_term_mode
);
1832 raw_term_mode
= saved_term_mode
;
1833 raw_term_mode
.c_lflag
&= ~(ICANON
| ECHO
);
1834 raw_term_mode
.c_cc
[VMIN
] = 1 ;
1835 raw_term_mode
.c_cc
[VTIME
] = 0;
1836 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw_term_mode
);
1838 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &saved_term_mode
);
1842 void signal_handler (int signum
) {
1847 signal(SIGTSTP
, SIG_DFL
); /* NOTE: assumes SysV semantics! */
1852 print_table(NO_HI
, NO_HI
);
1854 case SIGINT
: //TODO: don't exit; just warn like vim does
1857 ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &w
);
1863 void signal_setup(void) {
1864 struct sigaction saction
;
1866 saction
.sa_handler
= signal_handler
;
1867 sigemptyset(&saction
.sa_mask
);
1868 saction
.sa_flags
= 0;
1869 if (sigaction(SIGTSTP
, &saction
, NULL
) < 0) {
1873 if (sigaction(SIGCONT
, &saction
, NULL
) < 0) {
1877 if (sigaction(SIGINT
, &saction
, NULL
) < 0) {
1881 if (sigaction(SIGWINCH
, &saction
, NULL
) < 0) {
1882 perror ("SIGWINCH");
1888 //vim: foldmethod=marker