#include #include #include #include #include #include #include #include #include #include "chargen.hpp" #include "commodore64.hpp" struct GameState; struct Map_Action { geometry::rect box; using Action_Func = void (*)(GameState &); Action_Func action = nullptr; constexpr void execute_if_collision(geometry::rect object, GameState &game) const { if (action == nullptr) { return; } if (box.intersects(object)) { action(game); } } }; template struct Map { std::string_view name; petscii::Graphic layout; std::span actions; }; struct GameState { std::uint8_t endurance{ 10 }; std::uint8_t stamina{ max_stamina() }; std::uint16_t cash{ 100 }; geometry::point location{ 20, 12 }; bool redraw = true; c64::Clock game_clock{}; Map const *current_map = nullptr; constexpr void set_current_map(const Map &new_map) { current_map = &new_map; redraw = true; } constexpr void execute_actions(geometry::point new_location, const auto &character) noexcept { if (new_location.x + character.size().width > 40) { new_location.x = location.x; } if (new_location.y + character.size().height > 20) { new_location.y = location.y; } location = new_location; if (current_map) { for (auto &action : current_map->actions) { action.execute_if_collision({location, character.size()}, *this); } } } [[nodiscard]] constexpr std::uint8_t max_stamina() const noexcept { return endurance * 5; } struct JoyStick1StateChanged { c64::Joystick state; }; struct JoyStick2StateChanged { c64::Joystick state; }; struct TimeElapsed { c64::Clock::milliseconds us; }; using Event = std::variant; std::uint8_t last_joystick_2_state = mos6502::peek(0xDC00); Event next_event() noexcept { const auto new_joystick_2_state = mos6502::peek(0xDC00); if (new_joystick_2_state != last_joystick_2_state) { last_joystick_2_state = new_joystick_2_state; return JoyStick2StateChanged{ c64::Joystick{ new_joystick_2_state } }; } return TimeElapsed{ game_clock.restart() }; } }; static void puts(const geometry::point loc, const auto &range, const vicii::Colors color = vicii::Colors::white) { const auto offset = static_cast(loc.y * 40 + loc.x); const std::uint16_t start = 0x400 + offset; std::copy(begin(range), end(range), &mos6502::memory_loc(start)); for (std::uint16_t color_loc = 0; color_loc < range.size(); ++color_loc) { mos6502::poke(static_cast(0xD800 + color_loc + offset), static_cast(color)); } } static constexpr auto load_charset(const std::span &bits) { std::array, 256> results{}; for (std::size_t idx = 0; idx < 256; ++idx) { petscii::Graphic glyph{}; for (std::uint8_t row = 0; row < 8; ++row) { const auto input_row = bits[idx * 8 + row]; glyph[geometry::point{ 0, row }] = (0b1000'0000 & input_row) == 0 ? 0 : 1; glyph[geometry::point{ 0, row }] = (0b0100'0000 & input_row) == 0 ? 0 : 1; glyph[geometry::point{ 0, row }] = (0b0010'0000 & input_row) == 0 ? 0 : 1; glyph[geometry::point{ 0, row }] = (0b0001'0000 & input_row) == 0 ? 0 : 1; glyph[geometry::point{ 0, row }] = (0b0000'1000 & input_row) == 0 ? 0 : 1; glyph[geometry::point{ 0, row }] = (0b0000'0100 & input_row) == 0 ? 0 : 1; glyph[geometry::point{ 0, row }] = (0b0000'0010 & input_row) == 0 ? 0 : 1; glyph[geometry::point{ 0, row }] = (0b0000'0001 & input_row) == 0 ? 0 : 1; } results[idx] = glyph; } return results; } template static constexpr auto from_pixels_to_petscii(const petscii::Graphic &pixels) { petscii::Graphic result{}; constexpr auto charset = load_charset(uppercase); for (uint8_t x = 0; x < pixels.width(); x += 8) { for (uint8_t y = 0; y < pixels.height(); y += 8) { std::uint8_t best_match = 32; std::size_t match_count = 0; std::uint8_t cur_char = 0; for (const auto &glyph : charset) { const auto count = pixels.match_count(glyph, x, y); if (count > match_count) { best_match = cur_char; match_count = count; } ++cur_char; } result(x / 8, y / 8) = best_match; } } return result; } template static constexpr auto from_pixels_to_2x2(const petscii::Graphic &pixels) { petscii::Graphic result{}; using GlyphType = std::pair, std::uint8_t>; constexpr std::array lookup_map{ GlyphType{ { 0, 0, 0, 0 }, 32 }, GlyphType{ { 1, 0, 0, 0 }, 126 }, GlyphType{ { 0, 1, 0, 0 }, 124 }, GlyphType{ { 1, 1, 0, 0 }, 226 }, GlyphType{ { 0, 0, 1, 0 }, 123 }, GlyphType{ { 1, 0, 1, 0 }, 97 }, GlyphType{ { 0, 1, 1, 0 }, 255 }, GlyphType{ { 1, 1, 1, 0 }, 236 }, GlyphType{ { 0, 0, 0, 1 }, 108 }, GlyphType{ { 1, 0, 0, 1 }, 127 }, GlyphType{ { 0, 1, 0, 1 }, 225 }, GlyphType{ { 1, 1, 0, 1 }, 251 }, GlyphType{ { 0, 0, 1, 1 }, 98 }, GlyphType{ { 1, 0, 1, 1 }, 252 }, GlyphType{ { 0, 1, 1, 1 }, 254 }, GlyphType{ { 1, 1, 1, 1 }, 160 } }; for (uint8_t x = 0; x < pixels.width(); x += 2) { for (uint8_t y = 0; y < pixels.height(); y += 2) { for (const auto &glyph : lookup_map) { if (pixels.match(glyph.first, x, y)) { result(x / 2, y / 2) = glyph.second; break;// go to next Y, we found our match } } } } return result; } template struct SimpleSprite { geometry::point location{}; bool is_shown = false; petscii::Graphic graphic; petscii::Graphic saved_background{}; constexpr SimpleSprite(std::initializer_list data_) noexcept : graphic(data_) {} }; // helper type for the visitor #4 template struct overloaded : Ts... { using Ts::operator()...; }; // explicit deduction guide (not needed as of C++20) template overloaded(Ts...) -> overloaded; struct Menu { consteval Menu(std::span t_options) : options{ t_options }, box{ geometry::rect{ { 0, 0 }, { static_cast( std::max_element(begin(options), end(options), [](std::string_view lhs, std::string_view rhs) { return lhs.size() < rhs.size(); }) ->size() + 2), static_cast(options.size() + 2) } } .centered() } {} void highlight(std::uint8_t selection) { const auto cur_y = static_cast(selection + 1 + box.top_left().y); for (std::uint8_t cur_x = 1; cur_x < box.width() - 1; ++cur_x) { vicii::invertc(geometry::point{ static_cast(box.top_left().x + cur_x), cur_y }); } } void unhighlight(std::uint8_t selection) { highlight(selection); } void hide(GameState &game) { displayed = false; game.redraw = true; } bool show([[maybe_unused]] GameState &game, std::uint8_t &selection) { if (!displayed) { displayed = true; draw_box(box, vicii::Colors::white); for (auto pos = box.top_left() + geometry::point{ 1, 1 }; const auto &str : options) { puts(pos, str, vicii::Colors::grey); pos = pos + geometry::point{ 0, 1 }; } highlight(current_selection); } if (current_selection != next_selection) { unhighlight(current_selection); highlight(next_selection); current_selection = next_selection; } if (selected) { selected = false; selection = current_selection; return true; } else { return false; } } bool process_event(const GameState::Event &e) { if (not displayed) { return false; } if (const auto *ptr = std::get_if(&e); ptr) { if (ptr->state.up()) { next_selection = current_selection - 1; } if (ptr->state.down()) { next_selection = current_selection + 1; } if (next_selection > options.size() - 1) { next_selection = static_cast(options.size() - 1); } if (ptr->state.fire()) { selected = true; } return true; } return false; } std::span options; geometry::rect box; std::uint8_t current_selection{ 0 }; std::uint8_t next_selection{ 0 }; bool selected{ false }; bool displayed{ false }; }; enum struct State { Walking, SystemMenu, AboutBox }; int main() { // static constexpr auto charset = load_charset(uppercase); // clang-format off static constexpr auto inn = petscii::Graphic { 32,233,160,160,223,32, 233,160,160,160,160,223, 160,137,142,142,160,160, 160,160,160,160,79,160, 160,160,160,160,76,160, }; static constexpr auto gym = petscii::Graphic { 32,233,160,160,223,32, 233,160,160,160,160,223, 160,135,153,141,160,160, 160,160,160,160,79,160, 160,160,160,160,76,160, }; static constexpr auto trading_post = petscii::Graphic { 32,233,160,160,223,32, 233,160,160,160,160,223, 148,146,129,132,133,160, 160,160,160,160,79,160, 160,160,160,160,76,160, }; /* static constexpr auto town = Graphic<4, 4>{ 85, 67, 67, 73, 93, 233, 223, 93, 93, 160, 160, 93, 74, 67, 67, 75 }; */ static constexpr auto town = petscii::ColoredGraphic{ { 32, 32,32, 32, 233,223,233,223, 224,224,224,224, 104,104,104,104 }, { 2,2,10,10, 4,4,7,7, 4,4,7,7, 11,11,11,11 } }; /* static constexpr auto mountain = petscii::Graphic{ 32, 78, 77, 32, 32, 32, 78, 77, 78, 77, 32, 32, 32, 78, 77, 32 }; */ static constexpr auto colored_mountain = petscii::ColoredGraphic{ { 32, 78, 77, 32, 32, 32, 233, 223, 233, 223, 32, 32, 32, 78, 77, 32 }, { 1, 9, 9, 1, 1, 1, 8, 8, 9, 9, 1, 1, 1, 8, 8, 1 } }; auto character = SimpleSprite{ 32, 87, 78, 79, 78, 77 }; static constexpr auto city_map = Map{ "wood town", { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, std::span{} }; static constexpr auto overview_actions = std::array { Map_Action { {{16,0},{4,4}}, [](GameState &g) { g.set_current_map(city_map); } } }; static constexpr auto overview_map = Map{ "the world", { 3, 1, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 3, 0, 0, 0, }, std::span(begin(overview_actions), end(overview_actions)) }; // clang-format on static constexpr std::array tile_types{ [](geometry::point) {}, [](geometry::point p) { vicii::put_graphic(p, colored_mountain); }, [](geometry::point) {}, [](geometry::point p) { vicii::put_graphic(p, town); }, [](geometry::point p) { vicii::put_graphic(p, inn); }, [](geometry::point p) { vicii::put_graphic(p, gym); }, [](geometry::point p) { vicii::put_graphic(p, trading_post); }, }; const auto draw_map = [](const auto &map) { for (std::size_t tile = 0; tile < tile_types.size(); ++tile) { for (const auto &pos : map.size()) { if (map[pos] == tile) { tile_types[tile]({static_cast(pos.x * 4), static_cast(pos.y * 4)}); } } } }; GameState game; State state = State::Walking; game.current_map = &overview_map; constexpr auto show_stats = [](const auto &cur_game) { puts(geometry::point{ 1, 21 }, petscii::PETSCII("STAMINA:"), vicii::Colors::light_grey); put_hex(geometry::point{ 12, 21 }, cur_game.stamina, vicii::Colors::white); put_hex(geometry::point{ 15, 21 }, cur_game.max_stamina(), vicii::Colors::white); puts(geometry::point{ 14, 21 }, petscii::PETSCII("/"), vicii::Colors::light_grey); puts(geometry::point{ 1, 22 }, petscii::PETSCII("ENDURANCE:"), vicii::Colors::light_grey); put_hex(geometry::point{ 12, 22 }, cur_game.endurance, vicii::Colors::white); puts(geometry::point{ 1, 23 }, petscii::PETSCII("CASH:"), vicii::Colors::light_grey); put_hex(geometry::point{ 12, 23 }, cur_game.cash, vicii::Colors::white); }; vicii::Screen screen; static constexpr auto menu_options = std::array{ std::string_view{ "info" }, std::string_view{ "test2" }, std::string_view{ "test3" }, std::string_view{ "an even longer thing" } }; Menu m(menu_options); auto eventHandler = overloaded{ [&](const GameState::JoyStick2StateChanged &e) { auto new_loc = game.location; if (e.state.fire()) { state = State::SystemMenu; return; } if (e.state.up()) { --new_loc.y; } if (e.state.down()) { ++new_loc.y; } if (e.state.left()) { --new_loc.x; } if (e.state.right()) { ++new_loc.x; } game.execute_actions(new_loc, character.graphic); screen.show(game.location, character); put_hex({36, 1}, e.state.state, vicii::Colors::dark_grey); }, [](const GameState::TimeElapsed &e) { vicii::put_hex({36, 0}, e.us.count(), vicii::Colors::dark_grey); } }; while (true) { const auto next_event = game.next_event(); if (not m.process_event(next_event)) { // if no gui elements needed the event, then we handle it std::visit(eventHandler, next_event); } if (game.redraw) { screen.hide(character); vicii::cls(); mos6502::poke(53280, 0); mos6502::poke(53281, 0); game.redraw = false; draw_map(game.current_map->layout); draw_box(geometry::rect{ { 0, 20 }, { 40, 5 } }, vicii::Colors::dark_grey); puts(geometry::point{ 10, 20 }, game.current_map->name, vicii::Colors::white); show_stats(game); screen.show(game.location, character); } if (std::uint8_t result = 0; state == State::SystemMenu && m.show(game, result)) { // we had a menu item selected m.hide(game); if (result == 0) {} vicii::increment_border_color(); } /* const auto background_color = [](Colors col) { memory_loc(0xd021) = static_cast(col); }; background_color(Colors::WHITE); while(true) { if (joystick_down()) { increment_border_color(); } else { decrement_border_color(); } } */ } }