Skip to content

Commit e22685d

Browse files
committed
add simulation for recording files
add tests for the simulation
1 parent cb6f899 commit e22685d

File tree

10 files changed

+392
-51
lines changed

10 files changed

+392
-51
lines changed

src/game/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ graphics_src_files += files(
1111
'grid.hpp',
1212
'rotation.cpp',
1313
'rotation.hpp',
14+
'simulation.cpp',
15+
'simulation.hpp',
1416
'tetrion.cpp',
1517
'tetrion.hpp',
1618
'tetromino.cpp',

src/game/simulation.cpp

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
2+
#include <core/helper/magic_enum_wrapper.hpp>
3+
#include <core/helper/utils.hpp>
4+
5+
#include "core/helper/expected.hpp"
6+
#include "input/replay_input.hpp"
7+
#include "simulation.hpp"
8+
#include "ui/layout.hpp"
9+
10+
11+
namespace {
12+
13+
const auto dummy_layout = ui::AbsolutLayout{ 0, 0, 0, 0 };
14+
15+
}
16+
17+
18+
Simulation::Simulation(
19+
ServiceProvider* const service_provider,
20+
const std::shared_ptr<input::ReplayGameInput>& input,
21+
const tetrion::StartingParameters& starting_parameters
22+
)
23+
: ui::Widget{ dummy_layout, ui::WidgetType::Component, false },
24+
m_input{ input } {
25+
26+
27+
spdlog::info("[simulation] starting level for tetrion {}", starting_parameters.starting_level);
28+
29+
m_tetrion = std::make_unique<Tetrion>(
30+
starting_parameters.tetrion_index, starting_parameters.seed, starting_parameters.starting_level,
31+
service_provider, starting_parameters.recording_writer, dummy_layout, false
32+
);
33+
34+
m_tetrion->spawn_next_tetromino(0);
35+
36+
m_input->set_target_tetrion(m_tetrion.get());
37+
if (starting_parameters.recording_writer.has_value()) {
38+
const auto recording_writer = starting_parameters.recording_writer.value();
39+
const auto tetrion_index = starting_parameters.tetrion_index;
40+
m_input->set_event_callback([recording_writer,
41+
tetrion_index](InputEvent event, SimulationStep simulation_step_index) {
42+
spdlog::debug("event: {} (step {})", magic_enum::enum_name(event), simulation_step_index);
43+
44+
//TODO(Totto): Remove all occurrences of std::ignore, where we shouldn't ignore this return value
45+
std::ignore = recording_writer->add_record(tetrion_index, simulation_step_index, event);
46+
});
47+
}
48+
}
49+
50+
helper::expected<Simulation, std::string>
51+
Simulation::get_replay_simulation(ServiceProvider* service_provider, std::filesystem::path& recording_path) {
52+
53+
//TODO(Totto): Support multiple tetrions to be in the recorded file and simulated
54+
55+
auto maybe_recording_reader = recorder::RecordingReader::from_path(recording_path);
56+
57+
if (not maybe_recording_reader.has_value()) {
58+
return helper::unexpected<std::string>{
59+
fmt::format("an error occurred while reading recording: {}", maybe_recording_reader.error())
60+
};
61+
}
62+
63+
const auto recording_reader =
64+
std::make_shared<recorder::RecordingReader>(std::move(maybe_recording_reader.value()));
65+
66+
67+
const auto tetrion_headers = recording_reader->tetrion_headers();
68+
69+
if (tetrion_headers.size() != 1) {
70+
return helper::unexpected<std::string>{
71+
fmt::format("Expected 1 recording in the recording file, but got : {}", tetrion_headers.size())
72+
};
73+
}
74+
75+
const auto tetrion_index = 0;
76+
77+
auto input = std::make_shared<input::ReplayGameInput>(recording_reader, nullptr);
78+
79+
const auto& header = tetrion_headers.at(tetrion_index);
80+
81+
const auto seed = header.seed;
82+
const auto starting_level = header.starting_level;
83+
84+
const tetrion::StartingParameters starting_parameters = { 0, seed, starting_level, tetrion_index, std::nullopt };
85+
86+
return Simulation{ service_provider, input, starting_parameters };
87+
}
88+
89+
90+
void Simulation::update() {
91+
if (is_game_finished()) {
92+
return;
93+
}
94+
95+
++m_simulation_step_index;
96+
m_input->update(m_simulation_step_index);
97+
m_tetrion->update_step(m_simulation_step_index);
98+
m_input->late_update(m_simulation_step_index);
99+
}
100+
101+
void Simulation::render(const ServiceProvider&) const {
102+
utils::unreachable();
103+
}
104+
105+
[[nodiscard]] helper::BoolWrapper<std::pair<ui::EventHandleType, ui::Widget*>>
106+
Simulation::handle_event(const std::shared_ptr<input::InputManager>& /*input_manager*/, const SDL_Event& /*event*/) {
107+
return false;
108+
}
109+
110+
[[nodiscard]] bool Simulation::is_game_finished() const {
111+
if (m_tetrion->is_game_over()) {
112+
return true;
113+
};
114+
115+
const auto input_as_replay = utils::is_child_class<input::ReplayGameInput>(m_input);
116+
if (input_as_replay.has_value()) {
117+
return input_as_replay.value()->is_end_of_recording();
118+
}
119+
120+
return false;
121+
}

src/game/simulation.hpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#pragma once
2+
3+
#include <recordings/utility/recording.hpp>
4+
5+
#include "core/helper/expected.hpp"
6+
#include "input/input_creator.hpp"
7+
#include "input/replay_input.hpp"
8+
#include "tetrion.hpp"
9+
#include "ui/widget.hpp"
10+
11+
struct Simulation : public ui::Widget {
12+
private:
13+
using TetrionHeaders = std::vector<recorder::TetrionHeader>;
14+
15+
SimulationStep m_simulation_step_index{ 0 };
16+
std::unique_ptr<Tetrion> m_tetrion;
17+
std::shared_ptr<input::ReplayGameInput> m_input;
18+
19+
public:
20+
explicit Simulation(
21+
ServiceProvider* service_provider,
22+
const std::shared_ptr<input::ReplayGameInput>& input,
23+
const tetrion::StartingParameters& starting_parameters
24+
);
25+
26+
27+
static helper::expected<Simulation, std::string>
28+
get_replay_simulation(ServiceProvider* service_provider, std::filesystem::path& recording_path);
29+
30+
void update() override;
31+
32+
void render(const ServiceProvider& service_provider) const override;
33+
[[nodiscard]] Widget::EventHandleResult
34+
35+
handle_event(const std::shared_ptr<input::InputManager>& input_manager, const SDL_Event& event) override;
36+
37+
[[nodiscard]] bool is_game_finished() const;
38+
};

src/input/guid.hpp

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -52,78 +52,82 @@ struct fmt::formatter<sdl::GUID> : formatter<std::string> {
5252

5353
namespace { //NOLINT(cert-dcl59-cpp,google-build-namespaces)
5454

55-
// decode a single_hex_number
56-
[[nodiscard]] constexpr const_utils::Expected<u8, std::string> single_hex_number(char input) {
57-
if (input >= '0' && input <= '9') {
58-
return const_utils::Expected<u8, std::string>::good_result(static_cast<u8>(input - '0'));
59-
}
55+
namespace guid {
6056

61-
if (input >= 'A' && input <= 'F') {
62-
return const_utils::Expected<u8, std::string>::good_result(static_cast<u8>(input - 'A' + 10));
63-
}
57+
// decode a single_hex_number
58+
[[nodiscard]] constexpr const_utils::Expected<u8, std::string> single_hex_number(char input) {
59+
if (input >= '0' && input <= '9') {
60+
return const_utils::Expected<u8, std::string>::good_result(static_cast<u8>(input - '0'));
61+
}
62+
63+
if (input >= 'A' && input <= 'F') {
64+
return const_utils::Expected<u8, std::string>::good_result(static_cast<u8>(input - 'A' + 10));
65+
}
6466

65-
if (input >= 'a' && input <= 'f') {
66-
return const_utils::Expected<u8, std::string>::good_result(static_cast<u8>(input - 'a' + 10));
67+
if (input >= 'a' && input <= 'f') {
68+
return const_utils::Expected<u8, std::string>::good_result(static_cast<u8>(input - 'a' + 10));
69+
}
70+
71+
return const_utils::Expected<u8, std::string>::error_result("the input must be a valid hex character");
6772
}
6873

69-
return const_utils::Expected<u8, std::string>::error_result("the input must be a valid hex character");
70-
}
74+
// decode a single 2 digit color value in hex
75+
[[nodiscard]] constexpr const_utils::Expected<u8, std::string> single_hex_color_value(const char* input) {
7176

72-
// decode a single 2 digit color value in hex
73-
[[nodiscard]] constexpr const_utils::Expected<u8, std::string> single_hex_color_value(const char* input) {
77+
const auto first = single_hex_number(input[0]); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
7478

75-
const auto first = single_hex_number(input[0]); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
79+
PROPAGATE(first, u8, std::string);
7680

77-
PROPAGATE(first, u8, std::string);
81+
const auto second = single_hex_number(input[1]); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
7882

79-
const auto second = single_hex_number(input[1]); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
83+
PROPAGATE(second, u8, std::string);
8084

81-
PROPAGATE(second, u8, std::string);
85+
return const_utils::Expected<u8, std::string>::good_result((first.value() << 4) | second.value());
86+
}
8287

83-
return const_utils::Expected<u8, std::string>::good_result((first.value() << 4) | second.value());
84-
}
88+
[[nodiscard]] constexpr const_utils::Expected<sdl::GUID, std::string>
89+
get_guid_from_string_impl(const char* input, std::size_t size) {
8590

86-
[[nodiscard]] constexpr const_utils::Expected<sdl::GUID, std::string>
87-
get_guid_from_string_impl(const char* input, std::size_t size) {
91+
if (size == 0) {
92+
return const_utils::Expected<sdl::GUID, std::string>::error_result(
93+
"not enough data to determine the literal type"
94+
);
95+
}
8896

89-
if (size == 0) {
90-
return const_utils::Expected<sdl::GUID, std::string>::error_result(
91-
"not enough data to determine the literal type"
92-
);
93-
}
97+
constexpr std::size_t amount = 16;
9498

95-
constexpr std::size_t amount = 16;
99+
size_t width = 2;
96100

97-
size_t width = 2;
101+
if (size == amount * 2) {
102+
width = 2;
103+
} else if (size == (amount * 2 + (amount - 1))) {
104+
width = 3;
105+
} else {
98106

99-
if (size == amount * 2) {
100-
width = 2;
101-
} else if (size == (amount * 2 + (amount - 1))) {
102-
width = 3;
103-
} else {
107+
return const_utils::Expected<sdl::GUID, std::string>::error_result("Unrecognized guid literal");
108+
}
104109

105-
return const_utils::Expected<sdl::GUID, std::string>::error_result("Unrecognized guid literal");
106-
}
107110

111+
sdl::GUID::ArrayType result{};
108112

109-
sdl::GUID::ArrayType result{};
113+
for (size_t i = 0; i < amount; ++i) {
114+
const size_t offset = i * width;
110115

111-
for (size_t i = 0; i < amount; ++i) {
112-
const size_t offset = i * width;
113116

117+
const auto temp_result = single_hex_color_value(
118+
input + offset
119+
); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
114120

115-
const auto temp_result =
116-
single_hex_color_value(input + offset); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
121+
PROPAGATE(temp_result, sdl::GUID, std::string);
117122

118-
PROPAGATE(temp_result, sdl::GUID, std::string);
123+
const auto value = temp_result.value();
119124

120-
const auto value = temp_result.value();
125+
result.at(i) = value;
126+
}
121127

122-
result.at(i) = value;
128+
return const_utils::Expected<sdl::GUID, std::string>::good_result(sdl::GUID{ result });
123129
}
124-
125-
return const_utils::Expected<sdl::GUID, std::string>::good_result(sdl::GUID{ result });
126-
}
130+
} // namespace guid
127131

128132
} // namespace
129133

@@ -132,14 +136,14 @@ namespace detail {
132136

133137
[[nodiscard]] constexpr const_utils::Expected<sdl::GUID, std::string> get_guid_from_string(const std::string& input
134138
) {
135-
return get_guid_from_string_impl(input.c_str(), input.size());
139+
return guid::get_guid_from_string_impl(input.c_str(), input.size());
136140
}
137141

138142
} // namespace detail
139143

140144

141145
consteval sdl::GUID operator""_guid(const char* input, std::size_t size) {
142-
const auto result = get_guid_from_string_impl(input, size);
146+
const auto result = guid::get_guid_from_string_impl(input, size);
143147

144148
CONSTEVAL_STATIC_ASSERT(result.has_value(), "incorrect guid literal");
145149

0 commit comments

Comments
 (0)