Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
game.cpp 6.71 KiB
#include "game.h"

#include "input/sdl_input.h"
#include "renderer/sdl_renderer.h"
#include "single_shot.h"
#include "multi_shot.h"
#include "state/level.h"

#include <variant>
#include <chrono>
#include <cmath>

#include <SDL2/SDL.h>

game::game(uint32_t fps):current_state(),
                         input_processor(nullptr),
                         renderer(nullptr),
                         shoot_control(nullptr),
                         player_position_delta(0),
                         player_angle_delta(0.0),
                         player_force_delta(0.0),
                         delta_time(1.0 / static_cast<double>(fps)),
                         is_running(false),
                         shoot(false),
                         debug(false) {
    input_processor = new sdl_input();
    renderer = new sdl_renderer();
    shoot_control = new single_shot();

    load_levels();

    level first_level = std::move(level::get("start"));
    current_state.enemies = first_level.enemies;
    current_state.next_level_id = first_level.next_level_id;
}

void game::load_levels() {
    level::create().from_file("levels/01.txt");
    level::create().with_enemy_at({320, 120}).with_enemy_at({160, 240}).with_enemy_at({480, 360}).save_as("level_01");
}

game::~game() {
    if (!input_processor)
        delete input_processor;
    if (!renderer) {
        delete renderer;
    }
    if (!shoot_control) {
        delete shoot_control;
    }
}

void game::run_loop() {
    is_running = true;
    while (is_running) {
        auto start = std::chrono::system_clock::now();
        process_events();
        update_state();
        render_current_state();
        if (try_to_switch_level())
            break;
        auto end = std::chrono::system_clock::now();
        std::chrono::duration<double> diff = end - start;
        SDL_Delay(static_cast<uint32_t>((delta_time - diff.count())*1000));
    }
}

bool game::try_to_switch_level() {
    if (current_state.enemies.size() == 0 && current_state.projectiles.size() == 0) {
        if (current_state.next_level_id != "") {
            level next_level = std::move(level::get(current_state.next_level_id));
            current_state.enemies = next_level.enemies;
            current_state.next_level_id = next_level.next_level_id;
        } else {
            renderer->show_win_dialog();
            return true;
        }
    }
    return false;
}

void game::process_events() {
    input_processor->poll_events();
    while(input_processor->has_events()) {
        event& e = input_processor->get_next_event();
        // FIXME: use visit
        process_event(e);
    }
}

void game::process_event(const event& e) {
    do_event<quit_game>(e, [&](auto&) { is_running = false; });
    do_event<change<player_angle>>(e, [&](const auto& cpa){
        player_angle_delta += cpa.data.value;
        player_angle_delta = min(player_angle::DEFAULT,
                                 max(-player_angle::DEFAULT,
                                     player_angle_delta));
    });
    do_event<change<player_force>>(e, [&](const auto& cpf){
        player_force_delta += cpf.data.value;
        player_force_delta = min(player_force::DEFAULT,
                                 max(-player_force::DEFAULT,
                                     player_force_delta));
    });
    do_event<change<move_vector>>(e, [&](const auto& cmv) {
        player_position_delta += cmv.data.value; // FIXME: Use ranged value here;
        player_position_delta = min(move_vector::DEFAULT,
                                    max(-move_vector::DEFAULT,
                                        player_position_delta));
    });
    do_event<push_state>(e, [&](auto&) {
        if (debug)
            current_state.push();
    });
    do_event<pop_state>(e, [&](auto&) {
        if (state::can_pop())
            current_state = std::move(state::pop());
    });
    do_event<shoot_event>(e, [&](auto&) {shoot = true;});
    do_event<change<shooting_controller>>(e, [&](const auto& sc) {
        if (shoot_control->can_switch()) {
            delete shoot_control;
            switch(sc.data.mode) {
                case shooting_mode::SINGLE:
                    shoot_control = new single_shot();
                break;
                case shooting_mode::MULTIPLE:
                    shoot_control = new multi_shot();
                break;
            }
        }
    });
    do_event<switch_debug>(e, [&](auto&) {debug = !debug;});
}

template<typename T>
void game::do_event(const event& e, const std::function<void(const T&)>& trigger) {
    auto ptr = std::get_if<T>(&e);
    if (ptr) {
        trigger(*ptr);
    }
}

void game::update_state() {
    // Update shooter
    current_state.player.position.set(current_state.player.position.get() + player_position_delta);
    current_state.player.angle.set(current_state.player.angle.get() + player_angle_delta);
    current_state.player.force.set(current_state.player.force.get() + player_force_delta);
    
    
    // Check controler if can shoot
    if (shoot_control->shoot(shoot)) {
        // If no projectile is present preserve current state
        if (current_state.projectiles.size() == 0) {
            current_state.push();
        }
        // Create new projectile
        vector2 start_pos{13, current_state.player.position.get()};
        current_state.projectiles.emplace_back(start_pos, 15, current_state.player.force.get(), current_state.player.angle.get());
    }
    shoot_control->update_timer(delta_time);
    shoot = false;

    move_and_test_collisions();
}

void game::move_and_test_collisions() { // TODO: Replace with real physics
    // Update projectiles
    for (int i = static_cast<int>(current_state.projectiles.size()) - 1; i >= 0 ; --i) {
        auto& proj = current_state.projectiles[static_cast<size_t>(i)];
        // Is projectile out of window bounds
        if (proj.position.x > 640 || proj.position.y > 480) {
            current_state.projectiles.erase(current_state.projectiles.cbegin() + i);
        }
        // Did hit somebody?
        for (int j = static_cast<int>(current_state.enemies.size()) - 1; j >= 0 ; --j) {
            auto& en = current_state.enemies[static_cast<size_t>(j)];
            if (proj.did_collide(en)) {
                current_state.enemies.erase(current_state.enemies.cbegin() + j);
                current_state.score += 100;
            }
        }
        // Update position
        vector2 speed{static_cast<int>(cos(proj.angle) * 12),
                      -static_cast<int>(ceil(sin(proj.angle) * 12))};
        proj.position.x += speed.x;
        proj.position.y += speed.y;
        proj.angle -= M_PI/(75*proj.speed);
        if (proj.angle < -5*M_PI/12) {
            proj.angle = -5*M_PI/12;
        }
    }
}

void game::render_current_state() {
    renderer->render_state(current_state, debug);
}