From 1241b13feaed34a0e2a063fc0cc9e455e9599539 Mon Sep 17 00:00:00 2001 From: Michael Gerhold Date: Tue, 30 Jul 2024 18:37:00 +0200 Subject: [PATCH] implemented stuff --- src/obpf/CMakeLists.txt | 1 + src/obpf/include/obpf/simulator.h | 3 + src/obpf/include/obpf/stats.hpp | 17 +++++ src/obpf/simulator.cpp | 12 ++++ src/simulator/include/simulator/tetrion.hpp | 23 ++++++- src/simulator/tetrion.cpp | 73 +++++++++++++++------ 6 files changed, 105 insertions(+), 24 deletions(-) create mode 100644 src/obpf/include/obpf/stats.hpp diff --git a/src/obpf/CMakeLists.txt b/src/obpf/CMakeLists.txt index a6dbc3a..f51993f 100644 --- a/src/obpf/CMakeLists.txt +++ b/src/obpf/CMakeLists.txt @@ -15,6 +15,7 @@ add_library(obpf include/obpf/lobby.h lobby.cpp include/obpf/rotation.h + include/obpf/stats.hpp ) target_compile_definitions(obpf PRIVATE "simulator_EXPORTS") diff --git a/src/obpf/include/obpf/simulator.h b/src/obpf/include/obpf/simulator.h index 6fbb57f..d9c806e 100644 --- a/src/obpf/include/obpf/simulator.h +++ b/src/obpf/include/obpf/simulator.h @@ -13,6 +13,7 @@ extern "C" { #include "tetromino.h" #include "tetromino_type.h" #include "vec2.h" +#include "stats.hpp" typedef struct { uint8_t count; @@ -48,6 +49,8 @@ extern "C" { ); OBPF_EXPORT struct ObpfTetrion* obpf_create_tetrion(uint64_t seed); + OBPF_EXPORT ObpfStats obpf_tetrion_get_stats(struct ObpfTetrion const* tetrion); + OBPF_EXPORT bool obpf_tetrion_is_game_over(struct ObpfTetrion const* tetrion); OBPF_EXPORT ObpfLineClearDelayState obpf_tetrion_get_line_clear_delay_state(struct ObpfTetrion const* tetrion); OBPF_EXPORT bool obpf_tetrion_try_get_active_tetromino( struct ObpfTetrion const* tetrion, diff --git a/src/obpf/include/obpf/stats.hpp b/src/obpf/include/obpf/stats.hpp new file mode 100644 index 0000000..842a1f4 --- /dev/null +++ b/src/obpf/include/obpf/stats.hpp @@ -0,0 +1,17 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct { + uint64_t score; + uint32_t lines_cleared; + uint32_t level; +} ObpfStats; + +#ifdef __cplusplus +} +#endif diff --git a/src/obpf/simulator.cpp b/src/obpf/simulator.cpp index 731c5ff..8c238a4 100644 --- a/src/obpf/simulator.cpp +++ b/src/obpf/simulator.cpp @@ -144,3 +144,15 @@ ObpfKeyState obpf_key_state_create( .get_bitmask(), }; } + +ObpfStats obpf_tetrion_get_stats(ObpfTetrion const* tetrion) { + return ObpfStats{ + .score = tetrion->score(), + .lines_cleared = tetrion->num_lines_cleared(), + .level = tetrion->level(), + }; +} + +bool obpf_tetrion_is_game_over(ObpfTetrion const* const tetrion) { + return tetrion->is_game_over(); +} diff --git a/src/simulator/include/simulator/tetrion.hpp b/src/simulator/include/simulator/tetrion.hpp index 6192c1b..0851d89 100644 --- a/src/simulator/include/simulator/tetrion.hpp +++ b/src/simulator/include/simulator/tetrion.hpp @@ -37,9 +37,11 @@ struct ObpfTetrion final { LockDelayState m_lock_delay_state; EntryDelay m_entry_delay; LineClearDelay m_line_clear_delay; - u32 m_lines_cleared = 0; + u32 m_num_lines_cleared = 0; + u64 m_score = 0; u64 m_next_gravity_frame = gravity_delay_by_level(0); // todo: offset by starting frame given by the server bool m_is_soft_dropping = false; + bool m_is_game_over = false; static constexpr u64 gravity_delay_by_level(u32 const level) { constexpr auto delays = std::array{ @@ -81,8 +83,23 @@ struct ObpfTetrion final { return m_next_frame; } + [[nodiscard]] u32 level() const; + + [[nodiscard]] u64 score() const { + return m_score; + } + + [[nodiscard]] u32 num_lines_cleared() const { + return m_num_lines_cleared; + } + + [[nodiscard]] bool is_game_over() const { + return m_is_game_over; + } + private: void freeze_and_destroy_active_tetromino(); + [[nodiscard]] bool is_tetromino_completely_invisible(Tetromino const& tetromino) const; [[nodiscard]] bool is_tetromino_completely_visible(Tetromino const& tetromino) const; [[nodiscard]] bool is_tetromino_position_valid(Tetromino const& tetromino) const; [[nodiscard]] bool is_active_tetromino_position_valid() const; @@ -96,11 +113,11 @@ struct ObpfTetrion final { void rotate(RotationDirection direction); void rotate_clockwise(); void rotate_counter_clockwise(); - void drop(); + void hard_drop(); void hold(); void determine_lines_to_clear(); + [[nodiscard]] u64 score_for_num_lines_cleared(std::size_t num_lines_cleared) const; void clear_lines(c2k::StaticVector lines); - [[nodiscard]] u32 level() const; void refresh_ghost_tetromino(); [[nodiscard]] static std::array create_two_bags(c2k::Random& random); diff --git a/src/simulator/tetrion.cpp b/src/simulator/tetrion.cpp index 582b351..9108ecc 100644 --- a/src/simulator/tetrion.cpp +++ b/src/simulator/tetrion.cpp @@ -31,6 +31,11 @@ } void ObpfTetrion::simulate_next_frame(KeyState const key_state) { + if (is_game_over()) { + ++m_next_frame; + return; + } + auto const line_clear_delay_poll_result = m_line_clear_delay.poll(); if (std::get_if(&line_clear_delay_poll_result) != nullptr) { @@ -46,8 +51,12 @@ void ObpfTetrion::simulate_next_frame(KeyState const key_state) { switch (m_entry_delay.poll()) { case EntryDelayPollResult::ShouldSpawn: - spawn_next_tetromino(); + spawn_next_tetromino(); // this is where we could possibly have lost the game m_lock_delay_state.clear(); + if (is_game_over()) { + ++m_next_frame; + return; + } break; case EntryDelayPollResult::ShouldNotSpawn: break; @@ -69,8 +78,10 @@ void ObpfTetrion::simulate_next_frame(KeyState const key_state) { switch (m_lock_delay_state.poll()) { case LockDelayPollResult::ShouldLock: - freeze_and_destroy_active_tetromino(); + freeze_and_destroy_active_tetromino(); // we could lose the game here due to "Lock Out" m_is_hold_possible = false; + // Even if we lost the game, it's not an error to start the entry delay -- it will simply get + // ignored at the start of the next frame. m_entry_delay.start(); break; case LockDelayPollResult::ShouldNotLock: @@ -120,29 +131,35 @@ void ObpfTetrion::simulate_next_frame(KeyState const key_state) { return m_hold_piece; } +[[nodiscard]] u32 ObpfTetrion::level() const { + return m_num_lines_cleared / 10; +} + void ObpfTetrion::freeze_and_destroy_active_tetromino() { if (not active_tetromino().has_value()) { return; } auto const mino_positions = get_mino_positions(active_tetromino().value()); + if (is_tetromino_completely_invisible(active_tetromino().value())) { + m_is_game_over = true; + } for (auto const position : mino_positions) { m_matrix[position] = active_tetromino().value().type; } m_active_tetromino = std::nullopt; } -[[nodiscard]] bool ObpfTetrion::is_tetromino_completely_visible(Tetromino const& tetromino) const { - if (not is_tetromino_position_valid(tetromino)) { - return false; - } - - for (auto const& position : get_mino_positions(tetromino)) { - if (position.y < gsl::narrow(Matrix::num_invisible_lines)) { - return false; - } - } +bool ObpfTetrion::is_tetromino_completely_invisible(Tetromino const& tetromino) const { + return std::ranges::all_of(get_mino_positions(tetromino), [](auto const position) { + return position.y < gsl::narrow(Matrix::num_invisible_lines); + }); +} - return true; +[[nodiscard]] bool ObpfTetrion::is_tetromino_completely_visible(Tetromino const& tetromino) const { + return is_tetromino_position_valid(tetromino) + and std::ranges::all_of(get_mino_positions(tetromino), [](auto const position) { + return position.y >= gsl::narrow(Matrix::num_invisible_lines); + }); } [[nodiscard]] bool ObpfTetrion::is_tetromino_position_valid(Tetromino const& tetromino) const { @@ -184,7 +201,11 @@ void ObpfTetrion::spawn_next_tetromino() { m_is_hold_possible = true; } - // todo: check game over state here + if (not is_active_tetromino_position_valid()) { + m_is_game_over = true; + m_is_soft_dropping = false; + return; + } // clang-format off for ( @@ -236,7 +257,7 @@ void ObpfTetrion::handle_key_press(Key const key) { m_next_gravity_frame = m_next_frame; break; case Key::Drop: - drop(); + hard_drop(); break; case Key::RotateClockwise: rotate_clockwise(); @@ -303,6 +324,9 @@ void ObpfTetrion::move_down(DownMovementType const movement_type) { ++m_active_tetromino.value().position.y; if (is_active_tetromino_position_valid()) { m_lock_delay_state.on_tetromino_moved(LockDelayMovementType::MovedDown); + if (movement_type == DownMovementType::SoftDrop) { + ++m_score; + } } else { --m_active_tetromino.value().position.y; switch (movement_type) { @@ -346,14 +370,19 @@ void ObpfTetrion::rotate_counter_clockwise() { rotate(RotationDirection::CounterClockwise); } -void ObpfTetrion::drop() { +void ObpfTetrion::hard_drop() { if (not active_tetromino().has_value()) { return; } + auto num_lines_dropped = std::size_t{ 0 }; do { ++m_active_tetromino.value().position.y; + ++num_lines_dropped; } while (is_active_tetromino_position_valid()); --m_active_tetromino.value().position.y; + --num_lines_dropped; + static constexpr auto score_per_line = u64{ 2 }; + m_score += num_lines_dropped * score_per_line; m_lock_delay_state.on_hard_drop_lock(); } @@ -387,7 +416,13 @@ void ObpfTetrion::determine_lines_to_clear() { } } +[[nodiscard]] u64 ObpfTetrion::score_for_num_lines_cleared(std::size_t const num_lines_cleared) const { + static constexpr auto score_multipliers = std::array{ 0, 100, 300, 500, 800 }; + return score_multipliers.at(num_lines_cleared) * (level() + 1); +} + void ObpfTetrion::clear_lines(c2k::StaticVector const lines) { + m_score += score_for_num_lines_cleared(lines.size()); auto num_lines_cleared = decltype(lines.front()){ 0 }; for (auto const line_to_clear : lines) { for (auto i = decltype(line_to_clear){ 0 }; i < line_to_clear; ++i) { @@ -397,11 +432,7 @@ void ObpfTetrion::clear_lines(c2k::StaticVector const lines) { ++num_lines_cleared; m_matrix.fill(num_lines_cleared, TetrominoType::Empty); } - m_lines_cleared += gsl::narrow(lines.size()); -} - -[[nodiscard]] u32 ObpfTetrion::level() const { - return m_lines_cleared / 10; + m_num_lines_cleared += gsl::narrow(lines.size()); } void ObpfTetrion::refresh_ghost_tetromino() {