mirror of https://github.com/lefticus/6502-cpp.git
Reorganize game code
This commit is contained in:
parent
2536c4e2c7
commit
f3a37140ba
|
@ -80,3 +80,4 @@ if(ENABLE_FUZZING)
|
|||
endif()
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(examples/simple_game)
|
||||
|
|
|
@ -1,842 +0,0 @@
|
|||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <functional>
|
||||
|
||||
#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<std::size_t Size> constexpr auto PETSCII(const char (&value)[Size]) noexcept
|
||||
{
|
||||
std::array<char, Size - 1> 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<volatile uint8_t *>(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<char, 1024> &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<std::uint8_t>(color));
|
||||
}
|
||||
}
|
||||
|
||||
template<std::uint8_t Width, std::uint8_t Height> struct Graphic
|
||||
{
|
||||
std::array<std::uint8_t, Width * Height> data{};
|
||||
|
||||
static constexpr auto width() noexcept { return Width; }
|
||||
|
||||
static constexpr auto height() noexcept { return Height; }
|
||||
|
||||
constexpr Graphic() = default;
|
||||
|
||||
constexpr Graphic(std::array<uint8_t, Width * Height> data_) noexcept : data(data_) {}
|
||||
constexpr Graphic(std::initializer_list<uint8_t> 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<std::uint8_t Width, std::uint8_t Height> struct ColoredGraphic
|
||||
{
|
||||
Graphic<Width, Height> data;
|
||||
Graphic<Width, Height> colors;
|
||||
};
|
||||
|
||||
|
||||
static constexpr auto load_charset(const std::span<const std::uint8_t, 256 * 8> &bits)
|
||||
{
|
||||
std::array<Graphic<8, 8>, 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<std::uint8_t InWidth, std::uint8_t InHeight>
|
||||
static constexpr auto from_pixels_to_petscii(const Graphic<InWidth, InHeight> &pixels)
|
||||
{
|
||||
Graphic<InWidth / 8, InHeight / 8> 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<std::uint8_t InWidth, std::uint8_t InHeight>
|
||||
static constexpr auto from_pixels_to_2x2(const Graphic<InWidth, InHeight> &pixels)
|
||||
{
|
||||
Graphic<InWidth / 2, InHeight / 2> result{};
|
||||
|
||||
using GlyphType = std::pair<Graphic<2, 2>, std::uint8_t>;
|
||||
constexpr std::array<GlyphType, 17> 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<std::uint8_t>(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<std::uint8_t>(0xFF & value), color);
|
||||
put_hex(x, y, static_cast<std::uint8_t>(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<Colors>(graphic.colors(cur_x, cur_y)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct Clock
|
||||
{
|
||||
using milliseconds = std::chrono::duration<std::uint16_t, std::milli>;
|
||||
|
||||
// return elapsed time since last restart
|
||||
[[nodiscard]] milliseconds restart() noexcept
|
||||
{
|
||||
// stop Timer A
|
||||
poke(0xDC0E, 0b00000000);
|
||||
|
||||
// last value
|
||||
const auto previous_value = static_cast<std::uint16_t>(peek(0xDC04) | (static_cast<uint16_t>(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<std::uint8_t Width, std::uint8_t Height> struct SimpleSprite
|
||||
{
|
||||
std::uint8_t x = 0;
|
||||
std::uint8_t y = 0;
|
||||
bool is_shown = false;
|
||||
Graphic<Width, Height> graphic;
|
||||
Graphic<Width, Height> saved_background{};
|
||||
|
||||
constexpr SimpleSprite(std::initializer_list<uint8_t> data_) noexcept : graphic(data_) {}
|
||||
};
|
||||
|
||||
|
||||
// helper type for the visitor #4
|
||||
template<class... Ts> struct overloaded : Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
// explicit deduction guide (not needed as of C++20)
|
||||
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
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<std::uint8_t Width, std::uint8_t Height> 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<JoyStick2StateChanged, TimeElapsed>;
|
||||
|
||||
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<std::uint8_t Width, std::uint8_t Height> struct Map
|
||||
{
|
||||
std::string_view name;
|
||||
Graphic<Width, Height> layout;
|
||||
|
||||
std::span<const Map_Action> 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<const Map_Action>{}
|
||||
};
|
||||
|
||||
|
||||
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<const Map_Action>(begin(overview_actions), end(overview_actions))
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
static constexpr std::array<void (*)(std::uint8_t, std::uint8_t), 7> 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<const std::string_view> 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<GameState::JoyStick2StateChanged>(&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<const std::string_view> 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<uint8_t>(col);
|
||||
};
|
||||
|
||||
background_color(Colors::WHITE);
|
||||
|
||||
while(true) {
|
||||
if (joystick_down()) {
|
||||
increment_border_color();
|
||||
} else {
|
||||
decrement_border_color();
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef INC_6502_C_6502_HPP
|
||||
#define INC_6502_C_6502_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace mos6502 {
|
||||
|
||||
static volatile std::uint8_t &memory_loc(const std::uint16_t loc)
|
||||
{
|
||||
return *reinterpret_cast<volatile std::uint8_t *>(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
|
|
@ -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)
|
|
@ -0,0 +1,57 @@
|
|||
#ifndef INC_6502_C_C64_HPP
|
||||
#define INC_6502_C_C64_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
|
||||
#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<std::uint16_t, std::milli>;
|
||||
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<std::uint16_t>(
|
||||
mos6502::peek(TimerALowByte) | (static_cast<uint16_t>(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
|
|
@ -0,0 +1,553 @@
|
|||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
#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<geometry::size Size> struct Map
|
||||
{
|
||||
std::string_view name;
|
||||
petscii::Graphic<Size> layout;
|
||||
|
||||
std::span<const Map_Action> 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<geometry::size{ 10, 5 }> const *current_map = nullptr;
|
||||
|
||||
constexpr void set_current_map(const Map<geometry::size{ 10, 5 }> &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<JoyStick2StateChanged, TimeElapsed>;
|
||||
|
||||
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<std::uint16_t>(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<std::uint16_t>(0xD800 + color_loc + offset), static_cast<std::uint8_t>(color));
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr auto load_charset(const std::span<const std::uint8_t, 256 * 8> &bits)
|
||||
{
|
||||
std::array<petscii::Graphic<geometry::size{ 8, 8 }>, 256> results{};
|
||||
|
||||
for (std::size_t idx = 0; idx < 256; ++idx) {
|
||||
petscii::Graphic<geometry::size{ 8, 8 }> 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<geometry::size Size> static constexpr auto from_pixels_to_petscii(const petscii::Graphic<Size> &pixels)
|
||||
{
|
||||
petscii::Graphic<geometry::size{ Size.width / 8, Size.height / 8 }> 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<geometry::size Size> static constexpr auto from_pixels_to_2x2(const petscii::Graphic<Size> &pixels)
|
||||
{
|
||||
petscii::Graphic<geometry::size{ Size.width / 2, Size.height / 2 }> result{};
|
||||
|
||||
using GlyphType = std::pair<petscii::Graphic<geometry::size{ 2, 2 }>, std::uint8_t>;
|
||||
constexpr std::array<GlyphType, 17> 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<geometry::size Size> struct SimpleSprite
|
||||
{
|
||||
geometry::point location{};
|
||||
bool is_shown = false;
|
||||
petscii::Graphic<Size> graphic;
|
||||
petscii::Graphic<Size> saved_background{};
|
||||
|
||||
constexpr SimpleSprite(std::initializer_list<uint8_t> data_) noexcept : graphic(data_) {}
|
||||
};
|
||||
|
||||
|
||||
// helper type for the visitor #4
|
||||
template<class... Ts> struct overloaded : Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
// explicit deduction guide (not needed as of C++20)
|
||||
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
|
||||
struct Menu
|
||||
{
|
||||
consteval Menu(std::span<const std::string_view> t_options)
|
||||
: options{ t_options }, box{ geometry::rect{ { 0, 0 },
|
||||
{ static_cast<std::uint8_t>(
|
||||
std::max_element(begin(options),
|
||||
end(options),
|
||||
[](std::string_view lhs, std::string_view rhs) { return lhs.size() < rhs.size(); })
|
||||
->size()
|
||||
+ 2),
|
||||
static_cast<std::uint8_t>(options.size() + 2) } }
|
||||
.centered() }
|
||||
|
||||
{}
|
||||
|
||||
void highlight(std::uint8_t selection)
|
||||
{
|
||||
const auto cur_y = static_cast<std::uint8_t>(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<std::uint8_t>(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<GameState::JoyStick2StateChanged>(&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<std::uint8_t>(options.size() - 1); }
|
||||
|
||||
if (ptr->state.fire()) { selected = true; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::span<const std::string_view> 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<geometry::size{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 = petscii::Graphic<geometry::size{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 = petscii::Graphic<geometry::size{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 = petscii::ColoredGraphic<geometry::size{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 = petscii::Graphic<geometry::size{4, 4}>{
|
||||
32, 78, 77, 32,
|
||||
32, 32, 78, 77,
|
||||
78, 77, 32, 32,
|
||||
32, 78, 77, 32 };
|
||||
*/
|
||||
|
||||
static constexpr auto colored_mountain = petscii::ColoredGraphic<geometry::size{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<geometry::size{2,3}>{
|
||||
32, 87,
|
||||
78, 79,
|
||||
78, 77 };
|
||||
|
||||
static constexpr auto city_map = Map<geometry::size{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<const Map_Action>{}
|
||||
};
|
||||
|
||||
|
||||
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<geometry::size{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<const Map_Action>(begin(overview_actions), end(overview_actions))
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
static constexpr std::array<void (*)(geometry::point), 7> 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<std::uint8_t>(pos.x * 4), static_cast<std::uint8_t>(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<uint8_t>(col);
|
||||
};
|
||||
|
||||
background_color(Colors::WHITE);
|
||||
|
||||
while(true) {
|
||||
if (joystick_down()) {
|
||||
increment_border_color();
|
||||
} else {
|
||||
decrement_border_color();
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
#ifndef INC_6502_C_GEOMETRY_HPP
|
||||
#define INC_6502_C_GEOMETRY_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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<std::uint8_t>(lhs.x + rhs.x), static_cast<std::uint8_t>(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<std::uint8_t>(tl.y + size_.height - 1);
|
||||
}
|
||||
[[nodiscard]] constexpr std::uint8_t right() const noexcept
|
||||
{
|
||||
return static_cast<std::uint8_t>(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<std::uint8_t>((40 - size_.width) / 2), static_cast<std::uint8_t>((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
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef INC_6502_C_PETSCII_HPP
|
||||
#define INC_6502_C_PETSCII_HPP
|
||||
|
||||
#include "geometry.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
namespace petscii {
|
||||
|
||||
|
||||
template<geometry::size size_> struct Graphic
|
||||
{
|
||||
std::array<std::uint8_t, size_.height * size_.width> data{};
|
||||
|
||||
static constexpr const auto &size() noexcept { return size_; }
|
||||
|
||||
constexpr Graphic() = default;
|
||||
|
||||
constexpr explicit Graphic(std::array<uint8_t, size_.height * size_.width> data_) noexcept : data(data_) {}
|
||||
constexpr Graphic(std::initializer_list<uint8_t> data_) noexcept { std::copy(begin(data_), end(data_), begin(data)); }
|
||||
|
||||
constexpr auto &operator[](const geometry::point p) noexcept
|
||||
{
|
||||
return data[static_cast<std::size_t>(p.y * size_.width + p.x)];
|
||||
}
|
||||
|
||||
constexpr const auto &operator[](const geometry::point p) const noexcept { return data[static_cast<std::size_t>(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<geometry::size size_> struct ColoredGraphic
|
||||
{
|
||||
Graphic<size_> data;
|
||||
Graphic<size_> colors;
|
||||
};
|
||||
|
||||
|
||||
[[nodiscard]] constexpr char charToPETSCII(char c) noexcept
|
||||
{
|
||||
if (c >= 'A' && c <= 'Z') { return c - 'A' + 1; }
|
||||
return c;
|
||||
}
|
||||
|
||||
template<std::size_t Size> constexpr auto PETSCII(const char (&value)[Size]) noexcept
|
||||
{
|
||||
std::array<char, Size - 1> 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
|
|
@ -0,0 +1,153 @@
|
|||
#ifndef INC_6502_C_VICII_HPP
|
||||
#define INC_6502_C_VICII_HPP
|
||||
|
||||
#include "6502.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
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<std::uint16_t>(location.y * 40 + location.x);
|
||||
const auto start = static_cast<std::uint16_t>(0x400 + offset);
|
||||
mos6502::poke(start, c);
|
||||
mos6502::poke(offset + 0xD800, static_cast<std::uint8_t>(color));
|
||||
}
|
||||
|
||||
static std::uint8_t getc(geometry::point location)
|
||||
{
|
||||
const auto start = static_cast<std::uint16_t>(0x400 + (location.y * 40 + location.x));
|
||||
return mos6502::peek(start);
|
||||
}
|
||||
|
||||
static void invertc(geometry::point location)
|
||||
{
|
||||
const auto start = static_cast<std::uint16_t>(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<std::uint8_t>(0xFF & (value >> 8)), color);
|
||||
put_hex(location + geometry::point{ 2, 0 }, static_cast<std::uint8_t>(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<Colors>(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
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in New Issue