SpriteSheet Animation too fast

Hi I’m trying to implement Sprite Animation with a SpriteSheet but the animations seem very fast and I cannot slow it down. The normal speed is 0.1f, I want it at 5.0f but it does seem to make a difference. Game Project Download Link

int main(int argc, char** argv) {
    Uint32 frameStart;
    int frameTime;
    GameState gameState = MENU;

    // Initialize SDL
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
        SDL_Log("Failed to initialize SDL: %s", SDL_GetError());
        return 1;
    }
    if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) {
        SDL_Log("Failed to initialize SDL_image: %s", IMG_GetError());
        SDL_Quit();
        return 1;
    }
    if (TTF_Init() == -1) {
        SDL_Log("Failed to initialize SDL_ttf: %s", TTF_GetError());
        IMG_Quit();
        SDL_Quit();
        return 1;
    }
    if (!soundManager.init()) {
        SDL_Log("Failed to initialize SDL_mixer.");
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    std::unique_ptr<Game> game = std::make_unique<Game>();
    if (!game->init()) {
        SDL_Log("Failed to initialize game.");
        soundManager.shutdown();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    renderer = game->getRenderer();

    // Load fonts
    defaultFont = TTF_OpenFont("assets/Font/arial.ttf", FONT_SIZE);
    storylineFont = TTF_OpenFont("assets/Font/lucida_blackletter_regular.ttf", FONT_SIZE);
    if (!defaultFont || !storylineFont) {
        SDL_Log("Failed to load fonts: %s", TTF_GetError());
        soundManager.shutdown();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Load textures
    menuBackdropTexture = TextureManager::LoadTexture("assets/Backdrops/menu_backdrop.png", renderer);
    speechBubbleTexture = TextureManager::LoadTexture("assets/speech_bubble.png", renderer);

    // Initialize Knight_idle as a Sprite
    Knight_idle = new Sprite(renderer, "assets/Sprites/MC/BlueKnight.png");
    if (!Knight_idle->loadTexture()) {
        SDL_Log("Failed to load Knight idle sprite.");
        return 1;
    }
    Knight_idle->setAnimationParameters(128, 126, 8, 5.0f); // Set animation parameters with 5.0f frame rate

    if (!menuBackdropTexture || !speechBubbleTexture) {
        SDL_Log("Failed to load textures.");
        TTF_CloseFont(defaultFont);
        TTF_CloseFont(storylineFont);
        soundManager.shutdown();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Load music
    if (!soundManager.loadMusic("assets/Music/background_music.wav", "background")) {
        SDL_Log("Failed to load background music.");
    }
    if (!soundManager.loadMusic("assets/Music/story_music.wav", "story")) {
        SDL_Log("Failed to load story music.");
    }
    if (!soundManager.loadMusic("assets/Music/combat_music.wav", "combat")) {
        SDL_Log("Failed to load combat music.");
    }
    if (!soundManager.loadMusic("assets/Music/Lvl1_Story.wav", "lvl1_story")) {
        SDL_Log("Failed to load Level 1 Story music.");
    }

    // Initialize story instances
    static Story storyInstance(renderer, storylineFont, storyLines, 50);
    static Story tutorialStoryInstance(renderer, storylineFont, tutorialStoryLines, 50);
    static Story level1StoryInstance(renderer, storylineFont, level1StoryLines, 50);
    static Story level1WinInstance(renderer, storylineFont, level1Win, 50);
    static Story level1LoseInstance(renderer, storylineFont, level1Lose, 50);

    // Button setup
    const int BUTTON_WIDTH = 180;
    const int ABOUT_BUTTON_WIDTH = 250;
    const int BUTTON_HEIGHT = 70;
    const int BUTTON_SPACING = 50;
    const int NUM_BUTTONS = 4;
    int windowWidth, windowHeight;
    SDL_GetRendererOutputSize(renderer, &windowWidth, &windowHeight);
    int totalButtonHeight = NUM_BUTTONS * BUTTON_HEIGHT + (NUM_BUTTONS - 1) * BUTTON_SPACING;
    int startY = (windowHeight - totalButtonHeight) / 2 + 50;
    Button buttons[NUM_BUTTONS] = {
        Button((windowWidth - BUTTON_WIDTH) / 2, startY, BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/start button.png", "assets/Buttons/clicked sta2.png", "assets/Buttons/clicked sta2.png", renderer),
        Button((windowWidth - BUTTON_WIDTH) / 2, startY + (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/tutorial button.png", "assets/Buttons/clicked tutorial2.png", "assets/Buttons/clicked tutorial2.png", renderer),
        Button((windowWidth - BUTTON_WIDTH) / 2, startY + 2 * (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/option button.png", "assets/Buttons/option_clicked.png", "assets/Buttons/option_clicked.png", renderer),
        Button((windowWidth - ABOUT_BUTTON_WIDTH) / 2, startY + 3 * (BUTTON_HEIGHT + BUTTON_SPACING), ABOUT_BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/about develper button.png", "assets/Buttons/clicked ab de 2.png", "assets/Buttons/clicked ab de 2.png", renderer)
    };

    buttons[0].setOnClick([&]() {
        gameState = STORY;
        storyInstance.reset();
        storyBackdropTexture = TextureManager::LoadTexture("assets/Backdrops/castle/castle1.jpg", renderer);
        soundManager.stopMusic();
        soundManager.playMusic("story", -1);
        });
    buttons[1].setOnClick([&]() {
        gameState = TUTORIAL;
        tutorialStoryInstance.reset();
        });
    buttons[2].setOnClick([&]() {
        gameState = OPTIONS;
        });
    buttons[3].setOnClick([&]() {
        gameState = ABOUT;
        });

    int musicVolume = MIX_MAX_VOLUME;
    SDL_Texture* combatBackdropTexture = nullptr;

    // Main Game Loop
    while (gameState != EXIT) {
        float deltaTime = time_(); // Get delta time since the last frame
        frameStart = SDL_GetTicks();
        SDL_Event event;

        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT || (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)) {
                gameState = EXIT;
            }

            // Handle returning to menu
            if ((gameState == TUTORIAL || gameState == OPTIONS || gameState == ABOUT ||
                gameState == LEVEL1_LOSE) && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_b) {
                    gameState = MENU;
                    for (int i = 0; i < NUM_BUTTONS; ++i) {
                        buttons[i].resetState();
                    }
                }
            }

            // Handle story progression
            if ((gameState == STORY || gameState == LEVEL1_STORY || gameState == LEVEL1_WIN || gameState == LEVEL1_LOSE) &&
                event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_SPACE) {
                    if (gameState == STORY && storyInstance.isComplete()) {
                        soundManager.stopMusic();
                        gameState = LEVEL1_STORY;
                        level1StoryInstance.reset();
                        SDL_DestroyTexture(storyBackdropTexture);
                        storyBackdropTexture = nullptr;
                        soundManager.playMusic("lvl1_story", -1);// Play Level 1 Story music
                    }
                    else if (gameState == LEVEL1_STORY && level1StoryInstance.isComplete()) {
                        // Load forest backdrop for combat
                        combatBackdropTexture = TextureManager::LoadTexture("assets/Backdrops/forest/forest2.jpg", renderer);
                        gameState = PLAYING;
                        initCombat(1);
                        soundManager.playMusic("combat", -1);// Play combat music
                    }
                    else if (gameState == LEVEL1_WIN && level1WinInstance.isComplete()) {
                        gameState = MENU;
                    }
                    else if (gameState == STORY) {
                        storyInstance.advanceToNextLine();
                    }
                    else if (gameState == LEVEL1_STORY) {
                        level1StoryInstance.advanceToNextLine();
                    }
                    else if (gameState == LEVEL1_WIN) {
                        level1WinInstance.advanceToNextLine();
                    }
                    else if (gameState == LEVEL1_LOSE) {
                        level1LoseInstance.advanceToNextLine();
                    }
                }
            }

            // Handle tutorial
            if (gameState == TUTORIAL && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_SPACE) {
                    if (!tutorialStoryInstance.isComplete()) {
                        tutorialStoryInstance.advanceToNextLine();
                    }
                }
                else if (event.key.keysym.sym == SDLK_b && tutorialStoryInstance.isComplete()) {
                    gameState = MENU;
                }
            }

            // Handle buttons
            if (gameState == MENU) {
                for (int i = 0; i < NUM_BUTTONS; ++i) {
                    buttons[i].handleEvent(&event);
                }
            }

            // Handle combat
            if (gameState == PLAYING) {
                CombatContext context = {
                    renderer,
                    defaultFont,
                    {255, 255, 255, 255},
                    {255, 0, 0, 255},
                    {0, 0, 255, 255},
                    {0, 0, 0, 255}
                };
                handleCombatEvent(&event, &context);
                if (gameOver) {
                    SDL_DestroyTexture(combatBackdropTexture);
                    combatBackdropTexture = nullptr;
                    gameState = LEVEL1_LOSE;
                    level1LoseInstance.reset();
                    soundManager.stopMusic();
                    soundManager.playMusic("story", -1);
                }
                else if (victory1) {
                    SDL_DestroyTexture(combatBackdropTexture);
                    combatBackdropTexture = nullptr;
                    gameState = LEVEL1_WIN;
                    level1WinInstance.reset();
                    soundManager.stopMusic();
                    soundManager.playMusic("story", -1);
                }
            }
        }

        // Update knight animation
        if (Knight_idle) {
            Knight_idle->updateAnimation(deltaTime);
        }

        // Rendering
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);

        switch (gameState) {
        case MENU:
            if (!soundManager.isPlayingMusic()) {
                soundManager.playMusic("background", -1);
            }
            SDL_RenderCopy(renderer, menuBackdropTexture, nullptr, nullptr);
            for (int i = 0; i < NUM_BUTTONS; ++i) {
                buttons[i].render(renderer);
            }
            break;

        case STORY: {
            if (storyBackdropTexture) {
                SDL_RenderCopy(renderer, storyBackdropTexture, nullptr, nullptr);
            }
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2,
                windowHeight - 400,
                800,
                300
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);
            storyInstance.update(SDL_GetTicks());
            storyInstance.render(renderer,
                speechBubbleRect.x + 50,
                speechBubbleRect.y + 90,
                TEXT_WRAP_WIDTH);
            if (storyInstance.isComplete()) {
                renderText(renderer, defaultFont, "Press SPACE to continue", { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
            }
            break;
        }

        case LEVEL1_STORY: {
            // Use castle ruins backdrop for story
            static SDL_Texture* level1StoryBackdrop = nullptr;
            if (!level1StoryBackdrop) {
                level1StoryBackdrop = TextureManager::LoadTexture("assets/Backdrops/castle/castle1.jpg", renderer);
            }
            if (level1StoryBackdrop) {
                SDL_RenderCopy(renderer, level1StoryBackdrop, nullptr, nullptr);
            }

            // Render the knight character
            if (Knight_idle) {
                SDL_Rect knightRect = {
                    ((windowWidth - 200) / 2) - 500,  // X Position
                    windowHeight - 700,        // Y Position
                    200,                       // Width
                    300                        // Height
                };
                Knight_idle->render(knightRect.x, knightRect.y, knightRect.w, knightRect.h);
            }

            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2,
                windowHeight - 400,
                800,
                300
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);
            level1StoryInstance.update(SDL_GetTicks());
            level1StoryInstance.render(renderer,
                speechBubbleRect.x + 50,
                speechBubbleRect.y + 90,
                TEXT_WRAP_WIDTH);
            if (level1StoryInstance.isComplete()) {
                renderText(renderer, defaultFont, "Press SPACE to continue", { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
            }
            break;
        }

        case PLAYING: {
            // Only use forest backdrop during combat
            if (combatBackdropTexture) {
                SDL_RenderCopy(renderer, combatBackdropTexture, nullptr, nullptr);
            }
            CombatContext context = {
                renderer,
                defaultFont,
                {255, 255, 255, 255},
                {255, 0, 0, 255},
                {0, 0, 255, 255},
                {0, 0, 0, 255}
            };
            renderCombat(&context);
            break;
        }

        case LEVEL1_WIN: {
            // Use same backdrop as level 1 story for consistency
            static SDL_Texture* level1StoryBackdrop = nullptr;
            if (!level1StoryBackdrop) {
                level1StoryBackdrop = TextureManager::LoadTexture("assets/Backdrops/castle/castle1.jpg", renderer);
            }
            if (level1StoryBackdrop) {
                SDL_RenderCopy(renderer, level1StoryBackdrop, nullptr, nullptr);
            }
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2,
                windowHeight - 400,
                800,
                300
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);
            level1WinInstance.update(SDL_GetTicks());
            level1WinInstance.render(renderer,
                speechBubbleRect.x + 50,
                speechBubbleRect.y + 90,
                TEXT_WRAP_WIDTH);
            if (level1WinInstance.isComplete()) {
                renderText(renderer, defaultFont, "Press SPACE to continue", { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
            }
            break;
        }

        case LEVEL1_LOSE: {
            // Use same backdrop as level 1 story for consistency
            static SDL_Texture* level1StoryBackdrop = nullptr;
            if (!level1StoryBackdrop) {
                level1StoryBackdrop = TextureManager::LoadTexture("assets/Backdrops/castle/castle1.jpg", renderer);
            }
            if (level1StoryBackdrop) {
                SDL_RenderCopy(renderer, level1StoryBackdrop, nullptr, nullptr);
            }
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2,
                windowHeight - 400,
                800,
                300
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);
            level1LoseInstance.update(SDL_GetTicks());
            level1LoseInstance.render(renderer,
                speechBubbleRect.x + 50,
                speechBubbleRect.y + 90,
                TEXT_WRAP_WIDTH);
            if (level1LoseInstance.isComplete()) {
                renderText(renderer, defaultFont, "Press SPACE to return to menu", { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
            }
            break;
        }
//other cases
        SDL_RenderPresent(renderer);
        frameTime = SDL_GetTicks() - frameStart;
        if (FRAME_DELAY > frameTime) {
            SDL_Delay(FRAME_DELAY - frameTime);
        }
    }

    // Cleanup
    SDL_DestroyTexture(menuBackdropTexture);
    SDL_DestroyTexture(speechBubbleTexture);
    SDL_DestroyTexture(storyBackdropTexture);
    delete Knight_idle; // Clean up the Sprite object
    soundManager.shutdown();
    TTF_CloseFont(defaultFont);
    TTF_CloseFont(storylineFont);
    TTF_Quit();
    IMG_Quit();
    SDL_Quit();
    return 0;
}
#ifndef SPRITE_H
#define SPRITE_H

#include <SDL.h>
#include <string>

// Animation frame definitions
#define TZ_H (512 / 4) // Width of a single frame (128px)
#define TZ_V (252 / 2) // Height of a single frame (126px)

class Sprite {
public:
    Sprite(SDL_Renderer* renderer, const std::string& filePath);
    ~Sprite();

    bool loadTexture();
    void render(int x, int y);
    void updateAnimation(float deltaTime);
    void setSize(int width, int height);
    int getWidth() const;
    int getHeight() const;

    SDL_Texture* getTexture() const;
    void destroyTexture();
    void render(int x, int y, int renderWidth = -1, int renderHeight = -1);

    // Method to set the frame rate dynamically
    void setFrameRate(float rate);

    // Method to set animation parameters
    void setAnimationParameters(int frameW, int frameH, int frames, float rate);

    // Quick setup for standard animations
    void setupDefaultAnimation();

private:
    SDL_Renderer* renderer;
    SDL_Texture* texture;
    std::string filePath;
    int width;
    int height;
    int frameWidth;
    int frameHeight;
    int numFrames;
    float frameTimer;
    float frameRate;
    int currentFrame;
};

#endif // SPRITE_H
#include "Sprite.h"
#include <SDL_image.h>
#include <stdio.h>

Sprite::Sprite(SDL_Renderer* renderer, const std::string& filePath)
    : renderer(renderer), texture(nullptr), filePath(filePath),
    width(0), height(0), frameWidth(0), frameHeight(0),
    numFrames(0), frameTimer(0.0f), frameRate(0.1f), currentFrame(0) {
}

Sprite::~Sprite() {
    if (texture) {
        SDL_DestroyTexture(texture);
    }
}

bool Sprite::loadTexture() {
    SDL_Surface* surface = IMG_Load(filePath.c_str());
    if (!surface) {
        fprintf(stderr, "Failed to load image %s: %s\n", filePath.c_str(), IMG_GetError());
        return false;
    }

    texture = SDL_CreateTextureFromSurface(renderer, surface);
    SDL_FreeSurface(surface);

    if (!texture) {
        fprintf(stderr, "Failed to create texture: %s\n", SDL_GetError());
        return false;
    }

    SDL_QueryTexture(texture, nullptr, nullptr, &width, &height);
    return true;
}

void Sprite::setAnimationParameters(int frameW, int frameH, int frames, float rate) {
    frameWidth = frameW;
    frameHeight = frameH;
    numFrames = frames;
    frameRate = rate;
}

void Sprite::render(int x, int y, int renderWidth, int renderHeight) {
    if (!texture) {
        fprintf(stderr, "Texture not loaded for sprite: %s\n", filePath.c_str());
        return;
    }

    // Calculate source rectangle based on current frame
    SDL_Rect srcRect = {
        (currentFrame % (width / frameWidth)) * frameWidth,
        (currentFrame / (width / frameWidth)) * frameHeight,
        frameWidth,
        frameHeight
    };

    // Determine output size (use frame dimensions if not specified)
    int outputWidth = (renderWidth == -1) ? frameWidth : renderWidth;
    int outputHeight = (renderHeight == -1) ? frameHeight : renderHeight;

    SDL_Rect dstRect = { x, y, outputWidth, outputHeight };
    SDL_RenderCopy(renderer, texture, &srcRect, &dstRect);
}

// Overloaded render function with default parameters
void Sprite::render(int x, int y) {
    render(x, y, -1, -1); // Call the main render function with default values
}

void Sprite::updateAnimation(float deltaTime) {
    frameTimer += deltaTime;

    while (frameTimer >= frameRate) {
        currentFrame = (currentFrame + 1) % numFrames;
        frameTimer -= frameRate;
    }
}

void Sprite::setSize(int width, int height) {
    this->width = width;
    this->height = height;
}

int Sprite::getWidth() const { return width; }
int Sprite::getHeight() const { return height; }

SDL_Texture* Sprite::getTexture() const {
    return texture;
}

void Sprite::destroyTexture() {
    if (texture) {
        SDL_DestroyTexture(texture);
        texture = nullptr;
    }
}

A couple of notes on your code:

  • You’re initializing and deinitializing SDL twice, which is unnecessary.
  • You have some memory leaks in the Combat code (Combat.h/.cpp), where some pointers aren’t cleaned up properly upon game shutdown.

But I’ll just focus on the animation issue now.

Something is wrong with your deltatime calculation. The deltaTime variable is counting up to a larger and larger value, instead of containing the time between the last frame and the current frame.
You probably want to do something like float deltaTime = frameStart - time_(); or something similar. Since the result will probably be in milliseconds you might also want to multiply the result with 0.001 to get the deltatime in seconds.

Fix the deltatime calculation and the animation will probably get fixed.

ok modifed deltaTime and it works. What did you mean by initializing and deinitializing SDL twice? I could only find one // Initialize SDL if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { SDL_Log("Failed to initialize SDL: %s", SDL_GetError()); return 1; } in main.cpp. What do I have to delete?

// Time function that returns delta time in seconds
static float getDeltaTime() {
    static Uint32 lastTime = 0;
    Uint32 currentTime = SDL_GetTicks();
    float deltaTime = (currentTime - lastTime) / 1000.0f; // Convert to seconds
    lastTime = currentTime;
    return deltaTime;
}
#include "Combat.h"
#include <cstdlib> // For rand()
#include <ctime>   // For time()
#include <cstdio>  // For sprintf_s
#include "GameState.h"

// Combat global variables
int playerHealth = 10;
int playerDefense = 0;
int* redDiceValues = nullptr;
int* blueDiceValues = nullptr;
int selectedDice = -1;
int* redBox = nullptr;
int* blueBox = nullptr;
int* mixedBox1Red = nullptr;
int* mixedBox1Blue = nullptr;
int* mixedBox2Red = nullptr;
int* mixedBox2Blue = nullptr;
bool* isRed = nullptr;
bool* isBlue = nullptr;
bool gameOver = false;
bool victory1 = false;
bool victory2 = false;
bool victory3 = false;
int currentTurn = 1;
int currentLevel = 1;
Enemy* currentEnemy = nullptr;
int enemyDamage = 0;
int redDiceCount = 4;
int blueDiceCount = 4;
bool enemyOnFire = false;
bool playerOnFire = false;
bool showError = false;
bool enemyFrozen = false;
bool playerFortified = false;
bool playerAgile = false;
int level;

// Clean up all combat resources
void cleanupCombat() {
    delete[] redDiceValues;
    delete[] blueDiceValues;
    delete[] redBox;
    delete[] blueBox;
    delete[] mixedBox1Red;
    delete[] mixedBox1Blue;
    delete[] mixedBox2Red;
    delete[] mixedBox2Blue;
    delete[] isRed;
    delete[] isBlue;
    delete currentEnemy;

    // Reset pointers to nullptr
    redDiceValues = nullptr;
    blueDiceValues = nullptr;
    redBox = nullptr;
    blueBox = nullptr;
    mixedBox1Red = nullptr;
    mixedBox1Blue = nullptr;
    mixedBox2Red = nullptr;
    mixedBox2Blue = nullptr;
    isRed = nullptr;
    isBlue = nullptr;
    currentEnemy = nullptr;
}

// Initialize combat for a specific level
extern "C" void initCombat(int level) {
    // Clean up any existing resources first
    cleanupCombat();

    srand((unsigned int)time(NULL)); // Seed random number generator
    playerHealth = 10;
    playerDefense = 0;
    currentLevel = level;

    // Dynamically allocate arrays
    redDiceValues = new int[redDiceCount];
    blueDiceValues = new int[blueDiceCount];
    redBox = new int[3];
    blueBox = new int[3];
    mixedBox1Red = new int[2];
    mixedBox1Blue = new int[1];
    mixedBox2Red = new int[1];
    mixedBox2Blue = new int[2];
    isRed = new bool[redDiceCount];
    isBlue = new bool[blueDiceCount];

    // Initialize dice values and flags
    for (int i = 0; i < redDiceCount; ++i) {
        redDiceValues[i] = rand() % 6 + 1;
        isRed[i] = false;
    }
    for (int i = 0; i < blueDiceCount; ++i) {
        blueDiceValues[i] = rand() % 6 + 1;
        isBlue[i] = false;
    }

    // Initialize boxes
    for (int i = 0; i < 3; ++i) {
        redBox[i] = -1;
        blueBox[i] = -1;
    }
    for (int i = 0; i < 2; ++i) {
        mixedBox1Red[i] = -1;
        mixedBox2Blue[i] = -1;
    }
    mixedBox1Blue[0] = -1;
    mixedBox2Red[0] = -1;

    // Reset combat state
    selectedDice = -1;
    gameOver = false;
    victory1 = false;
    victory2 = false;
    victory3 = false;
    currentTurn = 1;
    enemyOnFire = false;
    playerOnFire = false;
    enemyFrozen = false;
    playerFortified = false;
    playerAgile = false;

    // Initialize enemy based on level
    if (level == 1) {
        currentEnemy = new EnemyA();
    }
    else if (level == 2) {
        currentEnemy = new EnemyB();
    }
    else if (level == 3) {
        currentEnemy = new EnemyC();
    }
}

// Render the combat UI
extern "C" void renderCombat(CombatContext* context) {
    if (currentEnemy == nullptr) {
        SDL_Log("Error: currentEnemy is nullptr");
        return;
    }

    // Render player and enemy stats
    char playerHealthText[20], enemyHealthText[20], playerDefenseText[20], enemyDamageText[20], levelText[20];
    sprintf_s(playerHealthText, "Player Health: %d", playerHealth);
    sprintf_s(enemyHealthText, "Enemy Health: %d", currentEnemy->getHealth());
    sprintf_s(playerDefenseText, "Player Defense: %d", playerDefense);
    sprintf_s(enemyDamageText, "Enemy Damage: %d", enemyDamage);
    sprintf_s(levelText, "Level %d", currentLevel);

    SDL_Surface* playerHealthSurface = TTF_RenderText_Solid(context->font, playerHealthText, context->whiteColor);
    SDL_Surface* enemyHealthSurface = TTF_RenderText_Solid(context->font, enemyHealthText, context->whiteColor);
    SDL_Surface* playerDefenseSurface = TTF_RenderText_Solid(context->font, playerDefenseText, context->whiteColor);
    SDL_Surface* enemyDamageSurface = TTF_RenderText_Solid(context->font, enemyDamageText, context->whiteColor);
    SDL_Surface* levelSurface = TTF_RenderText_Solid(context->font, levelText, context->whiteColor);

    SDL_Texture* playerHealthTexture = SDL_CreateTextureFromSurface(context->renderer, playerHealthSurface);
    SDL_Texture* enemyHealthTexture = SDL_CreateTextureFromSurface(context->renderer, enemyHealthSurface);
    SDL_Texture* playerDefenseTexture = SDL_CreateTextureFromSurface(context->renderer, playerDefenseSurface);
    SDL_Texture* enemyDamageTexture = SDL_CreateTextureFromSurface(context->renderer, enemyDamageSurface);
    SDL_Texture* levelTexture = SDL_CreateTextureFromSurface(context->renderer, levelSurface);

    SDL_Rect playerHealthRect = { 20, 20, playerHealthSurface->w, playerHealthSurface->h };
    SDL_Rect enemyHealthRect = { 20, 60, enemyHealthSurface->w, enemyHealthSurface->h };
    SDL_Rect playerDefenseRect = { 20, 100, playerDefenseSurface->w, playerDefenseSurface->h };
    SDL_Rect enemyDamageRect = { 500, 20, enemyDamageSurface->w, enemyDamageSurface->h };
    SDL_Rect levelRect = { 300, 20, levelSurface->w, levelSurface->h };

    SDL_RenderCopy(context->renderer, playerHealthTexture, NULL, &playerHealthRect);
    SDL_RenderCopy(context->renderer, enemyHealthTexture, NULL, &enemyHealthRect);
    SDL_RenderCopy(context->renderer, playerDefenseTexture, NULL, &playerDefenseRect);
    SDL_RenderCopy(context->renderer, enemyDamageTexture, NULL, &enemyDamageRect);
    SDL_RenderCopy(context->renderer, levelTexture, NULL, &levelRect);

    // Cleanup surfaces and textures
    SDL_FreeSurface(playerHealthSurface);
    SDL_FreeSurface(enemyHealthSurface);
    SDL_FreeSurface(playerDefenseSurface);
    SDL_FreeSurface(enemyDamageSurface);
    SDL_FreeSurface(levelSurface);
    SDL_DestroyTexture(playerHealthTexture);
    SDL_DestroyTexture(enemyHealthTexture);
    SDL_DestroyTexture(playerDefenseTexture);
    SDL_DestroyTexture(enemyDamageTexture);
    SDL_DestroyTexture(levelTexture);

    // Render red and blue dice
    for (int i = 0; i < redDiceCount; ++i) {
        if (!isRed[i]) {
            char diceText[2];
            sprintf_s(diceText, "%d", redDiceValues[i]);
            SDL_Surface* diceSurface = TTF_RenderText_Solid(context->font, diceText, context->redColor);
            SDL_Texture* diceTexture = SDL_CreateTextureFromSurface(context->renderer, diceSurface);
            SDL_Rect diceRect = { 100 + i * 50, 400, 40, 40 };
            SDL_RenderCopy(context->renderer, diceTexture, NULL, &diceRect);
            SDL_FreeSurface(diceSurface);
            SDL_DestroyTexture(diceTexture);
        }
    }
    for (int i = 0; i < blueDiceCount; ++i) {
        if (!isBlue[i]) {
            char diceText[2];
            sprintf_s(diceText, "%d", blueDiceValues[i]);
            SDL_Surface* diceSurface = TTF_RenderText_Solid(context->font, diceText, context->blueColor);
            SDL_Texture* diceTexture = SDL_CreateTextureFromSurface(context->renderer, diceSurface);
            SDL_Rect diceRect = { 100 + (i + redDiceCount) * 50, 400, 40, 40 };
            SDL_RenderCopy(context->renderer, diceTexture, NULL, &diceRect);
            SDL_FreeSurface(diceSurface);
            SDL_DestroyTexture(diceTexture);
        }
    }

    // Render red and blue boxes
    for (int i = 0; i < 3; ++i) {
        if (redBox[i] != -1) {
            char redBoxText[2];
            sprintf_s(redBoxText, "%d", redDiceValues[redBox[i]]);
            SDL_Surface* redBoxSurface = TTF_RenderText_Solid(context->font, redBoxText, context->redColor);
            SDL_Texture* redBoxTexture = SDL_CreateTextureFromSurface(context->renderer, redBoxSurface);
            SDL_Rect redBoxRect = { 100 + i * 50, 460, 40, 40 };
            SDL_RenderCopy(context->renderer, redBoxTexture, NULL, &redBoxRect);
            SDL_FreeSurface(redBoxSurface);
            SDL_DestroyTexture(redBoxTexture);
        }
        if (blueBox[i] != -1) {
            char blueBoxText[2];
            sprintf_s(blueBoxText, "%d", blueDiceValues[blueBox[i]]);
            SDL_Surface* blueBoxSurface = TTF_RenderText_Solid(context->font, blueBoxText, context->blueColor);
            SDL_Texture* blueBoxTexture = SDL_CreateTextureFromSurface(context->renderer, blueBoxSurface);
            SDL_Rect blueBoxRect = { 100 + i * 50, 520, 40, 40 };
            SDL_RenderCopy(context->renderer, blueBoxTexture, NULL, &blueBoxRect);
            SDL_FreeSurface(blueBoxSurface);
            SDL_DestroyTexture(blueBoxTexture);
        }
    }

    // Render mixed boxes
    for (int i = 0; i < 2; ++i) {
        if (mixedBox1Red[i] != -1) {
            char mixedBox1RedText[2];
            sprintf_s(mixedBox1RedText, "%d", redDiceValues[mixedBox1Red[i]]);
            SDL_Surface* mixedBox1RedSurface = TTF_RenderText_Solid(context->font, mixedBox1RedText, context->redColor);
            SDL_Texture* mixedBox1RedTexture = SDL_CreateTextureFromSurface(context->renderer, mixedBox1RedSurface);
            SDL_Rect mixedBox1RedRect = { 100 + i * 50, 580, 40, 40 };
            SDL_RenderCopy(context->renderer, mixedBox1RedTexture, NULL, &mixedBox1RedRect);
            SDL_FreeSurface(mixedBox1RedSurface);
            SDL_DestroyTexture(mixedBox1RedTexture);
        }
        if (mixedBox2Blue[i] != -1) {
            char mixedBox2BlueText[2];
            sprintf_s(mixedBox2BlueText, "%d", blueDiceValues[mixedBox2Blue[i]]);
            SDL_Surface* mixedBox2BlueSurface = TTF_RenderText_Solid(context->font, mixedBox2BlueText, context->blueColor);
            SDL_Texture* mixedBox2BlueTexture = SDL_CreateTextureFromSurface(context->renderer, mixedBox2BlueSurface);
            SDL_Rect mixedBox2BlueRect = { 150 + i * 50, 640, 40, 40 };
            SDL_RenderCopy(context->renderer, mixedBox2BlueTexture, NULL, &mixedBox2BlueRect);
            SDL_FreeSurface(mixedBox2BlueSurface);
            SDL_DestroyTexture(mixedBox2BlueTexture);
        }
    }
    if (mixedBox1Blue[0] != -1) {
        char mixedBox1BlueText[2];
        sprintf_s(mixedBox1BlueText, "%d", blueDiceValues[mixedBox1Blue[0]]);
        SDL_Surface* mixedBox1BlueSurface = TTF_RenderText_Solid(context->font, mixedBox1BlueText, context->blueColor);
        SDL_Texture* mixedBox1BlueTexture = SDL_CreateTextureFromSurface(context->renderer, mixedBox1BlueSurface);
        SDL_Rect mixedBox1BlueRect = { 200, 580, 40, 40 };
        SDL_RenderCopy(context->renderer, mixedBox1BlueTexture, NULL, &mixedBox1BlueRect);
        SDL_FreeSurface(mixedBox1BlueSurface);
        SDL_DestroyTexture(mixedBox1BlueTexture);
    }
    if (mixedBox2Red[0] != -1) {
        char mixedBox2RedText[2];
        sprintf_s(mixedBox2RedText, "%d", redDiceValues[mixedBox2Red[0]]);
        SDL_Surface* mixedBox2RedSurface = TTF_RenderText_Solid(context->font, mixedBox2RedText, context->redColor);
        SDL_Texture* mixedBox2RedTexture = SDL_CreateTextureFromSurface(context->renderer, mixedBox2RedSurface);
        SDL_Rect mixedBox2RedRect = { 100, 640, 40, 40 };
        SDL_RenderCopy(context->renderer, mixedBox2RedTexture, NULL, &mixedBox2RedRect);
        SDL_FreeSurface(mixedBox2RedSurface);
        SDL_DestroyTexture(mixedBox2RedTexture);
    }

    // Render selected dice
    if (selectedDice != -1) {
        SDL_Rect selectedDiceRect = { 100 + selectedDice * 50, 400, 40, 40 };
        SDL_SetRenderDrawColor(context->renderer, 255, 0, 0, 255);
        SDL_RenderDrawRect(context->renderer, &selectedDiceRect);
    }

    // Render red, blue, mixed, and white boxes
    SDL_Rect redBoxRect = { 100, 460, 150, 50 };
    SDL_Rect blueBoxRect = { 100, 520, 150, 50 };
    SDL_Rect mixedBox1RedRect = { 100, 580, 100, 50 };
    SDL_Rect mixedBox1BlueRect = { 200, 580, 50, 50 };
    SDL_Rect mixedBox2RedRect = { 100, 640, 50, 50 };
    SDL_Rect mixedBox2BlueRect = { 150, 640, 100, 50 };
    SDL_SetRenderDrawColor(context->renderer, 255, 0, 0, 255); // Red
    SDL_RenderDrawRect(context->renderer, &redBoxRect);
    SDL_SetRenderDrawColor(context->renderer, 0, 0, 255, 255); // Blue
    SDL_RenderDrawRect(context->renderer, &blueBoxRect);
    SDL_SetRenderDrawColor(context->renderer, 255, 0, 0, 255); // Red for mixedBox1Red and mixedBox2Red
    SDL_RenderDrawRect(context->renderer, &mixedBox1RedRect);
    SDL_RenderDrawRect(context->renderer, &mixedBox2RedRect);
    SDL_SetRenderDrawColor(context->renderer, 0, 0, 255, 255); // Blue for mixedBox1Blue and mixedBox2Blue
    SDL_RenderDrawRect(context->renderer, &mixedBox1BlueRect);
    SDL_RenderDrawRect(context->renderer, &mixedBox2BlueRect);

    // Render NEXT TURN button
    SDL_Rect nextTurnButtonRect = { 500, 500, 200, 50 };
    SDL_SetRenderDrawColor(context->renderer, 255, 255, 255, 255); // White background
    SDL_RenderFillRect(context->renderer, &nextTurnButtonRect);
    SDL_SetRenderDrawColor(context->renderer, 0, 0, 0, 255); // Black border
    SDL_RenderDrawRect(context->renderer, &nextTurnButtonRect);

    // Render NEXT TURN text
    SDL_Surface* nextTurnSurface = TTF_RenderText_Solid(context->font, "NEXT TURN", context->blackColor);
    SDL_Texture* nextTurnTexture = SDL_CreateTextureFromSurface(context->renderer, nextTurnSurface);
    SDL_Rect nextTurnTextRect = { 510, 510, nextTurnSurface->w, nextTurnSurface->h };
    SDL_RenderCopy(context->renderer, nextTurnTexture, NULL, &nextTurnTextRect);
    SDL_FreeSurface(nextTurnSurface);
    SDL_DestroyTexture(nextTurnTexture);

    // Render GAME OVER or VICTORY text
    if (gameOver) {
        SDL_Surface* gameOverSurface = TTF_RenderText_Solid(context->font, "GAME OVER", context->whiteColor);
        SDL_Texture* gameOverTexture = SDL_CreateTextureFromSurface(context->renderer, gameOverSurface);
        SDL_Rect gameOverRect = { 300, 300, gameOverSurface->w, gameOverSurface->h };
        SDL_RenderCopy(context->renderer, gameOverTexture, NULL, &gameOverRect);
        SDL_FreeSurface(gameOverSurface);
        SDL_DestroyTexture(gameOverTexture);

        // Render BACK TO MENU button
        SDL_Rect backToMenuButtonRect = { 300, 400, 200, 50 };
        SDL_SetRenderDrawColor(context->renderer, 255, 255, 255, 255); // White background
        SDL_RenderFillRect(context->renderer, &backToMenuButtonRect);
        SDL_SetRenderDrawColor(context->renderer, 0, 0, 0, 255); // Black border
        SDL_RenderDrawRect(context->renderer, &backToMenuButtonRect);

        // Render BACK TO MENU text
        SDL_Surface* backToMenuSurface = TTF_RenderText_Solid(context->font, "BACK TO MENU", context->blackColor);
        SDL_Texture* backToMenuTexture = SDL_CreateTextureFromSurface(context->renderer, backToMenuSurface);
        SDL_Rect backToMenuTextRect = { 310, 410, backToMenuSurface->w, backToMenuSurface->h };
        SDL_RenderCopy(context->renderer, backToMenuTexture, NULL, &backToMenuTextRect);
        SDL_FreeSurface(backToMenuSurface);
        SDL_DestroyTexture(backToMenuTexture);
    }
    else if (victory1 || victory2 || victory3) {
        SDL_Surface* victorySurface = TTF_RenderText_Solid(context->font, "VICTORY", context->whiteColor);
        SDL_Texture* victoryTexture = SDL_CreateTextureFromSurface(context->renderer, victorySurface);
        SDL_Rect victoryRect = { 300, 300, victorySurface->w, victorySurface->h };
        SDL_RenderCopy(context->renderer, victoryTexture, NULL, &victoryRect);
        SDL_FreeSurface(victorySurface);
        SDL_DestroyTexture(victoryTexture);

        // Render NEXT button
        SDL_Rect nextButtonRect = { 300, 400, 100, 50 };
        SDL_SetRenderDrawColor(context->renderer, 255, 255, 255, 255); // White background
        SDL_RenderFillRect(context->renderer, &nextButtonRect);
        SDL_SetRenderDrawColor(context->renderer, 0, 0, 0, 255); // Black border
        SDL_RenderDrawRect(context->renderer, &nextButtonRect);

        // Render NEXT text
        SDL_Surface* nextSurface = TTF_RenderText_Solid(context->font, "NEXT", context->blackColor);
        SDL_Texture* nextTexture = SDL_CreateTextureFromSurface(context->renderer, nextSurface);
        SDL_Rect nextTextRect = { 310, 410, nextSurface->w, nextSurface->h };
        SDL_RenderCopy(context->renderer, nextTexture, NULL, &nextTextRect);
        SDL_FreeSurface(nextSurface);
        SDL_DestroyTexture(nextTexture);
    }

    // Render status effects
    if (enemyOnFire) {
        SDL_Surface* fireSurface = TTF_RenderText_Solid(context->font, "The enemy is on fire!", context->redColor);
        SDL_Texture* fireTexture = SDL_CreateTextureFromSurface(context->renderer, fireSurface);
        SDL_Rect fireRect = { 300, 100, fireSurface->w, fireSurface->h };
        SDL_RenderCopy(context->renderer, fireTexture, NULL, &fireRect);
        SDL_FreeSurface(fireSurface);
        SDL_DestroyTexture(fireTexture);
    }

    if (playerOnFire) {
        SDL_Surface* playerFireSurface = TTF_RenderText_Solid(context->font, "You are on fire!", context->redColor);
        SDL_Texture* playerFireTexture = SDL_CreateTextureFromSurface(context->renderer, playerFireSurface);
        SDL_Rect playerFireRect = { 300, 150, playerFireSurface->w, playerFireSurface->h };
        SDL_RenderCopy(context->renderer, playerFireTexture, NULL, &playerFireRect);
        SDL_FreeSurface(playerFireSurface);
        SDL_DestroyTexture(playerFireTexture);
    }

    if (enemyFrozen) {
        SDL_Surface* frozenSurface = TTF_RenderText_Solid(context->font, "The enemy is frozen!", context->blueColor);
        SDL_Texture* frozenTexture = SDL_CreateTextureFromSurface(context->renderer, frozenSurface);
        SDL_Rect frozenRect = { 300, 200, frozenSurface->w, frozenSurface->h };
        SDL_RenderCopy(context->renderer, frozenTexture, NULL, &frozenRect);
        SDL_FreeSurface(frozenSurface);
        SDL_DestroyTexture(frozenTexture);
    }

    if (playerFortified) {
        SDL_Surface* fortifiedSurface = TTF_RenderText_Solid(context->font, "Fortification!", context->whiteColor);
        SDL_Texture* fortifiedTexture = SDL_CreateTextureFromSurface(context->renderer, fortifiedSurface);
        SDL_Rect fortifiedRect = { 300, 250, fortifiedSurface->w, fortifiedSurface->h };
        SDL_RenderCopy(context->renderer, fortifiedTexture, NULL, &fortifiedRect);
        SDL_FreeSurface(fortifiedSurface);
        SDL_DestroyTexture(fortifiedTexture);
    }

    if (playerAgile) {
        SDL_Surface* agileSurface = TTF_RenderText_Solid(context->font, "Agiled!", context->whiteColor);
        SDL_Texture* agileTexture = SDL_CreateTextureFromSurface(context->renderer, agileSurface);
        SDL_Rect agileRect = { 300, 300, agileSurface->w, agileSurface->h };
        SDL_RenderCopy(context->renderer, agileTexture, NULL, &agileRect);
        SDL_FreeSurface(agileSurface);
        SDL_DestroyTexture(agileTexture);
    }
}

// Handle combat events
extern "C" void handleCombatEvent(SDL_Event* event, CombatContext* context) {
    if (event->type == SDL_MOUSEBUTTONDOWN) {
        int x, y;
        SDL_GetMouseState(&x, &y);

        // Handle dice selection
        for (int i = 0; i < redDiceCount; ++i) {
            SDL_Rect diceRect = { 100 + i * 50, 400, 40, 40 };
            if (x >= diceRect.x && x <= diceRect.x + diceRect.w &&
                y >= diceRect.y && y <= diceRect.y + diceRect.h) {
                selectedDice = i;
                showError = false;
                return;
            }
        }
        for (int i = 0; i < blueDiceCount; ++i) {
            SDL_Rect diceRect = { 100 + (i + redDiceCount) * 50, 400, 40, 40 };
            if (x >= diceRect.x && x <= diceRect.x + diceRect.w &&
                y >= diceRect.y && y <= diceRect.y + diceRect.h) {
                selectedDice = i + redDiceCount;
                showError = false;
                return;
            }
        }

        // Handle box placement
        if (selectedDice != -1) {
            if (x >= 100 && x <= 250 && y >= 460 && y <= 510) {
                // Red box
                if (selectedDice < redDiceCount) {
                    for (int i = 0; i < 3; ++i) {
                        if (redBox[i] == -1) {
                            redBox[i] = selectedDice;
                            isRed[selectedDice] = true;
                            selectedDice = -1;
                            return;
                        }
                    }
                }
                else {
                    showError = true;
                }
            }
            else if (x >= 100 && x <= 250 && y >= 520 && y <= 570) {
                // Blue box
                if (selectedDice >= redDiceCount) {
                    for (int i = 0; i < 3; ++i) {
                        if (blueBox[i] == -1) {
                            blueBox[i] = selectedDice - redDiceCount;
                            isBlue[selectedDice - redDiceCount] = true;
                            selectedDice = -1;
                            return;
                        }
                    }
                }
                else {
                    showError = true;
                }
            }
            else if (x >= 100 && x <= 200 && y >= 580 && y <= 630) {
                // Mixed box 1 red (2 red)
                if (selectedDice < redDiceCount) {
                    for (int i = 0; i < 2; ++i) {
                        if (mixedBox1Red[i] == -1) {
                            mixedBox1Red[i] = selectedDice;
                            isRed[selectedDice] = true;
                            selectedDice = -1;
                            return;
                        }
                    }
                }
                else {
                    showError = true;
                }
            }
            else if (x >= 200 && x <= 250 && y >= 580 && y <= 630) {
                // Mixed box 1 blue (1 blue)
                if (selectedDice >= redDiceCount) {
                    if (mixedBox1Blue[0] == -1) {
                        mixedBox1Blue[0] = selectedDice - redDiceCount;
                        isBlue[selectedDice - redDiceCount] = true;
                        selectedDice = -1;
                        return;
                    }
                }
                else {
                    showError = true;
                }
            }
            else if (x >= 100 && x <= 150 && y >= 640 && y <= 690) {
                // Mixed box 2 red (1 red)
                if (selectedDice < redDiceCount) {
                    if (mixedBox2Red[0] == -1) {
                        mixedBox2Red[0] = selectedDice;
                        isRed[selectedDice] = true;
                        selectedDice = -1;
                        return;
                    }
                }
                else {
                    showError = true;
                }
            }
            else if (x >= 150 && x <= 250 && y >= 640 && y <= 690) {
                // Mixed box 2 blue (2 blue)
                if (selectedDice >= redDiceCount) {
                    for (int i = 0; i < 2; ++i) {
                        if (mixedBox2Blue[i] == -1) {
                            mixedBox2Blue[i] = selectedDice - redDiceCount;
                            isBlue[selectedDice - redDiceCount] = true;
                            selectedDice = -1;
                            return;
                        }
                    }
                }
                else {
                    showError = true;
                }
            }
        }

        // Handle NEXT TURN button click
        if (x >= 500 && x <= 650 && y >= 500 && y <= 550) {
            nextTurn();
        }
    }
    else if (event->type == SDL_KEYDOWN && event->key.keysym.sym == SDLK_ESCAPE) {
        cleanupCombat();
        SDL_Quit();
        exit(0);
    }
}

extern "C" void nextTurn() {
    if (gameOver || victory1 || victory2 || victory3) {
        return;
    }

    int redSum = 0;
    int blueSum = 0;
    int mixedSum1 = 0;
    int mixedSum2 = 0;
    int mixedCount1 = 0;
    int mixedCount2 = 0;

    // Calculate damage and defense
    for (int i = 0; i < 3; ++i) {
        if (redBox[i] != -1) {
            redSum += redDiceValues[redBox[i]];
            redBox[i] = -1;
        }
        if (blueBox[i] != -1) {
            blueSum += blueDiceValues[blueBox[i]];
            blueBox[i] = -1;
        }
    }
    for (int i = 0; i < 2; ++i) {
        if (mixedBox1Red[i] != -1) {
            mixedSum1 += redDiceValues[mixedBox1Red[i]];
            mixedCount1++;
            mixedBox1Red[i] = -1;
        }
        if (mixedBox2Blue[i] != -1) {
            mixedSum2 += blueDiceValues[mixedBox2Blue[i]];
            mixedCount2++;
            mixedBox2Blue[i] = -1;
        }
    }
    if (mixedBox1Blue[0] != -1) {
        mixedSum1 += blueDiceValues[mixedBox1Blue[0]];
        mixedCount1++;
        mixedBox1Blue[0] = -1;
    }
    if (mixedBox2Red[0] != -1) {
        mixedSum2 += redDiceValues[mixedBox2Red[0]];
        mixedCount2++;
        mixedBox2Red[0] = -1;
    }

    currentEnemy->takeDamage(redSum);
    playerDefense += blueSum;

    // Calculate average values for mixed boxes
    int mixedAvg1 = mixedCount1 > 0 ? mixedSum1 / mixedCount1 : 0;
    int mixedAvg2 = mixedCount2 > 0 ? mixedSum2 / mixedCount2 : 0;

    // Apply effects based on mixed box 1
    if (mixedAvg1 >= 9) {
        enemyFrozen = true;
    }
    else if (mixedAvg1 > 0 && mixedAvg1 < 9) {
        enemyOnFire = true;
    }

    // Apply effects based on mixed box 2
    if (mixedAvg2 > 0 && mixedAvg2 < 9) {
        playerFortified = true;
    }
    else if (mixedAvg2 >= 9) {
        playerAgile = true;
    }

    // Calculate enemy damage to player
    enemyDamage = currentEnemy->calculateDamage(currentTurn);
    if (enemyFrozen) {
        enemyDamage /= 2;
        enemyFrozen = false;
    }
    if (playerDefense >= enemyDamage) {
        playerDefense -= enemyDamage;
    }
    else {
        playerHealth -= (enemyDamage - playerDefense);
        playerDefense = 0;
    }

    // Check if player is on fire
    if (currentLevel == 3 && enemyDamage > 0) {
        playerOnFire = true;
    }

    // Apply fire damage to player
    if (playerOnFire) {
        playerHealth -= 1;
    }

    if (playerHealth <= 0) {
        gameOver = true;
    }
    else if (currentEnemy->getHealth() <= 0) {
        if (currentLevel == 1) victory1 = true;
        else if (currentLevel == 2) victory2 = true;
        else if (currentLevel == 3) victory3 = true;

        SDL_Delay(3000);
        currentLevel++;
        initCombat(currentLevel);
    }

    // Reset dice values
    for (int i = 0; i < redDiceCount; ++i) {
        redDiceValues[i] = rand() % 6 + 1;
        isRed[i] = false;
    }
    for (int i = 0; i < blueDiceCount; ++i) {
        blueDiceValues[i] = rand() % 6 + 1;
        isBlue[i] = false;
    }

    // Apply agile effect
    if (playerAgile) {
        redDiceValues[redDiceCount] = rand() % 6 + 1;
        blueDiceValues[blueDiceCount] = rand() % 6 + 1;
        redDiceCount++;
        blueDiceCount++;
        playerAgile = false;
    }

    // Apply fortified effect
    if (!playerFortified) {
        playerDefense = 0;
    }
    else {
        playerFortified = false;
    }

    currentTurn++;
}

I modified Combat.cpp is it ok?

You should change the line
static Uint32 lastTime = 0;
to
static Uint32 lastTime = SDL_GetTicks();
so that the very first frame doesn’t have a large delta time. A delta time of (essentially) zero is better than a very large one.

Also, be aware that SDL_GetTicks() is probably not accurate enough for this, since it only has millisecond resolution. Either use SDL_GetTicksNS(), SDL_GetPerformanceCounter(), or (since you’re using C++) std::chrono

And if you do use one of those, consider calculating and returning delta time as a double so you maintain the extra delta time precision.

You’re initializing/deinitializing SDL in the main file but also in the Game.cpp file’s init/quit function.

I haven’t tried out your latest code so you’ll have to check for memory leaks yourself.
It’s always good to keep track of allocated memory and clean it up properly.

One more tip: you have a lot of repetitive code that can be made info functions.
For example, you’re doing this a lot:

SDL_Surface* surface = TTF_RenderText_Solid(someFont, someText, someColor);
SDL_Texture* texture= SDL_CreateTextureFromSurface(renderer, surface);
SDL_Rect rect = {x, y, surface->w, surface->h};
SDL_RenderCopy(renderer, texture, nullptr, &rect);
SDL_FreeSurface(surface);
SDL_DestroyTexture(texture);

This can be made into a function and that function can then be used in many places of the code.

Example code for such function:

void RenderText(SDL_Renderer* renderer, TTF_Font* font, const std::string& text, const SDL_Point& position, const SDL_Color& color)
{
	SDL_Surface* surface = TTF_RenderText_Solid(font, text.c_str(), color);
	if(!surface)
		return;

	SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
	if(!texture)
	{
		SDL_FreeSurface(surface);
		surface = nullptr;

		return;
	}

	SDL_FreeSurface(surface);
	surface = nullptr;

	const SDL_Rect rect = {position.x, position.y, surface->w, surface->h};
	SDL_RenderCopy(renderer, texture, nullptr, &rect);

	SDL_DestroyTexture(texture);
	texture = nullptr;
}

Example usage:

RenderText(renderer, font, "Hello", {100, 100}, textColor);
RenderText(renderer, font, "World", {100, 130}, textColor);