Skip to content

Commit 22c8a78

Browse files
committed
Basic learning game working
1 parent a2f04bc commit 22c8a78

File tree

4 files changed

+246
-75
lines changed

4 files changed

+246
-75
lines changed

src/game.cpp

Lines changed: 104 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,56 @@ Game_Map make_map()// NOLINT cognitive complexity
88
{
99
Game_Map map{ Size{ 10, 10 } };// NOLINT magic numbers
1010

11-
auto solid_draw = []([[maybe_unused]] Vector2D_Span<Color> &pixels,
12-
[[maybe_unused]] const Game &game,
13-
[[maybe_unused]] Point map_location) {
14-
for (std::size_t cur_x = 0; cur_x < pixels.size().width; ++cur_x) {
15-
for (std::size_t cur_y = 0; cur_y < pixels.size().height; ++cur_y) {
16-
if (cur_x == 0 || cur_y == 0 || cur_x == pixels.size().width - 1 || cur_y == pixels.size().height - 1) {
17-
pixels.at(Point{ cur_x, cur_y }) = Color{ 128, 128, 128, 255 };// NOLINT Magic numbers
18-
} else {
19-
switch ((game.clock.count() / 1000) % 2) {// NOLINT Magic numbers
20-
case 0:
21-
pixels.at(Point{ cur_x, cur_y }) = Color{ 255, 255, 255, 255 };// NOLINT Magic numbers
22-
break;
23-
case 1:// NOLINT Magic Numbers
24-
pixels.at(Point{ cur_x, cur_y }) = Color{ 200, 240, 240, 255 };// NOLINT Magic numbers
25-
break;
26-
}
27-
}
28-
}
29-
}
30-
};
11+
12+
auto empty_draw =
13+
[](Vector2D_Span<Color> &pixels, [[maybe_unused]] const Game &game, [[maybe_unused]] Point map_location) {
14+
fill(pixels, Color{ 5, 5, 25, 255 });// NOLINT magic number
15+
};
3116

3217
auto cannot_enter = [](const Game &, Point, Direction) -> bool { return false; };
3318

34-
auto empty_draw = []([[maybe_unused]] Vector2D_Span<Color> &pixels,
35-
[[maybe_unused]] const Game &game,
36-
[[maybe_unused]] Point map_location) {
37-
for (std::size_t cur_x = 0; cur_x < pixels.size().width; ++cur_x) {
38-
for (std::size_t cur_y = 0; cur_y < pixels.size().height; ++cur_y) {
39-
pixels.at(Point{ cur_x, cur_y }) = Color{ 10, 10, 10, 255 };// NOLINT Magic numbers
40-
}
19+
auto water = [](
20+
Vector2D_Span<Color> &pixels, [[maybe_unused]] const Game &game, [[maybe_unused]] Point map_location) {
21+
fill(pixels, Color{ 0, 0, 250, 255 });// NOLINT magic number
22+
};
23+
24+
25+
auto wall_draw = []([[maybe_unused]] Vector2D_Span<Color> &pixels,
26+
[[maybe_unused]] const Game &game,
27+
[[maybe_unused]] Point map_location) {
28+
static constexpr auto wall_color = Color{ 100, 100, 100, 128 };
29+
30+
31+
switch ((game.clock.count() / 1000) % 2) {// NOLINT magic number
32+
case 0:
33+
fill(pixels, Color{ 64, 128, 64, 255 });// NOLINT magic number
34+
break;
35+
case 1:
36+
fill(pixels, Color{ 128, 64, 64, 255 });// NOLINT magic number
37+
break;
38+
}
39+
40+
41+
if (!game.maps.at(game.current_map).can_enter_from(game, map_location, Direction::East)) {
42+
fill_line(pixels,
43+
Point{ pixels.size().width - 1, 0 },
44+
Point{ pixels.size().width - 1, pixels.size().height - 1 },
45+
wall_color);
46+
}
47+
48+
if (!game.maps.at(game.current_map).can_enter_from(game, map_location, Direction::West)) {
49+
fill_line(pixels, Point{ 0, 0 }, Point{ 0, pixels.size().height - 1 }, wall_color);
50+
}
51+
52+
if (!game.maps.at(game.current_map).can_enter_from(game, map_location, Direction::North)) {
53+
fill_line(pixels, Point{ 0, 0 }, Point{ pixels.size().width - 1, 0 }, wall_color);
54+
}
55+
56+
if (!game.maps.at(game.current_map).can_enter_from(game, map_location, Direction::South)) {
57+
fill_line(pixels,
58+
Point{ 0, pixels.size().height - 1 },
59+
Point{ pixels.size().width - 1, pixels.size().height - 1 },
60+
wall_color);
4161
}
4262
};
4363

@@ -47,26 +67,47 @@ Game_Map make_map()// NOLINT cognitive complexity
4767
}
4868
}
4969

50-
map.locations.at(Point{ 2, 3 }).draw = solid_draw;
51-
map.locations.at(Point{ 2, 3 }).can_enter = cannot_enter;
52-
map.locations.at(Point{ 1, 4 }).draw = solid_draw;
53-
map.locations.at(Point{ 1, 4 }).can_enter = cannot_enter;
54-
map.locations.at(Point{ 0, 2 }).draw = solid_draw;
55-
map.locations.at(Point{ 0, 2 }).can_enter = [](const Game &, Point, Direction direction) {
56-
return direction == Direction::South;
70+
fill_border(map.locations, Location{ {}, {}, water, cannot_enter });
71+
72+
const auto Flashing_Tile = Location{ {}, {}, wall_draw, cannot_enter };
73+
74+
constexpr static auto special_location = Point{ 8, 8 };
75+
76+
map.locations.at(Point{ 3, 4 }) = Flashing_Tile;
77+
map.locations.at(Point{ 2, 5 }) = Flashing_Tile;// NOLINT magic numbers
78+
map.locations.at(Point{ 1, 2 }) = Flashing_Tile;
79+
map.locations.at(Point{ 8, 6 }) = Flashing_Tile;// NOLINT magic numbers
80+
map.locations.at(Point{ 5, 5 }) = Flashing_Tile;// NOLINT magic numbers
81+
82+
map.locations.at(Point{ 2, 1 }).enter_action = [](Game &game, Point, Direction) {
83+
game.last_message = "Hint: go to location {4,3}";
5784
};
5885

59-
map.locations.at(Point{0,3}).enter_action = [](Game &game, Point, Direction) {
60-
game.last_message = "There is a secret entrance to the north";
86+
map.locations.at(Point{ 4, 3 }).enter_action = [](Game &game, Point, Direction) {
87+
game.last_message = "Hint: go to location {8,8}";
6188
};
62-
map.locations.at(Point{ 0, 3 }).exit_action = [](Game &game, Point, Direction) {
63-
game.last_message = "";
89+
90+
map.locations.at(Point{ 7, 7 }).enter_action// NOLINT
91+
= [](Game &game, Point, Direction) { game.last_message = "A wall is blocking your way"; };
92+
map.locations.at(Point{ 8, 7 }).enter_action// NOLINT
93+
= [](Game &game, Point, Direction) { game.last_message = "You need to remove the wall"; };
94+
map.locations.at(Point{ 7, 8 }).enter_action// NOLINT
95+
= [](Game &game, Point, Direction) { game.last_message = "Look for 'special_location' in the source code"; };
96+
97+
map.locations.at(special_location) = Flashing_Tile;
98+
map.locations.at(special_location).can_enter = [](const Game &, Point, Direction direction) {
99+
return direction == Direction::South || direction == Direction::East;
64100
};
65101

66-
map.locations.at(Point{2,0}).enter_action = [](Game &game, Point, Direction) {
102+
map.locations.at(special_location).exit_action = [](Game &game, Point, Direction) { game.last_message = ""; };
103+
104+
map.locations.at(special_location).enter_action = [](Game &game, Point, Direction) {
105+
game.last_message = "You found the secret room!";
67106
Menu menu;
68-
menu.items.push_back(Menu::MenuItem{"Hello World", [](auto &){}});
69-
menu.items.push_back(Menu::MenuItem{ "Exit Menu", [](Game &menu_action_game) {menu_action_game.clear_menu();}});
107+
menu.items.push_back(
108+
Menu::MenuItem{ "Continue Game", [](Game &menu_action_game) { menu_action_game.clear_menu(); } });
109+
menu.items.push_back(
110+
Menu::MenuItem{ "Exit Game", [](Game &menu_action_game) { menu_action_game.exit_game = true; } });
70111
game.set_menu(menu);
71112
};
72113

@@ -76,26 +117,38 @@ Game_Map make_map()// NOLINT cognitive complexity
76117

77118
Game make_game()
78119
{
79-
Game retval;
120+
Game retval{};
80121
retval.maps.emplace("main", make_map());
81122
retval.current_map = "main";
82123
retval.tile_size = Size{ 8, 8 };// NOLINT Magic Number
83124

125+
retval.variables["Task"] = "Exit game";
126+
retval.display_variables.emplace_back("Task");
127+
84128
Character player;
85-
player.draw = [player_bitmap = load_png("player.png")]([[maybe_unused]] Vector2D_Span<Color> &pixels,
86-
[[maybe_unused]] const Game &game,
87-
[[maybe_unused]] Point map_location) {
88-
// with with a fully saturated red at 50% alpha
89-
for (std::size_t cur_x = 0; cur_x < pixels.size().width; ++cur_x) {
90-
for (std::size_t cur_y = 0; cur_y < pixels.size().height; ++cur_y) {
91-
pixels.at(Point{ cur_x, cur_y }) += player_bitmap.at(Point{ cur_x, cur_y });
129+
player.map_location = { 1, 1 };
130+
player.draw =
131+
[](Vector2D_Span<Color> &pixels, [[maybe_unused]] const Game &game, [[maybe_unused]] Point map_location) {
132+
for (std::size_t cur_x = 2; cur_x < pixels.size().width - 2; ++cur_x) {
133+
for (std::size_t cur_y = 2; cur_y < pixels.size().height - 2; ++cur_y) {
134+
if ((cur_x == 2 && cur_y == 2) || (cur_x == 2 && cur_y == pixels.size().height - 3)
135+
|| (cur_x == pixels.size().width - 3 && cur_y == pixels.size().height - 3)
136+
|| (cur_x == pixels.size().width - 3 && cur_y == 2)) {
137+
pixels.at(Point{ cur_x, cur_y }) += Color{ 128, 128, 0, 64 };// NOLINT
138+
} else {
139+
pixels.at(Point{ cur_x, cur_y }) += Color{ 128, 128, 0, 255 };// NOLINT
140+
}
141+
}
92142
}
93-
}
94-
};
143+
};
95144

96145

97146
retval.player = player;
98147

148+
retval.popup_message =
149+
"Welcome to 'Learning C++ With Game Hacking!' Your job is to get into the special square in the bottom right "
150+
"corner of the map. But to do that you'll need to modify the source code!";
151+
99152
return retval;
100153
}
101154
}// namespace lefticus::my_awesome_game

src/game_components.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ struct Menu
5959

6060
using Variable = std::variant<double, std::int64_t, std::string>;
6161

62+
inline std::string to_string(const Variable &variable)
63+
{
64+
return std::visit([](const auto &value) { return fmt::format("{}", value); }, variable);
65+
}
66+
6267
struct Game
6368
{
6469

@@ -73,6 +78,12 @@ struct Game
7378
Size tile_size;
7479

7580
std::string last_message;
81+
std::string popup_message;
82+
83+
84+
bool exit_game = false;
85+
86+
[[nodiscard]] bool has_popup_message() const { return !popup_message.empty(); }
7687

7788
[[nodiscard]] bool has_new_menu() const { return menu_is_new; }
7889

src/main.cpp

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace lefticus::my_awesome_game {
2828
void draw(Bitmap &viewport, Point map_center, const Game &game, const Game_Map &map)
2929
{
3030
const auto num_wide = viewport.pixels.size().width / game.tile_size.width;
31-
const auto num_high = viewport.pixels.size().width / game.tile_size.height;
31+
const auto num_high = viewport.pixels.size().height / game.tile_size.height;
3232

3333
const auto x_offset = num_wide / 2;
3434
const auto y_offset = num_high / 2;
@@ -37,7 +37,7 @@ void draw(Bitmap &viewport, Point map_center, const Game &game, const Game_Map &
3737
const auto min_y = y_offset;
3838

3939
const auto max_x = map.locations.size().width - x_offset - (num_wide % 2);
40-
const auto max_y = map.locations.size().height - y_offset - (num_wide % 2);
40+
const auto max_y = map.locations.size().height - y_offset - (num_high % 2);
4141

4242
const auto center_map_location =
4343
Point{ std::clamp(map_center.x, min_x, max_x), std::clamp(map_center.y, min_y, max_y) };
@@ -93,9 +93,11 @@ void game_iteration_canvas()// NOLINT cognitive complexity
9393

9494
Displayed_Menu current_menu{ Menu{}, game };
9595

96+
auto clear_popup_button = ftxui::Button("OK", [&]() { game.popup_message.clear(); });
97+
9698
// this should probably have a `bitmap` helper function that does what you expect
9799
// similar to the other parts of FTXUI
98-
auto bm = std::make_shared<Bitmap>(Size{ 40, 40 });// NOLINT magic numbers
100+
auto bm = std::make_shared<Bitmap>(Size{ 64, 40 });// NOLINT magic numbers
99101
auto small_bm = std::make_shared<Bitmap>(Size{ 6, 6 });// NOLINT magic numbers
100102

101103
double fps = 0;
@@ -120,9 +122,7 @@ void game_iteration_canvas()// NOLINT cognitive complexity
120122
game.clock = game_clock;
121123

122124
[&] {
123-
if (game.has_menu()) {
124-
return;
125-
}
125+
if (game.has_menu()) { return; }
126126

127127
if (current_event != last_event) {
128128
auto location = game.player.map_location;
@@ -173,40 +173,88 @@ void game_iteration_canvas()// NOLINT cognitive complexity
173173
auto container = ftxui::Container::Vertical({});
174174

175175
auto key_press = ftxui::CatchEvent(container, [&](const ftxui::Event &event) {
176-
if (game.has_menu()) {
177-
return false;
178-
} else {
179-
last_event = std::exchange(current_event, event);
180-
return true;
181-
}
176+
last_event = std::exchange(current_event, event);
177+
return false;
182178
});
183179

184180
auto make_layout = [&] {
185181
// This code actually processes the draw event
186182
const auto new_time = std::chrono::steady_clock::now();
187183

188-
if (game.has_new_menu()) { current_menu = Displayed_Menu{ game.get_menu(), game };
189-
key_press->DetachAllChildren();
190-
key_press->Add(current_menu.buttons);
191-
}
192184

193185
++counter;
186+
187+
if (game.exit_game) { screen.ExitLoopClosure()(); }
188+
194189
// we will dispatch to the game_iteration function, where the work happens
195190
game_iteration(new_time - last_time);
196191
last_time = new_time;
197192

193+
194+
ftxui::Elements text_components;
195+
text_components.push_back(ftxui::text("Frame: " + std::to_string(counter)));
196+
text_components.push_back(
197+
ftxui::text(fmt::format("Location: {{{},{}}}", game.player.map_location.x, game.player.map_location.y)));
198+
199+
for (const auto &variable : game.display_variables) {
200+
if (game.variables.contains(variable)) {
201+
text_components.push_back(ftxui::text(fmt::format("{}: {}", variable, to_string(game.variables.at(variable)))));
202+
}
203+
}
204+
198205
// now actually draw the game elements
199-
return ftxui::vbox({ ftxui::hbox({ (game.has_menu() ? current_menu.buttons->Render() : bm) | ftxui::border,
200-
ftxui::vbox({ ftxui::text("Frame: " + std::to_string(counter)),
201-
ftxui::text("FPS: " + std::to_string(fps)),
202-
ftxui::text("Character: " + last_character),
203-
small_bm | ftxui::border }) }),
204-
ftxui::text("Message: " + game.last_message) });
206+
return ftxui::vbox({ ftxui::hbox({ bm | ftxui::border, ftxui::vbox(std::move(text_components)) | ftxui::border }),
207+
ftxui::text("Message: " + game.last_message) | ftxui::border });
205208
};
206209

207210

211+
auto game_renderer = ftxui::Renderer(key_press, make_layout);
212+
213+
auto menu_renderer =
214+
ftxui::Renderer(current_menu.buttons, [&] { return current_menu.buttons->Render() | ftxui::border; });
215+
216+
auto popup_renderer = ftxui::Renderer(clear_popup_button, [&] {
217+
return ftxui::vbox({ ftxui::paragraphAlignCenter(game.popup_message), clear_popup_button->Render() })
218+
| ftxui::border;
219+
});
220+
221+
222+
int depth = 0;
223+
224+
auto main_container = ftxui::Container::Tab({ game_renderer, menu_renderer, popup_renderer }, &depth);
225+
226+
auto main_renderer = ftxui::Renderer(main_container, [&] {
227+
if (game.has_popup_message()) {
228+
depth = 2;
229+
} else if (game.has_menu()) {
230+
depth = 1;
231+
} else {
232+
depth = 0;
233+
}
234+
235+
if (game.has_new_menu()) {
236+
current_menu = Displayed_Menu{ game.get_menu(), game };
237+
menu_renderer->DetachAllChildren();
238+
menu_renderer->Add(current_menu.buttons);
239+
}
240+
241+
ftxui::Element document = game_renderer->Render();
242+
243+
if (depth > 0) {
244+
if (game.has_menu()) {
245+
document = ftxui::dbox({ document, menu_renderer->Render() | ftxui::clear_under | ftxui::center });
246+
}
247+
}
248+
249+
if (depth > 1) {
250+
if (game.has_popup_message()) {
251+
document = ftxui::dbox({ document, popup_renderer->Render() | ftxui::clear_under | ftxui::center });
252+
}
253+
}
254+
255+
return document;
256+
});
208257

209-
auto renderer = ftxui::Renderer(game.has_menu() ? current_menu.buttons : key_press, make_layout);
210258

211259
std::atomic<bool> refresh_ui_continue = true;
212260

@@ -220,7 +268,7 @@ void game_iteration_canvas()// NOLINT cognitive complexity
220268
}
221269
});
222270

223-
screen.Loop(renderer);
271+
screen.Loop(main_renderer);
224272

225273
refresh_ui_continue = false;
226274
refresh_ui.join();

0 commit comments

Comments
 (0)