diff options
Diffstat (limited to 'csol.c')
-rw-r--r-- | csol.c | 713 |
1 files changed, 713 insertions, 0 deletions
@@ -0,0 +1,713 @@ +/* + + 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 <http://www.gnu.org/licenses/>. + + ------------------------------------------------------------------------- + + Main + +*/ +#include <stdlib.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> + +#include <unistd.h> +#include <curses.h> + +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 < 500; 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("Failled 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("Failled 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--; + + pile->card = realloc(pile->card, sizeof *pile->card * pile->no); + + if (!pile->card) + { + Fatal("Failled to reallocate memory"); + } + } + 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 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 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; + } + } + } + + 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 'Q' to quit.", + "", + "", + "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 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': + quit = TRUE; + 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 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 (won) + { + Centre(5, "YOU WON!"); + Centre(7, "PRESS SPACE"); + refresh(); + while(getch() != ' '); + } +} + +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; + default: + break; + } + } + + erase(); + refresh(); + endwin(); + + return EXIT_SUCCESS; +} + +/* +vim: ai sw=4 ts=8 expandtab +*/ |