summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile14
-rw-r--r--csol.c833
-rw-r--r--deck.c68
-rw-r--r--deck.h53
-rw-r--r--klondike.c513
-rw-r--r--klondike.h36
-rw-r--r--pile.c146
-rw-r--r--pile.h50
-rw-r--r--util.c209
-rw-r--r--util.h43
10 files changed, 1133 insertions, 832 deletions
diff --git a/Makefile b/Makefile
index 1002baa..2028ced 100644
--- a/Makefile
+++ b/Makefile
@@ -24,8 +24,16 @@
#
LIBS=-lcurses
-csol: csol.c
- $(CC) -g -o csol csol.c $(LIBS)
+OBJS=csol.o deck.o klondike.o pile.o util.o
+
+csol: $(OBJS)
+ $(CC) -g -o csol $(OBJS) $(LIBS)
+
+csol.o: csol.c deck.h pile.h util.h klondike.h
+deck.o: deck.c deck.h
+klondike.o: klondike.c deck.h pile.h util.h
+pile.o: pile.c pile.h deck.h util.h
+util.o: util.c util.h deck.h
clean:
- -rm -f csol csol.exe core
+ -rm -f csol csol.exe core $(OBJS)
diff --git a/csol.c b/csol.c
index f07e755..1abfcc9 100644
--- a/csol.c
+++ b/csol.c
@@ -23,840 +23,15 @@
*/
#include <stdlib.h>
-#include <limits.h>
#include <stdio.h>
-#include <string.h>
-#include <stdarg.h>
#include <time.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 < 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].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]);
- }
-}
+#include "deck.h"
+#include "pile.h"
+#include "util.h"
+#include "klondike.h"
int main(int argc, char *argv[])
{
diff --git a/deck.c b/deck.c
new file mode 100644
index 0000000..ece61a6
--- /dev/null
+++ b/deck.c
@@ -0,0 +1,68 @@
+/*
+
+ 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/>.
+
+ -------------------------------------------------------------------------
+
+ The base card types
+
+*/
+#include <stdlib.h>
+
+#include "deck.h"
+
+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;
+ }
+}
+
+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;
+ }
+}
+
+/*
+vim: ai sw=4 ts=8 expandtab
+*/
diff --git a/deck.h b/deck.h
new file mode 100644
index 0000000..24d4a85
--- /dev/null
+++ b/deck.h
@@ -0,0 +1,53 @@
+/*
+
+ 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/>.
+
+ -------------------------------------------------------------------------
+
+ The base card types
+
+*/
+#ifndef CSOL_DECK_H
+
+#define CSOL_DECK_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];
+
+void Init(Deck deck);
+void Shuffle(Deck deck);
+
+#endif
+
+/*
+vim: ai sw=4 ts=8 expandtab
+*/
diff --git a/klondike.c b/klondike.c
new file mode 100644
index 0000000..0a5b760
--- /dev/null
+++ b/klondike.c
@@ -0,0 +1,513 @@
+/*
+
+ 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/>.
+
+ -------------------------------------------------------------------------
+
+ Klondike
+
+*/
+#include <curses.h>
+
+#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.",
+ "",
+ "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() != ' ');
+}
+
+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].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]);
+ }
+}
+
+
+/*
+vim: ai sw=4 ts=8 expandtab
+*/
diff --git a/klondike.h b/klondike.h
new file mode 100644
index 0000000..84d424c
--- /dev/null
+++ b/klondike.h
@@ -0,0 +1,36 @@
+/*
+
+ 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/>.
+
+ -------------------------------------------------------------------------
+
+ Klondike
+
+*/
+
+#ifndef CSOL_KLONDIKE_H
+
+#define CSOL_KLONDIKE_H
+
+void Klondike(int draw);
+
+#endif
+
+/*
+vim: ai sw=4 ts=8 expandtab
+*/
diff --git a/pile.c b/pile.c
new file mode 100644
index 0000000..793cc7c
--- /dev/null
+++ b/pile.c
@@ -0,0 +1,146 @@
+/*
+
+ 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/>.
+
+ -------------------------------------------------------------------------
+
+ Implements a pile of cards
+
+*/
+#include <stdlib.h>
+
+#include "pile.h"
+#include "util.h"
+
+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;
+ }
+}
+
+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;
+ }
+}
+
+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;
+}
+
+Card TopOfPile(Pile *pile)
+{
+ Card c = {0};
+
+ if (pile->no)
+ {
+ c = pile->card[pile->no - 1];
+ }
+
+ return c;
+}
+
+void MoveTopCard(Pile *from, Pile *to)
+{
+ Card c;
+
+ if (from->no)
+ {
+ c = PopPile(from);
+ AddToPile(to, c);
+ }
+}
+
+void SwapPile(Pile *a, Pile *b)
+{
+ Pile t;
+
+ t = *a;
+ *a = *b;
+ *b = t;
+}
+
+void FreePile(Pile *p)
+{
+ if (p->card)
+ {
+ free(p->card);
+ p->card = NULL;
+ }
+}
+
+/*
+vim: ai sw=4 ts=8 expandtab
+*/
diff --git a/pile.h b/pile.h
new file mode 100644
index 0000000..c3cfc16
--- /dev/null
+++ b/pile.h
@@ -0,0 +1,50 @@
+/*
+
+ 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/>.
+
+ -------------------------------------------------------------------------
+
+ Implements a pile of cards
+
+*/
+
+#ifndef CSOL_PILE_H
+
+#define CSOL_PILE_H
+
+#include "deck.h"
+
+typedef struct
+{
+ int no;
+ Card *card;
+} Pile;
+
+void AddToPile(Pile *pile, Card card);
+void InsertBottomOfPile(Pile *pile, Card card);
+Card PopPile(Pile *pile);
+Card TopOfPile(Pile *pile);
+void MoveTopCard(Pile *from, Pile *to);
+void SwapPile(Pile *a, Pile *b);
+void FreePile(Pile *p);
+
+#endif
+
+/*
+vim: ai sw=4 ts=8 expandtab
+*/
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..92a4ba0
--- /dev/null
+++ b/util.c
@@ -0,0 +1,209 @@
+/*
+
+ 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/>.
+
+ -------------------------------------------------------------------------
+
+ General purpose utilities
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <curses.h>
+
+#include "util.h"
+
+void Fatal(const char *p)
+{
+ endwin();
+ fprintf(stderr, "%s\n", p);
+ exit(EXIT_FAILURE);
+}
+
+void Centre(int y, const char *p)
+{
+ size_t l = strlen(p);
+
+ mvaddstr(y, COLS / 2 - l / 2, p);
+}
+
+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;
+}
+
+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);
+ }
+ }
+ }
+}
+
+double DRand()
+{
+ return (double)(rand() % 1000) / 1000.0;
+}
+
+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);
+}
+
+
+/*
+vim: ai sw=4 ts=8 expandtab
+*/
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..cc00b85
--- /dev/null
+++ b/util.h
@@ -0,0 +1,43 @@
+/*
+
+ 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/>.
+
+ -------------------------------------------------------------------------
+
+ General purpose utilities
+
+*/
+
+#ifndef CSOL_UTIL_H
+
+#define CSOL_UTIL_H
+
+#include "deck.h"
+
+void Fatal(const char *p);
+void Centre(int y, const char *p);
+const char *CardName(Card c);
+void DrawCard(int y, int x, int face_down, Card c);
+double DRand();
+void WinScreen();
+
+#endif
+
+/*
+vim: ai sw=4 ts=8 expandtab
+*/