summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoredGuy <osome3717@gmail.com>2025-07-02 22:39:48 -0700
committerBoredGuy <osome3717@gmail.com>2025-07-02 22:39:48 -0700
commit393b5fd3751cf34a81ecb1821ee37d34c72355fe (patch)
tree7511ed1f27e0b285bb1aa500c19433c7554c88c6
Initial Commit
-rw-r--r--Assets/Cards.pngbin0 -> 3119 bytes
-rw-r--r--CMakeLists.txt23
-rw-r--r--src/game.c360
3 files changed, 383 insertions, 0 deletions
diff --git a/Assets/Cards.png b/Assets/Cards.png
new file mode 100644
index 0000000..58fb882
--- /dev/null
+++ b/Assets/Cards.png
Binary files differ
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]);
+}