]> git.gir.st - solVItaire.git/blob - sol.c
bugfix: could move 2 before ace to foundation
[solVItaire.git] / sol.c
1 #define _DEFAULT_SOURCE
2 #include <poll.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <time.h>
6 #include <termios.h>
7 #include <unistd.h>
8
9 #include "sol.h"
10 #include "schemes.h"
11
12 #ifdef KLONDIKE
13 #define NUM_PILES 7
14 #define MAX_HIDDEN 6 /*how many cards are turned over at most in a tableu pile*/
15 #define MAX_STOCK 24 /*how many cards can be in the stock at most (=@start)*/
16 #define NUM_DECKS 1
17 #define PILE_SIZE MAX_HIDDEN+NUM_RANKS
18 #elif defined SPIDER
19 #define MAX_HIDDEN 5
20 #define NUM_PILES 10
21 #define MAX_STOCK 50 /*how many cards can be dealt onto the piles*/
22 #define NUM_DECKS 2
23 #define PILE_SIZE DECK_SIZE*NUM_DECKS /* no maximum stack size in spider :/ */
24 #endif
25
26 #define get_suit(card) \
27 ((card-1) % NUM_SUITS)
28 #define get_rank(card) \
29 ((card-1) / NUM_SUITS)
30 #define get_color(card) \
31 ((get_suit(card) ^ get_suit(card)>>1) & 1)
32
33 struct playfield {
34 //TODO: stock and waste are incompatible with undo{}
35 card_t s[MAX_STOCK]; /* stock */
36 int z; /* stock size */
37 int w; /* waste; index into stock (const -1 in spider) */
38 card_t f[NUM_DECKS*NUM_SUITS][PILE_SIZE]; /* foundation (XXX@spider:complete set gets put on seperate pile, so undo is easy) */
39 card_t t[NUM_PILES][PILE_SIZE]; /* tableu piles */
40 struct undo {
41 int from; /* pile cards were taken from */
42 int to; /* pile cards were moved to */
43 int n; /* number of cards moved */
44 struct undo* prev;
45 struct undo* next;
46 } u;
47 } f;
48 struct opts {
49 #ifdef SPIDER
50 int m; /* difficulty mode */
51 #endif
52 const struct scheme* s;
53 } op;
54
55 // action table {{{
56 #ifdef KLONDIKE
57 /* stores a function pointer for every takeable action; called by game loop */
58 int (*action[10][10])(int,int) = {
59 /*fnd 1 2 3 4 5 6 7 stk wst*/
60 /*fnd*/ { nop, f2t, f2t, f2t, f2t, f2t, f2t, f2t, nop, nop },
61 /* 1 */ { t2f, t2f, t2t, t2t, t2t, t2t, t2t, t2t, nop, nop },
62 /* 2 */ { t2f, t2t, t2f, t2t, t2t, t2t, t2t, t2t, nop, nop },
63 /* 3 */ { t2f, t2t, t2t, t2f, t2t, t2t, t2t, t2t, nop, nop },
64 /* 4 */ { t2f, t2t, t2t, t2t, t2f, t2t, t2t, t2t, nop, nop },
65 /* 5 */ { t2f, t2t, t2t, t2t, t2t, t2f, t2t, t2t, nop, nop },
66 /* 6 */ { t2f, t2t, t2t, t2t, t2t, t2t, t2f, t2t, nop, nop },
67 /* 7 */ { t2f, t2t, t2t, t2t, t2t, t2t, t2t, t2f, nop, nop },
68 /*stk*/ { nop, nop, nop, nop, nop, nop, nop, nop, nop, s2w },
69 /*wst*/ { w2f, w2t, w2t, w2t, w2t, w2t, w2t, w2t, w2s, w2f },
70 };
71 #elif defined SPIDER
72 int (*action[11][10])(int,int) = {
73 /* 0 1 2 3 4 5 6 7 8 9 */
74 /* 0 */ { nop, t2t, t2t, t2t, t2t, t2t, t2t, t2t, t2t, t2t },
75 /* 1 */ { t2t, nop, t2t, t2t, t2t, t2t, t2t, t2t, t2t, t2t },
76 /* 2 */ { t2t, t2t, nop, t2t, t2t, t2t, t2t, t2t, t2t, t2t },
77 /* 3 */ { t2t, t2t, t2t, nop, t2t, t2t, t2t, t2t, t2t, t2t },
78 /* 4 */ { t2t, t2t, t2t, t2t, nop, t2t, t2t, t2t, t2t, t2t },
79 /* 5 */ { t2t, t2t, t2t, t2t, t2t, nop, t2t, t2t, t2t, t2t },
80 /* 6 */ { t2t, t2t, t2t, t2t, t2t, t2t, nop, t2t, t2t, t2t },
81 /* 7 */ { t2t, t2t, t2t, t2t, t2t, t2t, t2t, nop, t2t, t2t },
82 /* 8 */ { t2t, t2t, t2t, t2t, t2t, t2t, t2t, t2t, nop, t2t },
83 /* 9 */ { t2t, t2t, t2t, t2t, t2t, t2t, t2t, t2t, t2t, nop },
84 /*stk*/ { s2t, s2t, s2t, s2t, s2t, s2t, s2t, s2t, s2t, s2t },
85 };
86 #endif
87 // }}}
88
89 int main(int argc, char** argv) {
90 op.s = &unicode_large_color;
91 #ifdef SPIDER
92 op.m = MEDIUM; //TODO: make configurable
93 op.m = EASY;
94 #endif
95 screen_setup(1);
96 sol(); //TODO: restart, etc.
97 screen_setup(0);
98 }
99
100 void sol(void) {
101 deal();
102
103 int from, to;
104 print_table();
105 for(;;) {
106 switch (get_cmd(&from, &to)) {
107 case CMD_MOVE:
108 switch (action[from][to](from,to)) {
109 case OK: break;
110 case ERR: visbell(); break;
111 case WON:
112 print_table();
113 win_anim();
114 getchar(); //consume the char left by win_anim()
115 return;
116 }
117 break;
118 case CMD_QUIT: return;
119 }
120 print_table();
121 }
122 }
123
124 int find_top(card_t* pile) {
125 int i;
126 for(i=MAX_HIDDEN+NUM_RANKS-1; i>=0 && !pile[i]; i--);
127 return i;
128 }
129 void turn_over(card_t* pile) {
130 int top = find_top(pile);
131 if (pile[top] < 0) pile[top] *= -1;
132 }
133 int check_won(void) {
134 for (int pile = 0; pile < NUM_DECKS*NUM_COLORS; pile++)
135 if (f.f[pile][NUM_RANKS-1] == NO_CARD) return 0;
136
137 return 1;
138 }
139 void win_anim(void) {
140 printf ("\033[?25l"); /* hide cursor */
141 for (;;) {
142 /* set cursor to random location */
143 int row = 1+random()%(24-op.s->width);
144 int col = 1+random()%(80-op.s->height);
145
146 /* draw random card */
147 int face = 1 + random() % 52;
148 for (int l = 0; l < op.s->height; l++) {
149 printf ("\033[%d;%dH", row+l, col);
150 printf ("%s", op.s->card[face][l]);
151 }
152 fflush (stdout);
153
154 /* exit on keypress */
155 struct pollfd p = {STDIN_FILENO, POLLIN};
156 if (poll (&p, 1, 80)) return;
157 }
158 }
159 // takeable actions {{{
160 #ifdef KLONDIKE
161 card_t stack_take(void) { /*NOTE: assert(f.w >= 0) */
162 card_t card = f.s[f.w];
163 /* move stack one over, so there are no gaps in it: */
164 for (int i = f.w; i < f.z-1; i++)
165 f.s[i] = f.s[i+1];
166 f.z--;
167 f.w--; /* make previous card visible again */
168 return card;
169 }
170 int t2f(int from, int to) { /* tableu to foundation */
171 from--; //remove off-by-one
172 int top_from = find_top(f.t[from]);
173 to = get_suit(f.t[from][top_from]);
174 int top_to = find_top(f.f[to]);
175 if ((top_to < 0 && get_rank(f.t[from][top_from]) == RANK_A)
176 || (top_to >= 0 && get_rank(f.f[to][top_to]) == get_rank(f.t[from][top_from])-1)) {
177 f.f[to][top_to+1] = f.t[from][top_from];
178 f.t[from][top_from] = NO_CARD;
179 turn_over(f.t[from]);
180 if (check_won()) return WON;
181 return OK;
182 } else return ERR;
183 }
184 int w2f(int from, int to) { /* waste to foundation */
185 if (f.w < 0) return ERR;
186 to = get_suit(f.s[f.w]);
187 int top_to = find_top(f.f[to]);
188 if ((top_to < 0 && get_rank(f.s[f.w]) == RANK_A)
189 || (top_to >= 0 && get_rank(f.f[to][top_to]) == get_rank(f.s[f.w])-1)) {
190 f.f[to][top_to+1] = stack_take();
191 if (check_won()) return WON;
192 return OK;
193 } else return ERR;
194
195 }
196 int s2w(int from, int to) { /* stock to waste */
197 if (f.z == 0) return ERR;
198 f.w++;
199 if (f.w == f.z) f.w = -1;
200 return OK;
201 }
202 int w2s(int from, int to) { /* waste to stock (undoes stock to waste) */
203 if (f.z == 0) return ERR;
204 f.w--;
205 if (f.w < -1) f.w = f.z-1;
206 return OK;
207 }
208 int f2t(int from, int to) { /* foundation to tableu */
209 to--; //remove off-by-one
210 int top_to = find_top(f.t[to]);
211 printf ("take from (1-4): "); fflush (stdout);
212 from = getchar() - '0';
213 if (from > 4 || from < 1) return ERR;
214 from--; //remove off-by-one
215 int top_from = find_top(f.f[from]);
216
217 if ((get_color(f.t[to][top_to]) != get_color(f.f[from][top_from]))
218 && (get_rank(f.t[to][top_to]) == get_rank(f.f[from][top_from])+1)) {
219 f.t[to][top_to+1] = f.f[from][top_from];
220 f.f[from][top_from] = NO_CARD;
221 return OK;
222 } else return ERR;
223 }
224 int w2t(int from, int to) { //waste to tableu
225 to--; //remove off-by-one
226 int top_to = find_top(f.t[to]);
227 if (((get_color(f.t[to][top_to]) != get_color(f.s[f.w]))
228 && (get_rank(f.t[to][top_to]) == get_rank(f.s[f.w])+1))
229 || (top_to < 0 && get_rank(f.s[f.w]) == RANK_K)) {
230 f.t[to][top_to+1] = stack_take();
231 return OK;
232 } else return ERR;
233 }
234 int t2t(int from, int to) {
235 from--; to--; //remove off-by-one
236 int top_to = find_top(f.t[to]);
237 int top_from = find_top(f.t[from]);
238 for (int i = top_from; i >=0; i--) {
239 if (((get_color(f.t[to][top_to]) != get_color(f.t[from][i]))
240 && (get_rank(f.t[to][top_to]) == get_rank(f.t[from][i])+1)
241 && f.t[from][i] > NO_CARD) /* card face up? */
242 || (top_to < 0 && get_rank(f.t[from][i]) == RANK_K)) {
243 /* move cards [i..top_from] to their destination */
244 for (;i <= top_from; i++) {
245 top_to++;
246 f.t[to][top_to] = f.t[from][i];
247 f.t[from][i] = NO_CARD;
248 }
249 turn_over(f.t[from]);
250 return OK;
251 }
252 }
253 return ERR; /* no such move possible */
254 }
255 #elif defined SPIDER
256 void remove_if_complete (card_t* pile) { //TODO: cleanup
257 /* test if K...A complete; move to foundation if so */
258 int new_top_to = find_top(pile);
259 for (int i = new_top_to; i>=0; i--) {
260 if ((i+1 < PILE_SIZE && pile[i+1] != NO_CARD) // card below or last? XXX: copied from t2t()--make function
261 && (get_rank(pile[i+1]) != get_rank(pile[i])-1) //cards not consecutive?
262 ) {
263 return;
264 }
265 if ((i+1 < PILE_SIZE && pile[i+1] != NO_CARD) // card below or last?
266 && (get_suit(pile[i+1]) != get_suit(pile[i])) //cards not same suit?
267 ) {
268 return;
269 }
270 }
271 if (get_rank(pile[new_top_to+RANK_K]) != RANK_K) return;
272 if (get_rank(pile[new_top_to]) != RANK_A) return;
273 int j = 0; //XXX: to seperate foundation piles!
274 for (int i = new_top_to; i >= new_top_to-13; i--) {//TODO: magic value 13
275 f.f[0][j++] = pile[i];
276 pile[i] = NO_CARD;
277 }
278 turn_over(pile);
279 }
280 int t2t(int from, int to) { //TODO: in dire need of cleanup
281 //TODO: segfaulted once on large column
282 //TODO: sometimes moving doesn't work (ERR when it should be OK) XXX
283 from--; to--; //remove off-by-one
284 (from < 0) && (from = 9); /* '0' is tenth ([9]) pile */
285 (to < 0) && (to = 9); /* ditto */
286
287 int top_from = find_top(f.t[from]);
288 int top_to = find_top(f.t[to]);
289 int empty_to = 0; //awful name :/
290 if (top_to < 0) { /* empty pile? */
291 printf ("\rup to (a23456789xjqk): ");
292 empty_to = getchar();
293 switch (empty_to) {
294 case 'a': case 'A': empty_to = RANK_A; break;
295 case '0': /* fallthrough */
296 case 'x': case 'X': empty_to = RANK_X; break;
297 case 'j': case 'J': empty_to = RANK_J; break;
298 case 'q': case 'Q': empty_to = RANK_Q; break;
299 case 'k': case 'K': empty_to = RANK_K; break;
300 default: empty_to -= '0'+1; /* NOTE@+1: ace == 0, not 1 */
301 }
302 if (empty_to < RANK_A || empty_to > RANK_K) return -1;
303 }
304 for (int i = top_from; i >= 0; i--) {
305 if ((i+1 < PILE_SIZE && f.t[from][i+1] != NO_CARD) // card below or last?
306 && (get_rank(f.t[from][i+1]) != get_rank(f.t[from][i])-1) //cards not consecutive?
307 ) {
308 break;
309 }
310 if ((i+1 < PILE_SIZE && f.t[from][i+1] != NO_CARD) // card below or last?
311 && (get_suit(f.t[from][i+1]) != get_suit(f.t[from][i])) //cards not same suit?
312 ) {
313 break;
314 }
315
316 if ((get_rank(f.t[from][i]) == get_rank(f.t[to][top_to])-1) // consecutive?
317 || (empty_to > 0 && get_rank(f.t[from][i]) == empty_to)) { //to empty pile and rank ok?
318 for (;i <= top_from; i++) {
319 top_to++;
320 f.t[to][top_to] = f.t[from][i];
321 f.t[from][i] = NO_CARD;
322 }
323 turn_over(f.t[from]);
324 remove_if_complete (f.t[to]);
325 if (check_won()) return WON;
326 return OK;
327 }
328 }
329
330 return ERR; /* no such move possible */
331 }
332 int s2t(int from, int to) {
333 if (f.z <= 0) return ERR; /* stack out of cards */
334 for (int pile = 0; pile < NUM_PILES; pile++)
335 if (f.t[pile][0]==NO_CARD) return ERR; /*no piles may be empty*/
336 for (int pile = 0; pile < NUM_PILES; pile++) {
337 f.t[pile][find_top(f.t[pile])+1] = f.s[--f.z];
338 }
339 return OK;
340 }
341 #endif
342 int nop(int from, int to) { return ERR; }
343 // }}}
344
345 int get_cmd (int* from, int* to) {
346 //returns 0 on success or an error code indicating game quit, new game,...
347 //TODO: check validity, escape sequences (mouse, cursor keys)
348 char f, t;
349 printf ("from: "); fflush (stdout);
350 f = getchar();
351 #ifdef SPIDER
352 if (f=='\n') { //TODO: also in klondike
353 *from = 10;
354 *to = 0;
355 return CMD_MOVE;
356 }
357 #endif
358 switch (f) {
359 case 'q': return CMD_QUIT;
360 case 'r': return CMD_NEW;
361 case 'h': return CMD_HINT;
362 case '?': return CMD_HELP;
363 default: if (f < '0' || f > '9') return CMD_INVAL;
364 }
365 printf ("\r \rto: "); fflush (stdout);
366 t =
367 #ifdef KLONDIKE
368 (f=='8')?'9':
369 #endif
370 getchar();
371 *from = f-'0';
372 *to = t-'0';
373 return CMD_MOVE;
374 }
375
376 void deal(void) {
377 f = (const struct playfield){0}; /* clear playfield */
378 card_t deck[DECK_SIZE*NUM_DECKS];
379 int avail = DECK_SIZE*NUM_DECKS;
380 for (int i = 0; i < DECK_SIZE*NUM_DECKS; i++) deck[i] = (i%DECK_SIZE)+1;
381 #ifdef SPIDER
382 if (op.m != NORMAL) for (int i = 0; i < DECK_SIZE*NUM_DECKS; i++) {
383 if (op.m == MEDIUM) deck[i] = 1+((deck[i]-1) | 2);
384 if (op.m == EASY) deck[i] = 1+((deck[i]-1) | 2 | 1);
385 /* the 1+ -1 dance gets rid of the offset created by NO_CARD */
386 }
387 #endif
388 //XXX srandom (time(NULL));
389 /*XXX*/ long seed = time(NULL);
390 /*XXX*/ srandom (seed);
391 for (int i = DECK_SIZE*NUM_DECKS-1; i > 0; i--) { //fisher-yates
392 int j = random() % (i+1);
393 if (j-i) deck[i]^=deck[j],deck[j]^=deck[i],deck[i]^=deck[j];
394 }
395 ///////////////////////////////////////////////XXX
396 //sometimes we see duplicate cards. this tries to catch that
397 int count[_NUM_CARDS_internal] = {0};
398 for (int i = 0; i < DECK_SIZE*NUM_DECKS; i++)
399 count[deck[i]]++;
400 for (int i = 0; i < _NUM_CARDS_internal; i++){ //0 is NO_CARD
401 #ifdef SPIDER
402 int x = op.m==MEDIUM?2:op.m==EASY?4:1;
403 #else
404 int x = 1;
405 #endif
406 if (deck[i] == NO_CARD) continue;
407 if (count[deck[i]] != NUM_DECKS*x) {
408 screen_setup(0);
409 printf ("found duplicate card with seed %lx!\n", seed);
410 for (int i = 1; i < _NUM_CARDS_internal; i++)
411 printf ("%3d of %2d\n", count[deck[i]], deck[i]);
412 exit(1);
413 }
414 }
415 ///////////////////////////////////////////////XXX
416
417 /* deal cards: */
418 for (int i = 0; i < NUM_PILES; i++) {
419 #ifdef KLONDIKE
420 int closed = i; /* pile n has n closed cards, then 1 open */
421 #elif defined SPIDER
422 int closed = i<4?5:4; /* pile 1-4 have 5, 5-10 have 4 closed */
423 #endif
424 /* face down cards are negated: */
425 for (int j = 0; j < closed; j++) f.t[i][j] = -deck[--avail];
426 f.t[i][closed] = deck[--avail]; /* the face-up card */
427 }
428 /* rest of the cards to the stock; NOTE: assert(avail==50) for spider */
429 for (f.z = 0; avail; f.z++) f.s[f.z] = deck[--avail];
430 f.w = -1; /* @start: nothing on waste (no waste in spider -> const) */
431 }
432
433 #define print_hi(test, str) /*for highlighting during get_cmd() */ \
434 printf ("%s%s%s", test?"\033[7m":"", str, test?"\033[27m":"") //TODO
435 void print_table(void) { //{{{
436 printf("\033[2J\033[H"); /* clear screen, reset cursor */
437 #ifdef KLONDIKE
438 /* print stock, waste and foundation: */
439 for (int line = 0; line < op.s->height; line++) {
440 printf ("%s", ( /* stock */
441 (f.w < f.z-1)?op.s->facedown
442 :op.s->placeholder)[line]);
443 printf ("%s", ( /* waste */
444 /* NOTE: cast, because f.w sometimes is (short)-1 !? */
445 ((short)f.w >= 0)?op.s->card[f.s[f.w]]
446 :op.s->placeholder)[line]);
447 printf ("%s", op.s->card[NO_CARD][line]); /* spacer */
448 /* foundation: */
449 for (int pile = 0; pile < NUM_SUITS; pile++) {
450 int card = find_top(f.f[pile]);
451 printf ("%s",
452 (card < 0)?op.s->placeholder[line]
453 :op.s->card[f.f[pile][card]][line]);
454 }
455 printf("\n");
456 }
457 printf("\n");
458 #endif
459 /* print tableu piles: */
460 int row[NUM_PILES] = {0};
461 int line[NUM_PILES]= {0};
462 int label[NUM_PILES]={0};// :|
463 int line_had_card; // :|
464 do {
465 line_had_card = 0;
466 for (int pile = 0; pile < NUM_PILES; pile++) {
467 card_t card = f.t[pile][row[pile]];
468 card_t next = f.t[pile][row[pile]+1];
469 printf ("%s", (
470 (card<0)?op.s->facedown
471 :op.s->card[card]
472 )[line[pile]]);
473
474 if (++line[pile] >= (next?op.s->overlap:op.s->height) //normal overlap
475 #if 0 //XXX
476 || (line[pile] >= 1 &&
477 f.t[pile][row[pile]] < 0 &&
478 f.t[pile][row[pile]+1] <0) //extreme overlap on closed
479 || (0) //extreme overlap on sequence TODO
480 #endif
481 ) {
482 line[pile]=0;
483 row[pile]++;
484 }
485 if(!card && !label[pile]) { /* tableu labels: */
486 label[pile] = 1;
487 printf ("\b\b%d ", (pile+1) % 10); //XXX: hack
488 }
489 line_had_card |= !!card;
490 }
491 printf ("\n");
492 } while (line_had_card);
493 }//}}}
494
495 void visbell (void) {
496 printf ("\033[?5h"); fflush (stdout);
497 usleep (100000);
498 printf ("\033[?5l"); fflush (stdout);
499 }
500
501 void append_undo (int n, int f, int t) {
502 //check if we have to free redo buffer (.next)
503 //malloc
504 //update pointers
505 *NULL;
506 }
507
508 void screen_setup (int enable) {
509 if (enable) {
510 raw_mode(1);
511 printf ("\033[s\033[?47h"); /* save cursor, alternate screen */
512 printf ("\033[H\033[J"); /* reset cursor, clear screen */
513 //TODO//printf ("\033[?1000h\033[?25l"); /* enable mouse, hide cursor */
514 //printf (op.s->init_seq); /*swich charset, if necessary*/
515 } else {
516 //printf (op.s->reset_seq);/*reset charset, if necessary*/
517 //TODO//printf ("\033[?9l\033[?25h"); /* disable mouse, show cursor */
518 printf ("\033[?47l\033[u"); /* primary screen, restore cursor */
519 raw_mode(0);
520 }
521 }
522
523 void raw_mode(int enable) { //{{{
524 static struct termios saved_term_mode;
525 struct termios raw_term_mode;
526
527 if (enable) {
528 tcgetattr(STDIN_FILENO, &saved_term_mode);
529 raw_term_mode = saved_term_mode;
530 raw_term_mode.c_lflag &= ~(ICANON | ECHO);
531 raw_term_mode.c_cc[VMIN] = 1 ;
532 raw_term_mode.c_cc[VTIME] = 0;
533 tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_term_mode);
534 } else {
535 tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_term_mode);
536 }
537 } //}}}
538
539 //vim: foldmethod=marker
Imprint / Impressum