#include #include #include #include "include/raylib.h" #include "raymath.h" #define SCREEN_WIDTH 1000 #define SCREEN_HEIGHT 800 #define IMG_W 1000 #define IMG_H 800 #define IMG_SIZE IMG_W * IMG_H #define TARGET_FPS 120 const int maxRadius = 25; const int paintTankMax = 250; const Color playerColor = { 0, 121, 241, 255 }; typedef enum Direction { NORTH, WEST, EAST, SOUTH, } Direction; typedef struct Pixel { int x; int y; } Pixel; typedef struct GameState { float dt; float time_elapsed; int frame_count; Texture displayTexture; Image imageBuffer; int paintTank; bool joystickActive; Vector2 joystickStartPos; Vector2 playerPos; Vector2 playerDir; Color colorUnderPlayer; int *bfsBuffer; Pixel *queue; int q_head; int q_tail; Pixel *fill; int fillCount; bool once; bool nomore; Texture debugBfsTx; Image debugBfsImg; } GameState; int PixelToIndex(Pixel p) {return p.y * IMG_W + p.x;} Pixel IndexToPixel(int idx) {return (Pixel) {idx % IMG_W, (int)(idx / IMG_W) }; } // Pixel IsInBounds(int idx, Direction direction) {return (Pixel) {idx % IMG_W, (int)(idx / IMG_W) }; } GameState *Init() { SetTraceLogLevel(LOG_ERROR); InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Flood Fill"); SetTargetFPS(TARGET_FPS); Image imageBuffer = GenImageColor(IMG_W, IMG_H, WHITE); Image debugBfsImg = GenImageColor(IMG_W, IMG_H, BLACK); GameState state = { .imageBuffer = imageBuffer, .displayTexture = LoadTextureFromImage(imageBuffer), .debugBfsImg = debugBfsImg, .debugBfsTx = LoadTextureFromImage(debugBfsImg), .playerPos = (Vector2){40, 40}, .paintTank = paintTankMax, .colorUnderPlayer = BLUE, .bfsBuffer = RL_CALLOC(IMG_SIZE, sizeof(int)), .queue = RL_CALLOC(IMG_SIZE, sizeof(Pixel)), .fill = RL_CALLOC(IMG_SIZE, sizeof(Pixel)), }; int r = IMG_W * 0.2f; ImageDrawCircle(&state.imageBuffer, 0, 0, r, playerColor); ImageDrawCircle(&state.imageBuffer, IMG_W, 0, r, GREEN); ImageDrawCircle(&state.imageBuffer, 0, IMG_H, r, VIOLET); ImageDrawCircle(&state.imageBuffer, IMG_W, IMG_H, r, ORANGE); UpdateTexture(state.displayTexture, state.imageBuffer.data); GameState *state_ptr = RL_MALLOC(sizeof(GameState)); memcpy(state_ptr, &state, sizeof(GameState)); return state_ptr; } void Input(GameState *S) { if (IsMouseButtonPressed(0)) { S->joystickStartPos = GetMousePosition(); S->joystickActive = true; } if (IsMouseButtonReleased(0)) { S->joystickActive = false; } } void Update(GameState *S) { bool dirty = false; if (S->joystickActive) { Vector2 dist = Vector2Subtract(GetMousePosition(), S->joystickStartPos); Vector2 joystickDelta = Vector2Normalize(dist); Vector2 dir = Vector2Normalize(joystickDelta); S->playerDir = Vector2Lerp(S->playerDir, dir, S->dt * 5.0f); S->playerPos = Vector2Add(S->playerPos, Vector2Scale(S->playerDir, 100.0f * S->dt)); Vector2 minBounds = (Vector2){25.0f, 25.0f}; Vector2 maxBounds = (Vector2){SCREEN_WIDTH - 25.0f, SCREEN_HEIGHT - 25.0f}; S->playerPos = Vector2Clamp(S->playerPos, minBounds, maxBounds); float r = (float)S->paintTank / 10.0f; dist = Vector2Scale(S->playerDir, r + 1); Vector2 offset = Vector2Add(S->playerPos, dist); S->colorUnderPlayer = GetImageColor(S->imageBuffer, offset.x, offset.y); float mag = Vector2Length(joystickDelta); if (mag > 0.0f) { bool sameColor = ColorIsEqual(S->colorUnderPlayer, playerColor); if (sameColor && S->paintTank < paintTankMax) { S->paintTank += 4; S->paintTank = S->paintTank > paintTankMax ? paintTankMax : S->paintTank; } else { S->paintTank -= 3; S->paintTank = S->paintTank < 0 ? 0 : S->paintTank; } if (S->paintTank > 0 && !sameColor) { ImageDrawCircleV(&S->imageBuffer, S->playerPos, r, BLUE); UpdateTexture(S->displayTexture, S->imageBuffer.data); dirty = true; } } } if (!dirty) { return; } memset(S->bfsBuffer, 0, IMG_SIZE * sizeof(int)); for (int i = 0; i < IMG_SIZE; i++) { Pixel pixel = IndexToPixel(i); Color color = GetImageColor(S->imageBuffer, pixel.x, pixel.y); bool sameColor = ColorIsEqual(color, playerColor); if (S->bfsBuffer[i] != 0 || sameColor) { if (sameColor) { S->bfsBuffer[i] = -1; } continue; } S->queue[0] = pixel; S->q_tail++; bool edgeDetected = false; while (S->q_head != S->q_tail) { Pixel pixel = S->queue[S->q_head++]; int idx = PixelToIndex(pixel); int left = abs(pixel.y * IMG_W - idx) - 1 >= 0 ? idx - 1 : -1; int right = abs(pixel.y * IMG_W - idx) + 1 < IMG_W ? idx + 1 : -1; int top = idx - IMG_W; int bottom = idx + IMG_W; int directions[] = { left, right, top, bottom }; for (int i = 0; i < 4; i++) { int j = directions[i]; if ((i < 2 && j >= 0) || (i >= 2 && j >= 0 && j < IMG_SIZE)) { Pixel pixel = IndexToPixel(j); Color c = GetImageColor(S->imageBuffer, pixel.x, pixel.y); if (S->bfsBuffer[j] == 0 && !ColorIsEqual(playerColor, c)) { S->bfsBuffer[j] = 1; S->queue[S->q_tail++] = pixel; } else if (S->bfsBuffer[j] == 0) { S->bfsBuffer[j] = -1; } } else { edgeDetected = true; } } if (!edgeDetected) { S->fill[S->fillCount++] = pixel; } else { } } // Fill that sucker if (!edgeDetected) { for (int i = 0; i < S->fillCount; i++) { Pixel p = S->fill[i]; ImageDrawPixel(&S->imageBuffer, p.x, p.y, playerColor); } UpdateTexture(S->displayTexture, S->imageBuffer.data); } S->fillCount = 0; S->q_head = 0; S->q_tail = 0; } S->once = true; } void Draw2D(const GameState *S) { Rectangle source = {0, 0, IMG_W, IMG_H}; Rectangle dest = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}; DrawTexturePro(S->displayTexture, source, dest, (Vector2){0,0}, 0, WHITE); DrawCircleV(S->playerPos, 20, BLACK); Vector2 lookPos = Vector2Scale(S->playerDir, 50.0f); lookPos = Vector2Add(S->playerPos, lookPos); DrawLineV(S->playerPos, lookPos, RED); int barWidth = 50, barHeight = 12; float perc = S->paintTank / (float)paintTankMax; float barX = S->playerPos.x - barWidth / 2; float barY = S->playerPos.y + 10; Rectangle barBGRect = {barX, barY, barWidth, barHeight}; Rectangle barFGRect = {barX, barY, barWidth * perc, barHeight}; DrawRectangleRec(barBGRect, GRAY); DrawRectangleRec(barFGRect, RED); if (S->joystickActive) { Color grayish = {200, 200, 200, 100}; DrawRing(S->joystickStartPos, 60, 80, 0.0f, 360.0f, 50, grayish); Vector2 mousePos = GetMousePosition(); Vector2 padDir = Vector2Normalize(Vector2Subtract(mousePos, S->joystickStartPos)); Vector2 offsetPos = Vector2Add(S->joystickStartPos, Vector2Scale(padDir, 30.0f)); Vector2 clampedPos = Vector2Clamp(mousePos, S->joystickStartPos, offsetPos); DrawCircleV(clampedPos, 25, grayish); } } void Draw2DDebug(GameState *S) { Rectangle rect = {SCREEN_WIDTH / 2 - 50, SCREEN_HEIGHT - 100, 110, 50}; DrawRectangleRec(rect, S->colorUnderPlayer); DrawRectangleLinesEx(rect, 2, BLACK); if (S->once) { // if (!S->nomore) { if (false) { Color color = BLACK; for (int i = 0; i < IMG_SIZE; i++) { int idx = S->bfsBuffer[i]; // int idx = 2; if (idx == 0) { color = BLUE; } else if (idx == 1) { color = GREEN; } else if (idx == -1) { color = RED; } else { } Pixel p = IndexToPixel(i); ImageDrawPixel(&S->debugBfsImg, p.x, p.y, color); } UpdateTexture(S->debugBfsTx, S->debugBfsImg.data); } Rectangle source = {0, 0, IMG_W, IMG_H}; Vector2 pos = {SCREEN_WIDTH * 0.5f - IMG_W * 0.5f,SCREEN_HEIGHT * 0.5f - IMG_H * 0.5f}; Rectangle dest = {pos.x, pos.y, IMG_W, IMG_H}; // DrawTexturePro(S->debugBfsTx, source, dest, (Vector2){0,0}, 0, WHITE); // S->nomore = true; } DrawFPS(rect.x + 17, SCREEN_HEIGHT - 33); } int main(void) { GameState *state = Init(); while (!WindowShouldClose()) { state->dt = GetFrameTime(); state->time_elapsed = GetTime(); Input(state); Update(state); BeginDrawing(); { ClearBackground(RAYWHITE); Draw2D(state); Draw2DDebug(state); } EndDrawing(); state->frame_count++; } UnloadImage(state->imageBuffer); UnloadTexture(state->displayTexture); UnloadImage(state->debugBfsImg); UnloadTexture(state->debugBfsTx); CloseWindow(); free(state->fill); free(state->queue); free(state->bfsBuffer); free(state); }