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