/* * Gnome Set game - cards.c * Copyright 2007 Kirill Gorelov * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include "cards.h" #include "img.h" #include "debug.h" CardsDeck card_deck; CardsPack card_pack; /* Card functions */ gboolean cards_check_set(Card *a, Card *b, Card *c) { if ((a == NULL) || (b == NULL) || (c == NULL)) return FALSE; if ( /* color */ (((a->color == b->color) && (b->color == c->color) && (a->color == c->color)) || ((a->color != b->color) && (b->color != c->color) && (a->color != c->color))) && /* number */ (((a->number == b->number) && (b->number == c->number) && (a->number == c->number)) || ((a->number != b->number) && (b->number != c->number) && (a->number != c->number))) && /* symbol */ (((a->symbol == b->symbol) && (b->symbol == c->symbol) && (a->symbol == c->symbol)) || ((a->symbol != b->symbol) && (b->symbol != c->symbol) && (a->symbol != c->symbol))) && /* shading */ (((a->shading == b->shading) && (b->shading == c->shading) && (a->shading == c->shading)) || ((a->shading != b->shading) && (b->shading != c->shading) && (a->shading != c->shading))) ) return TRUE; else return FALSE; } /* Cards image related functions */ CardsImage* cards_image_new(void) { CardsImage *cimg; cimg = g_new(CardsImage, 1); cimg->width = 0; cimg->height = 0; cimg->image = img_new(); cimg->pixbuf = NULL; return cimg; } CardsImage* cards_image_from_file(const gchar *file) { CardsImage *cimg; DBG(CARDS_DEBUG, "Loading cards file %s\n", file); cimg = g_new(CardsImage, 1); cimg->image = img_new_from_file(file); cimg->width = cimg->image->width / ICARDS_X; cimg->height = cimg->image->height / ICARDS_Y; cimg->ratio = (gdouble)cimg->width / (gdouble)cimg->height; cimg->pixbuf = NULL; return cimg; } void cards_image_render_to_pixmap(CardsImage *cimg, Card* card, GdkPixmap *target, GdkGC *target_gc, gint x, gint y) { gint cx, cy; DBG(RENDER_DEBUG, "Checking whether rendering is possible...\n"); /* check if we can render */ if (cimg->pixbuf == NULL || target == NULL) return; DBG(RENDER_DEBUG, "Calculating card position...\n"); cx = cimg->width * (card->symbol); cy = cimg->height * (ICARDS_Y - 1 - (card->number + 3*card->color + 9*card->shading)); DBG(RENDER_DEBUG, "Calculated. cx=%d cy=%d\n", cx, cy); DBG(RENDER_DEBUG, "cimg->width=%d cimg->height=%d\n", cimg->width, cimg->height); if (card->selected) { GdkPixbuf *active_card; guint px, py, width, height, rowstride; guchar *pixels, *p; DBG(RENDER_DEBUG, "Drawing an active card...\n"); /* copy the active card to a new pixbuf */ active_card = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(cimg->pixbuf), gdk_pixbuf_get_has_alpha(cimg->pixbuf), gdk_pixbuf_get_bits_per_sample(cimg->pixbuf), cimg->width, cimg->height); gdk_pixbuf_copy_area(cimg->pixbuf, cx, cy, cimg->width, cimg->height, active_card, 0, 0); pixels = gdk_pixbuf_get_pixels (active_card); width = gdk_pixbuf_get_width (active_card); height = gdk_pixbuf_get_height (active_card); rowstride = gdk_pixbuf_get_rowstride (active_card); /* Fade the card out (select) */ for (py = 0; py < cimg->height; py++) { for (px = 0; px < cimg->width; px++) { p = pixels + py*rowstride + px*4; p[0] /= 2; p[1] /= 2; p[2] /= 2; } } gdk_pixbuf_render_to_drawable(active_card, target, target_gc, 0, 0, x, y, cimg->width, cimg->height, GDK_RGB_DITHER_NONE, 0, 0); g_object_unref(active_card); } else { DBG(RENDER_DEBUG, "Drawing an inactive card...\n"); gdk_pixbuf_render_to_drawable(cimg->pixbuf, target, target_gc, cx, cy, x, y, cimg->width, cimg->height, GDK_RGB_DITHER_NONE, 0, 0); } } void cards_image_set_size(CardsImage *cimg, gint width, gint height) { DBG(CARDS_DEBUG, "width=%d height=%d\n", width, height); /* check there's an image loaded */ if (cimg->image->pixbuf == NULL) return; /* delete the existing pixbuf */ if (cimg->pixbuf != NULL) g_object_unref(cimg->pixbuf); /* recalculate the width and height with respect to * the height/width ratio */ if (((gdouble)width / (gdouble)height) < cimg->ratio) { height = (gint)((gdouble)width / cimg->ratio); } else { width = (gint)((gdouble)height * cimg->ratio); } /* resize the cimg */ cimg->pixbuf = img_render(cimg->image, width * ICARDS_X, height * ICARDS_Y); cimg->width = width; cimg->height = height; } void cards_image_free(CardsImage *cimg) { if (cimg == NULL) return; img_free(cimg->image); g_object_unref(cimg->pixbuf); g_free(cimg); } /******************************************************************************/ /* Cards deck functions */ static gboolean cards_cmp(Card *c1, Card *c2) { if ((c1->color == c2->color) && (c1->symbol == c2->symbol) && (c1->shading == c2->shading) && (c1->number == c2->number)) return TRUE; return FALSE; } static gboolean cards_deck_check(CardsDeck *deck) { gint i, j, N; N = (COLOR_MAX*SYM_MAX*NUM_MAX*SHAD_MAX); DBG(CARDS_DEBUG, "Card deck internal check..."); for (i = 0; i < N-1; i++) { for (j = i+1; j < N; j++) { if (cards_cmp(deck->cards[i], deck->cards[j])) { DBG(CARDS_DEBUG, "FAILED\n"); return FALSE; } } } DBG(CARDS_DEBUG, "PASSED\n"); return TRUE; } /* Initializes the deck */ void cards_deck_init(CardsDeck *deck) { gint col, sym, num, shad; gint i,j; Card *card; /* Remove old cards from the deck */ cards_deck_cleanup(deck); /* Fill the deck with cards */ DBG(CARDS_DEBUG, "Filling the deck with new cards\n"); deck->ncards = 0; for (col=0; col < COLOR_MAX; col++) for (sym=0; sym < SYM_MAX; sym++) for (num=0; num < NUM_MAX; num++) for (shad=0; shad < SHAD_MAX; shad++) { card = malloc(sizeof(Card)); if (card == NULL) { fprintf(stderr, "Error. Out of memory!\n"); exit(1); } /* Define the card */ card->color = col; card->symbol = sym; card->number = num; card->shading = shad; card->selected = FALSE; /* Put the card in the deck */ deck->cards[deck->ncards] = card; deck->ncards++; } /* Shuffle cards */ DBG(CARDS_DEBUG, "Shuffling cards...\n"); for (i=0; i < deck->ncards; i++) { j = (int) (deck->ncards * (rand() / (RAND_MAX + 1.0))); DBG(CARDS_DEBUG, "Swapping (%d, %d)\n", i, j); card = deck->cards[i]; deck->cards[i] = deck->cards[j]; deck->cards[j] = card; } cards_deck_check(&card_deck); } Card* cards_deck_draw(CardsDeck *deck) { Card* card; DBG(CARDS_DEBUG, "Drawing a card from the deck\n"); if (deck->ncards <= 0) return NULL; /* Return the top card */ card = deck->cards[deck->ncards-1]; deck->cards[deck->ncards-1] = NULL; deck->ncards--; return card; } gboolean cards_deck_is_empty(CardsDeck *deck) { return (deck->ncards <= 0); } void cards_deck_cleanup(CardsDeck *deck) { gint i; /* Free memory taken by the cards remaining in the deck */ DBG(CARDS_DEBUG, "Freeing memory (deleting old cards)\n"); for (i=0; i < deck->ncards; i++) { if (deck->cards[i] != NULL) free(deck->cards[i]); deck->cards[i] = NULL; } } /******************************************************************************/ /* Cards pack functions */ void cards_pack_init(CardsPack *pack, gint lmar, gint rmar, gint tmar, gint bmar, gint sp) { gint i,j; pack->ncards_x = 4; pack->ncards_y = 3; DBG(CARDS_DEBUG, "Initializing the card pack\n"); pack->left_margin = lmar; pack->right_margin = rmar; pack->top_margin = tmar; pack->bottom_margin = bmar; pack->spacing = sp; pack->nactive = 0; pack->hint_flag = 0; for (i=0; i<3; i++) pack->active_cards[i] = NULL; for (i=0; icards[i][j] = NULL; } /* Expands and/or shrinks the pack */ gboolean cards_pack_resize(CardsPack *pack, gint cards_x, gint cards_y) { int x,y; int x1,y1; int num2move; int numfree; DBG(CARDS_DEBUG, "Resizing the pack...\n"); DBG(CARDS_DEBUG, "olx_x = %d, old_y = %d, new_x = %d, new_y = %d\n", pack->ncards_x, pack->ncards_y, cards_x, cards_y); /* Make sure parameters are within the boundaries */ if ((cards_x <= 0) || (cards_y <= 0) || (cards_x > NCARDS_X) || (cards_y > NCARDS_Y)) return FALSE; /* Try to expand in x */ if (cards_x > pack->ncards_x) { DBG(CARDS_DEBUG, "Expanding the pack in X\n"); for (x = pack->ncards_x; x < cards_x; x++) for (y = 0; y < pack->ncards_y; y++) pack->cards[x][y] = NULL; } /* Try to expand in y */ if (cards_y > pack->ncards_y) { DBG(CARDS_DEBUG, "Expanding the pack in Y\n"); for (y = pack->ncards_y; y < cards_y; y++) for (x = 0; x < pack->ncards_x; x++) pack->cards[x][y] = NULL; } /* Try to shrink in x */ num2move = 0; numfree = 0; if (cards_x < pack->ncards_x) { DBG(CARDS_DEBUG, "Shrinking the pack in X\n"); /* count the number of cards to move */ for (x = cards_x; x < pack->ncards_x; x++) for (y = 0; y < pack->ncards_y; y++) if (pack->cards[x][y] != NULL) num2move++; /* count the number of free slots */ for (x = 0; x < cards_x; x++) for (y = 0; y < pack->ncards_y; y++) if (pack->cards[x][y] == NULL) numfree++; DBG(CARDS_DEBUG, "Num to move = %d, Free slots = %d\n", num2move, numfree); if (num2move > numfree) { DBG(CARDS_DEBUG, "Can not srink\n"); return FALSE; } for (x = cards_x; x < pack->ncards_x; x++) for (y = 0; y < pack->ncards_y; y++) { if (pack->cards[x][y] != NULL) for (x1 = 0; x1 < cards_x; x1++) for (y1 = 0; y1 < pack->ncards_y; y1++) if (pack->cards[x1][y1] == NULL) { pack->cards[x1][y1] = pack->cards[x][y]; pack->cards[x][y] = NULL; } } } /* Try to shrink in y */ num2move = 0; numfree = 0; if (cards_y < pack->ncards_y) { DBG(CARDS_DEBUG, "Shrinking the pack in Y\n"); /* count the number of cards to move */ for (y = cards_y; y < pack->ncards_y; y++) for (x = 0; x < pack->ncards_x; x++) if (pack->cards[x][y] != NULL) num2move++; /* count the number of free slots */ for (y = 0; y < cards_y; y++) for (x = 0; x < pack->ncards_x; x++) if (pack->cards[x][y] == NULL) numfree++; DBG(CARDS_DEBUG, "Num to move = %d, Free slots = %d\n", num2move, numfree); if (num2move > numfree) { DBG(CARDS_DEBUG, "Can not srink\n"); return FALSE; } for (y = cards_y; y < pack->ncards_y; y++) for (x = 0; x < pack->ncards_x; x++) { if (pack->cards[x][y] != NULL) for (y1 = 0; y1 < cards_y; y1++) for (x1 = 0; x1 < pack->ncards_x; x1++) if (pack->cards[x1][y1] == NULL) { pack->cards[x1][y1] = pack->cards[x][y]; pack->cards[x][y] = NULL; } } } /* Assign new dimentions and return */ pack->ncards_x = cards_x; pack->ncards_y = cards_y; return TRUE; } void cards_pack_resize_cards(CardsPack *pack, CardsImage *cards, gint width, gint height) { gint card_width, card_height; DBG(CARDS_DEBUG, "Resizing the card pack\n"); /* Calculate (x1,y1) and (x2,y2) */ pack->x1 = pack->left_margin; pack->y1 = pack->top_margin; pack->x2 = width - pack->right_margin; pack->y2 = height - pack->bottom_margin; DBG(CARDS_DEBUG, "Cardsx=%d\n", pack->ncards_x); DBG(CARDS_DEBUG, "Cardsy=%d\n", pack->ncards_y); /* Calculate appropriate cards width/height */ card_width = ((pack->x2 - pack->x1) - pack->spacing*(pack->ncards_x - 1)) / pack->ncards_x; card_height = ((pack->y2 - pack->y1) - pack->spacing*(pack->ncards_y - 1)) / pack->ncards_y; /* Resize cards image accordingly */ cards_image_set_size(cards, card_width, card_height); /* Adjust x1,x2,y1,y2 */ pack->x1 = pack->x1 + ((pack->x2 - pack->x1) - (pack->ncards_x*cards->width + (pack->ncards_x-1)*pack->spacing))/2; pack->x2 = pack->x2 - ((pack->x2 - pack->x1) - (pack->ncards_x*cards->width + (pack->ncards_x-1)*pack->spacing))/2; pack->y1 = pack->y1 + ((pack->y2 - pack->y1) - (pack->ncards_y*cards->height + (pack->ncards_y-1)*pack->spacing))/2; pack->y2 = pack->y2 - ((pack->y2 - pack->y1) - (pack->ncards_y*cards->height + (pack->ncards_y-1)*pack->spacing))/2; DBG(CARDS_DEBUG, "Resized\n"); } gboolean cards_pack_fill(CardsPack *pack, CardsDeck *deck) { /* Find empty placeholders and draw cards from the deck */ gint i,j; DBG(CARDS_DEBUG, "Filling the card pack\n"); for (i=0; i < pack->ncards_x; i++) for (j=0; j < pack->ncards_y; j++) { if (pack->cards[i][j] == NULL) { if ((pack->cards[i][j] = cards_deck_draw(deck)) == NULL) return FALSE; } } return TRUE; } /** * returns TRUE if a change has been made to a card */ gboolean cards_pack_check_active(CardsPack *pack, CardsImage *cards, gint x, gint y) { gint row,col; /* Reset selections if a hint was given previously */ if (pack->hint_flag) { cards_pack_reset(pack); pack->hint_flag = 0; } /* Find the card the (x,y) point belongs to and * invert its "selected" or "active" field */ DBG(CARDS_DEBUG, "Trying to find a card under (x=%d,y=%d)\n", x, y); /* Make sure the pointer is within the boundaries */ if ((x < pack->x1) || (x > pack->x2) || (y < pack->y1) || (y > pack->y2)) { return FALSE; } /* Calculate the card position */ col = (x - pack->x1) / (cards->width + pack->spacing); row = (y - pack->y1) / (cards->height + pack->spacing); DBG(CARDS_DEBUG,"Card @ %p\n", pack->cards[col][row]); /* Make sure the card is there */ if (pack->cards[col][row] == NULL) { DBG(CARDS_DEBUG, "No card under the cursor...\n"); return FALSE; } DBG(CARDS_DEBUG, "Card found (row=%d, col=%d, C=%d N=%d Sy=%d Sh=%d)\n", row, col, pack->cards[col][row]->color, pack->cards[col][row]->number, pack->cards[col][row]->symbol, pack->cards[col][row]->shading ); if (!pack->cards[col][row]->selected) { /* Not more than 3 cards can be selected simultaneously */ if (pack->nactive >= 3) return FALSE; /* Now, mark the card as seleted */ pack->active_cards[pack->nactive] = pack->cards[col][row]; pack->cards[col][row]->selected = TRUE; pack->nactive++; } else { gint i; gboolean foundflag = FALSE; for (i=0; i < NCARDS_ACTIVE; i++) { if (pack->cards[col][row] == pack->active_cards[i]) foundflag = TRUE; if (foundflag && (i < NCARDS_ACTIVE - 1)) pack->active_cards[i] = pack->active_cards[i+1]; } pack->nactive--; pack->active_cards[pack->nactive] = NULL; pack->cards[col][row]->selected = FALSE; } return TRUE; } gboolean cards_pack_check_set(CardsPack *pack) { /* Set consists of 3 cards */ if (pack->nactive != 3) return FALSE; return cards_check_set(pack->active_cards[0], pack->active_cards[1], pack->active_cards[2]); } void cards_pack_reset(CardsPack *pack) { gint index; for (index=0; index < pack->nactive; index++) { pack->active_cards[index]->selected = FALSE; } pack->nactive = 0; } void cards_pack_remove_selected(CardsPack *pack) { gint i,j,k; /* Remove all selected cards */ for (i=0; i < pack->nactive; i++) { /* Find the card in the pack and remove it */ for (j=0; j < pack->ncards_x; j++) for (k=0; k < pack->ncards_y; k++) { if (pack->cards[j][k] == pack->active_cards[i]) { DBG(CARDS_DEBUG, "removing (%d,%d) from the pack...\n", j,k); pack->cards[j][k] = NULL; } } if (pack->active_cards[i] != NULL) free(pack->active_cards[i]); pack->active_cards[i] = NULL; } pack->nactive = 0; } gboolean are_there_sets_left(CardsPack *pack) { gint i,j,k; gint ncards; gint nx,ny; if (pack == NULL) return FALSE; nx = pack->ncards_x; ny = pack->ncards_y; ncards = nx * ny; DBG(CARDS_DEBUG, "Searching for a set...\n"); for (i=0; i < ncards-2; i++) for (j=i+1; j < ncards-1; j++) for (k=j+1; k < ncards; k++) if (cards_check_set(pack->cards[i/ny][i%ny], pack->cards[j/ny][j%ny], pack->cards[k/ny][k%ny])) { return TRUE; } return FALSE; } gboolean cards_pack_hint(CardsPack *pack) { gint i,j,k; gint ncards; gint nx,ny; if (pack == NULL) return FALSE; nx = pack->ncards_x; ny = pack->ncards_y; ncards = nx * ny; DBG(CARDS_DEBUG, "Searching for a set...\n"); for (i=0; i < ncards-2; i++) for (j=i+1; j < ncards-1; j++) for (k=j+1; k < ncards; k++) { if (cards_check_set(pack->cards[i/ny][i%ny], pack->cards[j/ny][j%ny], pack->cards[k/ny][k%ny])) { DBG(CARDS_DEBUG, "Set found (%d,%d,%d)\n", i, j, k); /* Mark the cards */ pack->cards[i/ny][i%ny]->selected = pack->cards[j/ny][j%ny]->selected = pack->cards[k/ny][k%ny]->selected = TRUE; pack->active_cards[0] = pack->cards[i/ny][i%ny]; pack->active_cards[1] = pack->cards[j/ny][j%ny]; pack->active_cards[2] = pack->cards[k/ny][k%ny]; pack->nactive=3; pack->hint_flag = 1; return TRUE; } } return FALSE; } void cards_pack_render_to_pixmap(CardsPack *pack, CardsImage *cards, GdkPixmap *target, GdkGC *target_gc) { gint c,r; DBG(RENDER_DEBUG, "Rendering the card pack\n"); /* Render cards */ for (r=0; r < pack->ncards_y; r++) for (c=0; c < pack->ncards_x; c++) { if (pack->cards[c][r] != NULL) { cards_image_render_to_pixmap(cards, pack->cards[c][r], target, target_gc, pack->x1 + c*(cards->width + pack->spacing), pack->y1 + r*(cards->height + pack->spacing)); } } }