summaryrefslogtreecommitdiff
path: root/flappybird.c
diff options
context:
space:
mode:
authormiolin <sash.kryuko2015@gmail.com>2026-11-01 17:30:16 +0300
committermiolin <sash.kryuko2015@gmail.com>2026-11-01 17:30:16 +0300
commit2526b981585bb5621426d139832d9ff998ef8437 (patch)
treeb534f32524dff1d210543c038e22fa6ecbe2a4ca /flappybird.c
making it a git repoHEADmaster
Diffstat (limited to 'flappybird.c')
-rw-r--r--flappybird.c394
1 files changed, 394 insertions, 0 deletions
diff --git a/flappybird.c b/flappybird.c
new file mode 100644
index 0000000..62fe936
--- /dev/null
+++ b/flappybird.c
@@ -0,0 +1,394 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <raylib.h>
+#include <raymath.h>
+
+#define PIPE_COUNT 10
+#define PIPE_INTERVAL 300
+#define PIPE_WIDTH 100
+#define PIPE_GAP 150
+
+#define BIRD_ANIMATION_FRAME_COUNT 4
+#define BIRD_ANIMATION_FRAME_TIME 0.1
+#define PATH "./"
+
+struct game_state {
+ Vector2 bird_position;
+ Vector2 bird_velocity;
+ Texture2D texture;
+ Rectangle bird_texture[BIRD_ANIMATION_FRAME_COUNT];
+ struct animation {
+ int frame;
+ double clock;
+ } bird_animation;
+ NPatchInfo pipe_texture_top;
+ NPatchInfo pipe_texture_bottom;
+ struct pipe {
+ Vector2 position;
+ } pipe[PIPE_COUNT];
+ struct death_state {
+ double angle;
+ Vector2 velocity;
+ } death;
+ struct background_state {
+ Texture2D dune;
+ Texture2D cloud[2];
+ double scroll;
+ } background;
+};
+
+void init_game_state(struct game_state *state);
+
+Rectangle dune_destination_to_source(int texture_width, int texture_height, Rectangle destination) {
+ float q = 100 / destination.height;
+ return (Rectangle) {
+ 0, q
+ ,texture_height * destination.width / destination.height
+ ,texture_height - q * 2
+ };
+}
+
+void draw_background(struct background_state *state) {
+ int window_width = GetRenderWidth();
+ int window_height = GetRenderHeight();
+ ClearBackground((Color) { 135, 206, 235, 0 });
+ double y = window_height / 2;
+ double height = window_height * 0.05;
+ int offset = 1;
+ DrawRectangle(0, window_height / 2, window_width, window_height / 2, (Color) { 243, 202, 35, 255 });
+ double scroll_speed = 0.2;
+ for (int i = 0; i < 5; i++) {
+ Rectangle destination = (Rectangle) { 0, y, window_width, height };
+ Rectangle source = dune_destination_to_source(state->dune.width, state->dune.height, destination);
+ source.x = state->scroll * scroll_speed + offset;
+ scroll_speed *= 2.0;
+ offset = offset * 37 % 240;
+ DrawTexturePro(state->dune
+ ,source
+ ,destination
+ ,(Vector2) { 0, height }
+ ,0
+ ,WHITE
+ );
+ y += height * 0.8577;
+ height *= 1.8;
+ }
+ double texture_width[3] = { 392, 392, 500 };
+ Texture2D texture[3] = { state->cloud[1], state->cloud[1], state->cloud[0] };
+ double texture_height[3] = { 280, 220, 265 };
+ double texture_y[3] = { 0, 280, 0 };
+ height = window_height / 10;
+ y = window_height * 0.4;
+ scroll_speed = 0.1;
+ for (int j = 0; j < 8; j++) {
+ int i = j % 3;
+ Rectangle destination = (Rectangle) { 0, y, window_width, height };
+ Rectangle source = dune_destination_to_source(texture_width[i], texture_height[i], destination);
+ source.y = texture_y[i];
+ source.x = state->scroll * scroll_speed + offset;
+ offset = offset * 37 % 240;
+ scroll_speed *= 1.2;
+ DrawTexturePro(texture[i]
+ ,source
+ ,destination
+ ,(Vector2) { 0, height }
+ ,0
+ ,WHITE
+ );
+ y -= height * 0.083;
+ height *= 1.3;
+
+ }
+}
+
+void animation_update(struct animation *animation, double dt, double frame_time, int frame_count) {
+ animation->clock += dt;
+ if (animation->clock > frame_time) {
+ animation->frame = (animation->frame + 1) % frame_count;
+ animation->clock = 0;
+ }
+}
+
+double clamp(double x, double a, double b) {
+ return x < a ? a : x > b ? b : x;
+}
+
+double rect_distance(Rectangle r, Vector2 p) {
+ Vector2 dim = (Vector2) { r.width * 0.5, r.height * 0.5 };
+ p = (Vector2) { abs(p.x - r.x - r.width / 2), abs(p.y - r.y - r.height / 2) };
+ Vector2 d = Vector2Subtract(p, dim);
+ d = (Vector2) { d.x < 0 ? 0 : d.x, d.y < 0 ? 0 : d.y };
+ return Vector2Length(d);
+}
+
+double pipe_height(double window_height, double pipe_buffer) {
+ return rand() / (RAND_MAX / (window_height - pipe_buffer * 2)) + pipe_buffer;
+}
+
+Rectangle pipe_top(struct pipe pipe, double width, double gap, double window_height) {
+ return (Rectangle) {
+ pipe.position.x - width / 2, 0
+ ,width, pipe.position.y - gap / 2
+ };
+}
+
+Rectangle pipe_bottom(struct pipe pipe, double width, double gap, double window_height) {
+ return (Rectangle) {
+ pipe.position.x - width / 2, pipe.position.y + gap / 2
+ ,width, window_height - pipe.position.y - gap / 2
+ };
+}
+
+double pipe_last(struct pipe *pipe, int count) {
+ if (count < 1) {
+ return 0;
+ }
+ double last = pipe[0].position.x;
+ for (int i = 1; i < count; i++) {
+ last = pipe[i].position.x > last ? pipe[i].position.x : last;
+ }
+ return last;
+}
+
+double pipe_distance(struct pipe *pipe, int count, double width, double gap, double window_height, Vector2 p) {
+ double d = INFINITY;
+ for (int i = 0; i < count; i++) {
+ double top_distance = rect_distance(pipe_top(pipe[i], width, gap, window_height), p)
+ ,bottom_distance = rect_distance(pipe_bottom(pipe[i], width, gap, window_height), p);
+ d = top_distance < d ? top_distance : d;
+ d = bottom_distance < d ? bottom_distance : d;
+ }
+ return d;
+}
+
+enum game_screen {
+ PLAY_SCREEN
+ ,TAP_SPACE_SCREEN
+ ,DEATH_SCREEN
+ ,TEST_SCREEN
+};
+
+#define BIRD_WIDTH 80
+#define BIRD_HEIGHT (BIRD_WIDTH * 0.6)
+#define HIT_RADIUS (BIRD_HEIGHT * 0.4)
+
+void draw_game(struct game_state *state) {
+ int window_width = GetRenderWidth();
+ int window_height = GetRenderHeight();
+ float angle = atan(state->bird_velocity.y / state->bird_velocity.x) * 180 / M_PI;
+ if (state->bird_velocity.x < 0) {
+ angle += 180;
+ }
+ draw_background(&state->background);
+ DrawTexturePro(
+ state->texture
+ ,state->bird_texture[state->bird_animation.frame]
+ ,(Rectangle) {
+ state->bird_position.x, state->bird_position.y
+ ,BIRD_WIDTH, BIRD_HEIGHT
+ }
+ ,(Vector2) { BIRD_WIDTH / 2, BIRD_HEIGHT / 2 }
+ ,angle
+ ,WHITE
+ );
+ //DrawCircleLinesV(state->bird_position, HIT_RADIUS, PURPLE);
+ for (int i = 0; i < PIPE_COUNT; i++) {
+ DrawTextureNPatch(
+ state->texture
+ ,state->pipe_texture_top
+ ,pipe_top(state->pipe[i], PIPE_WIDTH, PIPE_GAP, window_height)
+ ,(Vector2) { 0, 0 }
+ ,0
+ ,WHITE
+ );
+ DrawTextureNPatch(
+ state->texture
+ ,state->pipe_texture_bottom
+ ,pipe_bottom(state->pipe[i], PIPE_WIDTH, PIPE_GAP, window_height)
+ ,(Vector2) { 0, 0 }
+ ,0
+ ,WHITE
+ );
+ }
+ return;
+}
+
+enum game_screen play_game(struct game_state *state) {
+ int window_width = GetRenderWidth();
+ int window_height = GetRenderHeight();
+ BeginDrawing();
+ ClearBackground(BLACK);
+ draw_game(state);
+ EndDrawing();
+ if (pipe_distance(state->pipe, PIPE_COUNT, PIPE_WIDTH, PIPE_GAP, window_height, state->bird_position) < HIT_RADIUS
+ || state->bird_position.y >= window_height - BIRD_HEIGHT / 2 - 1
+ ) {
+ state->death.angle = atan(state->bird_velocity.y / state->bird_velocity.x);
+ state->death.velocity = (Vector2) { state->bird_velocity.x, -state->bird_velocity.x };
+ return DEATH_SCREEN;
+ }
+ double dt = GetFrameTime();
+ state->bird_position.y += state->bird_velocity.y * dt * 10;
+ state->bird_velocity.y += 100 * dt;
+ state->bird_position.y = clamp(state->bird_position.y, BIRD_HEIGHT / 2, window_height - BIRD_HEIGHT / 2);
+ state->bird_velocity.y = clamp(state->bird_velocity.y, -100, 100);
+ if (IsKeyPressed(KEY_SPACE)) {
+ state->bird_velocity.y = -40;
+ }
+ for (int i = 0; i < PIPE_COUNT; i++) {
+ state->pipe[i].position.x -= state->bird_velocity.x * dt * 2;
+ if (state->pipe[i].position.x < -PIPE_WIDTH) {
+ state->pipe[i].position.x = pipe_last(state->pipe, PIPE_COUNT) + PIPE_INTERVAL;
+ state->pipe[i].position.y = pipe_height(window_height, 100);
+ }
+ }
+ state->background.scroll += state->bird_velocity.x * dt * 0.4;
+ animation_update(&state->bird_animation, dt, BIRD_ANIMATION_FRAME_TIME, BIRD_ANIMATION_FRAME_COUNT);
+ return PLAY_SCREEN;
+}
+
+enum game_screen tap_space_screen(struct game_state *state) {
+ state->bird_velocity.y = 0;
+ int window_width = GetRenderWidth();
+ int window_height = GetRenderHeight();
+ BeginDrawing();
+ {
+ draw_game(state);
+ char *text = "SPACE";
+ int text_height = window_width / 20;
+ int text_width = MeasureText(text, text_height);
+ DrawText(text, (window_width - text_width) / 2, window_height * 2 / 3, text_height, WHITE);
+ }
+ EndDrawing();
+ double dt = GetFrameTime();
+ state->background.scroll += state->bird_velocity.x * dt * 0.4;
+ if (IsKeyPressed(KEY_SPACE)) {
+ state->bird_velocity.y = -40;
+ return PLAY_SCREEN;
+ }
+ animation_update(&state->bird_animation, dt, BIRD_ANIMATION_FRAME_TIME, BIRD_ANIMATION_FRAME_COUNT);
+ return TAP_SPACE_SCREEN;
+}
+
+enum game_screen death_screen(struct game_state *state) {
+ int window_width = GetRenderWidth()
+ ,window_height = GetRenderHeight();
+ BeginDrawing();
+ {
+ ClearBackground((Color) { 135, 206, 235, 0 });
+ draw_game(state);
+ double fade = clamp((state->bird_position.y - window_height) / window_height, 0, 1);
+ DrawRectangle(0, 0, window_width, window_height, (Color) { 0, 0, 0, (fade) * 255 });
+ int text_height = window_height / 4;
+ char *text = "YOU DIED";
+ int text_width = MeasureText(text, text_height);
+ DrawText(text, window_width / 2 - text_width / 2, window_height / 2 - text_height / 2, text_height, (Color) { 255, 0, 0, clamp((fade - 0.5) * 2, 0, 1) * 255 });
+ }
+ EndDrawing();
+ double dt = GetFrameTime();
+ state->death.angle += dt * 20;
+ state->bird_velocity = (Vector2) { cos(state->death.angle), sin(state->death.angle) };
+ state->bird_position = Vector2Add(state->bird_position, Vector2Scale(state->death.velocity, dt * 8));
+ state->death.velocity.y += 200 * dt;
+ if (state->bird_position.y > window_height * 10) {
+ init_game_state(state);
+ return TAP_SPACE_SCREEN;
+ }
+ return DEATH_SCREEN;
+}
+
+enum game_screen test_screen(struct game_state *state) {
+ BeginDrawing();
+ draw_background(&state->background);
+ EndDrawing();
+ double dt = GetFrameTime();
+ state->background.scroll += dt * 100;
+ return TEST_SCREEN;
+}
+
+void init_game_state(struct game_state *state) {
+ int window_width = GetRenderWidth();
+ int window_height = GetRenderHeight();
+ state->bird_position = (Vector2) { window_width / 2, window_height / 2 };
+ state->bird_velocity = (Vector2) { 40, -40 };
+ state->background.scroll = 0;
+ for (int i = 0; i < PIPE_COUNT; i++) {
+ state->pipe[i].position = (Vector2) {
+ i * PIPE_INTERVAL + window_width + PIPE_WIDTH
+ ,pipe_height(window_height, PIPE_GAP * 0.5)
+ };
+ }
+}
+
+int main(int argc, char **argv) {
+ const int window_width = 800;
+ const int window_height = 450;
+
+ InitWindow(window_width, window_height, "flappybird");
+
+ SetTargetFPS(60);
+ struct game_state state;
+ state.texture = LoadTexture(PATH "bird2.png");
+ GenTextureMipmaps(&state.texture);
+ SetTextureFilter(state.texture, TEXTURE_FILTER_BILINEAR);
+ for (int i = 0; i < BIRD_ANIMATION_FRAME_COUNT; i++) {
+ state.bird_texture[i] = (Rectangle) { 2, 128 * (i == 3 ? 1 : i) + 9, -196, 110 };
+ }
+ state.bird_animation.frame = 0;
+ state.bird_animation.clock = 0;
+ state.pipe_texture_bottom = (NPatchInfo) {
+ (Rectangle) { 202, 2, 104, 124 }
+ ,0, 47, 0, 0
+ ,NPATCH_NINE_PATCH
+ };
+ state.pipe_texture_top = (NPatchInfo) {
+ (Rectangle) { 315, 0, 104, 124 }
+ ,0, 0, 0, 47
+ ,NPATCH_NINE_PATCH
+ };
+ srand(666);
+ state.background.dune = LoadTexture(PATH "dune.png");
+ state.background.cloud[0] = LoadTexture(PATH "cloud_1.png");
+ state.background.cloud[1] = LoadTexture(PATH "cloud_2.png");
+
+ GenTextureMipmaps(&state.background.dune);
+ GenTextureMipmaps(&state.background.cloud[0]);
+ GenTextureMipmaps(&state.background.cloud[1]);
+ SetTextureFilter(state.background.dune, TEXTURE_FILTER_BILINEAR);
+ SetTextureWrap(state.background.dune, TEXTURE_WRAP_REPEAT);
+ SetTextureFilter(state.background.cloud[0], TEXTURE_FILTER_BILINEAR);
+ SetTextureFilter(state.background.cloud[1], TEXTURE_FILTER_BILINEAR);
+ init_game_state(&state);
+
+ enum game_screen screen = TAP_SPACE_SCREEN;
+ while (!WindowShouldClose()) {
+ switch (screen) {
+ case PLAY_SCREEN:
+ screen = play_game(&state);
+ break;
+ case DEATH_SCREEN:
+ screen = death_screen(&state);
+ break;
+ case TEST_SCREEN:
+ screen = test_screen(&state);
+ break;
+ case TAP_SPACE_SCREEN:
+ screen = tap_space_screen(&state);
+ break;
+ default:
+ BeginDrawing();
+ ClearBackground(RED);
+ EndDrawing();
+ break;
+ }
+
+ }
+ CloseWindow();
+ UnloadTexture(state.texture);
+ UnloadTexture(state.background.dune);
+ UnloadTexture(state.background.cloud[0]);
+ UnloadTexture(state.background.cloud[1]);
+ return 0;
+}