summaryrefslogtreecommitdiff
path: root/csol.c
diff options
context:
space:
mode:
Diffstat (limited to 'csol.c')
-rw-r--r--csol.c713
1 files changed, 713 insertions, 0 deletions
diff --git a/csol.c b/csol.c
new file mode 100644
index 0000000..5d9524d
--- /dev/null
+++ b/csol.c
@@ -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
+*/