/* One-deck Napoleon solitaire 0.4
*
* Copyright 2018-2019 (C) Till A. Heilmann
*
* 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
* .
*
* You can contact the author by mail:
*
* The source must be compiled including the ncursesw library
* supporting 'wide' or Unicode characters like so:
*
* $ export C_INCLUDE_PATH=/path/to/ncurses/include/directory/
* $ cc napoleon-0.4.c -o napoleon -lncurses
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define AUTHOR "Till A. Heilmann"
#define PROGRAM_NAME "One-deck Napoleon solitaire"
#define VERSION_NUMBER "0.4"
#define COPYRIGHT_YEAR "2018-2019"
#define _XOPEN_SOURCE_EXTENDED
#define SAVE_STATE_FILE "/.napoleon-save-state.csv"
#define LABEL_MOVE "Move"
#define DELAY 12500 /* delay for card dealing animation */
#define TABLE_WIDTH 80 /* width of the screen in chars */
#define TABLE_HEIGHT 25 /* height of the screen in chars */
#define CARD_WIDTH 4
#define CARD_HEIGHT 3
#define X_OFFSET_ROWS (((TABLE_WIDTH / 2)) - (CARD_WIDTH / 2))
#define Y_OFFSET_ROWS 1
#define X_OFFSET_HEADER 0
#define Y_OFFSET_HEADER 0
#define X_OFFSET_FOOTER 0
#define Y_OFFSET_FOOTER (Y_OFFSET_HEADER + TABLE_HEIGHT - 1)
#define NUM_RANKS 13 /* 1 (Ace) to 13 (King) */
#define NUM_FAMILIES 4 /* Hearts, Spades, Diamonds, Clubs */
#define NUM_CARDS (NUM_RANKS * NUM_FAMILIES) /* deck of cards */
#define RANK_ACE 1
#define RANK_TWO 2
#define RANK_TEN 10
#define RANK_JACK 11
#define RANK_QUEEN 12
#define RANK_KING 13
#define SYMBOL_ACE "A"
#define SYMBOL_JACK "J"
#define SYMBOL_QUEEN "Q"
#define SYMBOL_KING "K"
#define SUIT_HEART 0 /* numbers for suits... */
#define SUIT_SPADE 1
#define SUIT_DIAMOND 2
#define SUIT_CLUB 3
#define SYMBOL_HEART L'\u2665' /* Unicode chars for suits... */
#define SYMBOL_SPADE L'\u2660'
#define SYMBOL_DIAMOND L'\u2666'
#define SYMBOL_CLUB L'\u2663'
#define SYMBOL_UNKNOWN '?'
#define KEY_ROW_1 '1' /* character literals for user input... */
#define KEY_ROW_8 '8'
#define KEY_ROW_9 '9'
#define KEY_ROW_10 '0'
#define KEY_CELLAR 'c'
#define KEY_FOUNDATION 'f'
#define KEY_NEW_DEAL 'n'
#define KEY_QUIT_GAME 'q'
#define KEY_DISPLAY_HELP 'h'
#define KEY_UNDO_MOVE 'u'
#define KEY_ESCAPE '\e'
#define CELLAR 10 /* subscript for cellar in card[][] */
#define FOUNDATION_BASE 11 /* subscript for first foundation in card[][] */
#define FIRST_RESERVE_ROW 1 /* subscript for first reserve row in card[][] */
#define LAST_RESERVE_ROW 8 /* subscript for last reserve row in card[][] */
#define EXTRA_ROW_1 9 /* subscript for 1st extra row in card[][] */
#define EXTRA_ROW_2 0 /* subscript for 2nd extra row in card[][] */
#define NUM_ROWS 15 /* total number of rows in card[][] (incl. foundations and cellar) */
#define NUM_REGULAR_ROWS 10 /* number of regular rows in card[][] (incl. two extra rows) */
#define MAX_CARDS_IN_ROW 16 /* maximal number of cards in regular rows */
#define OVERLAP_THRESHOLD_1 8
#define OVERLAP_THRESHOLD_2 12
#define OVERLAP_THRESHOLD_3 16
#define EMPTY 0
#define UP -1
#define DOWN 1
#define UNDETERMINED 0
#define NOTSELECTED -1
#define HISTORY 9999
#define TEXT_REGULAR 1
#define TEXT_HIGHLIGHT 2
#define HEART_LIGHT 3
#define SPADE_LIGHT 4
#define DIAMOND_LIGHT 5
#define CLUB_LIGHT 6
#define HEART_DARK 7
#define SPADE_DARK 8
#define DIAMOND_DARK 9
#define CLUB_DARK 10
#define CARD_SHADE 4 /* difference between XXX_LIGHT and XXX_DARK */
#define CARD_HIGHLIGHT 11
int rank_of (int c);
int suit_of (int c);
int cards_in_row (int r, int card[NUM_ROWS][MAX_CARDS_IN_ROW]);
int overlapping_cards (int n);
int key_label (int r);
int x_pt (int p, int r, int card[NUM_ROWS][MAX_CARDS_IN_ROW]);
int x_pt_key (int r);
int y_pt (int r);
int card_color (int c, int p, int n, bool s);
int collect_t_row (int r, int p, int card[NUM_ROWS][MAX_CARDS_IN_ROW]);
int collect_t_pos (int r, int p, int card[NUM_ROWS][MAX_CARDS_IN_ROW]);
int initial_tmp_row (int stock[], int n, int r);
int initial_tmp_pos (int stock[], int n, int p);
bool is_even (int n);
bool is_empty (int r, int card[NUM_ROWS][MAX_CARDS_IN_ROW]);
bool is_regular_row (int r);
bool is_extra_row (int r);
bool is_cellar (int r);
bool is_foundation (int r);
bool is_rank_difference (int a, int b, int d);
bool is_king (int c);
bool is_ace (int c);
bool is_match_suits (int a, int b);
bool is_match_regular_row (int s, int t);
bool is_match_foundation (int s, int t, int *up_or_down);
bool is_match_card (int s_row, int t_row, int card[NUM_ROWS][MAX_CARDS_IN_ROW], int *up_or_down);
bool is_foundation_complete (int card[NUM_ROWS][MAX_CARDS_IN_ROW]);
bool is_cellar_accessible (int card[NUM_ROWS][MAX_CARDS_IN_ROW]);
bool is_row_source (int r, int card[NUM_ROWS][MAX_CARDS_IN_ROW]);
bool is_targets_available (int r, int card[NUM_ROWS][MAX_CARDS_IN_ROW], int up_or_down);
bool is_card_shaded (int p, int n);
char *card_face (int c);
wchar_t suit_symbol (int c);
void do_generate (int stock[]);
void do_shuffle (int stock[]);
void move_card (int s_row, int t_row, int card[NUM_ROWS][MAX_CARDS_IN_ROW]);
void erase_card (WINDOW *win, int y, int x);
void display_card (WINDOW *win, int y, int x, int card);
void erase_row (WINDOW *win, int row, int card[NUM_ROWS][MAX_CARDS_IN_ROW]);
void display_row (WINDOW *win, int row, int card[NUM_ROWS][MAX_CARDS_IN_ROW]);
void set_initial_row_pos (int tmp_r, int *row, int *pos);
void make_initial_layout (WINDOW *win, int stock[], int card[NUM_ROWS][MAX_CARDS_IN_ROW], bool d);
void erase_keys (WINDOW *win);
void display_keys (WINDOW *win, int s_row, int card[NUM_ROWS][MAX_CARDS_IN_ROW], int up_or_down);
void display_title (WINDOW *win);
void erase_move (WINDOW *win);
void display_move (WINDOW *win, int m);
void erase_screen_lines (WINDOW *win, int f, int t);
void display_instructions (WINDOW *win, int s_row, int m, bool t, bool w);
void display_help (WINDOW *win);
void collect_cards (WINDOW *win, int card[NUM_ROWS][MAX_CARDS_IN_ROW]);
void display_win (WINDOW *win, int card[NUM_ROWS][MAX_CARDS_IN_ROW]);
int main()
{
WINDOW *table;
char *locale;
char *file_name = SAVE_STATE_FILE;
char *home_dir = getenv("HOME");
char *file_path = malloc(strlen(home_dir) + strlen(file_name) + 1);
FILE *save_file = NULL;
int stock[NUM_CARDS];
int card[NUM_ROWS][MAX_CARDS_IN_ROW]; /* [0][] to [9][] = regular rows
* [10][] = cellar
* [11][] to [14][] = foundations
*/
int key;
int source_row;
int target_row;
int from[HISTORY];
int to[HISTORY];
int up_or_down;
int up_or_down_on_move = UNDETERMINED;
int move;
int source_card;
int target_card;
int i;
bool is_modified_layout;
bool is_game_won;
bool is_quit_game = false;
bool is_new_game;
bool is_card_selected;
setlocale(LC_ALL, "");
initscr();
clear();
noecho();
cbreak();
curs_set(0);
if (has_colors() == FALSE)
{
endwin();
printf("Your terminal does not support color.\n");
exit(1);
}
start_color();
init_pair(TEXT_REGULAR, COLOR_WHITE, COLOR_GREEN);
init_pair(TEXT_HIGHLIGHT, COLOR_RED, COLOR_GREEN);
init_pair(HEART_LIGHT, COLOR_RED, COLOR_WHITE);
init_pair(SPADE_LIGHT, COLOR_BLACK, COLOR_WHITE);
init_pair(DIAMOND_LIGHT, COLOR_MAGENTA, COLOR_WHITE);
init_pair(CLUB_LIGHT, COLOR_BLUE, COLOR_WHITE);
init_pair(HEART_DARK, COLOR_RED, COLOR_YELLOW);
init_pair(SPADE_DARK, COLOR_BLACK, COLOR_YELLOW);
init_pair(DIAMOND_DARK, COLOR_MAGENTA, COLOR_YELLOW);
init_pair(CLUB_DARK, COLOR_BLUE, COLOR_YELLOW);
init_pair(CARD_HIGHLIGHT, COLOR_WHITE, COLOR_RED);
table = newwin(TABLE_HEIGHT, TABLE_WIDTH, 0, 0);
wbkgd(table, COLOR_PAIR(TEXT_REGULAR));
display_title(table);
strncpy(file_path, home_dir, strlen(home_dir) + 1);
strncat(file_path, file_name, strlen(file_name) + 1);
save_file = fopen(file_path, "r");
if (save_file != NULL)
{
for (i = 0; i < NUM_CARDS; i++)
{
fscanf(save_file, "%d\n", &stock[i]);
}
fscanf(save_file, "%d,%d\n", &up_or_down, &up_or_down_on_move);
i = 0;
while (!feof(save_file))
{
fscanf(save_file, "%d,%d\n", &from[i], &to[i]);
++i;
}
move = i;
make_initial_layout(table, stock, card, false);
for (i = 0; i < move; i++)
{
move_card(from[i], to[i], card);
}
for (i = 0; i < NUM_ROWS; i++)
{
display_row(table, i, card);
}
source_row = NOTSELECTED;
target_row = NOTSELECTED;
is_new_game = false;
}
else
{
is_new_game = true;
}
fclose(save_file);
while (!is_quit_game)
{
if (is_new_game)
{
is_new_game = false;
is_game_won = false;
memset(card, 0, sizeof(card[EXTRA_ROW_1][0]) * NUM_ROWS * MAX_CARDS_IN_ROW);
memset(from, 0, sizeof(from[0]) * HISTORY);
memset(to, -1, sizeof(to[0]) * HISTORY);
source_row = NOTSELECTED;
target_row = NOTSELECTED;
source_card = NOTSELECTED;
target_card = NOTSELECTED;
move = 0;
up_or_down = UNDETERMINED;
up_or_down_on_move = UNDETERMINED;
do_generate(stock);
do_shuffle(stock);
make_initial_layout(table, stock, card, true);
}
erase_keys(table);
if (is_game_won)
{
display_win(table, card);
}
else
{
display_keys(table, source_row, card, up_or_down);
}
is_modified_layout = false;
display_move(table, move);
display_instructions(table,
source_row,
move,
is_targets_available(source_row, card, up_or_down),
is_game_won);
key = wgetch(table);
if (key == KEY_NEW_DEAL)
{
is_new_game = true;
source_row = NOTSELECTED;
target_row = NOTSELECTED;
erase_move(table);
erase_keys(table);
erase_screen_lines(table, TABLE_HEIGHT - 1, TABLE_HEIGHT - 1);
collect_cards(table, card);
}
else if (key == KEY_DISPLAY_HELP)
{
erase_screen_lines(table, 1, TABLE_HEIGHT - 1);
display_help(table);
wgetch(table);
erase_screen_lines(table, 1, TABLE_HEIGHT - 1);
for (i = 0; i < NUM_ROWS; i++)
{
display_row(table, i, card);
}
}
else if (key == KEY_QUIT_GAME)
{
is_quit_game = true;
save_file = fopen(file_path, "w+");
for (i = 0; i < NUM_CARDS; i++)
{
fprintf(save_file, "%d\n", stock[i]);
}
fprintf(save_file, "%d,%d", up_or_down, up_or_down_on_move);
for (i = 0; i < move; i++)
{
fprintf(save_file, "\n%d,%d", from[i], to[i]);
}
fclose(save_file);
}
else if (key == KEY_UNDO_MOVE &&
move >= 1 && source_row == NOTSELECTED && !is_game_won)
{
source_row = to[move - 1];
target_row = from[move - 1];
if (move == up_or_down_on_move)
{
up_or_down = UNDETERMINED;
up_or_down_on_move = UNDETERMINED;
}
--move;
is_modified_layout = true;
}
else if (source_row == NOTSELECTED) /* User has chosen a row as source */
{
if (key == KEY_CELLAR &&
!is_empty(CELLAR, card) && is_cellar_accessible(card))
{
source_row = CELLAR;
}
else if (key >= KEY_ROW_10 && key <= KEY_ROW_9 &&
!is_empty(key - '0', card))
{
source_row = key - '0';
}
source_card = cards_in_row(source_row, card) - 1;
}
else if (key >= KEY_ROW_10 && key <= KEY_ROW_9) /* User has chosen a regular row as target */
/* KEY_ROW_9 and KEY_ROW_10 for extra rows 1 and 2 are
* included here so that (illegally) choosing these rows as
* targets will lead to unselecting the source row later
* on! */
{
target_row = key - '0';
target_card = cards_in_row(target_row, card) - 1;
if (is_regular_row(target_row) &&
(is_match_regular_row(card[source_row][source_card],
card[target_row][target_card]) ||
is_empty(target_row, card)))
{
is_modified_layout = true;
}
}
else if (key == KEY_CELLAR && is_empty(CELLAR, card)) /* User has chosen cellar as target */
{
target_row = CELLAR;
target_card = 0;
is_modified_layout = true;
}
else if (key == KEY_FOUNDATION) /* User has chosen foundation as target */
{
target_row = FOUNDATION_BASE + suit_of(card[source_row][source_card]);
target_card = cards_in_row(target_row, card) - 1;
if (is_match_foundation(card[source_row][source_card],
card[target_row][target_card],
&up_or_down))
{
is_modified_layout = true;
}
}
if (is_modified_layout) /* A card has been moved (or a move undone) */
{
erase_row(table, source_row, card);
erase_row(table, target_row, card);
move_card(source_row, target_row, card);
display_row(table, source_row, card);
display_row(table, target_row, card);
if (key != KEY_UNDO_MOVE)
{
++move;
from[move - 1] = source_row;
to[move - 1] = target_row;
}
if (up_or_down != UNDETERMINED && up_or_down_on_move == UNDETERMINED)
{
up_or_down_on_move = move;
}
}
else if (source_row >= 0)
{
if (target_row >= 0 || key == KEY_ESCAPE)
{
is_card_selected = false;
}
else
{
is_card_selected = true;
}
wattron(table, COLOR_PAIR(card_color(card[source_row][source_card],
source_card,
source_card + 1,
is_card_selected)));
display_card(table,
y_pt(source_row),
x_pt(source_card, source_row, card),
card[source_row][source_card]);
}
if (is_modified_layout ||
((source_row >= 0 && target_row >= 0) ||
(source_row >= 0 && key == KEY_ESCAPE)))
{
source_row = NOTSELECTED;
target_row = NOTSELECTED;
source_card = NOTSELECTED;
target_card = NOTSELECTED;
}
is_game_won = is_foundation_complete(card);
wrefresh(table);
}
delwin(table);
endwin();
free(file_path);
return 0;
}
int rank_of (int c)
/*
* Determines the rank of a given card.
*
* c: card (1 to NUM_CARDS)
*
* returns: numerical rank, i.e. 1 (Ace) to 13 (King)
*/
{
return c - (((c - 1) / NUM_RANKS) * NUM_RANKS);
}
int suit_of (int c)
/*
* Determines the suit of a given card.
*
* c: card (1 to NUM_CARDS)
*
* returns: number of suit, i.e. 0 (Hearts) to 3 (Clubs).
*/
{
return (c - 1) / NUM_RANKS;
}
int cards_in_row (int r,
int card[NUM_ROWS][MAX_CARDS_IN_ROW])
/*
* Determines the number of cards in a given row.
*
* r: row of cards (0 to NUM_ROWS - 1)
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
*
* returns: number of cards in row n
*/
{
int i = 0;
while (i < MAX_CARDS_IN_ROW && card[r][i] != EMPTY)
{
i++;
}
return i;
}
int overlapping_cards (int n)
/*
* Determines the number of overlapping cards in given row.
*
* n: number of cards in row (1 to MAX_CARDS_IN_ROW)
*
* returns: number of overlapping cards in a row of n cards
*/
{
if (n >= OVERLAP_THRESHOLD_3)
{
return n - 3;
}
else if (n >= OVERLAP_THRESHOLD_2)
{
return n - 4;
}
else if (n >= OVERLAP_THRESHOLD_1)
{
return n - 5;
}
else
{
return 0;
}
}
int key_label (int r)
/*
* Determines the label (i.e. the key) for a given row.
*
* r: row of cards (0 to NUM_ROWS - 1)
*
* returns: code of char for label for row r
*/
{
if (is_regular_row(r) || is_extra_row(r))
{
return r + '0';
}
else if (is_cellar(r))
{
return KEY_CELLAR;
}
else
{
return KEY_FOUNDATION;
}
}
int x_pt (int p,
int r,
int card[NUM_ROWS][MAX_CARDS_IN_ROW])
/*
* Determines the point on the X-axis at which the left-most parts of
* a given card in a given row are to be displayed.
*
* This point depends on the total number of cards in the given row
* since beginning with a total of 8 cards, a certain number of cards
* on the 'inside' of the row are displayed overlapping each other
* (see function overlapping_cards).
*
* p: position of card in row (0 to MAX_CARDS_IN_ROW - 1)
* r: row of cards (0 to NUM_ROWS - 1)
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
*
* returns: point on X-axis
*/
{
int n = cards_in_row(r, card);
int o = overlapping_cards(n) - 1;
int x;
if (is_cellar(r) || is_foundation(r))
{
return X_OFFSET_ROWS;
}
else
{
if ((n >= OVERLAP_THRESHOLD_3 && p < (n - 3)) ||
(n >= OVERLAP_THRESHOLD_2 && p < (n - 4)) ||
(n >= OVERLAP_THRESHOLD_1 && p < (n - 5)))
{
x = p + (CARD_WIDTH + 1);
}
else if (n >= OVERLAP_THRESHOLD_1)
{
x = o + ((CARD_WIDTH + 1) * (p - o + 1));
}
else
{
x = (1 + p) * (CARD_WIDTH + 1);
}
if (is_even(r))
{
return X_OFFSET_ROWS + x + 1;
}
else
{
return X_OFFSET_ROWS - x - 1;
}
}
}
int x_pt_key (int r)
/*
* Determines the point on the X-axis at which the label (i.e. the
* key) for a given row is to be displayed.
*
* r: row of cards (0 to NUM_ROWS - 1)
*
* returns: point on X-axis
*/
{
if (r >= 0 && r < NUM_REGULAR_ROWS && is_even(r))
{
return TABLE_WIDTH - 1;
}
else if (r >= 0 && r < NUM_REGULAR_ROWS)
{
return 0;
}
else /* Cellar and foundation */
{
return X_OFFSET_ROWS - 1;
}
}
int y_pt (int r)
/*
* Determines the point on the Y-axis at which the upper-most parts of
* a card in a given row are to be displayed.
*
* r: row of cards (0 to NUM_ROWS - 1)
*
* returns: point on Y-axis
*/
{
switch (r)
{
case 3: case 4: case FOUNDATION_BASE + 1:
return Y_OFFSET_ROWS + 1 * (CARD_HEIGHT + 2);
break;
case 5: case 6: case FOUNDATION_BASE + 2:
return Y_OFFSET_ROWS + 2 * (CARD_HEIGHT + 2);
break;
case 7: case 8: case FOUNDATION_BASE + 3:
return Y_OFFSET_ROWS + 3 * (CARD_HEIGHT + 2);
break;
case EXTRA_ROW_1: case EXTRA_ROW_2: case CELLAR:
return Y_OFFSET_ROWS + 4 * (CARD_HEIGHT + 2);
break;
default: /* 1, 2, FOUNDATION_BASE */
return Y_OFFSET_ROWS;
}
}
int card_color (int c,
int p,
int n,
bool s)
/*
* Determines the color pair (fore- and background) for a given card.
*
* Since the card could be overlapped by other cards in the same row
* (see function x_pt), its background color depends on its position
* in the row and the total number of cards in that row. Also, the
* card could have been selected as a source in which case it has to
* be highlighted using another background color.
*
* c: card (1 to NUM_CARDS)
* p: position of card in row (0 to MAX_CARDS_IN_ROW - 1)
* n: number of cards in row (0 to MAX_CARDS_IN_ROW)
* s: was card selected as source or not?
*
* returns: number for COLOR_PAIR
*/
{
if (s)
{
return CARD_HIGHLIGHT;
}
else
{
switch (suit_of(c))
{
case SUIT_HEART:
return HEART_LIGHT + (is_card_shaded(p, n) * CARD_SHADE);
break;
case SUIT_SPADE:
return SPADE_LIGHT + (is_card_shaded(p, n) * CARD_SHADE);
break;
case SUIT_DIAMOND:
return DIAMOND_LIGHT + (is_card_shaded(p, n) * CARD_SHADE);
break;
case SUIT_CLUB:
return CLUB_LIGHT + (is_card_shaded(p, n) * CARD_SHADE);
break;
default:
return TEXT_REGULAR;
break;
}
}
}
int collect_t_row (int r,
int p,
int card[NUM_ROWS][MAX_CARDS_IN_ROW])
/*
* Determines the target row into which the current collected card is
* to be placed next (in collect_cards).
*
* r: row from which card is taken (0 to NUM_ROWS - 1)
* p: position in the row from which card is taken (0 to
* MAX_CARDS_IN_ROW - 1)
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
*
* returns: number of row
*/
{
if (r == FOUNDATION_BASE + NUM_FAMILIES - 1 ||
(is_extra_row(r) && cards_in_row(r, card) == 1))
{
return CELLAR;
}
else if (r >= FOUNDATION_BASE && r < FOUNDATION_BASE + NUM_FAMILIES - 1)
{
return r + 1;
}
else if (is_regular_row(r) && p == 0)
{
return FOUNDATION_BASE + ((r - 1) / 2);
}
else
{
return r;
}
}
int collect_t_pos (int r,
int p,
int card[NUM_ROWS][MAX_CARDS_IN_ROW])
/*
* Determines the target position onto which the current collected
* card is to be placed next (in collect_cards).
*
* r: row from which card is taken (0 to NUM_ROWS - 1)
* p: position in the row from which card is taken (0 to
* MAX_CARDS_IN_ROW - 1)
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
*
* returns: number of position
*/
{
if (r == FOUNDATION_BASE + NUM_FAMILIES - 1 ||
(is_extra_row(r) && cards_in_row(r, card) == 1))
{
return 0;
}
else if (r >= FOUNDATION_BASE && r < FOUNDATION_BASE + NUM_FAMILIES - 1)
{
return cards_in_row(r, card);
}
else if (is_regular_row(r) && p == 0)
{
return card[collect_t_row(r, p, card)]\
[cards_in_row(collect_t_row(r, p, card), card)];
}
else
{
return p - 1;
}
}
int initial_tmp_row (int stock[],
int n,
int r)
/*
* Determines the temporary row in which the next card is to be placed
* when forming the inital layout.
*
* stock[]: initial stock of cards
* n: subscript in stock of card to be placed (0 to NUM_CARDS - 1)
* r: row in which the previous card was placed
*
* returns: number of row
*/
{
if (rank_of(stock[n]) == rank_of(stock[0]))
{
return FOUNDATION_BASE + suit_of(stock[n]);
}
else
{
return r;
}
}
int initial_tmp_pos (int stock[],
int n,
int p)
/*
* Determines the temporary position in a row atn which the next card
* is to be placed when forming the inital layout.
*
* stock[]: initial stock of cards
* n: subscript in stock of card to be placed (0 to NUM_CARDS - 1)
* p: positionin row at which the previous card was placed
*
* returns: number of position
*/
{
if (rank_of(stock[n]) == rank_of(stock[0]))
{
return 0;
}
else
{
return p;
}
}
bool is_even (int n)
/*
* Determines whether an integer is even.
*
* n: integer
*
* returns: TRUE if n is even OR if n is 0 (which is used to effect in
* this program!)
*/
{
return !(n % 2);
}
bool is_empty (int r,
int card[NUM_ROWS][MAX_CARDS_IN_ROW])
/*
* Determines whether a given row is empty (i.e. has no cards in it).
*
* r: row of cards (0 to NUM_ROWS - 1)
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
*
* returns: TRUE if there are no cards in r
*/
{
return cards_in_row(r, card) == 0;
}
bool is_regular_row (int r)
/*
* Determines whether a given row is a regular row.
*
* r: row (0 to NUM_ROWS - 1)
*
* returns: TRUE if r is one of the ten regular rows
*/
{
return r >= FIRST_RESERVE_ROW && r <= LAST_RESERVE_ROW;
}
bool is_extra_row (int r)
/*
* Determines whether a given row is one of the two extra rows.
*
* r: row of cards (0 to NUM_ROWS - 1)
*
* returns: TRUE if r is one of the two extra rows
*/
{
return r == EXTRA_ROW_1 || r == EXTRA_ROW_2;
}
bool is_cellar (int r)
/*
* Determines whether a given row is the cellar.
*
* r: row of cards (0 to NUM_ROWS - 1)
*
* returns: TRUE if r is the cellar
*/
{
return r == CELLAR;
}
bool is_foundation (int r)
/*
* Determines whether a given row is a foundation row.
*
* r: row of cards (0 to NUM_ROWS - 1)
*
* returns: TRUE if r is one of the four foundation rows
*/
{
return r >= FOUNDATION_BASE && r <= FOUNDATION_BASE + NUM_FAMILIES - 1;
}
bool is_rank_difference (int a,
int b,
int d)
/*
* Determines whether the ranks of two given cards differ by a given number.
*
* a: card (1 to NUM_CARDS)
* b: card (1 to NUM_CARDS)
* d: difference between a and b
*
* returns: TRUE if ranks of a and b differ by d
*/
{
return rank_of(a) == rank_of(b) + d;
}
bool is_king (int c)
/*
* Determines whether the rank of a given card is King.
*
* c: card (1 to NUM_CARDS)
*
* returns: TRUE if rank of c is King
*/
{
return rank_of(c) == RANK_KING;
}
bool is_ace (int c)
/*
* Determines whether the rank of a given card is Ace.
*
* c: card (1 to NUM_CARDS)
*
* returns: TRUE if rank of c is Ace
*/
{
return rank_of(c) == RANK_ACE;
}
bool is_match_suits (int a,
int b)
/*
* Determines whether the suits (Hearts, Spades, Diamonds, Clubs) of
* two given cards are the same.
*
* a: card (1 to NUM_CARDS)
* b: card (1 to NUM_CARDS)
*
* returns: TRUE if a and b are of the same suit
*/
{
return suit_of(a) == suit_of(b);
}
bool is_match_regular_row (int s,
int t)
/*
* Determines whether a source card matches a target card in a regular
* row according to the rules of One-deck Napoleon solitaire.
*
* s: card (1 to NUM_CARDS)
* t: card (1 to NUM_CARDS)
*
* returns: TRUE if s matches t
*/
{
return (is_match_suits(s, t) &&
(is_rank_difference(s, t, UP) ||
is_rank_difference(s, t, DOWN) ||
(is_king(s) && is_ace(t)) ||
(is_ace(s) && is_king(t))));
}
bool is_match_foundation (int s,
int t,
int *up_or_down)
/*
* Determines whether a given card matches its family, i.e. the card
* on the according foundation row.
*
* If the given card matches its family and is the very first card to
* be placed on the foundation, the function also sets up_or_down to
* 'remember' the chosen order in which families have to be built
* (ascending or descending by rank).
*
* s: card (1 to NUM_CARDS)
* t: card (1 to NUM_CARDS)
* up_or_down: order in which families have to be built (ascending or
* descending by rank) (up, down or UNDECIDED)
*
* returns: TRUE if s and t match
*/
{
if (is_match_suits(s, t) && *up_or_down == UNDETERMINED &&
(is_rank_difference(s, t, UP) || (is_king(s) && is_ace(t))))
{
*up_or_down = UP;
return true;
}
else if (is_match_suits(s, t) && *up_or_down == UNDETERMINED &&
(is_rank_difference(s, t, DOWN) || (is_ace(s) && is_king(t))))
{
*up_or_down = DOWN;
return true;
}
else if (is_match_suits(s, t) &&
(is_rank_difference(s, t, *up_or_down) ||
(*up_or_down == UP && is_king(s) && is_ace(t)) ||
(*up_or_down == DOWN && is_ace(s) && is_king(t))))
{
return true;
}
else
{
return false;
}
}
bool is_match_card (int s_row,
int t_row,
int card[NUM_ROWS][MAX_CARDS_IN_ROW],
int *up_or_down)
/*
* Determines whether cards from two given rows match by rank and suit
* according to the rules of One-deck Napoleon solitaire.
*
* s_row: source row (0 to NUM_ROWS - 1)
* t_row: target row (0 to NUM_ROWS - 1)
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
* up_or_down: order in which families have to be built (ascending or
* descending by rank) (up, down or UNDECIDED)
*
* returns: TRUE if cards from s_row and t_row match
*/
{
int s = card[s_row][cards_in_row(s_row, card) - 1];
int t = card[t_row][cards_in_row(t_row, card) - 1];
return ((is_regular_row(t_row) &&
(is_empty(t_row, card) || is_match_regular_row(s, t))) ||
(is_foundation(t_row) &&
is_match_foundation(s, t, up_or_down)) ||
(is_cellar(t_row) && is_empty(CELLAR, card)));
}
bool is_foundation_complete (int card[NUM_ROWS][MAX_CARDS_IN_ROW])
/*
* Determines whether all four families have been completed.
*
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
*
* returns: TRUE if all cards have been placed on foundation rows
*/
{
int s = 0;
for (int i = 0; i < NUM_FAMILIES; i++)
{
s = s + cards_in_row(FOUNDATION_BASE + i, card);
}
return s == NUM_CARDS;
}
bool is_cellar_accessible (int card[NUM_ROWS][MAX_CARDS_IN_ROW])
/*
* Determines whether a card in the cellar can be accessed (i.e.
* whether either extra row 1 or 2 are empty).
*
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
*
* returns: TRUE if there are no cards in either extra row 1 or 2
*/
{
return is_empty(EXTRA_ROW_1, card) || is_empty(EXTRA_ROW_2, card);
}
bool is_row_source (int r,
int card[NUM_ROWS][MAX_CARDS_IN_ROW])
/*
* Determines whether a given row can serve as a source for a card to
* be selected according to the rules of One-deck Napoleon solitaire.
*
* r: row of cards (0 to NUM_ROWS - 1)
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
*
* returns: TRUE if there is a card in r that can be selected
*/
{
return !is_empty(r, card) &&
(is_regular_row(r) ||
is_extra_row(r) ||
(is_cellar(r) && is_cellar_accessible(card)));
}
bool is_targets_available (int r,
int card[NUM_ROWS][MAX_CARDS_IN_ROW],
int up_or_down)
/*
* Determines whether there are any matching target cards for a given
* source card.
*
* r: row (1 to NUM_ROWS)
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
* up_or_down: order in which families are be built (ascending or
* descending by rank) (up, down or UNDECIDED)
*
* returns: TRUE if there is at least one possible target row for card
* in r
*/
{
bool t = false;
for (int i = 0; i < NUM_ROWS; i++)
{
if (is_match_card(r, i, card, &up_or_down))
{
t = true;
}
}
return t;
}
bool is_card_shaded (int p,
int n)
/*
* Determines whether a given (overlapping) card is shaded.
*
* p: position of card in row (0 to MAX_CARDS_IN_ROW - 1)
* n: total number of cards in row
*
* returns: TRUE if card on position n is shaded
*/
{
return (p + 1 <= overlapping_cards(n) &&
is_even(p + 1) != is_even(overlapping_cards(n)));
}
char *card_face (int c)
/*
* Determines the face (A, 2 .. 10, J, Q, K) of a given card.
*
* c: card (1 to NUM_CARDS)
*
* returns: char for face of c
*/
{
static char f[3];
switch (rank_of(c))
{
case RANK_ACE:
sprintf(f, "%s", SYMBOL_ACE);
break;
case RANK_JACK:
sprintf(f, "%s", SYMBOL_JACK);
break;
case RANK_QUEEN:
sprintf(f, "%s", SYMBOL_QUEEN);
break;
case RANK_KING:
sprintf(f, "%s", SYMBOL_KING);
break;
default:
sprintf(f, "%d", rank_of(c));
}
return f;
}
wchar_t suit_symbol (int c)
/*
* Determines the suit symbol (Heart, Spade, Diamond, Club) of a given card.
*
* c: card (1 to NUM_CARDS)
*
* returns: Unicode char for suit symbol of c
*/
{
switch (suit_of(c))
{
case SUIT_HEART:
return SYMBOL_HEART;
break;
case SUIT_SPADE:
return SYMBOL_SPADE;
break;
case SUIT_DIAMOND:
return SYMBOL_DIAMOND;
break;
case SUIT_CLUB:
return SYMBOL_CLUB;
break;
default:
return SYMBOL_UNKNOWN;
break;
}
}
void do_generate (int stock[])
/*
* Generates the initial stock, ordered stock of cards (0 to NUM_CARDS - 1)
* with values 1 to 52 (1 to 13 = Ace to King of Hearts, 14 to 26 = Ace to
* King of Spades, 27 to 39 = Ace to King of Diamonds, 40 to 52 = Act to King
* of Clubs).
*
* stock[]: initial stock of cards
*/
{
for (int i = 0; i < NUM_CARDS; i++)
{
stock[i] = i + 1;
}
}
void do_shuffle (int stock[])
/*
* Shuffles the cards (0 to NUM_CARDS - 1) in stock. We use the Fisher–Yates
* shuffle (see https://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
*
* stock[]: initial stock of cards
*/
{
int j;
int temp;
srand((unsigned) time(NULL));
for (int i = NUM_CARDS - 1; i > 0; i--)
{
j = rand() % (i + 1);
temp = stock[i];
stock[i] = stock[j];
stock[j] = temp;
}
}
void move_card (int s_row,
int t_row,
int card[NUM_ROWS][MAX_CARDS_IN_ROW])
/*
* Moves the outermost card from a given source row to a given target
* row.
*
* s_row: source row (0 to NUM_ROWS - 1)
* t_row: target row (0 to NUM_ROWS - 1)
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
*/
{
card[t_row][cards_in_row(t_row, card)] = card[s_row][cards_in_row(s_row, card) - 1];
card[s_row][cards_in_row(s_row, card) - 1] = EMPTY;
}
void erase_card (WINDOW *win,
int y,
int x)
/*
* Erases the card displayed at y,x points.
*
* win: window used for display
* y: point on Y-axis of uppermost parts of card to be erased
* x: point on X-axis of leftmost parts of card to be erased
*/
{
wattron(win, COLOR_PAIR(TEXT_REGULAR));
for (int i = 0; i <= CARD_HEIGHT - 1; i++)
{
mvwprintw(win, y + i, x, " ");
}
}
void display_card (WINDOW *win,
int y,
int x,
int c)
/*
* Displays a card at a given y,x point.
*
* win: window used for display
* y: point on Y-axis of uppermost parts of card to be displayed
* x: point on X-axis of leftmost parts of card to be displayed
* c: card (1 to NUM_CARDS)
*/
{
if (strlen(card_face(c)) == 1)
{
mvwprintw(win, y, x, "%s %lc", card_face(c), suit_symbol(c));
mvwprintw(win, y + 1, x, " ");
mvwprintw(win, y + 2, x, "%lc %s", suit_symbol(c), card_face(c));
}
else
{
mvwprintw(win, y, x, "%s %lc", card_face(c), suit_symbol(c));
mvwprintw(win, y + 1, x, " ");
mvwprintw(win, y + 2, x, "%lc %s", suit_symbol(c), card_face(c));
}
}
void erase_row (WINDOW *win,
int r,
int card[NUM_ROWS][MAX_CARDS_IN_ROW])
/*
* Erases all displayed cards in a given row.
*
* win: window used for display
* r: row to be erased
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
*/
{
for (int i = 0; i < cards_in_row(r, card); i++)
{
erase_card(win, y_pt(r), x_pt(i, r, card));
}
}
void display_row (WINDOW *win,
int r,
int card[NUM_ROWS][MAX_CARDS_IN_ROW])
/*
* Displays all cards in a given row.
*
* win: window used for display
* r: row to be displayed
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
*/
{
for (int i = 0; i < cards_in_row(r, card); i++)
{
wattron(win, COLOR_PAIR(card_color(card[r][i],
i,
cards_in_row(r, card),
false)));
display_card(win, y_pt(r), x_pt(i, r, card), card[r][i]);
}
}
void set_initial_row_pos (int tmp_r,
int *row,
int *pos)
/*
* Sets the correct value of 'row' and 'pos' for the next card to be
* placed when forming the initial layout (see make_initial_layout).
*
* tmp_r: row in which the previous card was placed
* row: row of card
* pos: position of card in row
*/
{
if (tmp_r < FOUNDATION_BASE &&
((*pos < 4 && is_regular_row(*row)) || (*pos < 3 && is_extra_row(*row))))
{
*pos = *pos + 1;
}
else if (tmp_r < FOUNDATION_BASE)
{
*pos = 0;
*row = *row + 1;
if (is_cellar(*row))
{
*row = EXTRA_ROW_2;
}
}
}
void make_initial_layout (WINDOW *win,
int stock[],
int card[NUM_ROWS][MAX_CARDS_IN_ROW],
bool d)
/*
* Makes the initial layout by places the cards from stock in the rows
* according to the rules of One-deck Napoleon solitaire. This process
* can be displayed (if there is a new deal) or not (if the layout is
* restored from a saved game).
*
* win: window used for display
* stock[]: initial stock of cards
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
* d: is process displayed or not?
*/
{
int row = 1;
int pos = 0;
int temp_row;
int temp_pos;
for (int i = 0; i < NUM_CARDS; i++)
{
temp_row = initial_tmp_row(stock, i, row);
temp_pos = initial_tmp_pos(stock, i, pos);
card[temp_row][temp_pos] = stock[i];
if (d)
{
wattron(win, COLOR_PAIR(card_color(stock[i], 1, 1, false)));
display_card(win, y_pt(temp_row), x_pt(temp_pos, temp_row, card), stock[i]);
wrefresh(win);
usleep(DELAY);
}
set_initial_row_pos(temp_row, &row, &pos);
}
}
void erase_keys (WINDOW *win)
/*
* Erases all displayed keys for source and target rows from the game
* screen.
*
* win: window used for display
*/
{
int x;
wattron(win, COLOR_PAIR(TEXT_REGULAR));
for (int i = 0; i <= NUM_ROWS; i++)
{
if (i >= 0 && i < NUM_REGULAR_ROWS && is_even(i))
{
x = TABLE_WIDTH - 1;
}
else if (i >=0 && i < NUM_REGULAR_ROWS)
{
x = 0;
}
else
{
x = X_OFFSET_ROWS - 1 ;
}
mvwprintw(win, y_pt(i), x, " ");
}
}
void display_keys (WINDOW *win,
int s_row,
int card[NUM_ROWS][MAX_CARDS_IN_ROW],
int up_or_down)
/*
* Displays the keys for currently available source or target rows.
*
* win: window used for display
* s_row: source row (0 to NUM_ROWS - 1)
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
* up_or_down: order in which families are be built (ascending or
* descending by rank) (up, down or UNDECIDED)
*/
{
for(int i = 0; i <= NUM_ROWS; i++)
{
if (s_row == NOTSELECTED)
{
wattron(win, COLOR_PAIR(TEXT_REGULAR + (s_row != NOTSELECTED)));
}
else
{
wattron(win, COLOR_PAIR(TEXT_HIGHLIGHT));
}
if ((s_row == NOTSELECTED && is_row_source(i, card)) ||
(s_row != NOTSELECTED && is_match_card(s_row, i, card, &up_or_down)))
{
mvwprintw(win, y_pt(i), x_pt_key(i), "%c", key_label(i));
}
}
}
void display_title (WINDOW *win)
/*
* Displays the title of the game in the header of the game screen.
*
* win: window used for display
*/
{
wattron(win, COLOR_PAIR(TEXT_REGULAR));
mvwprintw(win, Y_OFFSET_HEADER, X_OFFSET_HEADER, "%s %s, %s %s", PROGRAM_NAME, VERSION_NUMBER, AUTHOR, COPYRIGHT_YEAR);
}
void erase_move (WINDOW *win)
/*
* Erases the display of the number of moves made so far from the
* header of the game screen.
*
* win: window used for display
*/
{
char s[16];
int len = sprintf(s, "%s: %d", LABEL_MOVE, HISTORY);
wattron(win, COLOR_PAIR(TEXT_REGULAR));
mvwprintw(win, Y_OFFSET_HEADER, TABLE_WIDTH - len, " ");
}
void display_move (WINDOW *win,
int m)
/*
* Displays the number of moves made so far in the header of the game
* screen.
*
* win: window used for display
* m: number of moves
*/
{
char s[16];
int len = sprintf(s, " %s: %d", LABEL_MOVE, m);
wattron(win, COLOR_PAIR(TEXT_REGULAR));
mvwprintw(win, Y_OFFSET_HEADER, TABLE_WIDTH - len, "%s", s);
}
void erase_screen_lines (WINDOW *win,
int f,
int t)
/*
* Erases lines 'from' to 'to'.
*
* win: window used for display
* f: line number to start from
* t: line number to end at
*/
{
wattron(win, COLOR_PAIR(TEXT_REGULAR));
while (f <= t)
{
mvwprintw(win, f, X_OFFSET_FOOTER,\
" ");
f++;
}
}
void display_instructions (WINDOW *win,
int s_row,
int m,
bool t,
bool w)
/*
* Displays the currently available commands and their keys in the
* footer of the game screen.
*
* win: window used for display
* s_row: source row (0 to NUM_ROWS - 1)
* m: number of moves
* t: Are there possible targets for a chosen source card or not?
* w: Has the game been won or not?
*/
{
char s[80];
if (w)
{
sprintf(s, "Congratulations, you finished the game! [N]ew deal or [Q]uit.");
}
else if (s_row >= 0 && t)
{
sprintf(s, "Select target, [ESC] to deselect source, [H]elp, [N]ew deal or [Q]uit.");
}
else if (s_row >= 0)
{
sprintf(s, "[ESC] to deselect source, [H]elp, [N]ew deal or [Q]uit.");
}
else if (m > 0)
{
sprintf(s, "Select source, [U]ndo, [H]elp, [N]ew deal or [Q]uit.");
}
else
{
sprintf(s, "Select source, [H]elp, [N]ew deal or [Q]uit.");
}
wattron(win, COLOR_PAIR(TEXT_REGULAR));
erase_screen_lines(win, Y_OFFSET_FOOTER, Y_OFFSET_FOOTER);
mvwprintw(win, Y_OFFSET_FOOTER, X_OFFSET_FOOTER, "%s", s);
}
void display_help (WINDOW *win)
/*
* Displays the help screen.
*
* win: window used for display
*/
{
mvwprintw(win, 2, 0, " The goal is to build four complete families on the [f]oundation cards in the");
mvwprintw(win, 3, 0, "middle column.");
mvwprintw(win, 5, 0, " The cards of a family must match in suit (hearts, spades, diamonds, clubs)");
mvwprintw(win, 6, 0, "and line up by rank (1=Ace, 2, ..., 10, 11=Jack, 12=Queen, 13=King) in either");
mvwprintw(win, 7, 0, "ascending or descending order. Ascending order wraps around from 13=King to");
mvwprintw(win, 8, 0, "1=Ace, descending order wraps around the other way. The first card placed on");
mvwprintw(win, 9, 0, "the foundation determines the order for all families and the rest of the game.");
mvwprintw(win, 11, 0, " Only \"free\" cards, i.e. cards lying at the end of a row [0-9], can be moved.");
mvwprintw(win, 13, 0, " Cards may be moved to other auxiliary rows [1–8] if suits match and ranks");
mvwprintw(win, 14, 0, "line up in either ascending or descending order (see above). In the auxiliary");
mvwprintw(win, 15, 0, "rows, this order may change from one row to the next. Cards can only be moved");
mvwprintw(win, 16, 0, "from (but not to!) the two extra rows [9,0] at the bottom.");
mvwprintw(win, 18, 0, " A single card can be moved to the [c]ellar underneath the families. To move");
mvwprintw(win, 19, 0, "this card out of the cellar again, one of the extra rows must be empty.");
mvwprintw(win, 21, 0, " If an auxiliary row has been emptied, any free card can be moved there to");
mvwprintw(win, 22, 0, "start a new row.");
mvwprintw(win, 24, 0, " Have fun!");
}
void collect_cards (WINDOW *win,
int card[NUM_ROWS][MAX_CARDS_IN_ROW])
/*
* Collects the cards when a new deal is requested, resets the array
* 'card' and displays the process.
*
* win: window used for display
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
*/
{
int s_row[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 11, 12, 13, 14, 15, 10};
/* This is the sequence of rows in the order displayed from top left
* to bottom right (the 2nd extra row at the bottom right has
* subscript 0, while the cellar has subscript 10). */
int t_row;
int s_pos;
int t_pos;
for (int i = 0; i <= NUM_ROWS; i++)
{
while (!is_empty(s_row[i], card))
{
s_pos = cards_in_row(s_row[i], card) - 1;
t_row = collect_t_row(s_row[i], s_pos, card);
t_pos = collect_t_pos(s_row[i], s_pos, card);
card[t_row][t_pos] = card[s_row[i]][s_pos];
erase_row(win, s_row[i], card);
if (s_row[i] >= FOUNDATION_BASE)
{
/* Foundation cards are collected as a whole, so the
* entire row has to be reset. */
memset(&card[s_row[i]][0], 0, 16);
}
else
{
card[s_row[i]][s_pos] = EMPTY;
}
display_row(win, s_row[i], card);
display_row(win, t_row, card);
wrefresh(win);
usleep(DELAY + ((s_row[i] >= FOUNDATION_BASE) * DELAY * 5));
/* When collecting from the foundation, we pause a bit
* longer. */
}
}
}
void display_win (WINDOW *win,
int card[NUM_ROWS][MAX_CARDS_IN_ROW])
/*
* Displays a short animation when the game has been won.
*
* win: window used for display
* card[NUM_ROWS][MAX_CARDS_IN_ROW]: all cards in play
*/
{
for (int i = 1; i <= 4; i++)
{
for (int j = 0; j < NUM_FAMILIES; j++)
{
for (int m = 1; m > 0; m--)
{
wattron(win, COLOR_PAIR(card_color(card[FOUNDATION_BASE + j][12],
1,
1,
m)));
display_card(win,
y_pt(FOUNDATION_BASE + j),
x_pt(13, FOUNDATION_BASE + j, card),
card[FOUNDATION_BASE + j][12]);
wrefresh(win);
usleep(DELAY * 3);
}
}
}
}