From a4abba6c05d5c24056f07a9eb59f25a25c220c91 Mon Sep 17 00:00:00 2001 From: girst Date: Sat, 3 Nov 2018 19:37:55 +0100 Subject: [PATCH] sol prototype complete, bunch of small stuff detect if won, spider: increased tableu to num_cards*num_decks because cards can be put ontop without following order, vt220 preliminary charset (not really usable), ... --- Makefile | 3 + README.md | 9 +-- schemes.h | 63 +++++++++++++++---- sol.c | 177 +++++++++++++++++++++++++++++++++--------------------- sol.h | 13 ++++ 5 files changed, 181 insertions(+), 84 deletions(-) diff --git a/Makefile b/Makefile index dcb131e..30db1b0 100644 --- a/Makefile +++ b/Makefile @@ -16,3 +16,6 @@ clean: getfuns: grep -o '^\w.* \w.*(.*)[^/]*{' sol.c|sed 's/ *{$/;/' + +test: + grep --color=always 'TODO\|XXX\|\/xxx-slashes-xxx\/[^:]' * diff --git a/README.md b/README.md index e8de683..511a9f6 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ play klondike and spider solitaire in your unicode terminal. ## TODO + TODO: duplicate card ♠A found in tableu + * TODO: make piles 0-indexed in klondike as well * TODO: sigcont handler! * TODO: DATA STRUCTURES FOR UNDO CURRENT DS FOR STACK/WASTE ("move over") IS INCOMPATIBLE WITH UNDO!!!! @@ -14,7 +15,6 @@ keeping taken card slots empty allows them to be reinserted by undo() middle cards * TODO: cleanup: in `x2y()` functions there is a lot of replication and noise when calculating legal moves, top cards, etc. - * TODO: keyboard alias: twice same key == waste/pile -> foundation * TODO: hjkl keyboard mode * TODO: mouse mode (mouse already enabled) * TODO: highlight `from` pile, so users can see at what input stage they are @@ -22,11 +22,12 @@ keeping taken card slots empty allows them to be reinserted by undo() * TODO: spider: easy/medium difficulty: only deal 1/2 suits instead of 4 -> deal() * TODO: use `#ifdef`s to differentiate games (sol, spider, ed-sol, ed-spider) * TODO: patience: allow taking from 0(foundation) - * TODO: s/KLONDIKE/PATIENCE/g * TODO: scores (see !w), variants: draw 3, max. n overturns * TODO: undo + * TODO: suggest moves * TODO: vt220 mode * TODO: ed(1) mode (solEDaire): playable on a line printer; ascii/ibm only? + * DONE: keyboard alias: twice same key == waste/pile -> foundation ## Notes @@ -61,7 +62,7 @@ keeping taken card slots empty allows them to be reinserted by undo() otherwise if it is the last one, we draw the whole one. this will give a look like in `~/solitaire-tests` ``` - .grid=7, //vertical alignment + .grid=7, /*vertical alignment*/ .overlap=2, .cards = [ ["╭───╮", @@ -70,7 +71,7 @@ keeping taken card slots empty allows them to be reinserted by undo() "╰───╯", NULL], ] - //or: + /*or:*/ .grid=2, .overlap=1, .cards = [ diff --git a/schemes.h b/schemes.h index 8520b7a..1923c5f 100644 --- a/schemes.h +++ b/schemes.h @@ -30,7 +30,7 @@ const struct scheme unicode_large_mono = { .height = 4, .overlap = 2, .card = { - [NO_CARD] = (char*[]){" "," "," "," ", NULL}, + [NO_CARD] = (char*[]){" "," "," "," "}, [CLU_A] = ULCARD("♣","A"), [DIA_A] = ULCARD("♦","A"), [HEA_A] = ULCARD("♥","A"), [SPA_A] = ULCARD("♠","A"), [CLU_2] = ULCARD("♣","2"), [DIA_2] = ULCARD("♦","2"), @@ -62,13 +62,13 @@ const struct scheme unicode_large_mono = { "╭───╮", "│▚▚▚│", "│▚▚▚│", - "╰───╯", NULL + "╰───╯" }, .placeholder = (char*[]){ - "╭╌╌╌╮", //┄┈ - "╎ ╎", //┆┊ - "╎ ╎", //┆┊ - "╰╌╌╌╯", NULL + "╭╌╌╌╮", + "╎ ╎", + "╎ ╎", + "╰╌╌╌╯" }, .init_seq = NULL, .reset_seq = NULL, @@ -78,7 +78,7 @@ const struct scheme unicode_large_color = { .height = 4, .overlap = 2, .card = { - [NO_CARD] = (char*[]){" "," "," "," ", NULL}, + [NO_CARD] = (char*[]){" "," "," "," "}, [CLU_A] = BULCARD("♣","A"), [DIA_A] = RULCARD("♦","A"), [HEA_A] = RULCARD("♥","A"), [SPA_A] = BULCARD("♠","A"), [CLU_2] = BULCARD("♣","2"), [DIA_2] = RULCARD("♦","2"), @@ -110,13 +110,13 @@ const struct scheme unicode_large_color = { "╭───╮", "│\033[94m▚▚▚\033[0m│", "│\033[94m▚▚▚\033[0m│", - "╰───╯", NULL + "╰───╯" }, .placeholder = (char*[]){ - "╭╌╌╌╮", //┄┈ - "╎ ╎", //┆┊ - "╎ ╎", //┆┊ - "╰╌╌╌╯", NULL + "╭╌╌╌╮", + "╎ ╎", + "╎ ╎", + "╰╌╌╌╯" }, .init_seq = NULL, .reset_seq = NULL, @@ -159,4 +159,43 @@ const struct scheme unicode_small_mono = { .init_seq = NULL, .reset_seq = NULL, }; + +const struct scheme vt220_small = { //TODO: this is a placeholder + .width = 2, + .height = 1, + .overlap = 1, + .card = { + [NO_CARD] = (char*[]){" "}, + [CLU_A] = USCARD("CA"), [DIA_A] = USCARD("DA"), + [HEA_A] = USCARD("HA"), [SPA_A] = USCARD("SA"), + [CLU_2] = USCARD("C2"), [DIA_2] = USCARD("D2"), + [HEA_2] = USCARD("H2"), [SPA_2] = USCARD("S2"), + [CLU_3] = USCARD("C3"), [DIA_3] = USCARD("D3"), + [HEA_3] = USCARD("H3"), [SPA_3] = USCARD("S3"), + [CLU_4] = USCARD("C4"), [DIA_4] = USCARD("D4"), + [HEA_4] = USCARD("H4"), [SPA_4] = USCARD("S4"), + [CLU_5] = USCARD("C5"), [DIA_5] = USCARD("D5"), + [HEA_5] = USCARD("H5"), [SPA_5] = USCARD("S5"), + [CLU_6] = USCARD("C6"), [DIA_6] = USCARD("D6"), + [HEA_6] = USCARD("H6"), [SPA_6] = USCARD("S6"), + [CLU_7] = USCARD("C7"), [DIA_7] = USCARD("D7"), + [HEA_7] = USCARD("H7"), [SPA_7] = USCARD("S7"), + [CLU_8] = USCARD("C8"), [DIA_8] = USCARD("D8"), + [HEA_8] = USCARD("H8"), [SPA_8] = USCARD("S8"), + [CLU_9] = USCARD("C9"), [DIA_9] = USCARD("D9"), + [HEA_9] = USCARD("H9"), [SPA_9] = USCARD("S9"), + [CLU_X] = USCARD("CX"), [DIA_X] = USCARD("DX"), + [HEA_X] = USCARD("HX"), [SPA_X] = USCARD("SX"), + [CLU_J] = USCARD("CJ"), [DIA_J] = USCARD("DJ"), + [HEA_J] = USCARD("HJ"), [SPA_J] = USCARD("SJ"), + [CLU_Q] = USCARD("CQ"), [DIA_Q] = USCARD("DQ"), + [HEA_Q] = USCARD("HQ"), [SPA_Q] = USCARD("SQ"), + [CLU_K] = USCARD("CK"), [DIA_K] = USCARD("DK"), + [HEA_K] = USCARD("HK"), [SPA_K] = USCARD("SK"), + }, + .facedown = (char*[]){"##"}, + .placeholder = (char*[]){"()"}, + .init_seq = NULL, + .reset_seq = NULL, +}; #endif diff --git a/sol.c b/sol.c index aa75872..be820f0 100644 --- a/sol.c +++ b/sol.c @@ -13,11 +13,13 @@ #define MAX_HIDDEN 6 /*how many cards are turned over at most in a tableu pile*/ #define MAX_STOCK 24 /*how many cards can be in the stock at most (=@start)*/ #define NUM_DECKS 1 +#define PILE_SIZE MAX_HIDDEN+NUM_RANKS #elif defined SPIDER #define MAX_HIDDEN 5 #define NUM_PILES 10 #define MAX_STOCK 50 /*how many cards can be dealt onto the piles*/ #define NUM_DECKS 2 +#define PILE_SIZE DECK_SIZE*NUM_DECKS /* no maximum stack size in spider :/ */ #endif #define get_suit(card) \ @@ -32,13 +34,8 @@ struct playfield { card_t s[MAX_STOCK]; /* stock */ int z; /* stock size */ int w; /* waste; index into stock (const -1 in spider) */ -#ifdef KLONDIKE - card_t f[NUM_SUITS][MAX_HIDDEN+NUM_RANKS]; /* foundation (oversized to ease find_top()) */ - card_t t[NUM_PILES][MAX_HIDDEN+NUM_RANKS]; /* tableu piles */ -#elif defined SPIDER - card_t f[NUM_DECKS*NUM_COLORS][MAX_HIDDEN+NUM_RANKS]; //each completed set gets put on its own pile, so undo is possible - card_t t[NUM_PILES][MAX_HIDDEN+NUM_RANKS]; -#endif + card_t f[NUM_DECKS*NUM_SUITS][PILE_SIZE]; /* foundation (XXX@spider:complete set gets put on seperate pile, so undo is easy) */ + card_t t[NUM_PILES][PILE_SIZE]; /* tableu piles */ struct undo { int from; /* pile cards were taken from */ int to; /* pile cards were moved to */ @@ -48,29 +45,30 @@ struct playfield { } u; } f; struct opts { +#ifdef SPIDER + int m; /* difficulty mode */ +#endif const struct scheme* s; } op; +// action table {{{ #ifdef KLONDIKE -//declare action as array 10 of array 10 of pointer to function (int,int) returning int -// this stores a function pointer for every takeable action that is then called by execute() -//lines = from, cols = to +/* stores a function pointer for every takeable action; called by game loop */ int (*action[10][10])(int,int) = { - /* 0 1 2 3 4 5 6 7 8 9 */ -/* 0 */ { nop, f2t, f2t, f2t, f2t, f2t, f2t, f2t, nop, nop }, -/* 1 */ { t2f, nop, t2t, t2t, t2t, t2t, t2t, t2t, nop, nop }, -/* 2 */ { t2f, t2t, nop, t2t, t2t, t2t, t2t, t2t, nop, nop }, -/* 3 */ { t2f, t2t, t2t, nop, t2t, t2t, t2t, t2t, nop, nop }, -/* 4 */ { t2f, t2t, t2t, t2t, nop, t2t, t2t, t2t, nop, nop }, -/* 5 */ { t2f, t2t, t2t, t2t, t2t, nop, t2t, t2t, nop, nop }, -/* 6 */ { t2f, t2t, t2t, t2t, t2t, t2t, nop, t2t, nop, nop }, -/* 7 */ { t2f, t2t, t2t, t2t, t2t, t2t, t2t, nop, nop, nop }, -/* 8 */ { nop, nop, nop, nop, nop, nop, nop, nop, nop, s2w }, -/* 9 */ { w2f, w2t, w2t, w2t, w2t, w2t, w2t, w2t, w2s, nop }, + /*fnd 1 2 3 4 5 6 7 stk wst*/ +/*fnd*/ { nop, f2t, f2t, f2t, f2t, f2t, f2t, f2t, nop, nop }, +/* 1 */ { t2f, t2f, t2t, t2t, t2t, t2t, t2t, t2t, nop, nop }, +/* 2 */ { t2f, t2t, t2f, t2t, t2t, t2t, t2t, t2t, nop, nop }, +/* 3 */ { t2f, t2t, t2t, t2f, t2t, t2t, t2t, t2t, nop, nop }, +/* 4 */ { t2f, t2t, t2t, t2t, t2f, t2t, t2t, t2t, nop, nop }, +/* 5 */ { t2f, t2t, t2t, t2t, t2t, t2f, t2t, t2t, nop, nop }, +/* 6 */ { t2f, t2t, t2t, t2t, t2t, t2t, t2f, t2t, nop, nop }, +/* 7 */ { t2f, t2t, t2t, t2t, t2t, t2t, t2t, t2f, nop, nop }, +/*stk*/ { nop, nop, nop, nop, nop, nop, nop, nop, nop, s2w }, +/*wst*/ { w2f, w2t, w2t, w2t, w2t, w2t, w2t, w2t, w2s, w2f }, }; #elif defined SPIDER int (*action[11][10])(int,int) = { - //piles are zero-indexed in spider; stk=stock /* 0 1 2 3 4 5 6 7 8 9 */ /* 0 */ { nop, t2t, t2t, t2t, t2t, t2t, t2t, t2t, t2t, t2t }, /* 1 */ { t2t, nop, t2t, t2t, t2t, t2t, t2t, t2t, t2t, t2t }, @@ -85,11 +83,13 @@ int (*action[11][10])(int,int) = { /*stk*/ { s2t, s2t, s2t, s2t, s2t, s2t, s2t, s2t, s2t, s2t }, }; #endif +// }}} int main(int argc, char** argv) { - //op.s = &unicode_small_mono; - //op.s = &unicode_large_mono; op.s = &unicode_large_color; +#ifdef SPIDER + op.m = MEDIUM; //TODO: make configurable +#endif screen_setup(1); sol(); //TODO: restart, etc. screen_setup(0); @@ -103,8 +103,11 @@ void sol(void) { for(;;) { switch (get_cmd(&from, &to)) { case CMD_MOVE: - if (action[from][to](from,to)) - visbell(); + switch (action[from][to](from,to)) { + case OK: break; + case ERR: visbell(); break; + case WON: return; //TODO: do something nice + } break; case CMD_QUIT: return; } @@ -121,6 +124,13 @@ void turn_over(card_t* pile) { int top = find_top(pile); if (pile[top] < 0) pile[top] *= -1; } +int check_won(void) { + for (int pile = 0; pile < NUM_DECKS*NUM_COLORS; pile++) + if (f.f[pile][NUM_RANKS-1] == NO_CARD) return 0; + + return 1; +} +// takeable actions {{{ #ifdef KLONDIKE card_t stack_take(void) { /*NOTE: assert(f.w >= 0) */ card_t card = f.s[f.w]; @@ -141,43 +151,49 @@ int t2f(int from, int to) { /* tableu to foundation */ f.f[to][top_to+1] = f.t[from][top_from]; f.t[from][top_from] = NO_CARD; turn_over(f.t[from]); - return 0; - } else return 1; + if (check_won()) return WON; + return OK; + } else return ERR; } int w2f(int from, int to) { /* waste to foundation */ - if (f.w < 0) return 1; + if (f.w < 0) return ERR; to = get_suit(f.s[f.w]); int top_to = find_top(f.f[to]); if ((top_to < 0 && get_rank(f.s[f.w]) == RANK_A) || (get_rank(f.f[to][top_to]) == get_rank(f.s[f.w])-1)) { f.f[to][top_to+1] = stack_take(); - return 0; - } else return 1; + if (check_won()) return WON; + return OK; + } else return ERR; } int s2w(int from, int to) { /* stock to waste */ - if (f.z == 0) return 1; + if (f.z == 0) return ERR; f.w++; if (f.w == f.z) f.w = -1; - return 0; + return OK; } int w2s(int from, int to) { /* waste to stock (undoes stock to waste) */ - if (f.z == 0) return 1; + if (f.z == 0) return ERR; f.w--; if (f.w < -1) f.w = f.z-1; - return 0; + return OK; } int f2t(int from, int to) { /* foundation to tableu */ - //TODO: there are two possible cards one can take. choosing one isn't implemented yet! to--; //remove off-by-one int top_to = find_top(f.t[to]); - from = get_color(f.t[to][top_to]); + printf ("take from (1-4): "); fflush (stdout); + from = getchar() - '0'; + if (from > 4 || from < 1) return ERR; + from--; //remove off-by-one int top_from = find_top(f.f[from]); + if ((get_color(f.t[to][top_to]) != get_color(f.f[from][top_from])) && (get_rank(f.t[to][top_to]) == get_rank(f.f[from][top_from])+1)) { - f.t[to][top_to+1] = stack_take(); //XXX: not stack_take! - return 0; - } else return 1; + f.t[to][top_to+1] = f.f[from][top_from]; + f.f[from][top_from] = NO_CARD; + return OK; + } else return ERR; } int w2t(int from, int to) { //waste to tableu to--; //remove off-by-one @@ -186,17 +202,17 @@ int w2t(int from, int to) { //waste to tableu && (get_rank(f.t[to][top_to]) == get_rank(f.s[f.w])+1)) || (top_to < 0 && get_rank(f.s[f.w]) == RANK_K)) { f.t[to][top_to+1] = stack_take(); - return 0; - } else return 1; + return OK; + } else return ERR; } int t2t(int from, int to) { from--; to--; //remove off-by-one int top_to = find_top(f.t[to]); int top_from = find_top(f.t[from]); for (int i = top_from; i >=0; i--) { - //TODO: check that we aren't moving facedown cards! if (((get_color(f.t[to][top_to]) != get_color(f.t[from][i])) - && (get_rank(f.t[to][top_to]) == get_rank(f.t[from][i])+1)) + && (get_rank(f.t[to][top_to]) == get_rank(f.t[from][i])+1) + && f.t[from][i] > NO_CARD) /* card face up? */ || (top_to < 0 && get_rank(f.t[from][i]) == RANK_K)) { /* move cards [i..top_from] to their destination */ for (;i <= top_from; i++) { @@ -205,10 +221,10 @@ int t2t(int from, int to) { f.t[from][i] = NO_CARD; } turn_over(f.t[from]); - return 0; + return OK; } } - return 1; /* no such move possible */ + return ERR; /* no such move possible */ } #elif defined SPIDER int t2t(int from, int to) { //TODO: in dire need of cleanup @@ -219,13 +235,13 @@ int t2t(int from, int to) { //TODO: in dire need of cleanup int top_from = find_top(f.t[from]); int top_to = find_top(f.t[to]); for (int i = top_from; i >= 0; i--) { - if ((f.t[from][i+1] != NO_CARD) // if there is a card below (TODO: check maximum) - && (get_rank(f.t[from][i+1]) != get_rank(f.t[from][i])-1) //and cards not consecutive? + if ((i+1 < PILE_SIZE && f.t[from][i+1] != NO_CARD) // card below or last? + && (get_rank(f.t[from][i+1]) != get_rank(f.t[from][i])-1) //cards not consecutive? ) { break; } - if ((f.t[from][i+1] != NO_CARD) // if there is a card below (TODO: check maximum) - && (get_suit(f.t[from][i+1]) != get_suit(f.t[from][i])) //and cards not same suit? + if ((i+1 < PILE_SIZE && f.t[from][i+1] != NO_CARD) // card below or last? + && (get_suit(f.t[from][i+1]) != get_suit(f.t[from][i])) //cards not same suit? ) { break; } @@ -238,27 +254,50 @@ int t2t(int from, int to) { //TODO: in dire need of cleanup } turn_over(f.t[from]); //TODO: test if k..a complete; move to foundation if so - return 0; +#define x(n) (f.t[i][top_to-n]) + if (x(0)==RANK_A + && x(1)==RANK_2 + && x(2)==RANK_3 + && x(3)==RANK_4 + && x(4)==RANK_5 + && x(5)==RANK_6 + && x(6)==RANK_7 + && x(7)==RANK_8 + && x(8)==RANK_9 + && x(9)==RANK_X + && x(10)==RANK_J + && x(11)==RANK_Q + && x(12)==RANK_K) {//TODO: check suit +#undef x + int j = 0; + for (int i = top_to; i >= top_to-13; i--) { + f.f[0][j++] = f.t[to][i]; + f.t[to][i] = NO_CARD; + } + if (check_won()) return WON; + } + return OK; } } - return 1; /* no such move possible */ + return ERR; /* no such move possible */ } int s2t(int from, int to) { - if (f.z <= 0) return 1; /* stack out of cards */ + if (f.z <= 0) return ERR; /* stack out of cards */ for (int pile = 0; pile < NUM_PILES; pile++) - if (f.t[pile][0] == NO_CARD) return 1; /*no piles may be empty*/ + if (f.t[pile][0]==NO_CARD) return ERR; /*no piles may be empty*/ for (int pile = 0; pile < NUM_PILES; pile++) { f.t[pile][find_top(f.t[pile])+1] = f.s[--f.z]; } - return 0; + return OK; } #endif -int nop(int from, int to) { return 1; } +int nop(int from, int to) { return ERR; } +// }}} int get_cmd (int* from, int* to) { //returns 0 on success or an error code indicating game quit, new game,... - //TODO: check validity + //TODO: check validity, escape sequences (mouse, cursor keys) char f, t; f = getchar(); #ifdef SPIDER @@ -289,13 +328,10 @@ void deal(void) { int avail = DECK_SIZE*NUM_DECKS; for (int i = 0; i < DECK_SIZE*NUM_DECKS; i++) deck[i] = (i%DECK_SIZE)+1; #ifdef SPIDER - //if spider-mode easy/medium: - for (int i = 0; i < DECK_SIZE*NUM_DECKS; i++) { - int easy = 0; - int medium = 1; //XXX - if (easy||medium) deck[i] = 1+((deck[i]-1) | 2); - if (easy ) deck[i] = 1+((deck[i]-1) | 1); - // the 1+ -1 dance gets rid of the offset created by NO_CARD + if (op.m != NORMAL) for (int i = 0; i < DECK_SIZE*NUM_DECKS; i++) { + if (op.m == MEDIUM) deck[i] = 1+((deck[i]-1) | 2); + if (op.m == EASY) deck[i] = 1+((deck[i]-1) | 2 | 1); + /* the 1+ -1 dance gets rid of the offset created by NO_CARD */ } #endif srandom (time(NULL)); @@ -362,8 +398,13 @@ void print_table(void) { //{{{ )[line[pile]]); if (++line[pile] >= (next?op.s->overlap:op.s->height) //normal overlap - || (line[pile] >= 1 && f.t[pile][row[pile]] < 0)) { //extreme overlap (on closed) - //TODO: allow extreme overlap on sequences +#if 0 //XXX + || (line[pile] >= 1 && + f.t[pile][row[pile]] < 0 && + f.t[pile][row[pile]+1] <0) //extreme overlap on closed + || (0) //extreme overlap on sequence TODO +#endif + ) { line[pile]=0; row[pile]++; } @@ -395,13 +436,13 @@ void screen_setup (int enable) { raw_mode(1); printf ("\033[s\033[?47h"); /* save cursor, alternate screen */ printf ("\033[H\033[J"); /* reset cursor, clear screen */ - printf ("\033[?1000h\033[?25l"); /* enable mouse, hide cursor */ + //XXX//printf ("\033[?1000h\033[?25l"); /* enable mouse, hide cursor */ if (op.s->init_seq) printf (op.s->init_seq); /*swich charset, if necessary*/ } else { if (op.s->reset_seq) printf (op.s->reset_seq);/*reset charset, if necessary*/ - printf ("\033[?9l\033[?25h"); /* disable mouse, show cursor */ + //XXX//printf ("\033[?9l\033[?25h"); /* disable mouse, show cursor */ printf ("\033[?47l\033[u"); /* primary screen, restore cursor */ raw_mode(0); } diff --git a/sol.h b/sol.h index 5c6de9d..8d0b684 100644 --- a/sol.h +++ b/sol.h @@ -48,6 +48,12 @@ enum ranks { NUM_RANKS }; +enum action_return { + OK, /*move successful*/ + ERR, /*invalid move*/ + WON, /*game won*/ +}; + enum special_cmds { CMD_MOVE, CMD_INVAL, @@ -55,11 +61,18 @@ enum special_cmds { CMD_NEW, }; +enum difficulty { + NORMAL, + MEDIUM, + EASY, +}; + typedef signed char card_t; void sol(void); int find_top(card_t* pile); void turn_over(card_t* pile); +int check_won(void); #ifdef KLONDIKE card_t stack_take(void); int t2f(int from, int to); -- 2.39.3