├── Makefile ├── LICENSE ├── test.c └── main.c /Makefile: -------------------------------------------------------------------------------- 1 | CC=cc 2 | sol: 3 | $(CC) -o solitaire -DUNICODE -Wall main.c -lncursesw -ltinfo 4 | debug: 5 | $(CC) -g -o solitaire -DUNICODE -Wall main.c -lncursesw -ltinfo 6 | ascii: 7 | $(CC) -o solitaire -Wall main.c -lncursesw -ltinfo 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Juraj Borza 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | 2 | void test_cards() { 3 | card c5H = make_card(SUIT_HEART, RANK_5); 4 | card c5S = make_card(SUIT_SPADE, RANK_5); 5 | card c6H = make_card(SUIT_HEART, RANK_6); 6 | card c6D = make_card(SUIT_DIAMOND, RANK_6); 7 | card c5D = make_card(SUIT_DIAMOND, RANK_5); 8 | card c7D = make_card(SUIT_DIAMOND, RANK_7); 9 | printf("5s is black %d vs 1 \n", is_black(c5S)); 10 | printf("5s is red %d vs 0 \n", is_red(c5S)); 11 | printf("6h is red %d vs 1 \n", is_red(c6H)); 12 | printf("6h is black %d vs 0 \n", is_black(c6H)); 13 | printf("5h 6h is alternate %d vs 0 \n", is_alternate_color(c5H, c6H)); 14 | printf("5s 6h is alternate %d vs 1 \n", is_alternate_color(c5S, c6H)); 15 | printf("5d 6d is same suit %d vs 1 \n", is_same_suit(c5D, c6D)); 16 | printf("6h 6d is same suit %d vs 0 \n", is_same_suit(c6H, c6D)); 17 | printf("5s 6h in sequence %d vs 1 \n", is_in_sequence(c5S, c6H)); 18 | printf("6h 6d in sequence %d vs 0 \n", is_in_sequence(c6H, c6D)); 19 | printf("5s 7d in sequence %d vs 0 \n", is_in_sequence(c5S, c7D)); 20 | printf("5h 6h can be placed on foundation %d vs 1\n", 21 | can_be_placed_on_foundation(c5H, c6H)); 22 | printf("5h 6h can be placed on foundation %d vs 0\n", 23 | can_be_placed_on_foundation(c6H, c5H)); 24 | printf("6h 5s can be placed on the bottom %d vs 1\n", 25 | can_be_placed_bottom(c6H, c5S)); 26 | printf("5h 6h can be placed on the bottom %d vs 0\n", 27 | can_be_placed_bottom(c5H, c6H)); 28 | printf("6h 5h can be placed on the bottom %d vs 0\n", 29 | can_be_placed_bottom(c6H, c5H)); 30 | // TODO can_be_placed_on_bottom test 31 | } 32 | 33 | 34 | void test_pile_operations() { 35 | pile *initial_deck = make_pile(); 36 | fill_deck(initial_deck); 37 | print_deck(initial_deck); 38 | print_pile_ptrs(initial_deck); 39 | 40 | printf("\npop\n"); 41 | card_ptr popped_card = pop(initial_deck); 42 | PRINTCARD(popped_card); 43 | print_deck(initial_deck); 44 | print_pile_ptrs(initial_deck); 45 | 46 | printf("\npop\n"); 47 | card_ptr popped_card_2 = pop(initial_deck); 48 | PRINTCARD(popped_card_2); 49 | print_deck(initial_deck); 50 | print_pile_ptrs(initial_deck); 51 | 52 | printf("\nshift\n"); 53 | card_ptr dequeued_card = shift(initial_deck); 54 | PRINTCARD(dequeued_card); 55 | print_deck(initial_deck); 56 | print_pile_ptrs(initial_deck); 57 | 58 | printf("\nshift\n"); 59 | card_ptr dequeued_card_2 = shift(initial_deck); 60 | PRINTCARD(dequeued_card_2); 61 | print_deck(initial_deck); 62 | print_pile_ptrs(initial_deck); 63 | 64 | printf("\nunshift 2H\n"); 65 | unshift(initial_deck, dequeued_card_2); 66 | print_deck(initial_deck); 67 | print_pile_ptrs(initial_deck); 68 | 69 | printf("\nunshift 3H\n"); 70 | unshift(initial_deck, dequeued_card); 71 | print_deck(initial_deck); 72 | print_pile_ptrs(initial_deck); 73 | 74 | printf("\npush 4H\n"); 75 | push(initial_deck, popped_card_2); 76 | print_deck(initial_deck); 77 | print_pile_ptrs(initial_deck); 78 | } 79 | 80 | void print_pile_ptrs(pile *pile) { 81 | printf("@["); 82 | card_node *head = pile->head; 83 | for (int i = 0; i < pile->num_cards; i++) { 84 | card *c = head->value; 85 | print_card(c); 86 | printf(" @ %p | ", c); 87 | 88 | head = head->next; 89 | } 90 | printf("] (%d)\n", pile->num_cards); 91 | } 92 | 93 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | //#define DEBUG_PRINT 11 | 12 | // utility functions 13 | 14 | void *mallocz(size_t size) { 15 | void *ptr; 16 | ptr = malloc(size); 17 | if (!ptr) 18 | return NULL; 19 | memset(ptr, 0, size); 20 | return ptr; 21 | } 22 | 23 | enum { 24 | SUIT_HEART, 25 | SUIT_SPADE, 26 | SUIT_CLUB, 27 | SUIT_DIAMOND, 28 | SUIT_COUNT // ♥♠♣♦ 29 | }; 30 | 31 | enum { 32 | RANK_A, 33 | RANK_2, 34 | RANK_3, 35 | RANK_4, 36 | RANK_5, 37 | RANK_6, 38 | RANK_7, 39 | RANK_8, 40 | RANK_9, 41 | RANK_10, 42 | RANK_J, 43 | RANK_Q, 44 | RANK_K, 45 | RANK_COUNT 46 | }; 47 | 48 | typedef struct card { 49 | int suit; 50 | int rank; 51 | int revealed; 52 | } card; 53 | 54 | #ifdef UNICODE 55 | const char *suit_to_charptr(int suit) { 56 | switch (suit) { 57 | case SUIT_HEART: 58 | return "\u2665"; // 2665 BLACK HEART SUIT 59 | case SUIT_SPADE: 60 | return "\u2660"; // 2660 BLACK SPADE SUIT 61 | case SUIT_CLUB: 62 | return "\u2663"; // 2663 BLACK CLUB SUIT 63 | case SUIT_DIAMOND: 64 | return "\u2666"; // 2666 BLACK DIAMOND SUIT 65 | default: 66 | return "?"; 67 | } 68 | } 69 | 70 | #else 71 | const char *suit_to_charptr(int suit) { 72 | switch (suit) { 73 | case SUIT_HEART: 74 | return "H"; 75 | case SUIT_SPADE: 76 | return "S"; 77 | case SUIT_CLUB: 78 | return "C"; 79 | case SUIT_DIAMOND: 80 | return "D"; 81 | default: 82 | return "?"; 83 | } 84 | } 85 | #endif 86 | 87 | const char *rank_to_charptr(int rank) { 88 | switch (rank) { 89 | case RANK_A: 90 | return "A"; 91 | case RANK_2: 92 | return "2"; 93 | case RANK_3: 94 | return "3"; 95 | case RANK_4: 96 | return "4"; 97 | case RANK_5: 98 | return "5"; 99 | case RANK_6: 100 | return "6"; 101 | case RANK_7: 102 | return "7"; 103 | case RANK_8: 104 | return "8"; 105 | case RANK_9: 106 | return "9"; 107 | case RANK_10: 108 | return "10"; 109 | case RANK_J: 110 | return "J"; 111 | case RANK_Q: 112 | return "Q"; 113 | case RANK_K: 114 | return "K"; 115 | default: 116 | return "?"; 117 | } 118 | } 119 | 120 | void print_card(card *c) { 121 | printf("%s%s", rank_to_charptr(c->rank), suit_to_charptr(c->suit)); 122 | } 123 | 124 | void print_card_ptr(card *c) { printf("%p", c); } 125 | 126 | card make_card(int suit, int rank) { 127 | card c; 128 | c.suit = suit; 129 | c.rank = rank; 130 | return c; 131 | } 132 | 133 | card *make_card_ptr(int suit, int rank) { 134 | card *c = mallocz(sizeof(card)); 135 | c->suit = suit; 136 | c->rank = rank; 137 | return c; 138 | } 139 | 140 | int is_black(card c) { return c.suit == SUIT_CLUB || c.suit == SUIT_SPADE; } 141 | 142 | int is_red(card c) { return c.suit == SUIT_HEART || c.suit == SUIT_DIAMOND; } 143 | 144 | int is_ace(card c) { return c.rank == RANK_A; } 145 | 146 | int is_alternate_color(card first, card second) { 147 | return is_black(first) != is_black(second); 148 | } 149 | 150 | int is_in_sequence(card lower, card higher) { 151 | return higher.rank == lower.rank + 1; 152 | } 153 | 154 | int can_be_placed_bottom(card parent, card child) { 155 | return is_alternate_color(parent, child) && is_in_sequence(child, parent); 156 | } 157 | 158 | int is_same_suit(card first, card second) { return first.suit == second.suit; } 159 | 160 | int can_be_placed_on_foundation(card parent, card child) { 161 | return is_same_suit(parent, child) && is_in_sequence(parent, child); 162 | } 163 | 164 | #define CARD_COUNT 52 165 | 166 | #define PILE_LIST 167 | 168 | #ifdef PILE_LIST 169 | 170 | typedef struct card_node { 171 | card *value; 172 | struct card_node *next; 173 | } card_node; 174 | 175 | typedef struct pile { 176 | card_node *head; 177 | int num_cards; 178 | char type; 179 | } pile; 180 | 181 | card_node *find_tail(pile *pile) { 182 | card_node *tail = pile->head; 183 | if (tail == 0) 184 | return 0; 185 | while (tail->next != 0) { 186 | tail = tail->next; 187 | } 188 | return tail; 189 | } 190 | 191 | card_node *make_node(card *card) { 192 | card_node *node = mallocz(sizeof(card_node)); 193 | node->value = card; 194 | node->next = 0; 195 | return node; 196 | } 197 | 198 | int is_empty(pile *pile) { return pile->num_cards == 0; } 199 | 200 | // remove a card from a pile, relinking the list 201 | void delete (pile *pile, card *card) { 202 | if (is_empty(pile)) { 203 | return; 204 | } 205 | // no previous node for the first item 206 | card_node *prev = NULL; 207 | card_node *current; 208 | for (current = pile->head; current != NULL; 209 | prev = current, current = current->next) { 210 | if (current->value == card) { 211 | // special case if the first item was found 212 | if (prev == NULL) { 213 | pile->head = current->next; 214 | } else { 215 | // skip the current item in the list 216 | prev->next = current->next; 217 | } 218 | pile->num_cards--; 219 | free(current); 220 | return; 221 | } 222 | } 223 | } 224 | 225 | // append to the end of the list 226 | void push(pile *pile, card *card) { 227 | card_node *tail = find_tail(pile); 228 | if (tail == NULL) { 229 | pile->head = make_node(card); 230 | } else { 231 | tail->next = make_node(card); 232 | } 233 | pile->num_cards++; 234 | } 235 | 236 | // remove a card from the end of the list 237 | card *pop(pile *pile) { 238 | pile->num_cards--; 239 | // find the (n-1)th card 240 | card_node *pre_tail = pile->head; 241 | for (int i = 0; i < pile->num_cards - 1; i++) 242 | pre_tail = pre_tail->next; 243 | card_node *tail = pre_tail->next; 244 | card *card = tail->value; 245 | pre_tail->next = 0; 246 | free(tail); 247 | return card; 248 | } 249 | 250 | // append to the beginning of the list 251 | void unshift(pile *pile, card *card) { 252 | card_node *new_head = make_node(card); 253 | new_head->next = pile->head; 254 | pile->head = new_head; 255 | pile->num_cards++; 256 | } 257 | 258 | // remove a card from the beginning of the list 259 | card *shift(pile *pile) { 260 | pile->num_cards--; 261 | card_node *old_head = pile->head; 262 | pile->head = old_head->next; 263 | 264 | card *card = old_head->value; 265 | free(old_head); 266 | return card; 267 | } 268 | 269 | card *peek_card_at(pile *pile, int index) { 270 | card_node *head = pile->head; 271 | for (int i = 0; i < index; i++) 272 | head = head->next; 273 | return head->value; 274 | } 275 | 276 | card *peek(pile *pile) { 277 | if (pile->head == NULL) { 278 | return NULL; 279 | } 280 | return pile->head->value; 281 | } 282 | 283 | card *peek_last(pile *pile) { 284 | if (pile->head == NULL) { 285 | return NULL; 286 | } 287 | return peek_card_at(pile, pile->num_cards - 1); 288 | } 289 | 290 | pile *make_pile() { 291 | pile *pile_ptr = mallocz(sizeof(pile)); 292 | pile_ptr->num_cards = 0; 293 | return pile_ptr; 294 | } 295 | 296 | void fill_deck(pile *pile) { 297 | for (int rank = 0; rank < RANK_COUNT; rank++) { 298 | for (int suit = 0; suit < SUIT_COUNT; suit++) { 299 | push(pile, make_card_ptr(suit, rank)); 300 | } 301 | } 302 | } 303 | #endif 304 | 305 | #define COLUMN_COUNT 7 306 | #define FOUNDATION_COUNT 4 307 | 308 | enum { 309 | PILE_DECK, 310 | PILE_WASTE, 311 | PILE_FOUNDATION1, 312 | PILE_FOUNDATION2, 313 | PILE_FOUNDATION3, 314 | PILE_FOUNDATION4, 315 | PILE_COLUMN1, 316 | PILE_COLUMN2, 317 | PILE_COLUMN3, 318 | PILE_COLUMN4, 319 | PILE_COLUMN5, 320 | PILE_COLUMN6, 321 | PILE_COLUMN7, 322 | PILE_COUNT 323 | }; 324 | 325 | char pile_types[] = "dwffffccccccc"; 326 | 327 | typedef struct game_state { 328 | pile **piles; 329 | int pile_count; 330 | int score; 331 | } game_state; 332 | 333 | game_state *make_game_state() { 334 | game_state *state = mallocz(sizeof(game_state)); 335 | state->piles = mallocz(sizeof(pile *) * PILE_COUNT); 336 | for (int pile_idx = 0; pile_idx < PILE_COUNT; pile_idx++) { 337 | state->piles[pile_idx] = make_pile(); 338 | state->piles[pile_idx]->type = pile_types[pile_idx]; 339 | } 340 | return state; 341 | } 342 | 343 | void print_deck(pile *pile) { 344 | printf("["); 345 | card_node *head = pile->head; 346 | for (int i = 0; i < pile->num_cards; i++) { 347 | card *c = head->value; 348 | print_card(c); 349 | printf(" "); 350 | head = head->next; 351 | } 352 | printf("] (%d)\n", pile->num_cards); 353 | } 354 | 355 | void insert(pile *pile, card *card, int idx) { 356 | card_node *pre_tail = pile->head; 357 | for (int i = 0; i < idx; i++) 358 | pre_tail = pre_tail->next; 359 | card_node *card_node = make_node(card); 360 | card_node->next = pre_tail->next; 361 | pre_tail->next = card_node; 362 | pile->num_cards++; 363 | } 364 | 365 | void shuffle_pile(pile *pile) { 366 | int shuffle_times = pile->num_cards * 10; 367 | for (int i = 0; i < shuffle_times; i++) { 368 | // unshift a card and insert to random place 369 | int idx = rand() % pile->num_cards - 1; 370 | card *card = shift(pile); 371 | insert(pile, card, idx); 372 | } 373 | } 374 | 375 | #define PRINTCARD(x) \ 376 | print_card(x); \ 377 | printf(" "); \ 378 | print_card_ptr(x); \ 379 | printf("\n"); 380 | 381 | pile *stock(game_state *state) { return state->piles[PILE_DECK]; } 382 | 383 | pile *waste(game_state *state) { return state->piles[PILE_WASTE]; } 384 | 385 | pile *column(game_state *state, int index_one_based) { 386 | return state->piles[PILE_COLUMN1 + index_one_based - 1]; 387 | } 388 | 389 | pile *foundation(game_state *state, int index_one_based) { 390 | return state->piles[PILE_FOUNDATION1 + index_one_based - 1]; 391 | } 392 | 393 | // returns 1 if a card was revealed 394 | int reveal(card *card) { 395 | if (card == NULL) 396 | return 0; 397 | card->revealed = 1; 398 | return 1; 399 | } 400 | 401 | void hide(card *card) { 402 | if (card == NULL) 403 | return; 404 | card->revealed = 0; 405 | } 406 | 407 | void turn(game_state *state) { 408 | // moves 1 card from stock to waste 409 | card *revealed_card = shift(stock(state)); 410 | reveal(revealed_card); 411 | push(state->piles[PILE_WASTE], revealed_card); 412 | } 413 | 414 | void deal(game_state *state) { 415 | // assuming a shuffled deck 416 | pile *deck = state->piles[PILE_DECK]; 417 | // deal columns 418 | for (int i = 0; i < COLUMN_COUNT; i++) { 419 | int column_idx = i + 1; 420 | pile *column = state->piles[PILE_COLUMN1 + i]; 421 | // deal N cards in Nth column 422 | for (int card_num = 0; card_num < column_idx; card_num++) { 423 | card *card = shift(deck); 424 | push(column, card); 425 | // reveal last card from the column 426 | if (card_num == column_idx - 1) { 427 | reveal(card); 428 | } 429 | } 430 | } 431 | // reveal 1 card 432 | turn(state); 433 | } 434 | 435 | int rows, cols; 436 | 437 | #define BLACK_PAIR 1 438 | #define RED_PAIR 2 439 | #define DEFAULT_COLOR -1 440 | 441 | void init_curses() { 442 | initscr(); 443 | keypad(stdscr, TRUE); 444 | use_default_colors(); 445 | start_color(); 446 | getmaxyx(stdscr, rows, cols); 447 | init_pair(BLACK_PAIR, DEFAULT_COLOR, DEFAULT_COLOR); 448 | init_pair(RED_PAIR, COLOR_RED, DEFAULT_COLOR); 449 | } 450 | 451 | void printw_card(card *c) { 452 | if (c == NULL) { 453 | printw("[ ]"); 454 | return; 455 | } 456 | if (c->revealed) { 457 | int color_pair = is_black(*c) ? BLACK_PAIR : RED_PAIR; 458 | attron(COLOR_PAIR(color_pair)); 459 | printw("%s%s", rank_to_charptr(c->rank), suit_to_charptr(c->suit)); 460 | attroff(COLOR_PAIR(color_pair)); 461 | } else { 462 | #ifdef DEBUG_PRINT 463 | printw("(%s%s)", rank_to_charptr(c->rank), suit_to_charptr(c->suit)); 464 | #else 465 | printw("[ ]"); 466 | #endif 467 | } 468 | } 469 | 470 | void printw_pile_size(pile *pile) { printw("(%d cards)", pile->num_cards); } 471 | 472 | void end_curses() { endwin(); } 473 | 474 | char *first_row_headers[] = {"Stock", "Waste", "", 475 | "Foundation 1", "Foundation 2", "Foundation 3", 476 | "Foundation 4"}; 477 | char *second_row_headers[] = {"Column 1", "Column 2", "Column 3", "Column 4", 478 | "Column 5", "Column 6", "Column 7"}; 479 | 480 | void print_prompt(game_state *state) { 481 | move(rows - 3, 0); 482 | printw("Score: %d", state->score); 483 | move(rows - 1, 0); 484 | printw("solitaire-cli > "); 485 | } 486 | 487 | void debug_print_pile(pile *pile, int row, int column) { 488 | for (int i = 0; i < pile->num_cards; i++) { 489 | move(row + i, column); 490 | printw_card(peek_card_at(pile, i)); 491 | } 492 | } 493 | 494 | void print_all_curses(game_state *state) { 495 | // 2 rows, 7 columns 496 | // top row has a fixed height of 1 card 497 | // bottom row can have up to 13 cards 498 | move(0, 0); 499 | // first row header 500 | // let's assume 100 characters terminal 501 | int column_size = 14; 502 | for (int i = 0; i < 7; i++) { 503 | move(0, column_size * i); 504 | printw("%s", first_row_headers[i]); 505 | } 506 | pile *stock_pile = stock(state); 507 | pile *waste_pile = waste(state); 508 | // first row content 509 | move(1, 0); 510 | printw_card(peek(stock_pile)); 511 | move(2, 0); 512 | printw_pile_size(stock_pile); 513 | move(1, column_size); 514 | printw_card(peek_last(waste_pile)); 515 | move(2, column_size); 516 | printw_pile_size(waste_pile); 517 | 518 | // foundations 519 | for (int f = 0; f < FOUNDATION_COUNT; f++) { 520 | int foundation_1_column = 3; 521 | move(1, (foundation_1_column + f) * column_size); 522 | printw_card(peek_last(foundation(state, f + 1))); 523 | move(2, (foundation_1_column + f) * column_size); 524 | printw_pile_size(foundation(state, f + 1)); 525 | } 526 | 527 | // second row header 528 | for (int i = 0; i < COLUMN_COUNT; i++) { 529 | move(4, column_size * i); 530 | printw("%s", second_row_headers[i]); 531 | move(5, column_size * i); 532 | printw_pile_size(column(state, i + 1)); 533 | } 534 | 535 | for (int i = 0; i < COLUMN_COUNT; i++) { 536 | pile *col = column(state, i + 1); 537 | int base_row = 6; 538 | for (int c = 0; c < col->num_cards; c++) { 539 | move(base_row + c, column_size * i); 540 | printw_card(peek_card_at(col, c)); 541 | } 542 | } 543 | 544 | #ifdef DEBUG_PRINT 545 | // debug: stock, waste 546 | mvprintw(17, 0, "stock:"); 547 | debug_print_pile(stock_pile, 18, 0); 548 | mvprintw(17, 16, "waste:"); 549 | debug_print_pile(waste_pile, 18, 16); 550 | mvprintw(17, 32, "foundation 1:"); 551 | debug_print_pile(foundation(state, 1), 18, 32); 552 | mvprintw(17, 48, "foundation 2:"); 553 | debug_print_pile(foundation(state, 2), 18, 48); 554 | mvprintw(17, 64, "foundation 3:"); 555 | debug_print_pile(foundation(state, 3), 18, 64); 556 | mvprintw(17, 80, "foundation 4:"); 557 | debug_print_pile(foundation(state, 4), 18, 80); 558 | #endif 559 | 560 | // status bar for the commands 561 | print_prompt(state); 562 | } 563 | 564 | void prepare_game(game_state *state) { 565 | pile *stock_pile = stock(state); 566 | fill_deck(stock_pile); 567 | shuffle_pile(stock_pile); 568 | deal(state); 569 | state->score = 0; 570 | } 571 | 572 | typedef struct parsed_input { 573 | char source; 574 | char destination; 575 | int source_index; 576 | int destination_index; 577 | int source_amount; 578 | int success; 579 | } parsed_input; 580 | 581 | parsed_input parse_input(char *command) { 582 | parsed_input parsed; 583 | parsed.success = 1; 584 | parsed.source_amount = 1; 585 | // parser patterns 586 | char *pattern_multi_move = "%dc%d c%d"; 587 | char *pattern_single_move = "c%d %c%d"; 588 | char *pattern_single_move2 = "%d %d"; 589 | char *pattern_waste_move = "w %c%d"; 590 | char *pattern_multi_stock = "%ds"; 591 | char *pattern_stock = "s"; 592 | if (sscanf(command, pattern_multi_move, &parsed.source_amount, 593 | &parsed.source_index, &parsed.destination_index) == 3) { 594 | parsed.source = 'c'; 595 | parsed.destination = 'c'; 596 | } else if (sscanf(command, pattern_single_move, &parsed.source_index, 597 | &parsed.destination, &parsed.destination_index) == 3) { 598 | parsed.source = 'c'; 599 | } else if (sscanf(command, pattern_waste_move, &parsed.destination, 600 | &parsed.destination_index) == 2) { 601 | parsed.source = 'w'; 602 | } else if (sscanf(command, pattern_single_move2, &parsed.source_index, 603 | &parsed.destination_index) == 2) { 604 | parsed.source = 'c'; 605 | parsed.destination = 'c'; 606 | } else if (sscanf(command, pattern_multi_stock, &parsed.source_amount) == 1) { 607 | parsed.source = 's'; 608 | } else if (strcmp(command, pattern_stock) == 0) { 609 | parsed.source = 's'; 610 | } else { 611 | parsed.success = 0; 612 | } 613 | return parsed; 614 | } 615 | 616 | pile *get_pile(game_state *state, char pile_prefix, int pile_index_one_based) { 617 | switch (pile_prefix) { 618 | case 's': 619 | return stock(state); 620 | case 'w': 621 | return waste(state); 622 | case 'f': 623 | return foundation(state, pile_index_one_based); 624 | case 'c': 625 | return column(state, pile_index_one_based); 626 | default: 627 | return NULL; 628 | } 629 | } 630 | 631 | void add_score(game_state *state, int score) { 632 | state->score += score; 633 | if (state->score < 0) { 634 | state->score = 0; 635 | } 636 | } 637 | 638 | enum { 639 | MOVE_OK, 640 | MOVE_INVALID_COMMAND, 641 | MOVE_SOURCE_EMPTY, 642 | MOVE_INVALID_MOVE, 643 | MOVE_TOO_MANY_CARDS, 644 | MOVE_CANNOT_REDEAL, 645 | MOVE_INVALID_DESTINATION, 646 | MOVE_INVALID_SOURCE 647 | }; 648 | char *move_results[] = {"OK", 649 | "Invalid command", 650 | "Source pile empty", 651 | "Invalid move", 652 | "Too many cards to move!", 653 | "Cannot redeal, stock pile empty", 654 | "Invalid destination", "Invalid source"}; 655 | 656 | void move_card(game_state *state, card *card, pile *source_pile, 657 | pile *destination_pile) { 658 | delete (source_pile, card); 659 | if (reveal(peek_last(source_pile))) { 660 | add_score(state, 5); // turn over column card 661 | } 662 | push(destination_pile, card); 663 | 664 | // add score for the moves 665 | if (destination_pile->type == 'f') { 666 | add_score(state, 10); 667 | } 668 | if (source_pile->type == 'w' && destination_pile->type == 'c') { 669 | add_score(state, 5); 670 | } 671 | } 672 | 673 | void redeal(game_state *state) { 674 | while (!is_empty(waste(state))) { 675 | card *card = shift(waste(state)); 676 | hide(card); 677 | push(stock(state), card); 678 | } 679 | add_score(state, -100); 680 | } 681 | 682 | int attempt_move(game_state *state, char *command) { 683 | // format: c6 f3 684 | parsed_input parsed = parse_input(command); 685 | if (parsed.success != 1) { 686 | return MOVE_INVALID_COMMAND; 687 | } 688 | 689 | //catch source / destination too high 690 | if((parsed.destination == 'c' && ((parsed.destination_index >= COLUMN_COUNT) || (parsed.destination_index < 1))) 691 | || (parsed.destination == 'f' && ((parsed.destination_index >= FOUNDATION_COUNT) || (parsed.destination_index < 1)))) 692 | { 693 | return MOVE_INVALID_DESTINATION; 694 | } 695 | 696 | // source_index can also be broken 697 | if(parsed.source == 'c' && ((parsed.source_index >= COLUMN_COUNT) || (parsed.source_index < 1))){ 698 | return MOVE_INVALID_SOURCE; 699 | } 700 | 701 | // figure out destination 702 | if (parsed.source == 's') { 703 | for (int i = 0; i < parsed.source_amount; i++) { 704 | if (is_empty(stock(state))) { 705 | // try to redeal 706 | if (is_empty(waste(state))) { 707 | return MOVE_CANNOT_REDEAL; 708 | } 709 | redeal(state); 710 | } 711 | turn(state); 712 | } 713 | return MOVE_OK; 714 | } 715 | pile *source_pile = get_pile(state, parsed.source, parsed.source_index); 716 | pile *destination_pile = 717 | get_pile(state, parsed.destination, parsed.destination_index); 718 | 719 | // check if the move is valid 720 | if (is_empty(source_pile)) { 721 | return MOVE_SOURCE_EMPTY; 722 | } 723 | 724 | if (source_pile->num_cards < parsed.source_amount) { 725 | return MOVE_TOO_MANY_CARDS; 726 | } 727 | 728 | int first_card_index = source_pile->num_cards - parsed.source_amount; 729 | // multi-card move 730 | if (parsed.source_amount > 1) { 731 | // check if all cards have been revealed 732 | card *c = peek_card_at(source_pile, first_card_index); 733 | if (c->revealed == 0) { 734 | return MOVE_TOO_MANY_CARDS; 735 | } 736 | } 737 | 738 | for (int card_index = 0; card_index < parsed.source_amount; card_index++) { 739 | 740 | // card index doesn't move - the card is always at the same index 741 | card *source_card = peek_card_at(source_pile, first_card_index); 742 | 743 | // check if the move is valid based on the destination type 744 | if (parsed.destination == 'f') { 745 | // only ace goes if the destination is empty 746 | if (is_empty(destination_pile)) { 747 | if (source_card->rank == RANK_A) { 748 | move_card(state, source_card, source_pile, destination_pile); 749 | } else { 750 | return MOVE_INVALID_MOVE; 751 | } 752 | } else { 753 | // non-empty foundation, pick up the first card 754 | card *top_foundation_card = peek_last(destination_pile); 755 | if (can_be_placed_on_foundation(*top_foundation_card, *source_card)) { 756 | move_card(state, source_card, source_pile, destination_pile); 757 | } else { 758 | return MOVE_INVALID_MOVE; 759 | } 760 | } 761 | } else if (parsed.destination == 'c') { 762 | // king can go in an empty column 763 | if (is_empty(destination_pile)) { 764 | if (source_card->rank == RANK_K) { 765 | move_card(state, source_card, source_pile, destination_pile); 766 | } else { 767 | return MOVE_INVALID_MOVE; 768 | } 769 | } else { 770 | card *bottom_column_card = peek_last(destination_pile); 771 | if (can_be_placed_bottom(*bottom_column_card, *source_card)) { 772 | move_card(state, source_card, source_pile, destination_pile); 773 | } else { 774 | return MOVE_INVALID_MOVE; 775 | } 776 | } 777 | } else { 778 | return MOVE_INVALID_DESTINATION; 779 | } 780 | } 781 | 782 | // set the return code 783 | return MOVE_OK; 784 | } 785 | 786 | int main() { 787 | srand(time(NULL)); 788 | // srand(3); 789 | setlocale(LC_ALL, ""); 790 | init_curses(); 791 | 792 | // prepare the game state 793 | game_state *state = make_game_state(); 794 | prepare_game(state); 795 | 796 | char buffer[80]; 797 | print_all_curses(state); 798 | 799 | // game loop 800 | while (1) { 801 | getstr(buffer); 802 | erase(); 803 | // pick up the source, destination and attempt the move 804 | int result = attempt_move(state, buffer); 805 | mvprintw(rows - 2, 0, "Move status: %s", move_results[result]); 806 | // show new status in the status bar 807 | print_all_curses(state); 808 | } 809 | getch(); 810 | end_curses(); 811 | } 812 | --------------------------------------------------------------------------------