/* 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 . ------------------------------------------------------------------------- Main */ #include #include #include #include #include #include #include typedef enum { NoSuit = 0, Heart = 'H', Diamond = 'D', Club = 'C', Spade = 'S' } Suit; typedef struct { int value; Suit suit; } Card; typedef Card Deck[52]; typedef struct { int no; Card *card; } Pile; static void Fatal(const char *p) { endwin(); fprintf(stderr, "%s\n", p); exit(EXIT_FAILURE); } static void Init(Deck deck) { int i = 0; int f; for(f=1; f < 14; f++) { deck[i].suit = Heart; deck[i++].value = f; deck[i].suit = Diamond; deck[i++].value = f; deck[i].suit = Club; deck[i++].value = f; deck[i].suit = Spade; deck[i++].value = f; } } static void Shuffle(Deck deck) { int f; for(f = 0; f < 1000; f++) { int c1 = rand() % 52; int c2 = rand() % 52; Card c; c = deck[c1]; deck[c1] = deck[c2]; deck[c2] = c; } } static void AddToPile(Pile *pile, Card card) { pile->no++; pile->card = realloc(pile->card, sizeof *pile->card * pile->no); if (!pile->card) { Fatal("Failed to reallocate memory"); } else { pile->card[pile->no - 1] = card; } } static void InsertBottomOfPile(Pile *pile, Card card) { pile->no++; pile->card = realloc(pile->card, sizeof *pile->card * pile->no); if (!pile->card) { Fatal("Failed to reallocate memory"); } else { int f; for(f = pile->no - 1; f > 0; f--) { pile->card[f] = pile->card[f - 1]; } pile->card[0] = card; } } static Card PopPile(Pile *pile) { Card c = {0}; if (pile->no) { c = pile->card[pile->no - 1]; pile->no--; if (pile->no) { pile->card = realloc(pile->card, sizeof *pile->card * pile->no); if (!pile->card) { Fatal("Failed to reallocate memory"); } } else { free(pile->card); pile->card = NULL; } } else { free(pile->card); pile->card = NULL; } return c; } static Card TopOfPile(Pile *pile) { Card c = {0}; if (pile->no) { c = pile->card[pile->no - 1]; } return c; } static void MoveTopCard(Pile *from, Pile *to) { Card c; if (from->no) { c = PopPile(from); AddToPile(to, c); } } static void SwapPile(Pile *a, Pile *b) { Pile t; t = *a; *a = *b; *b = t; } static void FreePile(Pile *p) { if (p->card) { free(p->card); p->card = NULL; } } static void Centre(int y, const char *p) { size_t l = strlen(p); mvaddstr(y, COLS / 2 - l / 2, p); } static const char *CardName(Card c) { static char buff[32]; static const char *num[]= { "ERR", " A", " 2", " 3", " 4", " 5", " 6", " 7", " 8", " 9", "10", " J", " Q", " K" }; sprintf(buff, "%s%c", num[c.value], c.suit); return buff; } static void DrawCard(int y, int x, int face_down, Card c) { if (c.suit == NoSuit) { mvaddstr(y, x, " "); } else { if (face_down) { mvaddstr(y, x, "###"); } else { if (c.suit == Spade || c.suit == Club) { attron(A_REVERSE); } mvaddstr(y, x, CardName(c)); if (c.suit == Spade || c.suit == Club) { attroff(A_REVERSE); } } } } static double DRand() { return (double)(rand() % 1000) / 1000.0; } static void WinScreen() { static const char *text[] = { "# # ## # # # # ## # #", " # # # # # # # # # # ## #", " # # # # # # # # # # # ##", " # # # # # ## ## # # # #", " # ## ## # # ## # #", NULL, }; static const int NO_STAR = 100; typedef struct { double x,y,yi; } Star; Star *star; int f; star = malloc(sizeof *star * NO_STAR); if (!star) { Fatal("Memory allocation failed"); } for(f = 0; f < NO_STAR; f++) { star[f].x = rand() % COLS; star[f].y = rand() % LINES; do { star[f].yi = DRand() * 2; } while(star[f].yi == 0); } halfdelay(1); erase(); while(getch() != ' ') { for(f = 0; f < NO_STAR; f++) { mvaddch(star[f].y, star[f].x, ' '); star[f].y -= star[f].yi; if (star[f].y < 0) { star[f].x = rand() % COLS; star[f].y = LINES - 1; do { star[f].yi = DRand() * 2; } while(star[f].yi == 0); } } for(f = 0; f < NO_STAR; f++) { mvaddch(star[f].y, star[f].x, '.'); } for(f = 0 ; text[f]; f++) { int x = COLS / 2 - strlen(text[f]) / 2; int i; for(i = 0; text[f][i]; i++) { if (text[f][i] != ' ') { mvaddch(f + 4, x++, text[f][i]); } else { x++; } } } Centre(f + 7, "PRESS SPACE"); refresh(); } nocbreak(); cbreak(); erase(); free(star); } 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.", "", "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.", "", "", "Press SPACE to continue.", NULL }; int y = 2; int f; erase(); for(f = 0; text[f]; f++) { Centre(y++, text[f]); } refresh(); while(getch() != ' '); } static void Klondike(int draw) { 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 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, TRUE, 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 (hearts.no == 13 && diamonds.no == 13 && spades.no == 13 && clubs.no == 13) { won = TRUE; } refresh(); if (!won) { key = getch(); } switch(key) { case ' ': if (pile.no) { for(f = 0; f < draw; f++) { MoveTopCard(&pile, &turned); } } else { SwapPile(&pile, &turned); } 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 'Q': case 'q': { int k; quit = TRUE; 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].card[move_pos_y].value == 13 && column_up[pos_x].no == 0 && column_down[pos_x].no == 0) { can_move = TRUE; } else { if (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]); } } int main(int argc, char *argv[]) { int key; int opt = 1; int quit = FALSE; srand(time(NULL)); initscr(); cbreak(); noecho(); keypad(stdscr, TRUE); while(!quit) { erase(); Centre(1, "CURSES solitaire"); Centre(3, "1 .. Klondike (draw three)"); Centre(4, "2 .. Klondike (draw one)"); Centre(5, "Q .. Quit"); key = getch(); switch(key) { case '1': erase(); Klondike(3); break; case '2': erase(); Klondike(1); break; case 'q': case 'Q': quit = TRUE; break; case '?': erase(); Centre(2, "Press the indicated key to select the menu item."); Centre(4, "Press SPACE to continue."); refresh(); while(getch() != ' '); break; default: break; } } erase(); refresh(); endwin(); return EXIT_SUCCESS; } /* vim: ai sw=4 ts=8 expandtab */