#include "game.h" #include "sys.h" #include "shader.h" #include "texture.h" #include "sprite.h" #include #include "gl_utils.h" #include #include #include static v2 initial_ball_velocity = {100.0f, -350.0f}; static void init_gl(struct game game) { i32 flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags); if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) { info("debug context initialized successfully"); glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(gl_debug_output, 0); glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, 0, GL_TRUE); } glViewport(0, 0, game.width, game.height); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glClearColor(0.16f, 0.16f, 0.16f, 1.0f); } static void reset_powerups(struct game *game) { for (i32 i = 0; i < max_powerups; i++) { game->powerups[i].type = powerup_none; game->powerups[i].destroyed = 1; } } struct game init_game(i32 width, i32 height) { struct game game = {0}; game.state = game_menu; game.running = 1; game.width = width; game.height = height; game.input = init_input(); init_gl(game); game.bindir = get_bin_dir(); char dir[512] = {0}; snprintf(dir, 512, "%s/%s", game.bindir, "shaders"); load_shader(dir, "shader.vert", "shader.frag", 0, "shader"); load_shader(dir, "particle.vert", "particle.frag", 0, "particle"); load_shader(dir, "post.vert", "post.frag", 0, "post"); load_shader(dir, "text.vert", "text.frag", 0, "text"); mat projection = ortho(0.0f, game.width, game.height, 0.0f, -1.0f, 1.0f); u32 shader = get_shader("shader"); glUseProgram(shader); uniform_mat(shader, "projection", projection); glUseProgram(0); shader = get_shader("particle"); glUseProgram(shader); uniform_mat(shader, "projection", projection); glUseProgram(0); snprintf(dir, 512, "%s/%s", game.bindir, "textures"); load_texture(dir, "gentleman.jpg", "gentleman", 0); load_texture(dir, "block.png", "block", 0); load_texture(dir, "solid.png", "solid", 0); load_texture(dir, "paddle.png", "paddle", 1); load_texture(dir, "ball.png", "ball", 1); load_texture(dir, "particle.png", "particle", 1); load_texture(dir, "powerup_chaos.png", "powerup_chaos", 1); load_texture(dir, "powerup_confuse.png", "powerup_confuse", 1); load_texture(dir, "powerup_increase.png", "powerup_increase", 1); load_texture(dir, "powerup_passthrough.png", "powerup_passthrough", 1); load_texture(dir, "powerup_speed.png", "powerup_speed", 1); load_texture(dir, "powerup_sticky.png", "powerup_sticky", 1); game.renderer = init_renderer(get_shader("shader")); snprintf(dir, 512, "%s/%s", game.bindir, "levels"); game.levels[0] = load_level(dir, "first.txt", game.width, game.height / 2.0f); game.levels[1] = load_level(dir, "second.txt", game.width, game.height / 2.0f); game.levels[2] = load_level(dir, "third.txt", game.width, game.height / 2.0f); snprintf(dir, 512, "%s/%s", game.bindir, "audio"); load_wav(dir, "background.wav", "back"); load_wav(dir, "solid.wav", "solid"); load_wav(dir, "tile.wav", "tile"); load_wav(dir, "powerup.wav", "powerup"); play_audio("back", 1); game.text_renderer = init_text_renderer(game.width, game.height); snprintf(dir, 512, "%s/%s", game.bindir, "fonts"); load_font(&game.text_renderer, dir, "ibm.ttf", 32); game.player = (struct object)default_object; v2 size = get_texture_size("paddle"); game.player.size = scale_v2(size, 0.2f); game.player.pos = (v2){ game.width / 2.0f - game.player.size.x / 2.0f, game.height - game.player.size.y }; game.player.vel = (v2){200.0f, 0.0f}; game.player.texture = get_texture_id("paddle"); game.lives = 3; game.ball.o = (struct object)default_object; game.ball.radius = 12.5f; game.ball.o.size = v2a(2.0f * game.ball.radius); game.ball.o.pos = (v2){ game.player.pos.x + game.player.size.x / 2.0f - game.ball.o.size.x / 2.0f, game.player.pos.y - game.ball.o.size.y }; game.ball.o.vel = initial_ball_velocity; game.ball.o.texture = get_texture_id("ball"); game.ball.stuck = 1; game.generator = init_generator(2, get_shader("particle"), get_texture_id("particle")); game.post_processor = init_post_processor(game.width, game.height, get_shader("post")); reset_powerups(&game); return game; } static void reset_level(struct game *game) { for (i32 i = 0; i < game->levels[game->level].count; i++) game->levels[game->level].tiles[i].destroyed = 0; game->lives = 3; } static void reset_player(struct game *game) { game->player.pos = (v2){ game->width / 2.0f - game->player.size.x / 2.0f, game->height - game->player.size.y }; game->ball.o.pos = (v2){ game->player.pos.x + game->player.size.x / 2.0f - game->ball.o.size.x / 2.0f, game->player.pos.y - game->ball.o.size.y }; game->ball.o.vel = initial_ball_velocity; game->ball.stuck = 1; } void process_input(struct game *game) { if (key_first_press(game->input.escape)) game->running = 0; if (key_first_press(game->input.start)) { if (game->state == game_menu) game->state = game_active; else if (game->state == game_win) { game->post_processor.chaos = 0; game->state = game_menu; } else if (game->state == game_active) { game->state = game_menu; } } if (game->state != game_menu) return; if (key_first_press(game->input.up)) { game->level = (game->level + 1) % max_levels; if (!game->levels[game->level].tiles) game->level--; reset_level(game); reset_player(game); } if (key_first_press(game->input.down)) { i32 new = game->level - 1; if (new == -1) new = max_levels - 1; if (game->levels[new].tiles) { game->level = new; reset_level(game); reset_player(game); } } } static i32 should_spawn(u32 chance) { u32 random = rand() % chance; return random == 0; } static i32 can_spawn_powerup(struct game *game) { for (i32 i = 0; i < max_powerups; i++) if (game->powerups[i].type == powerup_none) return 1; return 0; } static void spawn_powerups(struct game *game, v2 pos) { if (!can_spawn_powerup(game)) return; struct powerup *powerup; for (i32 i = 0; i < max_powerups; i++) { powerup = game->powerups + i; if (powerup->type == powerup_none) { break; } } if (should_spawn(75)) { *powerup = spawn_powerup(pos, (v3){0.5f, 0.5f, 1.0f}, powerup_speed, 0.0f, get_texture_id("powerup_speed")); return; } if (should_spawn(75)) { *powerup = spawn_powerup(pos, (v3){1.0f, 0.5f, 1.0f}, powerup_sticky, 20.0f, get_texture_id("powerup_sticky")); return; } if (should_spawn(75)) { *powerup = spawn_powerup(pos, (v3){0.5f, 1.0f, 0.5f}, powerup_pass, 10.0f, get_texture_id("powerup_pass")); return; } if (should_spawn(75)) { *powerup = spawn_powerup(pos, (v3){1.0f, 0.6f, 0.4f}, powerup_increase, 0.0f, get_texture_id("powerup_increase")); return; } if (should_spawn(15)) { *powerup = spawn_powerup(pos, (v3){1.0f, 0.3f, 0.3f}, powerup_confuse, 15.0f, get_texture_id("powerup_confuse")); return; } if (should_spawn(15)) { *powerup = spawn_powerup(pos, (v3){0.9f, 0.25f, 0.25f}, powerup_chaos, 15.0f, get_texture_id("powerup_chaos")); return; } } static void activate_powerup(struct game *game, struct powerup powerup) { switch (powerup.type) { case powerup_speed: game->ball.o.vel = scale_v2(game->ball.o.vel, 1.2f); break; case powerup_sticky: game->ball.sticky = 1; game->player.color = (v3){1.0f, 0.5f, 1.0f}; break; case powerup_pass: game->ball.pass = 1; game->ball.o.color = (v3){1.0f, 0.5f, 0.5f}; break; case powerup_increase: game->player.size.x += 50.0f; break; case powerup_confuse: if (!game->post_processor.confuse) game->post_processor.confuse = 1; break; case powerup_chaos: if (!game->post_processor.chaos) game->post_processor.chaos = 1; break; default: break; } } static void process_tile_collision(struct object *tile, struct game *game) { if (tile->destroyed) return; struct collision collision = check_ball_collision(game->ball, *tile); if (!collision.collide) return; if (!tile->solid) { tile->destroyed = 1; v2 pos = sub_v2(add_v2(tile->pos, scale_v2(tile->size, 0.5f)), scale_v2(default_powerup_size, 0.5f)); spawn_powerups(game, pos); } else { game->shake_time = 0.05f; game->post_processor.shake = 1; } direction_enum dir = collision.dir; v2 diff = collision.diff; if (game->ball.pass && !tile->solid) return; if (dir == direction_left || dir == direction_right) { game->ball.o.vel.x = -game->ball.o.vel.x; f32 offset = game->ball.radius - abs(diff.x); game->ball.o.pos.x += (dir == direction_left) ? offset : -offset; } else { game->ball.o.vel.y = -game->ball.o.vel.y; f32 offset = game->ball.radius - abs(diff.y); game->ball.o.pos.y += (dir == direction_up) ? -offset : offset; } if (tile->solid) play_audio("solid", 0); else play_audio("tile", 0); } static void process_powerup_collisions(struct game *game, struct powerup *powerup) { if (powerup->destroyed) return; if (powerup->pos.y >= game->height) { powerup->destroyed = 1; powerup->active = 0; powerup->type = powerup_none; } if (check_powerup_collision(*powerup, game->player)) { activate_powerup(game, *powerup); powerup->destroyed = 1; powerup->active = 1; play_audio("powerup", 0); } } static void check_collisions(struct game *game) { for (i32 i = 0; i < game->levels[game->level].count; i++) { struct object *tile = game->levels[game->level].tiles + i; process_tile_collision(tile, game); } struct collision collision = check_ball_collision(game->ball, game->player); if (!game->ball.stuck && collision.collide) { f32 center = game->player.pos.x + game->player.size.x / 2.0f; f32 distance = game->ball.o.pos.x + game->ball.radius - center; f32 percentage = distance / game->player.size.x / 2.0f; f32 strength = 10.0f; v2 old_vel = game->ball.o.vel; game->ball.o.vel.x = initial_ball_velocity.x * percentage * strength; game->ball.o.vel.y = -1.0f * abs(game->ball.o.vel.y); game->ball.o.vel = scale_v2(norm_v2(game->ball.o.vel), length_v2(old_vel)); game->ball.stuck = game->ball.sticky; play_audio("tile", 0); } for (i32 i = 0; i < max_powerups; i++) { struct powerup *powerup = game->powerups + i; process_powerup_collisions(game, powerup); } } static void update_powerups(struct game *game, f32 dt) { for (i32 i = 0; i < max_powerups; i++) { struct powerup *powerup = game->powerups + i; if (!powerup->destroyed) powerup->pos = add_v2(powerup->pos, scale_v2(powerup->vel, dt)); if (powerup->active) { powerup->duration -= dt; if (powerup->duration <= 0.0f) { powerup->duration = 0.0f; powerup->active = 0; switch (powerup->type) { case powerup_sticky: game->ball.sticky = 0; game->player.color = v3a(1.0f); break; case powerup_pass: game->ball.pass = 0; game->ball.o.color = v3a(1.0f); break; case powerup_confuse: game->post_processor.confuse = 0; break; case powerup_chaos: game->post_processor.chaos = 0; break; default: break; } powerup->type = powerup_none; } } } } void update_game(struct game *game, f32 dt) { if ((game->state != game_active) && (game->state != game_win)) return; f32 vel = game->player.vel.x * dt; if (game->state == game_active) { if (key_is_pressed(game->input.right)) { if (game->player.pos.x + game->player.size.x <= game->width) { game->player.pos.x += vel; if (game->ball.stuck) game->ball.o.pos.x += vel; } } if (key_is_pressed(game->input.left)) { if (game->player.pos.x >= 0.0f) { game->player.pos.x -= vel; if (game->ball.stuck) game->ball.o.pos.x -= vel; } } if (key_first_press(game->input.space)) game->ball.stuck = 0; } move_ball(&game->ball, dt, game->width); check_collisions(game); if (game->ball.o.pos.y >= game->height) { game->lives--; if (game->lives == 0) { reset_level(game); game->state = game_menu; } reset_player(game); } update_particles(&game->generator, game->ball.o, dt, v2a(game->ball.radius / 2.0f)); game->time += dt; if (game->shake_time > 0.0f) { game->shake_time -= dt; if (game->shake_time < 0.0f) game->post_processor.shake = 0; } update_powerups(game, dt); if (game->state == game_active && level_is_complete(game->levels[game->level])) { reset_level(game); reset_player(game); game->post_processor.chaos = 1; game->state = game_win; } } static void render_powerups(struct game game) { for (i32 i = 0; i < max_powerups; i++) if (!game.powerups[i].destroyed) render_powerup(game.renderer, game.powerups[i]); } void render_game(struct game game) { if ((game.state == game_active) || (game.state == game_menu) || (game.state == game_win)) { begin_render(game.post_processor); render_level(game.renderer, game.levels[game.level]); render_powerups(game); render_object(game.renderer, game.player); render_particles(game.generator); render_object(game.renderer, game.ball.o); end_render(game.post_processor); render_post(game.post_processor, game.time); char lives_str[512] = {0}; snprintf(lives_str, 512, "lives: %d", game.lives); render_text(game.text_renderer, lives_str, v2a(5.0f), 1.0f, v3a(1.0f)); } if (game.state == game_menu) { render_text_centered(game.text_renderer, "Press Enter to start", (v2){game.width * 0.5f, game.height * 0.5f}, 1.0f, v3a(1.0f)); render_text_centered(game.text_renderer, "Press UP and DOWN to select the level", (v2){game.width * 0.5f, game.height * 0.55f}, 0.75f, v3a(1.0f)); } if (game.state == game_win) { render_text_centered(game.text_renderer, "You WON!!!", (v2){game.width * 0.5f, game.height * 0.5f}, 1.0f, v3a(1.0f)); render_text_centered(game.text_renderer, "Press Enter to retry or ESC to quit", (v2){game.width * 0.5f, game.height * 0.55f}, 0.75f, v3a(1.0f)); } }