]> git.gir.st - solVItaire.git/blob - sol.c
implement restart same game, increase portability, update TODOs
[solVItaire.git] / sol.c
1 #define _DEFAULT_SOURCE /* for getopt, sigaction, usleep */
2 #include <poll.h>
3 #include <signal.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <sys/ioctl.h>
7 #include <time.h>
8 #include <termios.h>
9 #include <unistd.h>
10
11 #include "sol.h"
12 #include "schemes.h"
13
14 struct playfield f;
15 struct opts op;
16
17 // action table {{{
18 /* stores a function pointer for every takeable action; called by game loop */
19 int (*action[NUM_PLACES][10])(int,int,int) = {
20 #ifdef KLONDIKE
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 },
32 #elif defined SPIDER
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 #endif
46 };
47 // }}}
48
49 // argv parsing, game loops, cleanup {{{
50 int main(int argc, char** argv) {
51 /* opinionated defaults: */
52 op.s = &unicode_large_color;
53 #ifdef SPIDER
54 op.m = MEDIUM;
55 #endif
56
57 int optget;
58 opterr = 0; /* don't print message on unrecognized option */
59 while ((optget = getopt (argc, argv, "+:hs:vbcm")) != -1) {
60 switch (optget) {
61 #ifdef SPIDER
62 case 's': /* number of suits */
63 switch (optarg[0]) {
64 case '1': op.m = EASY; break;
65 case '2': op.m = MEDIUM; break;
66 case '4': op.m = NORMAL; break;
67 default: goto error;
68 } break;
69 #endif
70 case 'b': op.s = &unicode_large_mono; break;
71 case 'c': op.s = &unicode_large_color; break;
72 case 'm': op.s = &unicode_small_mono; break; /* "mini" */
73 case 'h': default: goto error;
74 error:
75 fprintf (stderr, SHORTHELP LONGHELP KEYHELP, argv[0]);
76 return optget != 'h';
77 }
78 }
79
80 signal_setup();
81 atexit (*quit);
82
83 signal_handler(SIGWINCH); /* initialize window size */
84
85 newgame:
86 screen_setup(1);
87
88 switch(sol()) {
89 case GAME_NEW: goto newgame;
90 case GAME_WON:
91 print_table(NO_HI, NO_HI);
92 win_anim();
93 if (getchar()=='q') return 0;
94 goto newgame;
95 case GAME_QUIT: return 0;
96 }
97 }
98
99 int sol(void) {
100 long seed = time(NULL);
101 restart:
102 free_undo(f.u);
103 deal(seed);
104
105 int from, to, opt;
106 for(;;) {
107 switch (get_cmd(&from, &to, &opt)) {
108 case CMD_MOVE:
109 switch (action[from][to](from,to,opt)) {
110 case OK: break;
111 case ERR: visbell(); break; //TODO: try again with from/to swapped
112 case WON: return GAME_WON;
113 }
114 break;
115 case CMD_JOIN:
116 switch (join(to)) {
117 case OK: break;
118 case ERR: visbell(); break;
119 case WON: return GAME_WON;
120 }
121 break;
122 case CMD_HINT: break;//TODO: show a possible (and sensible) move. if possible, involve active cursor
123 case CMD_UNDO: undo_pop(f.u); break;
124 case CMD_INVAL: visbell(); break;
125 case CMD_NEW: return GAME_NEW;
126 case CMD_AGAIN: goto restart;
127 case CMD_QUIT: return GAME_QUIT;
128 }
129 }
130 }
131
132 void quit(void) {
133 screen_setup(0);
134 /* free undo data structures: */
135 free_undo(f.u);
136 }
137 //}}}
138
139 // card games helper functions {{{
140 #define get_suit(card) \
141 ((card-1) % NUM_SUITS)
142 #define get_rank(card) \
143 ((card-1) / NUM_SUITS)
144 #define get_color(card) \
145 ((get_suit(card) ^ get_suit(card)>>1) & 1)
146
147 #define is_tableu(where) (where <= TAB_MAX)
148
149 int find_top(card_t* pile) {
150 int i;
151 for(i=PILE_SIZE-1; i>=0 && !pile[i]; i--);
152 return i;
153 }
154 int first_movable(card_t* pile) {
155 int i = 0;
156 for (;pile[i] && !is_movable(pile, i); i++);
157 return i;
158 }
159 int turn_over(card_t* pile) {
160 int top = find_top(pile);
161 if (pile[top] < 0) {
162 pile[top] *= -1;
163 return 1;
164 } else return 0;
165 }
166 int check_won(void) {
167 for (int pile = 0; pile < NUM_DECKS*NUM_SUITS; pile++)
168 if (f.f[pile][NUM_RANKS-1] == NO_CARD) return 0;
169
170 return 1;
171 }
172 int rank_next (card_t a, card_t b) {
173 return get_rank(a) == get_rank(b)-1;
174 }
175 int is_consecutive (card_t* pile, int pos) {
176 if (pos+1 >= PILE_SIZE) return 1; /* card is last */
177 if (pile[pos+1] == NO_CARD) return 1; /* card is first */
178
179 #ifdef KLONDIKE
180 /* ranks consecutive? */
181 if (!rank_next(pile[pos+1], pile[pos])) return 0;
182 /* color opposite? */
183 if (get_color(pile[pos+1]) == get_color(pile[pos])) return 0;
184 #elif defined SPIDER
185 /* ranks consecutive? */
186 if (!rank_next(pile[pos+1], pile[pos])) return 0;
187 /* same suit? */
188 if (get_suit(pile[pos+1]) != get_suit(pile[pos])) return 0;
189 #endif
190
191 return 1;
192 }
193
194 int is_movable(card_t* pile, int n) {
195 #ifdef KLONDIKE
196 return(pile[n] > NO_CARD); /*non-movable cards don't exist in klondike*/
197 #elif defined SPIDER
198 int top = find_top(pile);
199 for (int i = top; i >= 0; i--) {
200 if (pile[i] <= NO_CARD) return 0; /*no card or card face down?*/
201 if (!is_consecutive(pile, i)) return 0;
202 if (i == n) return 1; /* card reached, must be movable */
203 }
204 return 0;
205 #endif
206 }
207 //}}}
208
209 // takeable actions {{{
210 #ifdef KLONDIKE
211 card_t stack_take(void) { /*NOTE: assert(f.w >= 0) */
212 card_t card = f.s[f.w];
213 /* move stack one over, so there are no gaps in it: */
214 for (int i = f.w; i < f.z-1; i++)
215 f.s[i] = f.s[i+1];
216 f.z--;
217 f.w--; /* make previous card visible again */
218 return card;
219 }
220 int t2f(int from, int to, int opt) { /* tableu to foundation */
221 (void) to; (void) opt; /* don't need */
222 int top_from = find_top(f.t[from]);
223 to = get_suit(f.t[from][top_from]);
224 int top_to = find_top(f.f[to]);
225 if ((top_to < 0 && get_rank(f.t[from][top_from]) == RANK_A)
226 || (top_to >= 0 && rank_next(f.f[to][top_to],f.t[from][top_from]))) {
227 f.f[to][top_to+1] = f.t[from][top_from];
228 f.t[from][top_from] = NO_CARD;
229 undo_push(from, FOUNDATION, to,
230 turn_over(f.t[from]));
231 if (check_won()) return WON;
232 return OK;
233 } else return ERR;
234 }
235 int w2f(int from, int to, int opt) { /* waste to foundation */
236 (void) from; (void) to; (void) opt; /* don't need */
237 if (f.w < 0) return ERR;
238 to = get_suit(f.s[f.w]);
239 int top_to = find_top(f.f[to]);
240 if ((top_to < 0 && get_rank(f.s[f.w]) == RANK_A)
241 || (top_to >= 0 && rank_next(f.f[to][top_to], f.s[f.w]))) {
242 undo_push(WASTE, FOUNDATION, f.w | to<<16, 0);//ugly encoding :|
243 f.f[to][top_to+1] = stack_take();
244 if (check_won()) return WON;
245 return OK;
246 } else return ERR;
247
248 }
249 int s2w(int from, int to, int opt) { /* stock to waste */
250 (void) from; (void) to; (void) opt; /* don't need */
251 if (f.z == 0) return ERR;
252 f.w++;
253 if (f.w == f.z) f.w = -1;
254 return OK;
255 }
256 int w2s(int from, int to, int opt) { /* waste to stock (undo stock to waste) */
257 (void) from; (void) to; (void) opt; /* don't need */
258 if (f.z == 0) return ERR;
259 f.w--;
260 if (f.w < -1) f.w = f.z-1;
261 return OK;
262 }
263 int f2t(int from, int to, int opt) { /* foundation to tableu */
264 (void) from; /* don't need */
265 int top_to = find_top(f.t[to]);
266 from = opt;
267 int top_from = find_top(f.f[from]);
268
269 if ((get_color(f.t[to][top_to]) != get_color(f.f[from][top_from]))
270 && (rank_next(f.f[from][top_from], f.t[to][top_to]))) {
271 f.t[to][top_to+1] = f.f[from][top_from];
272 f.f[from][top_from] = NO_CARD;
273 undo_push(FOUNDATION, to, from, 0);
274 return OK;
275 } else return ERR;
276 }
277 int w2t(int from, int to, int opt) { /* waste to tableu */
278 (void) from; (void) opt; /* don't need */
279 int top_to = find_top(f.t[to]);
280 if (((get_color(f.t[to][top_to]) != get_color(f.s[f.w]))
281 && (rank_next(f.s[f.w], f.t[to][top_to])))
282 || (top_to < 0 && get_rank(f.s[f.w]) == RANK_K)) {
283 undo_push(WASTE, to, f.w, 0);
284 f.t[to][top_to+1] = stack_take();
285 return OK;
286 } else return ERR;
287 }
288 int t2t(int from, int to, int opt) { /* tableu to tableu */
289 (void) opt; /* don't need */
290 int top_to = find_top(f.t[to]);
291 int top_from = find_top(f.t[from]);
292 int count = 0; //NOTE: could probably be factored out
293 for (int i = top_from; i >=0; i--) {
294 if (((get_color(f.t[to][top_to]) != get_color(f.t[from][i]))
295 && (rank_next(f.t[from][i], f.t[to][top_to]))
296 && f.t[from][i] > NO_CARD) /* card face up? */
297 || (top_to < 0 && get_rank(f.t[from][i]) == RANK_K)) {
298 /* move cards [i..top_from] to their destination */
299 for (;i <= top_from; i++) {
300 top_to++;
301 f.t[to][top_to] = f.t[from][i];
302 f.t[from][i] = NO_CARD;
303 count++;
304 }
305 undo_push(from, to, count,
306 turn_over(f.t[from]));
307 return OK;
308 }
309 }
310 return ERR; /* no such move possible */
311 }
312 #elif defined SPIDER
313 int remove_if_complete (int pileno) { //cleanup!
314 card_t* pile = f.t[pileno];
315 /* test if K...A complete; move to foundation if so */
316 int top_from = find_top(pile);
317 if (get_rank(pile[top_from]) != RANK_A) return 0;
318 for (int i = top_from; i>=0; i--) {
319 if (!is_consecutive (pile, i)) return 0;
320 if (i+RANK_K == top_from /* if ace to king: remove it */
321 && get_rank(pile[top_from-RANK_K]) == RANK_K) {
322 for(int i=top_from, j=0; i>top_from-NUM_RANKS; i--,j++){
323 f.f[f.w][j] = pile[i];
324 pile[i] = NO_CARD;
325 }
326 undo_push(pileno, FOUNDATION, f.w,
327 turn_over(pile));
328 f.w++;
329 return 1;
330 }
331 }
332
333 return 0;
334 }
335 int t2t(int from, int to, int opt) { //in dire need of cleanup
336 int top_from = find_top(f.t[from]);
337 int top_to = find_top(f.t[to]);
338 int empty_to = (top_to < 0)? opt: -1; /* empty pile? */
339 int count = 0; //NOTE: could probably be factored out
340
341 for (int i = top_from; i >= 0; i--) {
342 if (!is_consecutive(f.t[from], i)) break;
343
344 /* is consecutive OR to empty pile and rank ok? */
345 if (rank_next(f.t[from][i], f.t[to][top_to])
346 || (empty_to >= RANK_A && get_rank(f.t[from][i]) == empty_to)) {
347 for (;i <= top_from; i++) {
348 top_to++;
349 f.t[to][top_to] = f.t[from][i];
350 f.t[from][i] = NO_CARD;
351 count++;
352 }
353 undo_push(from, to, count,
354 turn_over(f.t[from]));
355 remove_if_complete(to);
356 if (check_won()) return WON;
357 return OK;
358 }
359 }
360
361 return ERR; /* no such move possible */
362 }
363 int s2t(int from, int to, int opt) {
364 (void) from; (void) to; (void) opt; /* don't need */
365 if (f.z <= 0) return ERR; /* stack out of cards */
366 for (int pile = 0; pile < NUM_PILES; pile++)
367 if (f.t[pile][0]==NO_CARD) return ERR; /*no piles may be empty*/
368 for (int pile = 0; pile < NUM_PILES; pile++) {
369 f.t[pile][find_top(f.t[pile])+1] = f.s[--f.z];
370 remove_if_complete(pile);
371 if (check_won()) return WON;
372 }
373 undo_push(STOCK, TABLEU, 1, 0); /*NOTE: puts 1 card on each tableu pile*/
374 return OK;
375 }
376 int t2f(int from, int to, int opt) {
377 (void) to; (void) opt; /* don't need */
378 /* manually retrigger remove_if_complete() (e.g. after undo_pop) */
379 return remove_if_complete(from)?OK:ERR;
380 }
381 #endif
382 int join(int to) {
383 //TODO: allow joining to foundation in klondike
384 //TODO: which pile to take from should form the basis of CMD_HINT
385
386 int top_to = find_top(f.t[to]); //TODO: handle empty
387 int from = -1;
388
389 struct rating {
390 int ok:1; /* card to move in pile? */
391 int above; /* number of movable cards above */
392 int below; /* number of cards below ours */
393 int pos; /* where the card to move is in the pile */
394 } r[NUM_PILES] = {{0}};
395
396 /* 1. rate each pile: */
397 for (int pile = 0; pile < NUM_PILES; pile++) {
398 r[pile].pos = find_top(f.t[pile]);
399 /* backtrack until we find a compatible-to-'to'-pile card: */
400 while (r[pile].pos >= 0 && is_movable(f.t[pile], r[pile].pos)) {
401 int rankdiff = get_rank(f.t[pile][r[pile].pos])
402 - get_rank(f.t[to][top_to]);
403 if (rankdiff >= 0) break; /* past our card */
404 if (rankdiff == -1 /* rank matches */
405 #ifdef KLONDIKE
406 && get_color(f.t[pile][r[pile].pos]) /* color OK */
407 != get_color(f.t[to][top_to])
408 #elif defined SPIDER
409 && get_suit(f.t[pile][r[pile].pos]) /* color OK */
410 == get_suit(f.t[to][top_to])
411 #endif
412 ) {
413 r[pile].ok++;
414 for (int i = r[pile].pos; i >= 0; i--)
415 if (is_movable(f.t[pile], i-1))
416 r[pile].above++;
417 else break;
418 break;
419 }
420 r[pile].pos--;
421 r[pile].below++;
422 }
423 }
424
425 /* 2. find optimal pile: (optimized for spider) */
426 for (int pile = 0, above = 99, turn = 0, empty = 0, below = 99, e=0,t=0;
427 pile < NUM_PILES; pile++) {
428 if (!r[pile].ok) continue;
429
430 if ((e=(r[pile].pos == 0)) /* will become empty */
431 || ((t=(f.t[pile][r[pile].pos-1] < 0)) && !empty) /* will turn_over */
432 || (r[pile].above < above && !empty) /* less cards above */
433 || (r[pile].above ==above && r[pile].below < below && !empty && !turn)) { /* if tied, use shorter pile */
434 from = pile;
435 above = r[pile].above;
436 below = r[pile].below;
437 empty |= e;
438 turn |= t;
439 }
440 }
441
442 /* 3a. give up if nothing found: */
443 if (from < 0) {
444 #ifdef KLONDIKE /* check if we can take from waste before giving up */
445 return w2t(WASTE, to, 0);
446 #elif defined SPIDER
447 return ERR;
448 #endif
449 }
450
451 /* 3b. move cards over and return: */
452 #ifdef KLONDIKE
453 return t2t(from, to, 0);
454 #elif defined SPIDER
455 int bottom = first_movable(f.t[from]);
456 return t2t(from, to, get_rank(f.t[from][bottom]));
457 #endif
458 }
459 int nop(int from, int to, int opt) { (void)from;(void)to;(void)opt;return ERR; }
460 // }}}
461
462 // keyboard input handling {{{
463 // cursor functions{{{
464 #ifdef KLONDIKE
465 void cursor_left (struct cursor* cursor) {
466 if (is_tableu(cursor->pile)) {
467 if (cursor->pile > 0) cursor->pile--;
468 cursor->opt = 0;
469 } else { /* stock/waste/foundation*/
470 switch (cursor->pile) {
471 case WASTE: cursor->pile = STOCK; cursor->opt = 0; break;
472 case FOUNDATION:
473 if (cursor->opt <= 0)
474 cursor->pile = WASTE;
475 else
476 cursor->opt--;
477 }
478 }
479 }
480 void cursor_down (struct cursor* cursor) {
481 if (!is_tableu(cursor->pile)) {
482 switch (cursor->pile) {
483 case STOCK: cursor->pile = TAB_1; break;
484 case WASTE: cursor->pile = TAB_2; break;
485 case FOUNDATION:
486 cursor->pile = TAB_4 + cursor->opt;
487 }
488 cursor->opt = 0;
489 }
490 }
491 void cursor_up (struct cursor* cursor) {
492 if (is_tableu(cursor->pile)) {
493 switch (cursor->pile) { //ugly :|
494 case TAB_1: cursor->pile = STOCK; break;
495 case TAB_2: cursor->pile = WASTE; break;
496 case TAB_3: cursor->pile = WASTE; break;
497 case TAB_4: case TAB_5: case TAB_6: case TAB_7:
498 cursor->opt=cursor->pile-TAB_4;
499 cursor->pile = FOUNDATION;
500 break;
501 }
502 }
503 }
504 void cursor_right (struct cursor* cursor) {
505 if (is_tableu(cursor->pile)) {
506 if (cursor->pile < TAB_MAX) cursor->pile++;
507 } else {
508 switch (cursor->pile) {
509 case STOCK: cursor->pile = WASTE; break;
510 case WASTE: cursor->pile = FOUNDATION;cursor->opt = 0; break;
511 case FOUNDATION:
512 if (cursor->opt < NUM_SUITS-1)
513 cursor->opt++;
514 }
515 }
516 }
517 #elif defined SPIDER
518 /*NOTE: one can't highlight the stock due to me being too lazy to implement it*/
519 void cursor_left (struct cursor* cursor) {
520 if (cursor->pile > 0) cursor->pile--;
521 cursor->opt = 0;
522 }
523 void cursor_down (struct cursor* cursor) {
524 int first = first_movable(f.t[cursor->pile]);
525 int top = find_top(f.t[cursor->pile]);
526 if (first + cursor->opt < top)
527 cursor->opt++;
528 }
529 void cursor_up (struct cursor* cursor) {
530 if (cursor->opt > 0) cursor->opt--;
531 }
532 void cursor_right (struct cursor* cursor) {
533 if (cursor->pile < TAB_MAX) cursor->pile++;
534 cursor->opt = 0;
535 }
536 #endif
537 void cursor_to (struct cursor* cursor, int pile) {
538 cursor->pile = pile;
539 cursor->opt = 0;
540 }
541 //}}}
542 int get_cmd (int* from, int* to, int* opt) {
543 //TODO: escape sequences (mouse, cursor keys)
544 int _f, t;
545 struct cursor inactive = {-1,-1};
546 static struct cursor active = {0,0};
547 active.opt = 0; /* always reset offset, but keep pile */
548
549 /***/
550 from_l: print_table(&active, &inactive);
551 _f = getchar();
552
553 switch (_f) {
554 /* direct addressing: */
555 case '1': *from = TAB_1; break;
556 case '2': *from = TAB_2; break;
557 case '3': *from = TAB_3; break;
558 case '4': *from = TAB_4; break;
559 case '5': *from = TAB_5; break;
560 case '6': *from = TAB_6; break;
561 case '7': *from = TAB_7; break;
562 #ifdef SPIDER
563 case '8': *from = TAB_8; break;
564 case '9': *from = TAB_9; break;
565 case '0': *from = TAB_10;break;
566 #elif defined KLONDIKE
567 case '9': *from = WASTE; break;
568 case '0': *from = FOUNDATION; break;
569 case '8': /* fallthrough */
570 #endif
571 case '\n': /* shortcut for dealing from stock */
572 *from = STOCK;
573 *to = WASTE;
574 return CMD_MOVE;
575 /* cursor keys addressing: */
576 case 'h': cursor_left (&active); goto from_l;
577 case 'j': cursor_down (&active); goto from_l;
578 case 'k': cursor_up (&active); goto from_l;
579 case 'l': cursor_right(&active); goto from_l;
580 case 'H': cursor_to(&active,TAB_1); goto from_l; /* leftmost tableu */
581 case 'L': cursor_to(&active,TAB_MAX);goto from_l; /* rigthmost tableu */
582 case 'M': cursor_to(&active,TAB_MAX/2); goto from_l; /* center tableu */
583 //TODO: real cursor keys, home/end
584 case ' ': /* continue with second cursor */
585 *from = active.pile;
586 if (*from == STOCK) {
587 *to = WASTE;
588 return CMD_MOVE;
589 }
590 #ifdef KLONDIKE
591 *opt = active.opt; /* when FOUNDATION */
592 #endif
593 inactive = active;
594 break;
595 /* misc keys: */
596 case ':':
597 {char buf[256];
598 fprintf (stderr, ":");
599 raw_mode(0); /* turn on echo */
600 fgets (buf, 256, stdin);
601 raw_mode(1);
602 switch(buf[0]) {
603 case 'q': return CMD_QUIT;
604 case 'n': return CMD_NEW;
605 case 'r': return CMD_AGAIN;
606 default: return CMD_INVAL;
607 }}
608 case 'J':
609 *to = active.pile;
610 if (*to > TAB_MAX) return CMD_INVAL;
611 return CMD_JOIN;
612 case 'K': /* fallthrough */
613 case '?': return CMD_HINT;
614 case 'u': return CMD_UNDO;
615 case EOF: return CMD_NONE; /* sent by SIGCONT */
616 default: return CMD_INVAL;
617 }
618 inactive.pile = *from; /* for direct addressing highlighting */
619 if (is_tableu(*from) && f.t[*from][0] == NO_CARD) return CMD_INVAL;
620
621 /***/
622 to_l: print_table(&active, &inactive);
623 t = getchar();
624
625 switch (t) {
626 case 'h': cursor_left (&active); goto to_l;
627 case 'j': cursor_down (&active); goto to_l;
628 case 'k': cursor_up (&active); goto to_l;
629 case 'l': cursor_right(&active); goto to_l;
630 case 'H': cursor_to(&active,TAB_1); goto to_l;
631 case 'L': cursor_to(&active,TAB_MAX); goto to_l;
632 case 'M': cursor_to(&active,TAB_MAX/2); goto to_l;
633 case 'J': /* fallthrough; just join selected pile */
634 case ' ':
635 *to = active.pile;
636 break; /* continues with the foundation/empty tableu check */
637 case 'K': /* fallthrough */
638 case '?': return CMD_HINT;
639 case 'u': return CMD_NONE; /* cancel selection */
640 case EOF: return CMD_NONE; /* sent by SIGCONT */
641 default:
642 if (t < '0' || t > '9') return CMD_INVAL;
643 if (t == '0')
644 #ifdef KLONDIKE
645 *to = FOUNDATION;
646 #elif defined SPIDER
647 *to = TAB_10;
648 #endif
649 else
650 *to = t-'1';
651 }
652
653 /***/
654 #ifdef KLONDIKE
655 if (*from == FOUNDATION) {
656 int top = find_top(f.t[*to]);
657 if (top < 0) return CMD_INVAL;
658 int color = get_color(f.t[*to][top]);
659 int choice_1 = 1-color; /* selects piles of */
660 int choice_2 = 2+color; /* the opposite color */
661 int top_c1 = find_top(f.f[choice_1]);
662 int top_c2 = find_top(f.f[choice_2]);
663
664 switch ((rank_next(f.f[choice_1][top_c1], f.t[*to][top])
665 && top_c1 >= 0 ) << 0
666 |(rank_next(f.f[choice_2][top_c2], f.t[*to][top])
667 && top_c2 >= 0 ) << 1) {
668 case ( 1<<0): *opt = choice_1; break; /* choice_1 only */
669 case (1<<1 ): *opt = choice_2; break; /* choice_2 only */
670 case (1<<1 | 1<<0): /* both, ask user which to pick from */
671 printf ("take from (1-4): "); fflush (stdout);
672 *opt = getchar() - '1';
673 if (*opt < 0 || *opt > 3) return CMD_INVAL;
674 break;
675 default: return CMD_INVAL; /* none matched */
676 }
677 /* `opt` is the foundation index (0..3) */
678 }
679 #elif defined SPIDER
680 /* moving to empty tableu? */
681 if (is_tableu(*to) && f.t[*to][0] == NO_CARD) {
682 int bottom = first_movable(f.t[*from]);
683 if (inactive.opt >= 0) { /*if from was cursor addressed: */
684 *opt = get_rank(f.t[*from][bottom + inactive.opt]);
685 return CMD_MOVE;
686 }
687 int top = find_top(f.t[*from]);
688 if (top < 0) return CMD_INVAL;
689 if (top >= 0 && !is_movable(f.t[*from], top-1)) {
690 *opt = get_rank(f.t[*from][top]);
691 } else { /* only ask the user if it's unclear: */
692 printf ("\rup to ([a23456789xjqk] or space/return): ");
693 *opt = getchar();
694 switch (*opt) {
695 case ' ': *opt = get_rank(f.t[*from][top]); break;
696 case'\n': *opt = get_rank(f.t[*from][bottom]); break;
697 case 'a': case 'A': *opt = RANK_A; break;
698 case '0': /* fallthrough */
699 case 'x': case 'X': *opt = RANK_X; break;
700 case 'j': case 'J': *opt = RANK_J; break;
701 case 'q': case 'Q': *opt = RANK_Q; break;
702 case 'k': case 'K': *opt = RANK_K; break;
703 default: *opt -= '1';
704 }
705 if (*opt < RANK_A || *opt > RANK_K) return ERR;
706 }
707 /* `opt` is the rank of the highest card to move */
708 }
709 #endif
710 return CMD_MOVE;
711 }
712 // }}}
713
714 // shuffling and dealing {{{
715 void deal(long seed) {
716 f = (const struct playfield){0}; /* clear playfield */
717 card_t deck[DECK_SIZE*NUM_DECKS];
718 int avail = DECK_SIZE*NUM_DECKS;
719 for (int i = 0; i < DECK_SIZE*NUM_DECKS; i++) deck[i] = (i%DECK_SIZE)+1;
720 #ifdef SPIDER
721 if (op.m != NORMAL) for (int i = 0; i < DECK_SIZE*NUM_DECKS; i++) {
722 if (op.m == MEDIUM) deck[i] = 1+((deck[i]-1) | 2);
723 if (op.m == EASY) deck[i] = 1+((deck[i]-1) | 2 | 1);
724 /* the 1+ -1 dance gets rid of the offset created by NO_CARD */
725 }
726 #endif
727 srand (seed);
728 for (int i = DECK_SIZE*NUM_DECKS-1; i > 0; i--) { /* fisher-yates */
729 int j = rand() % (i+1);
730 if (j-i) deck[i]^=deck[j],deck[j]^=deck[i],deck[i]^=deck[j];
731 }
732
733 /* deal cards: */
734 for (int i = 0; i < NUM_PILES; i++) {
735 #ifdef KLONDIKE
736 int closed = i; /* pile n has n closed cards, then 1 open */
737 #elif defined SPIDER
738 int closed = i<4?5:4; /* pile 1-4 have 5, 5-10 have 4 closed */
739 #endif
740 /* face down cards are negated: */
741 for (int j = 0; j < closed; j++) f.t[i][j] = -deck[--avail];
742 f.t[i][closed] = deck[--avail]; /* the face-up card */
743 }
744 /* rest of the cards to the stock; NOTE: assert(avail==50) for spider */
745 for (f.z = 0; avail; f.z++) f.s[f.z] = deck[--avail];
746 #ifdef KLONDIKE
747 f.w = -1; /* @start: nothing on waste */
748 #elif defined SPIDER
749 f.w = 0; /* number of used foundations */
750 #endif
751
752 f.u = &undo_sentinel;
753 }
754 //}}}
755
756 // screen drawing routines {{{
757 void print_hi(int invert, int grey_bg, int bold, char* str) {
758 if (bold && op.s == &unicode_large_color){//ARGH! awful hack for bold with faint
759 int offset = str[3]==017?16:str[4]==017?17:0;
760 printf ("%s%s%s""%.*s%s%s""%s%s%s",
761 bold?"\033[1m":"", invert?"\033[7m":"", grey_bg?"\033[100m":"",
762 offset, str, bold?"\033[1m":"", str+offset,
763 grey_bg?"\033[49m":"", invert?"\033[27m":"",bold?"\033[22m":"");
764 return;
765 }
766 printf ("%s%s%s%s%s%s%s",
767 bold?"\033[1m":"", invert?"\033[7m":"", grey_bg?"\033[100m":"",
768 str,
769 grey_bg?"\033[49m":"", invert?"\033[27m":"",bold?"\033[22m":"");
770 }
771 void print_table(const struct cursor* active, const struct cursor* inactive) {
772 printf("\033[2J\033[H"); /* clear screen, reset cursor */
773 #ifdef KLONDIKE
774 /* print stock, waste and foundation: */
775 for (int line = 0; line < op.s->height; line++) {
776 /* stock: */
777 print_hi (active->pile == STOCK, inactive->pile == STOCK, 1, (
778 (f.w < f.z-1)?op.s->facedown
779 :op.s->placeholder)[line]);
780 /* waste: */
781 print_hi (active->pile == WASTE, inactive->pile == WASTE, 1, (
782 /* NOTE: cast, because f.w sometimes is (short)-1 !? */
783 ((short)f.w >= 0)?op.s->card[f.s[f.w]]
784 :op.s->placeholder)[line]);
785 printf ("%s", op.s->card[NO_CARD][line]); /* spacer */
786 /* foundation: */
787 for (int pile = 0; pile < NUM_SUITS; pile++) {
788 int card = find_top(f.f[pile]);
789 print_hi (active->pile==FOUNDATION && active->opt==pile,
790 inactive->pile==FOUNDATION && (
791 /* cursor addr. || direct addr. */
792 inactive->opt==pile || inactive->opt < 0
793 ), 1,
794 (card < 0)?op.s->placeholder[line]
795 :op.s->card[f.f[pile][card]][line]);
796 }
797 printf("\n");
798 }
799 printf("\n");
800 #elif defined SPIDER
801 int fdone; for (fdone = NUM_DECKS*NUM_SUITS; fdone; fdone--)
802 if (f.f[fdone-1][RANK_K]) break; /*number of completed stacks*/
803 int spacer_from = f.z?(f.z/10-1) * op.s->halfwidth[0] + op.s->width:0;
804 int spacer_to = NUM_PILES*op.s->width -
805 ((fdone?(fdone-1) * op.s->halfwidth[1]:0)+op.s->width);
806 for (int line = 0; line < op.s->height; line++) {
807 /* available stock: */
808 for (int i = f.z/10; i; i--) {
809 if (i==1) printf ("%s", op.s->facedown[line]);
810 else printf ("%s", op.s->halfstack[line]);
811 }
812 /* spacer: */
813 for (int i = spacer_from; i < spacer_to; i++) printf (" ");
814 /* foundation (overlapping): */
815 for (int i = 0; i < NUM_DECKS*NUM_SUITS; i++) { //TODO: print in revrse order (otherwise new piles get put 'below' older ones)
816 int overlap = i? op.s->halfcard[line]: 0;
817 if (f.f[i][RANK_K]) printf ("%.*s", op.s->halfwidth[2],
818 op.s->card[f.f[i][RANK_K]][line]+overlap);
819 }
820 printf("\n");
821 }
822 printf("\n");
823 #endif
824 #ifdef KLONDIKE
825 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty))
826 #define TOP_HI(c) 1 /* can't select partial stacks in KLONDIKE */
827 #define INC_OFFSET
828 #elif defined SPIDER
829 int offset[NUM_PILES]={1,1,1,1,1,1,1,1,1,1}; // :|
830 #define DO_HI(cursor) (cursor->pile == pile && (movable || empty) \
831 && offset[pile] > cursor->opt)
832 #define TOP_HI(cursor) (cursor->pile == pile && movable \
833 && offset[pile]-1 == cursor->opt)
834 #define INC_OFFSET if (movable) offset[pile]++
835 #endif
836 /* print tableu piles: */
837 int row[NUM_PILES] = {0};
838 int line[NUM_PILES]= {0};
839 int label[NUM_PILES]={0};
840 int line_had_card;
841 int did_placeholders = 0;
842 do {
843 line_had_card = 0;
844 for (int pile = 0; pile < NUM_PILES; pile++) {
845 card_t card = f.t[pile][row[pile]];
846 card_t next = f.t[pile][row[pile]+1];
847 int movable = is_movable(f.t[pile], row[pile]);
848 int empty = !card && row[pile] == 0;
849
850 print_hi (DO_HI(active), DO_HI(inactive), movable, (
851 (!card && row[pile] == 0)?op.s->placeholder
852 :(card<0)?op.s->facedown
853 :op.s->card[card]
854 )[line[pile]]);
855
856 int extreme_overlap = ( 3 /* spacer, labels, status */
857 + 2 * op.s->height /* stock, top tableu card */
858 + find_top(f.t[pile]) * op.s->overlap) >op.w[0];
859 /* normal overlap: */
860 if (++line[pile] >= (next?op.s->overlap:op.s->height)
861 /* extreme overlap on closed cards: */
862 || (extreme_overlap &&
863 line[pile] >= 1 &&
864 f.t[pile][row[pile]] < 0 &&
865 f.t[pile][row[pile]+1] <0)
866 /* extreme overlap on sequences: */
867 || (extreme_overlap &&
868 !TOP_HI(active) && /*always show top selected card*/
869 line[pile] >= 1 && row[pile] > 0 &&
870 f.t[pile][row[pile]-1] > NO_CARD &&
871 is_consecutive (f.t[pile], row[pile]) &&
872 is_consecutive (f.t[pile], row[pile]-1) &&
873 f.t[pile][row[pile]+1] != NO_CARD)
874 ) {
875 line[pile]=0;
876 row[pile]++;
877 INC_OFFSET;
878 }
879 /* tableu labels: */
880 if(!card && !label[pile] && row[pile]>0&&line[pile]>0) {
881 label[pile] = 1;
882 printf ("\b\b%d ", (pile+1) % 10); //XXX: hack
883 }
884 line_had_card |= !!card;
885 did_placeholders |= row[pile] > 0;
886 }
887 printf ("\n");
888 } while (line_had_card || !did_placeholders);
889 }
890
891 void visbell (void) {
892 printf ("\033[?5h"); fflush (stdout);
893 usleep (100000);
894 printf ("\033[?5l"); fflush (stdout);
895 }
896 void win_anim(void) {
897 printf ("\033[?25l"); /* hide cursor */
898 for (;;) {
899 /* set cursor to random location */
900 int row = 1+rand()%(24-op.s->width);
901 int col = 1+rand()%(80-op.s->height);
902
903 /* draw random card */
904 int face = 1 + rand() % 52;
905 for (int l = 0; l < op.s->height; l++) {
906 printf ("\033[%d;%dH", row+l, col);
907 printf ("%s", op.s->card[face][l]);
908 }
909 fflush (stdout);
910
911 /* exit on keypress */
912 struct pollfd p = {STDIN_FILENO, POLLIN, 0};
913 if (poll (&p, 1, 80)) goto fin;
914 }
915 fin:
916 printf ("\033[?25h"); /* show cursor */
917 return;
918 }
919 //}}}
920
921 // undo logic {{{
922 void undo_push (int _f, int t, int n, int o) {
923 struct undo* new = malloc(sizeof(struct undo));
924 new->f = _f;
925 new->t = t;
926 new->n = n;
927 new->o = o;
928 new->prev = f.u;
929 new->next = NULL;
930 f.u->next = new;
931 f.u = f.u->next;
932 }
933 void undo_pop (struct undo* u) {
934 if (u == &undo_sentinel) return;
935
936 #ifdef KLONDIKE
937 if (u->f == FOUNDATION) {
938 /* foundation -> tableu */
939 int top_f = find_top(f.f[u->n]);
940 int top_t = find_top(f.t[u->t]);
941 f.f[u->n][top_f+1] = f.t[u->t][top_t];
942 f.t[u->t][top_t] = NO_CARD;
943 } else if (u->f == WASTE && u->t == FOUNDATION) {
944 /* waste -> foundation */
945 /* split u->n into wst and fnd: */
946 int wst = u->n & 0xffff;
947 int fnd = u->n >> 16;
948 /* move stock cards one position up to make room: */
949 for (int i = f.z; i >= wst; i--) f.s[i+1] = f.s[i];
950 /* move one card from foundation to waste: */
951 int top = find_top(f.f[fnd]);
952 f.s[wst] = f.f[fnd][top];
953 f.f[fnd][top] = NO_CARD;
954 f.z++;
955 f.w++;
956 } else if (u->f == WASTE) {
957 /* waste -> tableu */
958 /* move stock cards one position up to make room: */
959 for (int i = f.z; i >= u->n; i--) f.s[i+1] = f.s[i];
960 /* move one card from tableu to waste: */
961 int top = find_top(f.t[u->t]);
962 f.s[u->n] = f.t[u->t][top];
963 f.t[u->t][top] = NO_CARD;
964 f.z++;
965 f.w++;
966 } else if (u->t == FOUNDATION) {
967 /* tableu -> foundation */
968 int top_f = find_top(f.t[u->f]);
969 int top_t = find_top(f.f[u->n]);
970 /* close topcard if previous action caused turn_over(): */
971 if (u->o) f.t[u->f][top_f] *= -1;
972 /* move one card from foundation to tableu: */
973 f.t[u->f][top_f+1] = f.f[u->n][top_t];
974 f.f[u->n][top_t] = NO_CARD;
975 } else {
976 /* tableu -> tableu */
977 int top_f = find_top(f.t[u->f]);
978 int top_t = find_top(f.t[u->t]);
979 /* close topcard if previous action caused turn_over(): */
980 if (u->o) f.t[u->f][top_f] *= -1;
981 /* move n cards from tableu[f] to tableu[t]: */
982 for (int i = 0; i < u->n; i++) {
983 f.t[u->f][top_f+u->n-i] = f.t[u->t][top_t-i];
984 f.t[u->t][top_t-i] = NO_CARD;
985 }
986 }
987 #elif defined SPIDER
988 if (u->f == STOCK) {
989 /* stock -> tableu */
990 /*remove a card from each pile and put it back onto the stock:*/
991 for (int pile = NUM_PILES-1; pile >= 0; pile--) {
992 int top = find_top(f.t[pile]);
993 f.s[f.z++] = f.t[pile][top];
994 f.t[pile][top] = NO_CARD;
995 }
996 } else if (u->t == FOUNDATION) {
997 /* tableu -> foundation */
998 int top = find_top(f.t[u->f]);
999 /* close topcard if previous action caused turn_over(): */
1000 if (u->o) f.t[u->f][top] *= -1;
1001 /* append cards from foundation to tableu */
1002 for (int i = RANK_K; i >= RANK_A; i--) {
1003 f.t[u->f][++top] = f.f[u->n][i];
1004 f.f[u->n][i] = NO_CARD;
1005 }
1006 f.w--; /* decrement complete-foundation-counter */
1007
1008 } else {
1009 /* tableu -> tableu */
1010 int top_f = find_top(f.t[u->f]);
1011 int top_t = find_top(f.t[u->t]);
1012 /* close topcard if previous action caused turn_over(): */
1013 if (u->o) f.t[u->f][top_f] *= -1;
1014 /* move n cards from tableu[f] to tableu[t]: */
1015 for (int i = 0; i < u->n; i++) {
1016 f.t[u->f][top_f+u->n-i] = f.t[u->t][top_t-i];
1017 f.t[u->t][top_t-i] = NO_CARD;
1018 }
1019 }
1020 #endif
1021
1022 void* old = f.u;
1023 f.u = f.u->prev;
1024 free(old);
1025 }
1026 void free_undo (struct undo* u) {
1027 while (u && u != &undo_sentinel) {
1028 void* old = u;
1029 u = u->prev;
1030 free (old);
1031 }
1032 }
1033 //}}}
1034
1035 // initialization stuff {{{
1036 void screen_setup (int enable) {
1037 if (enable) {
1038 raw_mode(1);
1039 printf ("\033[s\033[?47h"); /* save cursor, alternate screen */
1040 printf ("\033[H\033[J"); /* reset cursor, clear screen */
1041 //TODO//printf ("\033[?1000h\033[?25l"); /* enable mouse, hide cursor */
1042 } else {
1043 //TODO//printf ("\033[?9l\033[?25h"); /* disable mouse, show cursor */
1044 printf ("\033[?47l\033[u"); /* primary screen, restore cursor */
1045 raw_mode(0);
1046 }
1047 }
1048
1049 void raw_mode(int enable) {
1050 static struct termios saved_term_mode;
1051 struct termios raw_term_mode;
1052
1053 if (enable) {
1054 if (saved_term_mode.c_lflag == 0)/*don't overwrite stored mode*/
1055 tcgetattr(STDIN_FILENO, &saved_term_mode);
1056 raw_term_mode = saved_term_mode;
1057 raw_term_mode.c_lflag &= ~(ICANON | ECHO);
1058 raw_term_mode.c_cc[VMIN] = 1 ;
1059 raw_term_mode.c_cc[VTIME] = 0;
1060 tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_term_mode);
1061 } else {
1062 tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_term_mode);
1063 }
1064 }
1065
1066 void signal_handler (int signum) {
1067 struct winsize w;
1068 switch (signum) {
1069 case SIGCONT:
1070 screen_setup(0);
1071 screen_setup(1);
1072 print_table(NO_HI, NO_HI);
1073 break;
1074 case SIGINT: //TODO: don't exit; just warn like vim does
1075 exit(128+SIGINT);
1076 case SIGWINCH:
1077 ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
1078 op.w[0] = w.ws_row;
1079 op.w[1] = w.ws_col;
1080 break;
1081 }
1082 }
1083 void signal_setup(void) {
1084 struct sigaction saction;
1085
1086 saction.sa_handler = signal_handler;
1087 sigemptyset(&saction.sa_mask);
1088 saction.sa_flags = 0;
1089 if (sigaction(SIGCONT, &saction, NULL) < 0) {
1090 perror ("SIGCONT");
1091 exit (1);
1092 }
1093 if (sigaction(SIGINT, &saction, NULL) < 0) {
1094 perror ("SIGINT");
1095 exit (1);
1096 }
1097 if (sigaction(SIGWINCH, &saction, NULL) < 0) {
1098 perror ("SIGWINCH");
1099 exit (1);
1100 }
1101 }
1102 //}}}
1103
1104 //vim: foldmethod=marker
Imprint / Impressum