Skip to content

Commit 0b9d0fa

Browse files
committed
implemented snapshots and their validation
1 parent 160a20e commit 0b9d0fa

16 files changed

+486
-27
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ add_executable(oopetris
6363
src/controls.hpp
6464
src/mino_stack.cpp
6565
src/mino_stack.hpp
66+
src/tetrion_snapshot.hpp
67+
src/tetrion_snapshot.cpp
6668
)
6769

6870
foreach (target ${TARGET_LIST})

src/controls.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#pragma once
22

33
#include "key_codes.hpp"
4-
#include "recording.hpp"
54

65
struct KeyboardControls final {
76
KeyCode rotate_left = KeyCode::Left;

src/input.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "input.hpp"
22
#include "application.hpp"
33
#include "key_codes.hpp"
4+
#include "recording.hpp"
45
#include "tetrion.hpp"
56

67
void Input::handle_event(const InputEvent event) {
@@ -176,3 +177,51 @@ void ReplayInput::update() {
176177

177178
Input::update();
178179
}
180+
181+
void ReplayInput::late_update() {
182+
Input::late_update();
183+
184+
while (true) {
185+
if (m_next_snapshot_index >= m_recording_reader->m_snapshots.size()) {
186+
break;
187+
}
188+
189+
const auto& snapshot = m_recording_reader->m_snapshots.at(m_next_snapshot_index);
190+
if (snapshot.tetrion_index() != m_tetrion_index) {
191+
++m_next_snapshot_index;
192+
continue;
193+
}
194+
195+
// the snapshot corresponds to this tetrion
196+
assert(snapshot.tetrion_index() == m_tetrion_index);
197+
198+
if (snapshot.simulation_step_index() != Application::simulation_step_index()) {
199+
break;
200+
}
201+
202+
// create a snapshot from the current state of the tetrion and compare it to the loaded snapshot
203+
const auto current_snapshot = TetrionSnapshot{ *m_target_tetrion };
204+
#ifdef DEBUG_BUILD
205+
static constexpr auto verbose_logging = true;
206+
#else
207+
static constepxr auto verbose_logging = false;
208+
#endif
209+
if constexpr (verbose_logging) {
210+
spdlog::info("comparing tetrion snapshots");
211+
}
212+
const auto snapshots_are_equal = current_snapshot.compare_to(snapshot, verbose_logging);
213+
if (snapshots_are_equal) {
214+
if constexpr (verbose_logging) {
215+
spdlog::info("snapshots are equal");
216+
}
217+
} else {
218+
spdlog::error("snapshots are not equal");
219+
throw std::exception{};
220+
}
221+
++m_next_snapshot_index;
222+
}
223+
}
224+
225+
[[nodiscard]] bool ReplayInput::is_end_of_recording() const {
226+
return m_next_record_index >= m_recording_reader->num_records();
227+
}

src/input.hpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include "controls.hpp"
34
#include "event_listener.hpp"
45
#include "input_event.hpp"
56
#include "random.hpp"
@@ -51,6 +52,7 @@ struct Input {
5152

5253
public:
5354
virtual void update();
55+
virtual void late_update() {};
5456
virtual ~Input() = default;
5557
};
5658

@@ -73,11 +75,14 @@ struct KeyboardInput : public Input, public EventListener {
7375
[[nodiscard]] tl::optional<InputEvent> sdl_event_to_input_event(const SDL_Event& event) const;
7476
};
7577

78+
struct RecordingReader;
79+
7680
struct ReplayInput : public Input {
7781
private:
7882
u8 m_tetrion_index;
7983
RecordingReader* m_recording_reader;
8084
usize m_next_record_index{ 0 };
85+
usize m_next_snapshot_index{ 0 };
8186

8287
public:
8388
ReplayInput(
@@ -88,9 +93,8 @@ struct ReplayInput : public Input {
8893
);
8994

9095
void update() override;
96+
void late_update() override;
9197

9298
private:
93-
[[nodiscard]] bool is_end_of_recording() const {
94-
return m_next_record_index >= m_recording_reader->num_records();
95-
}
99+
[[nodiscard]] bool is_end_of_recording() const;
96100
};

src/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ src_files += files(
4747
'controls.hpp',
4848
'mino_stack.hpp',
4949
'mino_stack.cpp',
50+
'tetrion_snapshot.hpp',
51+
'tetrion_snapshot.cpp',
5052
)
5153

5254
inc_dirs += include_directories('.')

src/mino.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,12 @@ struct Mino final {
2828
Point& position() {
2929
return m_position;
3030
}
31+
32+
[[nodiscard]] bool operator==(const Mino& other) const {
33+
return m_position == other.m_position and m_type == other.m_type;
34+
}
35+
36+
[[nodiscard]] bool operator!=(const Mino& other) const {
37+
return not (*this == other);
38+
}
3139
};

src/mino_stack.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,17 @@ void MinoStack::draw_minos(const Application& app, const Grid& grid) const {
4040
}
4141
}
4242
}
43+
44+
std::ostream& operator<<(std::ostream& os, const MinoStack& mino_stack) {
45+
os << "MinoStack(";
46+
for (usize i = 0; i < mino_stack.num_minos(); ++i) {
47+
const auto& mino = mino_stack.minos().at(i);
48+
os << "{" << mino.position().x << ", " << mino.position().y << ", " << magic_enum::enum_name(mino.type())
49+
<< "}";
50+
if (i < mino_stack.num_minos() - 1) {
51+
os << ", ";
52+
}
53+
}
54+
os << ")";
55+
return os;
56+
}

src/mino_stack.hpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
#pragma once
22

33
#include "mino.hpp"
4+
#include "types.hpp"
5+
#include <algorithm>
6+
#include <iostream>
7+
#include <magic_enum.hpp>
8+
#include <ranges>
49
#include <vector>
510

611
struct Grid;
@@ -14,4 +19,38 @@ struct MinoStack final {
1419
[[nodiscard]] bool is_empty(Point coordinates) const;
1520
void set(Point coordinates, TetrominoType type);
1621
void draw_minos(const Application& app, const Grid& grid) const;
22+
23+
[[nodiscard]] usize num_minos() const {
24+
return m_minos.size();
25+
}
26+
27+
[[nodiscard]] const std::vector<Mino>& minos() const {
28+
return m_minos;
29+
}
30+
31+
[[nodiscard]] bool operator==(const MinoStack& other) const {
32+
using std::ranges::all_of, std::ranges::find, std::ranges::end;
33+
34+
if (m_minos.size() != other.m_minos.size()) {
35+
return false;
36+
}
37+
38+
const auto all_of_this_in_other =
39+
all_of(m_minos, [&](const auto& mino) { return find(other.m_minos, mino) != end(other.m_minos); });
40+
41+
if (not all_of_this_in_other) {
42+
return false;
43+
}
44+
45+
const auto all_of_other_in_this =
46+
all_of(other.m_minos, [&](const auto& mino) { return find(m_minos, mino) != end(m_minos); });
47+
48+
return all_of_other_in_this;
49+
}
50+
51+
[[nodiscard]] bool operator!=(const MinoStack& other) const {
52+
return not(*this == other);
53+
}
1754
};
55+
56+
std::ostream& operator<<(std::ostream& os, const MinoStack& mino_stack);

src/recording.hpp

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#include "input_event.hpp"
44
#include "random.hpp"
5+
#include "tetrion.hpp"
6+
#include "tetrion_snapshot.hpp"
57
#include "types.hpp"
68
#include "utils.hpp"
79
#include <filesystem>
@@ -14,6 +16,12 @@
1416
struct RecordingError : public std::exception { };
1517

1618
struct Recording {
19+
protected:
20+
enum class MagicByte : u8 {
21+
Record = 42,
22+
Snapshot = 43,
23+
};
24+
1725
public:
1826
struct TetrionHeader final {
1927
Random::Seed seed;
@@ -34,6 +42,10 @@ struct Recording {
3442
explicit Recording(std::vector<TetrionHeader> tetrion_headers) : m_tetrion_headers{ std::move(tetrion_headers) } { }
3543

3644
public:
45+
Recording(const Recording&) = delete;
46+
Recording(Recording&&) = delete;
47+
Recording& operator=(const Recording&) = delete;
48+
Recording& operator=(Recording&&) = delete;
3749
virtual ~Recording() = default;
3850

3951
[[nodiscard]] const std::vector<TetrionHeader>& tetrion_headers() const {
@@ -43,6 +55,7 @@ struct Recording {
4355

4456
struct RecordingReader : public Recording {
4557
std::vector<Record> m_records;
58+
std::vector<TetrionSnapshot> m_snapshots;
4659

4760
public:
4861
explicit RecordingReader(const std::filesystem::path& path) {
@@ -68,16 +81,33 @@ struct RecordingReader : public Recording {
6881
}
6982

7083
while (true) {
71-
const auto record = read_record_from_file(file);
72-
if (not record.has_value()) {
73-
if (record.error() == ReadError::EndOfFile) {
74-
// finished reading
75-
break;
84+
const auto magic_byte = read_integral_from_file<std::underlying_type_t<MagicByte>>(file);
85+
if (not magic_byte.has_value()) {
86+
if (magic_byte.error() == ReadError::InvalidStream) {
87+
spdlog::error("unable to read magic byte");
88+
throw RecordingError{};
89+
}
90+
break;
91+
}
92+
if (*magic_byte == std::to_underlying(MagicByte::Record)) {
93+
const auto record = read_record_from_file(file);
94+
if (not record.has_value()) {
95+
if (record.error() == ReadError::EndOfFile) {
96+
// finished reading
97+
break;
98+
}
99+
spdlog::error("invalid record while reading recorded game");
100+
throw RecordingError{};
76101
}
77-
spdlog::error("invalid record while reading recorded game");
102+
m_records.push_back(*record);
103+
} else if (*magic_byte == std::to_underlying(MagicByte::Snapshot)) {
104+
auto snapshot = TetrionSnapshot{ file }; // todo: handle exception
105+
m_snapshots.push_back(std::move(snapshot));
106+
spdlog::info("read snapshot");
107+
} else {
108+
spdlog::error("invalid magic byte: {}", static_cast<int>(*magic_byte));
78109
throw RecordingError{};
79110
}
80-
m_records.push_back(*record);
81111
}
82112
}
83113

@@ -97,6 +127,10 @@ struct RecordingReader : public Recording {
97127
return m_records.cend();
98128
}
99129

130+
[[nodiscard]] const std::vector<TetrionSnapshot>& snapshots() const {
131+
return m_snapshots;
132+
}
133+
100134
private:
101135
enum class ReadError {
102136
EndOfFile,
@@ -192,11 +226,20 @@ struct RecordingWriter : public Recording {
192226

193227
void add_event(const u8 tetrion_index, const u64 simulation_step_index, const InputEvent event) {
194228
assert(tetrion_index < m_tetrion_headers.size());
229+
write(std::to_underlying(MagicByte::Record));
195230
write(tetrion_index);
196231
write(simulation_step_index);
197232
write(static_cast<u8>(event));
198233
}
199234

235+
void add_snapshot(const u8 tetrion_index, const u64 simulation_step_index, const Tetrion& tetrion) {
236+
write(std::to_underlying(MagicByte::Snapshot));
237+
const auto snapshot = TetrionSnapshot{ tetrion_index, tetrion.level(), tetrion.score(),
238+
tetrion.lines_cleared(), simulation_step_index, tetrion.mino_stack() };
239+
const auto bytes = snapshot.to_bytes();
240+
m_output_file.write(bytes.data(), static_cast<std::streamsize>(bytes.size()));
241+
}
242+
200243
private:
201244
static void write_integral_to_file(std::ofstream& file, const std::integral auto data) {
202245
if (not file) {

src/settings.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
#include "controls.hpp"
44
#include "magic_enum_wrapper.hpp"
5-
#include "recording.hpp"
65
#include <array>
76
#include <nlohmann/json.hpp>
87
#include <string>

0 commit comments

Comments
 (0)