/* csol - CURSES solitaire Copyright (C) 2018 Ian Cowburn (ianc@noddybox.co.uk) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ------------------------------------------------------------------------- Klondike */ #include #include #include #include "deck.h" #include "pile.h" #include "util.h" static int CanCardSitOnTop(Card card, Card on) { int ret = FALSE; if (card.suit != NoSuit && on.suit != NoSuit) { if (card.value == on.value - 1) { switch(card.suit) { case Spade: case Club: ret = (on.suit == Heart || on.suit == Diamond); break; case Heart: case Diamond: ret = (on.suit == Spade || on.suit == Club); break; case NoSuit: break; } } } return ret; } static int PutCardOnSuitPile(Pile *from, Pile *hearts, Pile *diamonds, Pile *spades, Pile *clubs) { Card c; Pile *p; int move = FALSE; c = TopOfPile(from); if (c.suit != NoSuit) { switch(c.suit) { case Heart: p = hearts; break; case Diamond: p = diamonds; break; case Spade: p = spades; break; case Club: p = clubs; break; default: p = NULL; break; } if (p->no) { if (c.value == TopOfPile(p).value + 1) { move = TRUE; } } else { if (c.value == 1 ) { move = TRUE; } } if (move) { MoveTopCard(from, p); } } return move; } static void RevealCard(Pile *column_down, Pile *column_up) { if (column_up->no == 0) { if (column_down->no) { InsertBottomOfPile(column_up, PopPile(column_down)); } } } static void KlondikeHelp(void) { static const char *text[] = { "Use the cursor keys to move the cursor around.", "", "Press RETURN to select a bunch of cards to move.", "Press RETURN again when you are where you want to move the cards.", "", "Use space to draw the next set of cards.", "", "Use 'S' to move a card from the drawn cards to the current column.", "", "Use 'P' to put the current card on the suits piles.", "If invalid it will attempt to move the card from the drawn pile.", "", "Use 'A' if all the cards are shown and there is nothing on the pile", "to automatically finish the puzzle.", "", "Press '1', '2', '3' or '4' to move the top card from the hearts, ", "diamonds, spades or clubs suit pile respectively back onto the board.", "", "Press 'Q' to quit. You'll be asked if you want to see the cards", "if not playing a thoughtful game.", "", "Press SPACE to continue.", NULL }; int y = 2; int f; erase(); for(f = 0; text[f]; f++) { Centre(y++, text[f]); } refresh(); while(getch() != ' '); } int Klondike(int draw, int thoughtful) { Deck deck; Pile pile = {0}; Pile turned = {0}; Pile hearts = {0}; Pile diamonds = {0}; Pile clubs = {0}; Pile spades = {0}; Pile column_down[7] = {{0}}; Pile column_up[7] = {{0}}; int f; int n; int i; int won = FALSE; int quit = FALSE; int show = FALSE; int autofill = FALSE; int pos_x = 0; int pos_y = 0; int move_pos_x = -1; int move_pos_y = -1; Init(deck); Shuffle(deck); i = 0; for(f = 0; f < 7; f++) { for(n = 0; n < f; n++) { AddToPile(&column_down[f], deck[i++]); } AddToPile(&column_up[f], deck[i++]); } while(i < 52) { AddToPile(&pile, deck[i++]); } while(!quit && !won) { Card c = {0}; int key = 0; erase(); DrawCard(0, 0, TRUE, TopOfPile(&pile)); DrawCard(0, 4, FALSE, TopOfPile(&turned)); DrawCard(0, 10, FALSE, TopOfPile(&hearts)); DrawCard(0, 15, FALSE, TopOfPile(&diamonds)); DrawCard(0, 20, FALSE, TopOfPile(&spades)); DrawCard(0, 25, FALSE, TopOfPile(&clubs)); for(f = 0; f < 7; f++) { for(n = 0; n < column_down[f].no; n++) { DrawCard(2 + n, 1 + f * 5, !thoughtful, column_down[f].card[n]); } for(n = 0; n < column_up[f].no; n++) { DrawCard(2 + column_down[f].no + n, 1 + f * 5, FALSE, column_up[f].card[n]); } } if (move_pos_x != -1) { mvaddch(2 + column_down[move_pos_x].no + move_pos_y, move_pos_x * 5, '*'); mvaddch(2 + column_down[move_pos_x].no + move_pos_y, move_pos_x * 5 + 4, '*'); } if (!column_up[pos_x].no && !column_down[pos_x].no) { mvaddch(2, pos_x * 5, '>'); mvaddch(2, pos_x * 5 + 4, '<'); } else { mvaddch(2 + column_down[pos_x].no + pos_y, pos_x * 5, '>'); mvaddch(2 + column_down[pos_x].no + pos_y, pos_x * 5 + 4, '<'); } if (autofill) { for(f = 0; f < 7; f++) { if (PutCardOnSuitPile(&column_up[f], &hearts, &diamonds, &spades, &clubs)) { RevealCard(&column_down[pos_x], &column_up[pos_x]); } } } if (hearts.no == 13 && diamonds.no == 13 && spades.no == 13 && clubs.no == 13) { won = TRUE; } refresh(); if (autofill) { struct timespec spec = {0}; spec.tv_nsec = 250000000; nanosleep(&spec, NULL); } if (!won && !autofill) { key = getch(); } switch(key) { case ' ': if (pile.no) { for(f = 0; f < draw; f++) { MoveTopCard(&pile, &turned); } } else { MovePile(&turned, &pile); } break; case 'S': case 's': c = TopOfPile(&turned); if (c.value == 13 && column_up[pos_x].no == 0 && column_down[pos_x].no == 0) { MoveTopCard(&turned, &column_up[pos_x]); } else { if (CanCardSitOnTop(c, TopOfPile(&column_up[pos_x]))) { MoveTopCard(&turned, &column_up[pos_x]); pos_y++; } } break; case 'P': case 'p': if (PutCardOnSuitPile(&column_up[pos_x], &hearts, &diamonds, &spades, &clubs)) { RevealCard(&column_down[pos_x], &column_up[pos_x]); pos_y--; if (pos_y < 0) { pos_y = 0; } } else { PutCardOnSuitPile(&turned, &hearts, &diamonds, &spades, &clubs); } break; case 'A': case 'a': if (pile.no == 0 && turned.no == 0) { autofill = TRUE; for(f = 0; f < 7; f++) { if (column_down[f].no) { autofill = FALSE; } } } break; case 'Q': case 'q': { int k; quit = TRUE; if (!thoughtful) { Centre(LINES-1, "Show cards?"); refresh(); k = getch(); show = (k == 'y' || k == 'Y'); } break; } case 10: if (move_pos_x == -1) { move_pos_x = pos_x; move_pos_y = pos_y; } else { int can_move = FALSE; if (column_up[move_pos_x].no && column_up[move_pos_x].card[move_pos_y].value == 13 && column_up[pos_x].no == 0 && column_down[pos_x].no == 0) { can_move = TRUE; } else { if (column_up[move_pos_x].no && CanCardSitOnTop (column_up[move_pos_x].card[move_pos_y], TopOfPile(&column_up[pos_x]))) { can_move = TRUE; } } if (can_move) { Pile temp = {0}; while(column_up[move_pos_x].no > move_pos_y) { MoveTopCard(&column_up[move_pos_x], &temp); } while(temp.no) { MoveTopCard(&temp, &column_up[pos_x]); } RevealCard(&column_down[move_pos_x], &column_up[move_pos_x]); } move_pos_x = -1; move_pos_y = -1; } break; case '1': if (CanCardSitOnTop(TopOfPile(&hearts), TopOfPile(&column_up[pos_x]))) { MoveTopCard(&hearts, &column_up[pos_x]); } break; case '2': if (CanCardSitOnTop(TopOfPile(&diamonds), TopOfPile(&column_up[pos_x]))) { MoveTopCard(&diamonds, &column_up[pos_x]); } break; case '3': if (CanCardSitOnTop(TopOfPile(&spades), TopOfPile(&column_up[pos_x]))) { MoveTopCard(&spades, &column_up[pos_x]); } break; case '4': if (CanCardSitOnTop(TopOfPile(&clubs), TopOfPile(&column_up[pos_x]))) { MoveTopCard(&clubs, &column_up[pos_x]); } break; case KEY_LEFT: if (pos_x) { pos_x--; pos_y = column_up[pos_x].no - 1; if (pos_y < 0) { pos_y = 0; } } break; case KEY_RIGHT: if (pos_x < 6) { pos_x++; pos_y = column_up[pos_x].no - 1; if (pos_y < 0) { pos_y = 0; } } break; case KEY_UP: if (pos_y) { pos_y--; } break; case KEY_DOWN: if (pos_y < column_up[pos_x].no - 1) { pos_y++; } break; case '?': KlondikeHelp(); break; default: break; } } if (show) { for(f = 0; f < 7; f++) { for(n = 0; n < column_down[f].no; n++) { DrawCard(2 + n, 1 + f * 5, FALSE, column_down[f].card[n]); } for(n = 0; n < column_up[f].no; n++) { DrawCard(2 + column_down[f].no + n, 1 + f * 5, FALSE, column_up[f].card[n]); } } Centre(LINES - 1, "Press any key"); refresh(); getch(); } if (won) { WinScreen(); } FreePile(&pile); FreePile(&turned); FreePile(&hearts); FreePile(&diamonds); FreePile(&clubs); FreePile(&spades); for(f = 0; f < 7; f++) { FreePile(&column_down[f]); FreePile(&column_up[f]); } return won; } /* vim: ai sw=4 ts=8 expandtab */