#include #include #include #include #include #include #define WINDOW_WIDTH 1280 #define WINDOW_HEIGHT 720 #define DECK_SIZE 52 #define MAX_NUM_PLAYERS 4 #define STARTING_HAND 5 typedef enum Suite { HEART = 0, DIAMOND = 1, CLUB = 2, SPADE = 3 } Suite; typedef struct Card { //Info for game logic Suite suite; int face; void (*afterMove)(); //Info for drawing and animations Rectangle bounds; Vector2 startPosition; Vector2 targetPosition; float travelTime, travelTimer; bool hidden; } Card; typedef struct Game { int turn; Card deck[DECK_SIZE]; Card hands[MAX_NUM_PLAYERS][DECK_SIZE]; Card stack[DECK_SIZE]; int cardsInDeck; int playerCount; int cardsInStack; int cardsInHand[MAX_NUM_PLAYERS]; } Game; Game game; void LoadAssets(); void UpdateGame(float dt); void InitGame(int numPlayers); void DrawGame(); int main(void) { SetConfigFlags(FLAG_VSYNC_HINT); //Better than target FPS imo InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Card Game"); srand((unsigned int)time(NULL)); LoadAssets(); InitGame(2); while(!WindowShouldClose()) { UpdateGame(GetFrameTime()); BeginDrawing(); ClearBackground(RAYWHITE); DrawGame(); EndDrawing(); } CloseWindow(); return 0; } Texture spriteSheet; void LoadAssets() { spriteSheet = LoadTexture("Assets/Cards.png"); } //The offset from (0, 0) to the first card on the spritesheet #define SRC_OFFSETY 2 #define SRC_OFFSETX 11 //The space between two consecutive cards in the spritesheet //Is not equal to the width of the card for some reason #define CARD_STRIDEX 64 #define CARD_STRIDEY 64 #define CARD_SRCWIDTH 42 #define CARD_SRCHEIGHT 60 #define CARD_WIDTH (CARD_SRCWIDTH * 2) #define CARD_HEIGHT (CARD_SRCHEIGHT * 2) //The center of the board will be occupied by the stack //followed by an offset and then the deck #define STACK_DECK_OFFSET 30.0f #define CENTER_ELEMENTS_WIDTH (2 * CARD_WIDTH + STACK_DECK_OFFSET) #define CENTER_ELEMENTS_X (WINDOW_WIDTH - CENTER_ELEMENTS_WIDTH) / 2.0f #define CENTER_ELEMENTS_Y (WINDOW_HEIGHT - CARD_HEIGHT) / 2.0f static inline void StartTravelling(Card* c, Vector2 targetPosition, float travelTime) { c->startPosition = (Vector2) {c->bounds.x, c->bounds.y}; c->targetPosition = targetPosition; c->travelTime = travelTime; c->travelTimer = 0.0; } static inline void MoveToStack(Card* cards, int card, int* numCards, float travelTime) { Card* c = &cards[card]; StartTravelling(c, (Vector2) {CENTER_ELEMENTS_X, CENTER_ELEMENTS_Y}, travelTime ); c->hidden = false; game.cardsInStack++; game.stack[game.cardsInStack - 1] = *c; for(int i = card; i < *numCards - 1; i++) { cards[i] = cards[i+1]; } (*numCards)--; } static inline bool MouseOn(const Card* c) { const int mouseX = GetMouseX(); const int mouseY = GetMouseY(); return mouseX >= c->bounds.x && mouseX <= (c->bounds.x + c->bounds.width) && mouseY >= c->bounds.y && mouseY <= (c->bounds.y + c->bounds.height); } bool IsTravelling(const Card* c) { return c->travelTime > 0; } bool IsAnyTravelling() { for(int i = 0; i < game.cardsInDeck; i++) if (IsTravelling(&game.deck[i])) return true; for(int i = 0; i < game.cardsInStack; i++) if (IsTravelling(&game.stack[i])) return true; for(int i = 0; i < game.playerCount; i++) for(int j = 0; j < game.cardsInHand[i]; j++) if (IsTravelling(&game.hands[i][j])) return true; return false; } void UpdateCard(Card* c, float dt) { if (IsTravelling(c)) { float travelDistance = c->travelTimer / c->travelTime; c->bounds.x = (1 - travelDistance) * c->startPosition.x + travelDistance * c->targetPosition.x; c->bounds.y = (1 - travelDistance) * c->startPosition.y + travelDistance * c->targetPosition.y; c->travelTimer += dt; if (c->travelTimer >= c->travelTime) { //Avoid overshoot c->bounds.x = c->targetPosition.x; c->bounds.y = c->targetPosition.y; c->travelTimer = c->travelTime = 0.0; if (c->afterMove) { c->afterMove(); c->afterMove = NULL; } } } } static inline float HandWidth(int hand) { return (float)game.cardsInHand[hand] * CARD_WIDTH; } //Moves all the cards in the hand indexed to their correct positions void PositionCardsInHand(int hand) { float centerXpos = (WINDOW_WIDTH - HandWidth(hand)) / 2.0f; float positionsX[] = { game.playerCount < 3 ? centerXpos : 0.0f, game.playerCount < 4 ? centerXpos : 0.0f, WINDOW_WIDTH - HandWidth(hand), WINDOW_WIDTH - HandWidth(hand) }; float xpos = positionsX[hand]; float ypos = (hand % 2) == 0 ? 0.0f : (WINDOW_HEIGHT - CARD_HEIGHT); if (hand < game.playerCount) { int numCards = game.cardsInHand[hand]; for(int i = 0; i < numCards; i++) { Card* c = &game.hands[hand][i]; StartTravelling(c, (Vector2){xpos, ypos}, 0.4f); xpos += CARD_WIDTH; } } } void FlipCards() { for(int i = 0; i < game.playerCount; i++) { for(int j = 0; j < game.cardsInHand[i]; j++) { if(i == game.turn) game.hands[i][j].hidden = false; else game.hands[i][j].hidden = true; } } } static inline bool CanPlayCard(const Card* card) { Card topCard = game.stack[game.cardsInStack-1]; return topCard.face == card->face || topCard.suite == card->suite; } void Deal(int toPlayer) { if (game.cardsInDeck > 0) { game.deck[game.cardsInDeck-1].hidden = false; memcpy( &game.hands[toPlayer][game.cardsInHand[toPlayer]], &game.deck[game.cardsInDeck-1], sizeof(Card) ); game.cardsInDeck--; game.cardsInHand[toPlayer]++; PositionCardsInHand(toPlayer); } } static inline Card* TopCardInHand(int hand) { return &game.hands[hand][game.cardsInHand[hand] - 1]; } void NextTurn() { game.turn = (game.turn + 1) % game.playerCount; FlipCards(); for(int i = 0; i < game.cardsInHand[game.turn]; i++) if(CanPlayCard(&game.hands[game.turn][i])) return; Deal(game.turn); Card* c = TopCardInHand(game.turn); if(!CanPlayCard(c)) c->afterMove = NextTurn; } bool IsSpecialCard(const Card* c) { const int specialFaces[] = {1, 4, 6, 7, 10}; if (c->suite == SPADE && c->face == 0) return true; for(int i = 0; i < 5; i++) { if (c->face == specialFaces[i]) return true; } return false; } static inline int NextPlayer() { return (game.turn + 1) % game.playerCount; } //Make next player draw 5 void AceOfSpadesEffect() { puts("Here!"); for (int i = 0; i < 5; i++) Deal(NextPlayer()); NextTurn(); } //Make next player draw 2 void TwoEffect() { for(int i = 0; i < 2; i++) Deal(NextPlayer()); NextTurn(); } //Skip next turn; void FiveEffect() { game.turn = NextPlayer(); NextTurn(); } void CardEffect(Card* c) { puts("HereToo!"); if (c->suite == SPADE && c->face == 0) { puts("TeeHeee!"); c->afterMove = AceOfSpadesEffect; } switch (c->face) { case 1: c->afterMove = TwoEffect; break; case 4: c->afterMove = FiveEffect; break; default: c->afterMove = NextTurn; } } void HandleMousePress() { if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && !IsAnyTravelling()) { for(int i = 0; i < game.cardsInHand[game.turn]; i++) { Card* c = &game.hands[game.turn][i]; if (MouseOn(c) && CanPlayCard(c)) { if(IsSpecialCard(c)) { puts("Shloobolo"); CardEffect(c); } else { c->afterMove = NextTurn; } MoveToStack(game.hands[game.turn], i, &game.cardsInHand[game.turn], 0.5 ); PositionCardsInHand(game.turn); } } } } void UpdateGame(float dt) { for(int i = 0; i < game.cardsInDeck; i++) UpdateCard(&game.deck[i], dt); for(int i = 0; i < game.cardsInStack; i++) UpdateCard(&game.stack[i], dt); for(int i = 0; i < game.playerCount; i++) for(int j = 0; j < game.cardsInHand[i]; j++) UpdateCard(&game.hands[i][j], dt); HandleMousePress(); } void ShuffleDeck() { for(int i = game.cardsInDeck - 1; i > 0; i--) { int selection = rand() % (i + 1); Card temp; memcpy(&temp, &game.deck[selection], sizeof(Card)); memcpy(&game.deck[selection], &game.deck[i], sizeof(Card)); memcpy(&game.deck[i], &temp, sizeof(Card)); } } void InitGame(int playerCount) { memset(&game, 0, sizeof(game)); int c = 0; //Fill the deck with one of each card for(Suite suite = HEART; suite <= SPADE; suite++) { for(int face = 0; face < 13; face++) { game.deck[c].suite = suite; game.deck[c].face = face; game.deck[c].bounds = (Rectangle) { .x = CENTER_ELEMENTS_X + CARD_WIDTH + STACK_DECK_OFFSET, .y = CENTER_ELEMENTS_Y, .width = CARD_WIDTH, .height = CARD_HEIGHT }; game.deck[c].hidden = true; c++; } } game.cardsInDeck = DECK_SIZE; game.playerCount = playerCount; //Shuffle the deck and deal to all the players ShuffleDeck(); for(int i = 0; i < playerCount; i++) { for(int j = 0; j < STARTING_HAND; j++) Deal(i); } game.turn = -1; NextTurn(); //Find the first non-special card and send it to the stack to begin the game int i; for(i = 0; i < game.cardsInDeck && IsSpecialCard(&game.deck[i]); i++); if (i >= game.cardsInDeck) { fprintf(stderr, "There are no non-special cards to start the game with(something went horribly wrong)!\n"); exit(-1); } MoveToStack(game.deck, i, &game.cardsInDeck, 0.2f); } void DrawCard(const Card* c) { const Rectangle backFace = (Rectangle) { .x = 13 * CARD_STRIDEX + SRC_OFFSETX, .y = CARD_STRIDEY + SRC_OFFSETY, .width = CARD_SRCWIDTH, .height = CARD_SRCHEIGHT }; if(c->hidden) { DrawTexturePro(spriteSheet, backFace, c->bounds, (Vector2) {}, 0.0, WHITE); } else { Rectangle srcRec = (Rectangle) { .x = (float)c->face * CARD_STRIDEX + SRC_OFFSETX, .y = (float)c->suite * CARD_STRIDEY + SRC_OFFSETY, .width = CARD_SRCWIDTH, .height = CARD_SRCHEIGHT }; DrawTexturePro(spriteSheet, srcRec, c->bounds, (Vector2) {}, 0.0, WHITE); } } void DrawGame() { for(int i = 0; i < game.cardsInDeck; i++) DrawCard(&game.deck[i]); for(int i = 0; i < game.playerCount; i++) for(int j = 0; j < game.cardsInHand[i]; j++) DrawCard(&game.hands[i][j]); for(int i = 0; i < game.cardsInStack; i++) DrawCard(&game.stack[i]); }