diff options
author | BoredGuy <osome3717@gmail.com> | 2025-07-02 22:39:48 -0700 |
---|---|---|
committer | BoredGuy <osome3717@gmail.com> | 2025-07-02 22:39:48 -0700 |
commit | 393b5fd3751cf34a81ecb1821ee37d34c72355fe (patch) | |
tree | 7511ed1f27e0b285bb1aa500c19433c7554c88c6 |
Initial Commit
-rw-r--r-- | Assets/Cards.png | bin | 0 -> 3119 bytes | |||
-rw-r--r-- | CMakeLists.txt | 23 | ||||
-rw-r--r-- | src/game.c | 360 |
3 files changed, 383 insertions, 0 deletions
diff --git a/Assets/Cards.png b/Assets/Cards.png Binary files differnew file mode 100644 index 0000000..58fb882 --- /dev/null +++ b/Assets/Cards.png diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fbe6b93 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.15) +project(Raylib-Cards VERSION 0.0.1 LANGUAGES C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +find_package(raylib REQUIRED) + +add_executable(game + "src/game.c" +) + +target_link_libraries(game raylib) + +if(UNIX) + target_link_libraries(game m) +endif() + +if(MSVC) + target_link_options(game PRIVATE /SUBSYSTEM:CONSOLE /nologo) + target_compile_options(game PRIVATE /EHsc /W4 /WX) + target_link_libraries(game winmm) +endif() diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..6456618 --- /dev/null +++ b/src/game.c @@ -0,0 +1,360 @@ +#include <raylib.h> +#include <stdbool.h> +#include <time.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#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; + + //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; +} + +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; + } + } +} + +//Moves all the cards in the hand indexed to their correct positions +void PositionCardsInHand(int hand) { + float xpos = 0.0; + 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); + } +} + +void NextTurn() { + game.turn = (game.turn + 1) % game.playerCount; + FlipCards(); + + //If the player has no playable cards deal them one and if that is also + //not playable just skip their turn + for(int i = 0; i < game.cardsInHand[game.turn]; i++) + if (CanPlayCard(&game.hands[game.turn][i])) return; + + Deal(game.turn); +} + +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; +} + +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); + + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + + for(int i = 0; i < game.cardsInHand[game.turn]; i++) { + const Card* c = &game.hands[game.turn][i]; + + if (MouseOn(c) && CanPlayCard(c)) { + MoveToStack(game.hands[game.turn], + i, + &game.cardsInHand[game.turn], + 0.5 + ); + PositionCardsInHand(game.turn); + + NextTurn(); + } + } + + } +} + +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 = 0; + FlipCards(); + + //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]); +} |