From f3a37140ba6f206d5366fd5e9b1c540d5c77df87 Mon Sep 17 00:00:00 2001 From: Jason Turner Date: Thu, 10 Jun 2021 21:08:27 -0600 Subject: [PATCH] Reorganize game code --- CMakeLists.txt | 1 + examples/graphics.cpp | 842 ------------------------- examples/simple_game/6502.hpp | 20 + examples/simple_game/CMakeLists.txt | 11 + examples/{ => simple_game}/chargen.hpp | 0 examples/simple_game/commodore64.hpp | 57 ++ examples/simple_game/game.cpp | 553 ++++++++++++++++ examples/simple_game/geometry.hpp | 99 +++ examples/simple_game/petscii.hpp | 69 ++ examples/simple_game/vicii.hpp | 153 +++++ src/6502-c++.cpp | 39 +- 11 files changed, 995 insertions(+), 849 deletions(-) delete mode 100644 examples/graphics.cpp create mode 100644 examples/simple_game/6502.hpp create mode 100644 examples/simple_game/CMakeLists.txt rename examples/{ => simple_game}/chargen.hpp (100%) create mode 100644 examples/simple_game/commodore64.hpp create mode 100644 examples/simple_game/game.cpp create mode 100644 examples/simple_game/geometry.hpp create mode 100644 examples/simple_game/petscii.hpp create mode 100644 examples/simple_game/vicii.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bdaad0e..8bfcf1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,3 +80,4 @@ if(ENABLE_FUZZING) endif() add_subdirectory(src) +add_subdirectory(examples/simple_game) diff --git a/examples/graphics.cpp b/examples/graphics.cpp deleted file mode 100644 index f145cd6..0000000 --- a/examples/graphics.cpp +++ /dev/null @@ -1,842 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "chargen.hpp" - -enum struct Colors : std::uint8_t { - black = 0, - white = 1, - red = 2, - cyan = 3, - violet = 4, - green = 5, - blue = 6, - yellow = 7, - orange = 8, - brown = 9, - light_red = 10, - dark_grey = 11, - grey = 12, - light_green = 13, - light_blue = 14, - light_grey = 15 -}; - -constexpr char charToPETSCII(char c) noexcept -{ - if (c >= 'A' && c <= 'Z') { return c - 'A' + 1; } - return c; -} - -template constexpr auto PETSCII(const char (&value)[Size]) noexcept -{ - std::array result{}; - std::transform(std::begin(value), std::prev(std::end(value)), std::begin(result), charToPETSCII); - return result; -} - - -static volatile uint8_t &memory_loc(const uint16_t loc) { return *reinterpret_cast(loc); } - -static void poke(const uint16_t loc, const uint8_t value) { memory_loc(loc) = value; } - -static std::uint8_t peek(const std::uint16_t loc) { return memory_loc(loc); } - -static void decrement_border_color() { --memory_loc(0xd020); } - -static void increment_border_color() { ++memory_loc(0xd020); } - - -struct Joystick -{ - std::uint8_t state{}; - - constexpr bool up() const noexcept { return (state & 1) == 0; } - constexpr bool left() const noexcept { return (state & 4) == 0; } - constexpr bool fire() const noexcept { return (state & 16) == 0; } - constexpr bool right() const noexcept { return (state & 8) == 0; } - constexpr bool down() const noexcept { return (state & 2) == 0; } -}; - -static bool joystick_down() -{ - uint8_t joystick_state = peek(0xDC00); - return (joystick_state & 2) == 0; -} - -void use_data(std::array &data); - -static void puts(uint8_t x, uint8_t y, const auto &range, const Colors color = Colors::white) -{ - const auto offset = y * 40 + x; - - const auto start = 0x400 + offset; - std::copy(begin(range), end(range), &memory_loc(start)); - - for (std::uint16_t color_loc = 0; color_loc < range.size(); ++color_loc) { - poke(0xD800 + color_loc + offset, static_cast(color)); - } -} - -template struct Graphic -{ - std::array data{}; - - static constexpr auto width() noexcept { return Width; } - - static constexpr auto height() noexcept { return Height; } - - constexpr Graphic() = default; - - constexpr Graphic(std::array data_) noexcept : data(data_) {} - constexpr Graphic(std::initializer_list data_) noexcept { std::copy(begin(data_), end(data_), begin(data)); } - - constexpr auto &operator()(const std::uint8_t x, const std::uint8_t y) noexcept { return data[y * Width + x]; } - - constexpr const auto &operator()(const std::uint8_t x, const std::uint8_t y) const noexcept - { - return data[y * Width + x]; - } - - constexpr std::size_t match_count(const auto &graphic, const std::uint8_t x, const std::uint8_t y) const - { - std::size_t count = 0; - for (std::uint8_t cur_x = 0; cur_x < graphic.width(); ++cur_x) { - for (std::uint8_t cur_y = 0; cur_y < graphic.height(); ++cur_y) { - if (graphic(cur_x, cur_y) == (*this)(cur_x + x, cur_y + y)) { ++count; } - } - } - - return count; - } - - - constexpr bool match(const auto &graphic, const std::uint8_t x, const std::uint8_t y) const - { - return match_count(graphic, x, y) == (graphic.width() * graphic.height()); - } -}; - -template struct ColoredGraphic -{ - Graphic data; - Graphic colors; -}; - - -static constexpr auto load_charset(const std::span &bits) -{ - std::array, 256> results{}; - - for (std::size_t idx = 0; idx < 256; ++idx) { - Graphic<8, 8> glyph{}; - - for (std::uint8_t row = 0; row < 8; ++row) { - const auto input_row = bits[idx * 8 + row]; - glyph(0, row) = (0b1000'0000 & input_row) == 0 ? 0 : 1; - glyph(1, row) = (0b0100'0000 & input_row) == 0 ? 0 : 1; - glyph(2, row) = (0b0010'0000 & input_row) == 0 ? 0 : 1; - glyph(3, row) = (0b0001'0000 & input_row) == 0 ? 0 : 1; - glyph(4, row) = (0b0000'1000 & input_row) == 0 ? 0 : 1; - glyph(5, row) = (0b0000'0100 & input_row) == 0 ? 0 : 1; - glyph(6, row) = (0b0000'0010 & input_row) == 0 ? 0 : 1; - glyph(7, row) = (0b0000'0001 & input_row) == 0 ? 0 : 1; - } - - results[idx] = glyph; - } - - return results; -} - -template -static constexpr auto from_pixels_to_petscii(const Graphic &pixels) -{ - 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 Graphic &pixels) -{ - 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; -} - -static void putc(uint8_t x, uint8_t y, uint8_t c, Colors color) -{ - const auto offset = (y * 40 + x); - const auto start = 0x400 + offset; - poke(start, c); - poke(offset + 0xD800, static_cast(color)); -} - -static std::uint8_t loadc(uint8_t x, uint8_t y) -{ - const auto start = 0x400 + (y * 40 + x); - return peek(start); -} - -static void invertc(uint8_t x, uint8_t y) -{ - const auto start = 0x400 + (y * 40 + x); - memory_loc(start) += 128; -} - - -static void put_hex(uint8_t x, uint8_t y, uint8_t value, Colors color) -{ - const auto put_nibble = [color](auto x, auto y, uint8_t nibble) { - if (nibble <= 9) { - putc(x, y, nibble + 48, color); - } else { - putc(x, y, nibble - 9, color); - } - }; - - put_nibble(x + 1, y, 0xF & value); - put_nibble(x, y, 0xF & (value >> 4)); -} - -static void put_hex(uint8_t x, uint8_t y, uint16_t value, Colors color) -{ - put_hex(x + 2, y, static_cast(0xFF & value), color); - put_hex(x, y, static_cast(0xFF & (value >> 8)), color); -} - -static void put_graphic(uint8_t x, uint8_t y, const auto &graphic) -{ - for (uint8_t cur_y = 0; cur_y < graphic.height(); ++cur_y) { - for (uint8_t cur_x = 0; cur_x < graphic.width(); ++cur_x) { - putc(cur_x + x, cur_y + y, graphic(cur_x, cur_y), Colors::white); - } - } -} - -static void put_graphic(uint8_t x, uint8_t y, const auto &graphic) requires requires { graphic.colors(0, 0); } -{ - for (uint8_t cur_y = 0; cur_y < graphic.data.height(); ++cur_y) { - for (uint8_t cur_x = 0; cur_x < graphic.data.width(); ++cur_x) { - putc(cur_x + x, cur_y + y, graphic.data(cur_x, cur_y), static_cast(graphic.colors(cur_x, cur_y))); - } - } -} - - -struct Clock -{ - using milliseconds = std::chrono::duration; - - // return elapsed time since last restart - [[nodiscard]] milliseconds restart() noexcept - { - // stop Timer A - poke(0xDC0E, 0b00000000); - - // last value - const auto previous_value = static_cast(peek(0xDC04) | (static_cast(peek(0xDC05)) << 8)); - - // reset timer - poke(0xDC04, 0xFF); - poke(0xDC05, 0xFF); - - // restart timer A - poke(0xDC0E, 0b00010001); - - return milliseconds{ 0xFFFF - previous_value }; - } - - Clock() noexcept { [[maybe_unused]] const auto value = restart(); } -}; - -static void cls() -{ - for (std::uint16_t i = 0x400; i < 0x400 + 1000; ++i) { poke(i, 32); } -} - -template struct SimpleSprite -{ - std::uint8_t x = 0; - std::uint8_t y = 0; - bool is_shown = false; - Graphic graphic; - 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 Screen -{ - void load(std::uint8_t x, std::uint8_t y, auto &s) - { - for (std::uint8_t cur_y = 0; cur_y < s.height(); ++cur_y) { - for (std::uint8_t cur_x = 0; cur_x < s.width(); ++cur_x) { s(cur_x, cur_y) = loadc(cur_x + x, cur_y + y); } - } - } - - void hide(auto &s) - { - if (s.is_shown) { - put_graphic(s.x, s.y, s.saved_background); - s.is_shown = false; - } - } - - void show(std::uint8_t x, std::uint8_t y, auto &s) - { - if (s.is_shown) { put_graphic(s.x, s.y, s.saved_background); } - - s.is_shown = true; - - s.x = x; - s.y = y; - load(x, y, s.saved_background); - - put_graphic(x, y, s.graphic); - } -}; - -template struct Map; - -struct GameState -{ - - std::uint8_t endurance{ 10 }; - std::uint8_t stamina{ max_stamina() }; - std::uint16_t cash{ 100 }; - std::uint8_t x = 20; - std::uint8_t y = 12; - - bool redraw = true; - - Clock game_clock{}; - - Map<10, 5> const *current_map = nullptr; - - constexpr void set_current_map(const Map<10, 5> &new_map) - { - current_map = &new_map; - redraw = true; - } - - constexpr void execute_actions(std::uint8_t new_x, std::uint8_t new_y, const auto &character) noexcept - { - if (new_x + character.width() > 40) { new_x = x; } - - if (new_y + character.height() > 20) { new_y = y; } - - x = new_x; - y = new_y; - - if (current_map) { - for (auto &action : current_map->actions) { - action.execute_if_collision(x, y, character.width(), character.height(), *this); - } - } - } - - constexpr std::uint8_t max_stamina() const noexcept { return endurance * 5; } - - struct JoyStick1StateChanged - { - Joystick state; - }; - - - struct JoyStick2StateChanged - { - Joystick state; - }; - - struct TimeElapsed - { - Clock::milliseconds us; - }; - - using Event = std::variant; - - std::uint8_t last_joystick_2_state = peek(0xDC00); - - Event next_event() noexcept - { - const auto new_joystick_2_state = peek(0xDC00); - - if (new_joystick_2_state != last_joystick_2_state) { - last_joystick_2_state = new_joystick_2_state; - return JoyStick2StateChanged{ Joystick{ new_joystick_2_state } }; - } - - return TimeElapsed{ game_clock.restart() }; - } -}; - -struct Map_Action -{ - std::uint8_t x{}; - std::uint8_t y{}; - - std::uint8_t width{}; - std::uint8_t height{}; - - using Action_Func = void (*)(GameState &); - Action_Func action = nullptr; - - constexpr void execute_if_collision(std::uint8_t obj_x, - std::uint8_t obj_y, - std::uint8_t obj_width, - std::uint8_t obj_height, - GameState &game) const - { - if (action == nullptr) { return; } - - const std::uint8_t rect1_x1 = x; - const std::uint8_t rect1_x2 = x + width; - const std::uint8_t rect1_y1 = y; - const std::uint8_t rect1_y2 = y + height; - - const std::uint8_t rect2_x1 = obj_x; - const std::uint8_t rect2_x2 = obj_x + obj_width; - const std::uint8_t rect2_y1 = obj_y; - const std::uint8_t rect2_y2 = obj_y + obj_height; - - if (rect1_x1 < rect2_x2 && rect1_x2 > rect2_x1 && rect1_y1 < rect2_y2 && rect1_y2 > rect2_y1) { action(game); } - } -}; - - -template struct Map -{ - std::string_view name; - Graphic layout; - - std::span actions; -}; - - void draw_box(uint8_t x, uint8_t y, uint8_t width, uint8_t height, Colors color) { - putc(x, y, 85, color); - putc(x + width - 1, y, 73, color); - putc(x + width - 1, y + height - 1, 75, color); - putc(x, y + height - 1, 74, color); - - for (uint8_t cur_x = x + 1; cur_x < x + width - 1; ++cur_x) { - putc(cur_x, y, 67, color); - putc(cur_x, y + height - 1, 67, color); - } - - for (uint8_t cur_y = y + 1; cur_y < y + height - 1; ++cur_y) { - putc(x, cur_y, 93, color); - putc(x + width - 1, cur_y, 93, color); - } - } - - - -int main() -{ - // static constexpr auto charset = load_charset(uppercase); - - // clang-format off - static constexpr auto inn = Graphic<6,5> { - 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 = Graphic<6,5> { - 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 = Graphic<6,5> { - 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 = ColoredGraphic<4, 4>{ - { - 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 = Graphic<4, 4>{ - 32, 78, 77, 32, - 32, 32, 78, 77, - 78, 77, 32, 32, - 32, 78, 77, 32 }; - - static constexpr auto colored_mountain = ColoredGraphic<4, 4>{ - { - 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<2, 3>{ - 32, 87, - 78, 79, - 78, 77 }; - - static constexpr auto city_map = Map<10, 5>{ - "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<10, 5>{ - "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{ - [](std::uint8_t x, std::uint8_t y) { - /* do nothing for 0th */ - }, - [](std::uint8_t x, std::uint8_t y) { put_graphic(x, y, colored_mountain); }, - [](std::uint8_t x, std::uint8_t y) { - /* do nothing for 2 */ - }, - [](std::uint8_t x, std::uint8_t y) { put_graphic(x, y, town); }, - [](std::uint8_t x, std::uint8_t y) { put_graphic(x, y, inn); }, - [](std::uint8_t x, std::uint8_t y) { put_graphic(x, y, gym); }, - [](std::uint8_t x, std::uint8_t y) { put_graphic(x, y, trading_post); }, - }; - - - const auto draw_map = [](const auto &map) { - for (std::size_t tile = 0; tile < tile_types.size(); ++tile) { - for (std::uint8_t map_large_y = 0; map_large_y < map.height(); ++map_large_y) { - for (std::uint8_t map_large_x = 0; map_large_x < map.width(); ++map_large_x) { - if (map(map_large_x, map_large_y) == tile) { tile_types[tile](map_large_x * 4, map_large_y * 4); } - } - } - } - }; - - GameState game; - game.current_map = &overview_map; - - constexpr auto show_stats = [](const auto &cur_game) { - puts(1, 21, PETSCII("STAMINA:"), Colors::light_grey); - put_hex(12, 21, cur_game.stamina, Colors::white); - put_hex(15, 21, cur_game.max_stamina(), Colors::white); - puts(14, 21, PETSCII("/"), Colors::light_grey); - puts(1, 22, PETSCII("ENDURANCE:"), Colors::light_grey); - put_hex(12, 22, cur_game.endurance, Colors::white); - puts(1, 23, PETSCII("CASH:"), Colors::light_grey); - put_hex(12, 23, cur_game.cash, Colors::white); - }; - - 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"} - }; - struct Menu { - consteval Menu(std::span t_options) - : options{t_options}, width{std::max_element(begin(options), end(options), - [](std::string_view lhs, std::string_view rhs) { - return lhs.size() < rhs.size(); - } - )->size() + 2}, - height{options.size() + 2}, - x{(40 - width) / 2}, - y{(20 - height) / 2} - { - - } - - void highlight(std::uint8_t selection) { - const std::uint8_t cur_y = selection + 1 + y; - for (auto cur_x = 1; cur_x < width - 1; ++cur_x) { - invertc(x + cur_x, cur_y); - } - } - - void unhighlight(std::uint8_t selection) - { - highlight(selection); - } - - void hide(GameState &game) { - displayed = false; - game.redraw = true; - } - - bool show(GameState &game, std::uint8_t &selection) { - if (!displayed) { - displayed = true; - - draw_box(x, y, width, height, Colors::white); - - for (auto cur_y = y + 1; const auto &str : options) { - puts(x+1, cur_y++, str, Colors::grey); - } - - 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 = options.size() - 1; - } - - if (ptr->state.fire()) { - selected = true; - } - - return true; - } - - return false; - } - - std::span options; - std::uint8_t width; - std::uint8_t height; - std::uint8_t x; - std::uint8_t y; - std::uint8_t current_selection{0}; - std::uint8_t next_selection{0}; - bool selected{false}; - bool displayed{false}; - - }; - - Menu m(menu_options); - - bool show_game_menu = false; - - auto eventHandler = overloaded{ [&](const GameState::JoyStick2StateChanged &e) { - auto new_x = game.x; - auto new_y = game.y; - - if (e.state.fire()) { - show_game_menu = true; - return; - } - - if (e.state.up()) { --new_y; } - if (e.state.down()) { ++new_y; } - if (e.state.left()) { --new_x; } - if (e.state.right()) { ++new_x; } - - - game.execute_actions(new_x, new_y, character.graphic); - - screen.show(game.x, game.y, character); - - put_hex(36, 1, e.state.state, Colors::dark_grey); - }, - [](const GameState::TimeElapsed &e) { put_hex(36, 0, e.us.count(), 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); - cls(); - - poke(53280, 0); - poke(53281, 0); - - game.redraw = false; - draw_map(game.current_map->layout); - draw_box(0, 20, 40, 5, Colors::dark_grey); - - puts(10, 20, game.current_map->name, Colors::white); - show_stats(game); - screen.show(game.x, game.y, character); - } - - if (std::uint8_t result = 0; show_game_menu && m.show(game, result)) { - // we had a menu item selected - m.hide(game); - show_game_menu = false; - } - - - - - 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(); - } - } - */ -} diff --git a/examples/simple_game/6502.hpp b/examples/simple_game/6502.hpp new file mode 100644 index 0000000..2ca43ee --- /dev/null +++ b/examples/simple_game/6502.hpp @@ -0,0 +1,20 @@ +#ifndef INC_6502_C_6502_HPP +#define INC_6502_C_6502_HPP + +#include + + +namespace mos6502 { + +static volatile std::uint8_t &memory_loc(const std::uint16_t loc) +{ + return *reinterpret_cast(loc); +} + +static void poke(const std::uint16_t loc, const std::uint8_t value) { memory_loc(loc) = value; } + +static std::uint8_t peek(const std::uint16_t loc) { return memory_loc(loc); } + +}// namespace mos6502 + +#endif// INC_6502_C_6502_HPP diff --git a/examples/simple_game/CMakeLists.txt b/examples/simple_game/CMakeLists.txt new file mode 100644 index 0000000..8db3333 --- /dev/null +++ b/examples/simple_game/CMakeLists.txt @@ -0,0 +1,11 @@ + +# Note: this is compiling for the host OS, so not really useful +# but it is handy as +add_executable(game game.cpp geometry.hpp) + +target_link_libraries( + game + PRIVATE project_options + project_warnings) + +target_compile_options(game PRIVATE -Wno-volatile) diff --git a/examples/chargen.hpp b/examples/simple_game/chargen.hpp similarity index 100% rename from examples/chargen.hpp rename to examples/simple_game/chargen.hpp diff --git a/examples/simple_game/commodore64.hpp b/examples/simple_game/commodore64.hpp new file mode 100644 index 0000000..6eb746f --- /dev/null +++ b/examples/simple_game/commodore64.hpp @@ -0,0 +1,57 @@ +#ifndef INC_6502_C_C64_HPP +#define INC_6502_C_C64_HPP + +#include +#include +#include + +#include "6502.hpp" +#include "petscii.hpp" +#include "vicii.hpp" + +namespace c64 { + +struct Joystick +{ + std::uint8_t state{}; + + [[nodiscard]] constexpr bool up() const noexcept { return (state & 1) == 0; } + [[nodiscard]] constexpr bool left() const noexcept { return (state & 4) == 0; } + [[nodiscard]] constexpr bool fire() const noexcept { return (state & 16) == 0; } + [[nodiscard]] constexpr bool right() const noexcept { return (state & 8) == 0; } + [[nodiscard]] constexpr bool down() const noexcept { return (state & 2) == 0; } +}; + +struct Clock +{ + using milliseconds = std::chrono::duration; + static constexpr std::uint16_t TimerAControl = 0xDC0E; + static constexpr std::uint16_t TimerALowByte = 0xDC04; + static constexpr std::uint16_t TimerAHighByte = 0xDC05; + + + // return elapsed time since last restart + [[nodiscard]] milliseconds restart() noexcept + { + // stop Timer A + mos6502::poke(TimerAControl, 0b00000000); + + // last value + const auto previous_value = static_cast( + mos6502::peek(TimerALowByte) | (static_cast(mos6502::peek(TimerAHighByte)) << 8)); + + // reset timer + mos6502::poke(TimerALowByte, 0xFF); + mos6502::poke(TimerAHighByte, 0xFF); + + // restart timer A + mos6502::poke(TimerAControl, 0b00010001); + + return milliseconds{ 0xFFFF - previous_value }; + } + + Clock() noexcept { [[maybe_unused]] const auto value = restart(); } +}; +}// namespace c64 + +#endif// INC_6502_C_C64_HPP diff --git a/examples/simple_game/game.cpp b/examples/simple_game/game.cpp new file mode 100644 index 0000000..4fbd6d8 --- /dev/null +++ b/examples/simple_game/game.cpp @@ -0,0 +1,553 @@ +#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(); + } + } + */ + } +} \ No newline at end of file diff --git a/examples/simple_game/geometry.hpp b/examples/simple_game/geometry.hpp new file mode 100644 index 0000000..7115e40 --- /dev/null +++ b/examples/simple_game/geometry.hpp @@ -0,0 +1,99 @@ +#ifndef INC_6502_C_GEOMETRY_HPP +#define INC_6502_C_GEOMETRY_HPP + +#include + +namespace geometry { +struct point +{ + std::uint8_t x; + std::uint8_t y; + + [[nodiscard]] constexpr auto operator<=>(const point &other) const = default; + // [[nodiscard]] constexpr bool operator==(const point &other) const = default; + // [[nodiscard]] constexpr bool operator!=(const point &other) const = default; +}; + +constexpr point operator+(const point &lhs, const point &rhs) noexcept +{ + return point{ static_cast(lhs.x + rhs.x), static_cast(lhs.y + rhs.y) }; +} + +struct size +{ + std::uint8_t width; + std::uint8_t height; + [[nodiscard]] constexpr bool operator==(const size &other) const = default; + [[nodiscard]] constexpr bool operator!=(const size &other) const = default; +}; + +struct rect +{ + [[nodiscard]] constexpr std::uint8_t left() const noexcept { return tl.x; } + [[nodiscard]] constexpr std::uint8_t top() const noexcept { return tl.y; } + [[nodiscard]] constexpr std::uint8_t bottom() const noexcept + { + return static_cast(tl.y + size_.height - 1); + } + [[nodiscard]] constexpr std::uint8_t right() const noexcept + { + return static_cast(tl.x + size_.width - 1); + } + + [[nodiscard]] constexpr const point &top_left() const noexcept { return tl; } + [[nodiscard]] constexpr point bottom_right() const noexcept { return point{ right(), bottom() }; } + [[nodiscard]] constexpr point bottom_left() const noexcept { return point{ left(), bottom() }; } + [[nodiscard]] constexpr point top_right() const noexcept { return point{ right(), top() }; } + [[nodiscard]] constexpr std::uint8_t width() const noexcept { return size_.width; } + [[nodiscard]] constexpr std::uint8_t height() const noexcept { return size_.height; } + + [[nodiscard]] constexpr const auto &size() const noexcept { return size_; } + + [[nodiscard]] constexpr auto centered() const noexcept + { + return rect{ + { static_cast((40 - size_.width) / 2), static_cast((20 - size_.height) / 2) }, size_ + }; + } + + [[nodiscard]] constexpr bool intersects(const rect &other) const noexcept + { + const auto my_tl = top_left(); + const auto my_br = bottom_right(); + + const auto other_tl = other.top_left(); + const auto other_br = other.bottom_right(); + + return my_tl <= other_tl && my_br >= other_br; + }; + + point tl; + geometry::size size_; +}; + +struct point_in_size +{ + point p; + size s; + + constexpr auto &operator++() noexcept + { + ++p.x; + if (p.x == s.width) { + p.x = 0; + ++p.y; + } + return *this; + } + [[nodiscard]] constexpr bool operator==(const point_in_size &other) const = default; + [[nodiscard]] constexpr bool operator!=(const point_in_size &other) const = default; + [[nodiscard]] constexpr const point &operator*() const { return p; } +}; + + +constexpr auto begin(const size s) { return point_in_size{ { 0, 0 }, s }; } + +constexpr auto end(const size s) { return point_in_size{ { 0, s.height }, s }; } +}// namespace geometry + +#endif// INC_6502_C_GEOMETRY_HPP diff --git a/examples/simple_game/petscii.hpp b/examples/simple_game/petscii.hpp new file mode 100644 index 0000000..2f83e03 --- /dev/null +++ b/examples/simple_game/petscii.hpp @@ -0,0 +1,69 @@ +#ifndef INC_6502_C_PETSCII_HPP +#define INC_6502_C_PETSCII_HPP + +#include "geometry.hpp" +#include +#include + +namespace petscii { + + +template struct Graphic +{ + std::array data{}; + + static constexpr const auto &size() noexcept { return size_; } + + constexpr Graphic() = default; + + constexpr explicit Graphic(std::array data_) noexcept : data(data_) {} + constexpr Graphic(std::initializer_list data_) noexcept { std::copy(begin(data_), end(data_), begin(data)); } + + constexpr auto &operator[](const geometry::point p) noexcept + { + return data[static_cast(p.y * size_.width + p.x)]; + } + + constexpr const auto &operator[](const geometry::point p) const noexcept { return data[static_cast(p.y * size_.width + p.x)]; } + + constexpr std::size_t match_count(const auto &graphic, const geometry::point start_point) const + { + std::size_t count = 0; + + for (const auto &point : graphic.size()) { + if (graphic[point] == (*this)[point + start_point]) { ++count; } + } + + return count; + } + + + constexpr bool match(const auto &graphic, const geometry::point p) const + { + return match_count(graphic, p) == (graphic.width() * graphic.height()); + } +}; + +template struct ColoredGraphic +{ + Graphic data; + Graphic colors; +}; + + +[[nodiscard]] constexpr char charToPETSCII(char c) noexcept +{ + if (c >= 'A' && c <= 'Z') { return c - 'A' + 1; } + return c; +} + +template constexpr auto PETSCII(const char (&value)[Size]) noexcept +{ + std::array result{}; + std::transform(std::begin(value), std::prev(std::end(value)), std::begin(result), charToPETSCII); + return result; +} +}// namespace petscii + + +#endif// INC_6502_C_PETSCII_HPP diff --git a/examples/simple_game/vicii.hpp b/examples/simple_game/vicii.hpp new file mode 100644 index 0000000..9b99f6b --- /dev/null +++ b/examples/simple_game/vicii.hpp @@ -0,0 +1,153 @@ +#ifndef INC_6502_C_VICII_HPP +#define INC_6502_C_VICII_HPP + +#include "6502.hpp" +#include + +namespace vicii { +enum struct Colors : std::uint8_t { + black = 0, + white = 1, + red = 2, + cyan = 3, + violet = 4, + green = 5, + blue = 6, + yellow = 7, + orange = 8, + brown = 9, + light_red = 10, + dark_grey = 11, + grey = 12, + light_green = 13, + light_blue = 14, + light_grey = 15 +}; + + +[[maybe_unused]] static void decrement_border_color() { mos6502::poke(0xd020, mos6502::peek(0xd020) - 1); } + +static void increment_border_color() { mos6502::poke(0xd020, mos6502::peek(0xd020) + 1); } + +static void putc(geometry::point location, std::uint8_t c, Colors color) +{ + const auto offset = static_cast(location.y * 40 + location.x); + const auto start = static_cast(0x400 + offset); + mos6502::poke(start, c); + mos6502::poke(offset + 0xD800, static_cast(color)); +} + +static std::uint8_t getc(geometry::point location) +{ + const auto start = static_cast(0x400 + (location.y * 40 + location.x)); + return mos6502::peek(start); +} + +static void invertc(geometry::point location) +{ + const auto start = static_cast(0x400 + (location.y * 40 + location.x)); + mos6502::memory_loc(start) += 128; +} + + +static void put_hex(geometry::point start, uint8_t value, Colors color) +{ + const auto put_nibble = [color](geometry::point location, std::uint8_t nibble) { + if (nibble <= 9) { + putc(location, nibble + 48, color); + } else { + putc(location, nibble - 9, color); + } + }; + + put_nibble(start + geometry::point{ 1, 0 }, 0xF & value); + put_nibble(start , 0xF & (value >> 4)); +} + +static void put_hex(geometry::point location, std::uint16_t value, Colors color) +{ + put_hex(location, static_cast(0xFF & (value >> 8)), color); + put_hex(location + geometry::point{ 2, 0 }, static_cast(0xFF & value), color); +} + +static void put_graphic(geometry::point location, const auto &graphic) +{ + for (const auto &p : graphic.size()) { putc(p + location, graphic[p], Colors::white); } +} + +static void put_graphic(geometry::point location, const auto &graphic) requires requires { graphic.colors[{0, 0}]; } +{ + for (const auto &p : graphic.data.size()) { + putc(p + location, graphic.data[p], static_cast(graphic.colors[p])); + } +} + + +static void cls() +{ + for (std::uint16_t i = 0x400; i < 0x400 + 1000; ++i) { mos6502::poke(i, 32); } +} + +struct Screen +{ + void load(geometry::point location, auto &s) + { + for (const auto &p : s.size()) { + s[p] = getc(p + location); + } + } + + void hide(auto &s) + { + if (s.is_shown) { + put_graphic(s.location, s.saved_background); + s.is_shown = false; + } + } + + void show(geometry::point loc, auto &s) + { + if (s.is_shown) { put_graphic(s.location, s.saved_background); } + + s.is_shown = true; + + s.location = loc; + + load(loc, s.saved_background); + + put_graphic(loc, s.graphic); + } +}; + +void draw_vline(geometry::point begin, const geometry::point end, Colors c) +{ + while (begin < end) { + putc(begin, 93, c); + begin = begin + geometry::point{ 0, 1 }; + } +} + +void draw_hline(geometry::point begin, const geometry::point end, Colors c) +{ + while (begin < end) { + putc(begin, 67, c); + begin = begin + geometry::point{1,0}; + } +} + +void draw_box(geometry::rect geo, Colors color) +{ + putc(geo.top_left(), 85, color); + putc(geo.top_right(), 73, color); + putc(geo.bottom_right(), 75, color); + putc(geo.bottom_left(), 74, color); + + draw_hline(geo.top_left() + geometry::point{1,0}, geo.top_right(), color); + draw_hline(geo.bottom_left() + geometry::point{ 1, 0 }, geo.bottom_right(), color); + + draw_vline(geo.top_left() + geometry::point{ 0, 1 }, geo.bottom_left(), color); + draw_vline(geo.top_right() + geometry::point{ 0, 1 }, geo.bottom_right(), color); +} +}// namespace vicii + +#endif// INC_6502_C_VICII_HPP diff --git a/src/6502-c++.cpp b/src/6502-c++.cpp index 4d02dad..9834af6 100644 --- a/src/6502-c++.cpp +++ b/src/6502-c++.cpp @@ -513,16 +513,41 @@ void translate_instruction(const Personality &personality, } case AVR::OpCode::brlt: { const auto [s_set, s_clear] = setup_S_flag(instructions); - instructions.emplace_back(ASMLine::Type::Label, s_set); - instructions.emplace_back(mos6502::OpCode::jmp, o1); - instructions.emplace_back(ASMLine::Type::Label, s_clear); - return; + + + if (o1.value == ".+2") { + // assumes 6502 'borrow' for Carry flag instead of carry, so bcc instead of bcs + std::string new_label_name = "skip_next_instruction_" + std::to_string(instructions.size()); + instructions.emplace_back(ASMLine::Type::Label, s_set); + instructions.emplace_back(mos6502::OpCode::jmp, Operand(Operand::Type::literal, new_label_name)); + instructions.emplace_back(ASMLine::Type::Label, s_clear); + instructions.emplace_back(ASMLine::Type::Directive, new_label_name); + return; + } else { + instructions.emplace_back(ASMLine::Type::Label, s_set); + instructions.emplace_back(mos6502::OpCode::jmp, o1); + instructions.emplace_back(ASMLine::Type::Label, s_clear); + return; + } } case AVR::OpCode::brge: { const auto [s_set, s_clear] = setup_S_flag(instructions); - instructions.emplace_back(ASMLine::Type::Label, s_clear); - instructions.emplace_back(mos6502::OpCode::jmp, o1); - instructions.emplace_back(ASMLine::Type::Label, s_set); + if (o1.value == ".+2") { + // assumes 6502 'borrow' for Carry flag instead of carry, so bcc instead of bcs + std::string new_label_name = "skip_next_instruction_" + std::to_string(instructions.size()); + instructions.emplace_back(ASMLine::Type::Label, s_clear); + instructions.emplace_back(mos6502::OpCode::jmp, Operand(Operand::Type::literal, new_label_name)); + instructions.emplace_back(ASMLine::Type::Label, s_set); + instructions.emplace_back(ASMLine::Type::Directive, new_label_name); + return; + } else { + instructions.emplace_back(ASMLine::Type::Label, s_clear); + instructions.emplace_back(mos6502::OpCode::jmp, o1); + instructions.emplace_back(ASMLine::Type::Label, s_set); + return; + } + + return; } case AVR::OpCode::sub: {