summaryrefslogtreecommitdiff
path: root/in_practice/breakout/game.c
diff options
context:
space:
mode:
Diffstat (limited to 'in_practice/breakout/game.c')
-rw-r--r--in_practice/breakout/game.c397
1 files changed, 371 insertions, 26 deletions
diff --git a/in_practice/breakout/game.c b/in_practice/breakout/game.c
index cf3c0a2..3a84f01 100644
--- a/in_practice/breakout/game.c
+++ b/in_practice/breakout/game.c
@@ -9,8 +9,11 @@
#include <stdio.h>
#include <math.h>
+#include <stdlib.h>
-static void init_gl(game_t game)
+static v2 initial_ball_velocity = {100.0f, -350.0f};
+
+static void init_gl(struct game game)
{
i32 flags;
glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
@@ -27,56 +30,360 @@ static void init_gl(game_t game)
glClearColor(0.16f, 0.16f, 0.16f, 1.0f);
}
-game_t init_game(i32 width, i32 height)
+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)
{
- game_t game = {0};
- game.state = game_active;
+ 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");
- u32 shader = load_shader(dir, "shader.vert", "shader.frag", 0, "shader");
- glUseProgram(shader);
+ 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);
- game.renderer = init_renderer(shader);
+ 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.level = load_level(0, "first.txt", game.width, game.height / 2.0f);
- game.player = (object_t)default_object;
+ 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.4f);
- game.player.pos = (v2){width / 2.0f - game.player.size.x / 2.0f, height - game.player.size.y};
- game.player.vel = v2a(500.0f);
+ 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.ball.o = (object_t)default_object;
+ 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 = (v2){100.0f, -350.0f};
+ 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;
}
-void process_input(game_t *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(game_t *game, f32 dt)
+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)) {
@@ -97,21 +404,59 @@ void update_game(game_t *game, f32 dt)
game->ball.stuck = 0;
}
move_ball(&game->ball, dt, game->width);
- /* collisions */
- for (i32 i = 0; i < game->level.count; i++) {
- object_t *tile = game->level.tiles + i;
- if (!tile->destroyed && check_collision(game->ball.o, *tile) && (!tile->solid)) {
- tile->destroyed = 1;
+ 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;
}
}
-void render_game(game_t game)
+static void render_powerups(struct game game)
{
- glClear(GL_COLOR_BUFFER_BIT);
- if (game.state == game_active) {
- render_level(game.renderer, game.level);
+ 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));
}
}