1
0
mirror of https://github.com/lefticus/6502-cpp.git synced 2024-06-09 20:29:28 +00:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Jason Turner
165c63701a Stub in support for commander x16 2021-09-09 22:35:04 -06:00
Jason Turner
95f50f59af Minor updates to petscii generator 2021-09-05 17:35:12 -06:00
Jason Turner
f410dfd38e Presentation code working 2021-08-31 17:57:08 -06:00
Jason Turner
bda3c6a9fc "die" from exhaustion and full map movement 2021-07-18 18:57:17 -06:00
Jason Turner
17962801a0 Remove redundant flag fix-ups 2021-06-10 21:43:36 -06:00
Jason Turner
d5c8460e83 Support labels + offset in translation 2021-06-10 21:37:02 -06:00
Jason Turner
f3a37140ba Reorganize game code 2021-06-10 21:08:27 -06:00
17 changed files with 1786 additions and 871 deletions

View File

@ -80,3 +80,4 @@ if(ENABLE_FUZZING)
endif()
add_subdirectory(src)
add_subdirectory(examples/simple_game)

View File

@ -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();
}
}
*/
}

7
examples/hello_x16.cpp Normal file
View File

@ -0,0 +1,7 @@
#include <x16.hpp>
int main()
{
x16::vera::puts({15,20}, petscii::PETSCII("HELLO X16 FROM C++!"));
}

View File

@ -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

View File

@ -0,0 +1,11 @@
# Note: this is compiling for the host OS, so not really useful
# but it is handy as a sanity check
add_executable(game game.cpp geometry.hpp)
target_link_libraries(
game
PRIVATE project_options
project_warnings)
target_compile_options(game PRIVATE -Wno-volatile)

View File

@ -1,7 +1,8 @@
#include <cstdint>
constexpr std::uint8_t uppercase[] = {
0x3C, 0x66, 0x6E, 0x6E, 0x60, 0x62, 0x3C, 0x00,
namespace petscii {
constexpr static std::array<std::uint8_t, 256*8> uppercase = {
0x3C, 0x66, 0x6E, 0x6E, 0x60, 0x62, 0x3C, 0x00,
0x18, 0x3C, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00,
0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00,
0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00,
@ -258,7 +259,8 @@ constexpr std::uint8_t uppercase[] = {
0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,
0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0,
};
constexpr std::uint8_t lowercase[] = { 0x3C, 0x66, 0x6E, 0x6E, 0x60, 0x62, 0x3C, 0x00,
constexpr static std::uint8_t lowercase[] = {
0x3C, 0x66, 0x6E, 0x6E, 0x60, 0x62, 0x3C, 0x00,
0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x3E, 0x00,
0x00, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00,
0x00, 0x00, 0x3C, 0x60, 0x60, 0x60, 0x3C, 0x00,
@ -514,3 +516,5 @@ constexpr std::uint8_t lowercase[] = { 0x3C, 0x66, 0x6E, 0x6E, 0x60, 0x62, 0x3C,
0xE7, 0xE7, 0xE7, 0x07, 0x07, 0xFF, 0xFF, 0xFF,
0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,
0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0 };
}

View File

@ -0,0 +1,60 @@
#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; }
[[nodiscard]] constexpr bool operator==(const Joystick &other) const = default;
[[nodiscard]] constexpr bool operator!=(const Joystick &other) const = default;
};
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

View File

@ -0,0 +1,700 @@
#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;
using Square_Passable = bool (*)(const std::uint8_t type) noexcept;
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, std::size_t Scale> struct Map
{
std::string_view name;
petscii::Graphic<Size> layout;
Square_Passable passable = nullptr;
std::uint8_t step_scale;
std::span<const Map_Action> actions;
[[nodiscard]] constexpr std::uint8_t location_value(geometry::point loc) const noexcept
{
const auto descaled_location =
geometry::point{ static_cast<std::uint8_t>(loc.x / Scale), static_cast<std::uint8_t>(loc.y / Scale) };
return layout[descaled_location];
}
[[nodiscard]] constexpr bool location_passable(geometry::point loc, geometry::size obj_size) const noexcept
{
if (passable != nullptr) {
for (const auto &p : obj_size) {
if (!passable(location_value(p + loc))) { return false; }
}
return true;
}
return false;
}
};
struct GameState
{
enum struct State { Walking, SystemMenu, AboutBox, Exit, AlmostDead };
State state = State::Walking;
std::uint8_t endurance{ 8 };
std::uint8_t stamina{ max_stamina() };
std::uint16_t cash{ 100 };
std::uint8_t step_counter{ 0 };
std::uint8_t stamina_counter{ 0 };
geometry::point location{ 20, 12 };
bool redraw = true;
bool redraw_stats = true;
c64::Clock game_clock{};
Map<geometry::size{ 10, 5 }, 4> const *current_map = nullptr;
Map<geometry::size{ 10, 5 }, 4> const *last_map = nullptr;
constexpr void goto_last_map(geometry::point new_location)
{
std::swap(current_map, last_map);
location = new_location;
redraw = true;
}
constexpr void set_current_map(const Map<geometry::size{ 10, 5 }, 4> &new_map)
{
last_map = std::exchange(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; }
if (current_map && current_map->location_passable(new_location, character.size())) {
step_counter += current_map->step_scale;
while (step_counter >= endurance) {
redraw_stats = true;
step_counter -= endurance;
if (stamina == 1) {
state = State::AlmostDead;
} else {
stamina -= 1;
}
stamina_counter += 1;
if (stamina_counter == endurance * 3) {
endurance += 1;
stamina_counter = 0;
}
}
location = new_location;
for (const 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() };
}
};
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 = petscii::load_charset(petscii::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> 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 TextBox
{
consteval TextBox(std::span<const std::string_view> t_lines)
: lines{ t_lines }, box{ geometry::rect{ { 0, 0 },
{ static_cast<std::uint8_t>(
std::max_element(begin(lines),
end(lines),
[](std::string_view lhs, std::string_view rhs) { return lhs.size() < rhs.size(); })
->size()
+ 1),
static_cast<std::uint8_t>(lines.size() + 1) } }
.centered() }
{}
void hide(GameState &game)
{
displayed = false;
game.redraw = true;
}
bool show([[maybe_unused]] GameState &game)
{
if (!displayed) {
displayed = true;
clear(box, vicii::Colors::grey);
draw_box(box, vicii::Colors::white);
for (auto pos = box.top_left() + geometry::point{ 1, 1 }; const auto &str : lines) {
puts(pos, str, vicii::Colors::grey);
pos = pos + geometry::point{ 0, 1 };
}
}
if (selected) {
selected = false;
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.fire()) { selected = true; }
return true;
}
return false;
}
std::span<const std::string_view> lines;
geometry::rect box;
bool selected{ false };
bool displayed{ false };
};
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()
+ 1),
static_cast<std::uint8_t>(options.size() + 1) } }
.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(); ++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;
clear(box, vicii::Colors::grey);
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) {
// wrap around up and down during selection
if (ptr->state.up()) {
if (current_selection == 0) {
next_selection = static_cast<std::uint8_t>(options.size() - 1);
} else {
next_selection = current_selection - 1;
}
}
if (ptr->state.down()) {
if (current_selection == options.size() - 1) {
next_selection = 0;
} else {
next_selection = current_selection + 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 };
};
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 = 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::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 ore_town_actions = std::array {
Map_Action { {{0,19},{40,1}}, [](GameState &g) { g.goto_last_map({0, 4}); } },
};
static constexpr auto ore_town = Map<geometry::size{10,5}, 4>{
"ore town",
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 4, 0, 0, 0, 0, 6, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 5, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
},
[](const std::uint8_t type) noexcept { return type != 1; },
1,
ore_town_actions
};
static constexpr auto wool_town_actions = std::array {
Map_Action { {{0,0},{1,20}}, [](GameState &g) { g.goto_last_map({6, 12}); } },
Map_Action { {{39,0},{1,20}}, [](GameState &g) { g.goto_last_map({12, 12}); } },
};
static constexpr auto wool_town = Map<geometry::size{10,5}, 4>{
"wool town",
{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
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,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
},
[](const std::uint8_t type) noexcept { return type != 1; },
1,
wool_town_actions
};
static constexpr auto wheat_town_actions = std::array {
Map_Action { {{0,0},{1,20}}, [](GameState &g) { g.goto_last_map({22, 16}); } },
Map_Action { {{39,0},{1,20}}, [](GameState &g) { g.goto_last_map({28, 16}); } },
Map_Action { {{0,0},{40,1}}, [](GameState &g) { g.goto_last_map({22, 13}); } },
};
static constexpr auto wheat_town = Map<geometry::size{10,5}, 4>{
"wheat 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,
},
[](const std::uint8_t type) noexcept { return type != 1; },
1,
wheat_town_actions
};
static constexpr auto brick_town_actions = std::array {
Map_Action { {{0,0},{1,20}}, [](GameState &g) { g.goto_last_map({30, 4}); } },
Map_Action { {{39,0},{1,20}}, [](GameState &g) { g.goto_last_map({36, 4}); } },
Map_Action { {{0,0},{40,1}}, [](GameState &g) { g.goto_last_map({32, 0}); } },
Map_Action { {{0,19},{40,1}}, [](GameState &g) { g.goto_last_map({32, 8}); } },
};
static constexpr auto brick_town = Map<geometry::size{10,5}, 4>{
"brick 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,
},
[](const std::uint8_t type) noexcept { return type != 1; },
1,
brick_town_actions
};
static constexpr auto wood_town_actions = std::array {
Map_Action { {{0,0},{1,20}}, [](GameState &g) { g.goto_last_map({14, 0}); } },
Map_Action { {{39,0},{1,20}}, [](GameState &g) { g.goto_last_map({20, 0}); } },
Map_Action { {{0,19},{40,1}}, [](GameState &g) { g.goto_last_map({16, 4}); } },
};
static constexpr auto wood_town = Map<geometry::size{10,5}, 4>{
"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,
},
[](const std::uint8_t type) noexcept { return type != 1; },
1,
wood_town_actions
};
static constexpr auto overview_actions = std::array {
Map_Action { {{0,0},{4,4}}, [](GameState &g) { g.set_current_map(ore_town); } },
Map_Action { {{8,12},{4,4}}, [](GameState &g) { g.set_current_map(wool_town); } },
Map_Action { {{24,16},{4,4}}, [](GameState &g) { g.set_current_map(wheat_town); } },
Map_Action { {{32,4},{4,4}}, [](GameState &g) { g.set_current_map(brick_town); } },
Map_Action { {{16,0},{4,4}}, [](GameState &g) { g.set_current_map(wood_town); } }
};
static constexpr auto overview_map = Map<geometry::size{10, 5}, 4>{
"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,
},
[](const std::uint8_t type) noexcept { return type != 1; },
10,
overview_actions
};
// clang-format on
static constexpr std::array<void (*)(geometry::point), 7> tile_types{
[](geometry::point) {},
[](geometry::point p) { vicii::put_graphic(p, 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;
game.state = GameState::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{ "about game" }, std::string_view{ "exit menu" } };
Menu m(menu_options);
static constexpr auto url = petscii::PETSCII("HTTPS://GITHUB.COM/LEFTICUS/6502-CPP");
static constexpr auto about_text = std::array{ std::string_view{ "created in c++20 by jason turner" },
std::string_view{ "using an automated conversion of" },
std::string_view{ "gcc generated avr code to 6502" },
std::string_view{ "assembly." },
std::string_view{ url.data(), url.size() } };
TextBox about_box(about_text);
static constexpr auto almost_dead_text = std::array{ std::string_view{ "you became so exhausted that you" },
std::string_view{ "passed out and passers by stole" },
std::string_view{ "some of your cash and items." },
std::string_view{ "" },
std::string_view{ "a kind soul has dropped you off at a" },
std::string_view{ "nearby inn." } };
TextBox almost_dead(almost_dead_text);
auto eventHandler = overloaded{ [&](const GameState::JoyStick2StateChanged &e) {
auto new_loc = game.location;
put_hex({ 36, 1 }, e.state.state, vicii::Colors::dark_grey);
if (e.state.fire()) {
game.state = GameState::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; }
if (new_loc != game.location) {
game.execute_actions(new_loc, character.graphic);
screen.show(game.location, character);
}
},
[](const GameState::TimeElapsed &e) {
vicii::put_hex({ 36, 0 }, e.us.count(), vicii::Colors::dark_grey);
} };
while (game.state != GameState::State::Exit) {
const auto next_event = game.next_event();
if (not m.process_event(next_event) && not about_box.process_event(next_event)
&& not almost_dead.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;
game.redraw_stats = true;
draw_map(game.current_map->layout);
draw_box(geometry::rect{ { 0, 20 }, { 39, 4 } }, vicii::Colors::dark_grey);
puts(geometry::point{ 10, 20 }, game.current_map->name, vicii::Colors::white);
screen.show(game.location, character);
}
if (game.redraw_stats) {
show_stats(game);
game.redraw_stats = false;
}
if (std::uint8_t result = 0; game.state == GameState::State::SystemMenu && m.show(game, result)) {
// we had a menu item selected
m.hide(game);
if (result == 0) {
game.state = GameState::State::AboutBox;
} else {
game.state = GameState::State::Walking;
}
} else if (game.state == GameState::State::AboutBox && about_box.show(game)) {
about_box.hide(game);
game.state = GameState::State::Walking;
} else if (game.state == GameState::State::AlmostDead && almost_dead.show(game)) {
almost_dead.hide(game);
game.set_current_map(wheat_town);
game.cash /= 2;
game.stamina = game.max_stamina();
game.state = GameState::State::Walking;
}
}
}

View File

@ -0,0 +1,96 @@
#ifndef INC_6502_C_GEOMETRY_HPP
#define INC_6502_C_GEOMETRY_HPP
#include <cstdint>
#include <compare>
namespace geometry {
struct point
{
std::uint8_t x;
std::uint8_t y;
[[nodiscard]] constexpr auto 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);
}
[[nodiscard]] constexpr std::uint8_t right() const noexcept { return static_cast<std::uint8_t>(tl.x + size_.width); }
[[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_; }
// returns a rectangle of this size, but centered in the screen (40x20)
[[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.x < other_br.x && my_br.x > other_tl.x && my_tl.y < other_br.y && my_br.y > other_tl.y;
};
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

View File

@ -0,0 +1,155 @@
#ifndef INC_6502_C_PETSCII_HPP
#define INC_6502_C_PETSCII_HPP
#include "geometry.hpp"
#include <algorithm>
#include <array>
#include <span>
namespace petscii {
enum named_chars : std::uint8_t {
closed_circle = 81
};
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.size().width * graphic.size().height);
}
};
template<geometry::size size_> struct ColoredGraphic
{
Graphic<size_> data;
Graphic<size_> colors;
};
[[nodiscard]] constexpr char charToPETSCII2(char c) noexcept
{
if (c >= 'a' && c <= 'z') { return c - 'a' + 1; }
if (c >= 'A' && c <= 'Z') { return c - 'A' + 65; }
if (c == '@') { return 0; }
if (c =='_') { return 100; }
return c;
}
[[nodiscard]] constexpr char charToPETSCII(char c) noexcept
{
if (c == '@') { return 0; }
if (c >= 'A' && c <= 'Z') { return c - 'A' + 1; }
return c;
}
template<std::size_t Size> constexpr auto PETSCII2(const char (&value)[Size]) noexcept
{
std::array<char, Size - 1> result{};
std::transform(std::begin(value), std::prev(std::end(value)), std::begin(result), charToPETSCII2);
return result;
}
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 constexpr auto load_charset(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{ 1, row }] = (0b0100'0000 & input_row) == 0 ? 0 : 1;
glyph[geometry::point{ 2, row }] = (0b0010'0000 & input_row) == 0 ? 0 : 1;
glyph[geometry::point{ 3, row }] = (0b0001'0000 & input_row) == 0 ? 0 : 1;
glyph[geometry::point{ 4, row }] = (0b0000'1000 & input_row) == 0 ? 0 : 1;
glyph[geometry::point{ 5, row }] = (0b0000'0100 & input_row) == 0 ? 0 : 1;
glyph[geometry::point{ 6, row }] = (0b0000'0010 & input_row) == 0 ? 0 : 1;
glyph[geometry::point{ 7, row }] = (0b0000'0001 & input_row) == 0 ? 0 : 1;
}
results[idx] = glyph;
}
return results;
}
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.size().width; x += 2) {
for (uint8_t y = 0; y < pixels.size().height; y += 2) {
for (const auto &glyph : lookup_map) {
if (pixels.match(glyph.first, {x, y})) {
result[geometry::point{static_cast<std::uint8_t>(x / 2), static_cast<std::uint8_t>(y / 2)}] = glyph.second;
break;// go to next Y, we found our match
}
}
}
}
return result;
}
}// namespace petscii
#endif// INC_6502_C_PETSCII_HPP

View File

@ -0,0 +1,187 @@
#ifndef INC_6502_C_VICII_HPP
#define INC_6502_C_VICII_HPP
#include "6502.hpp"
#include "geometry.hpp"
#include <cstdint>
#include <algorithm>
#include <iterator>
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 inline 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 inline 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 inline 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 inline void set_background(const vicii::Colors color) {
mos6502::poke(53280, static_cast<std::uint8_t>(color));
}
static inline void set_border(const vicii::Colors color) {
mos6502::poke(53281, static_cast<std::uint8_t>(color));
}
static inline 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;
using namespace std;
std::copy(begin(range), end(range), &mos6502::memory_loc(start));
for (std::uint16_t color_loc = 0; color_loc < size(range); ++color_loc) {
mos6502::poke(static_cast<std::uint16_t>(0xD800 + color_loc + offset), static_cast<std::uint8_t>(color));
}
}
static inline 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 inline 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 inline void put_graphic(geometry::point location, const auto &graphic)
{
for (const auto &p : graphic.size()) { putc(p + location, graphic[p], Colors::white); }
}
static inline 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 inline 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);
}
};
static 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 };
}
}
static 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};
}
}
static inline 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);
}
static inline void clear(geometry::rect box, Colors color) {
for (const auto &p : box.size()) {
putc(p + box.top_left(), ' ', color);
}
}
}// namespace vicii
#endif// INC_6502_C_VICII_HPP

View File

@ -0,0 +1,29 @@
#ifndef INC_6502_C_CPP_X16
#define INC_6502_C_CPP_X16
#include <6502.hpp>
#include <geometry.hpp>
#include <span>
#include <petscii.hpp>
namespace x16::vera {
static constexpr std::uint16_t ADDR_L = 0x9f20;
static constexpr std::uint16_t ADDR_M = 0x9f21;
static constexpr std::uint16_t ADDR_H = 0x9f22;
static constexpr std::uint16_t DATA0 = 0x9f23;
// this assumes we are already in a text mode, initialized by the kernal
static void puts(const geometry::point &loc, std::span<const char> str) {
mos6502::poke(ADDR_L, loc.x * 2);
mos6502::poke(ADDR_M, loc.y);
mos6502::poke(ADDR_H, 0x20); // auto increment by 2 after each poke of the character, we are skipping the attribute byte
for (const auto c : str) {
mos6502::poke(DATA0, c);
}
}
}
#endif

280
include/np_int.hpp Normal file
View File

@ -0,0 +1,280 @@
#include <algorithm>
#include <concepts>
#include <cstdint>
#include <utility>
template<std::integral Int> struct np_int
{
Int val;
// intentionally implicit conversions
// from the underlying type allowed
[[gnu::always_inline]] constexpr np_int(Int i) noexcept : val{ i } {}
[[gnu::always_inline]] constexpr explicit operator Int() noexcept { return val; }
[[gnu::always_inline]] constexpr auto operator<=>(const np_int &) const noexcept = default;
template<typename Other>
[[gnu::always_inline]] constexpr auto operator<=>(const np_int<Other> &other) const noexcept
requires(std::is_signed_v<Int> == std::is_signed_v<Other>)
{
return val == other.val ? std::strong_ordering::equal
: val < other.val ? std::strong_ordering::less
: std::strong_ordering::greater;
}
};
template<typename T> constexpr bool np_or_integral_v = std::is_integral_v<T>;
template<typename T> constexpr bool np_or_integral_v<np_int<T>> = true;
template<typename T> concept np_or_integral = np_or_integral_v<T>;
template<std::integral Int> [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto val(Int i) noexcept
{
return i;
}
template<typename T> [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto val(np_int<T> i) noexcept
{
return i.val;
}
template<typename LHS, typename RHS> consteval auto calculate_common_int()
{
constexpr auto size = std::max(sizeof(LHS), sizeof(RHS));
if constexpr (std::is_unsigned_v<LHS> && std::is_unsigned_v<RHS>) {
if constexpr (size == 1) {
return std::uint8_t{};
} else if constexpr (size == 2) {
return std::uint16_t{};
} else if constexpr (size == 4) {
return std::uint32_t{};
} else if constexpr (size == 8) {
return std::uint64_t{};
}
} else {
if constexpr (size == 1) {
return std::int8_t{};
} else if constexpr (size == 2) {
return std::int16_t{};
} else if constexpr (size == 4) {
return std::int32_t{};
} else if constexpr (size == 8) {
return std::int64_t{};
}
}
}
template<std::integral LHS, std::integral RHS> using common_int_t = decltype(calculate_common_int<LHS, RHS>());
template<std::integral Int>
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator>>(np_int<Int> lhs,
np_or_integral auto rhs) noexcept
{
return np_int<Int>{ static_cast<Int>(lhs.val >> static_cast<Int>(rhs)) };
}
template<std::integral Int>
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator<<(np_int<Int> lhs,
np_or_integral auto rhs) noexcept
{
return np_int<Int>{ static_cast<Int>(lhs.val << static_cast<Int>(rhs)) };
}
template<std::integral LHS, std::integral RHS>
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator+(np_int<LHS> lhs, np_int<RHS> rhs) noexcept
{
using Type = common_int_t<LHS, RHS>;
return np_int{ static_cast<Type>(lhs.val + rhs.val) };
}
template<std::integral LHS, std::integral RHS>
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator-(np_int<LHS> lhs, np_int<RHS> rhs) noexcept
{
using Type = common_int_t<LHS, RHS>;
return np_int{ static_cast<Type>(lhs.val - rhs.val) };
}
template<std::integral LHS, std::integral RHS>
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator*(np_int<LHS> lhs, np_int<RHS> rhs) noexcept
{
using Type = common_int_t<LHS, RHS>;
return np_int{ static_cast<Type>(lhs.val * rhs.val) };
}
template<std::integral LHS, std::integral RHS>
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator/(np_int<LHS> lhs, np_int<RHS> rhs) noexcept
{
using Type = common_int_t<LHS, RHS>;
return np_int{ static_cast<Type>(lhs.val / rhs.val) };
}
template<std::integral LHS>
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator|(np_int<LHS> lhs, np_int<LHS> rhs) noexcept
{
return np_int{ static_cast<LHS>(lhs.val | rhs.val) };
}
template<std::integral LHS>
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator&(np_int<LHS> lhs, np_int<LHS> rhs) noexcept
{
return np_int{ static_cast<LHS>(lhs.val & rhs.val) };
}
template<std::integral LHS, std::integral RHS>
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator%(np_int<LHS> lhs, np_int<RHS> rhs) noexcept
{
using Type = common_int_t<LHS, RHS>;
return np_int{ static_cast<Type>(lhs.val % rhs.val) };
}
template<std::integral LHS, std::integral RHS>
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator^(np_int<LHS> lhs, np_int<LHS> rhs) noexcept
{
return np_int{ static_cast<LHS>(lhs.val ^ rhs.val) };
}
template<std::integral LHS>
[[gnu::always_inline]] constexpr auto &operator+=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
{
lhs.val += static_cast<LHS>(rhs);
return lhs;
}
template<std::integral LHS>
[[gnu::always_inline]] constexpr auto &operator-=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
{
lhs.val -= static_cast<LHS>(rhs);
return lhs;
}
template<std::integral LHS>
[[gnu::always_inline]] constexpr auto &operator/=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
{
lhs.val /= static_cast<LHS>(rhs);
return lhs;
}
template<std::integral LHS>
[[gnu::always_inline]] constexpr auto &operator*=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
{
lhs.val /= static_cast<LHS>(rhs);
return lhs;
}
template<std::integral LHS>
[[gnu::always_inline]] constexpr auto &operator%=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
{
lhs.val %= static_cast<LHS>(rhs);
return lhs;
}
template<std::integral LHS>
[[gnu::always_inline]] constexpr auto &operator&=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
{
lhs.val &= static_cast<LHS>(rhs);
return lhs;
}
template<std::integral LHS>
[[gnu::always_inline]] constexpr auto &operator|=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
{
lhs.val |= static_cast<LHS>(rhs);
return lhs;
}
template<std::integral LHS>
[[gnu::always_inline]] constexpr auto &operator^=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
{
lhs.val ^= static_cast<LHS>(rhs);
return lhs;
}
template<std::integral LHS>
[[gnu::always_inline]] constexpr auto &operator<<=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
{
lhs.val <<= static_cast<LHS>(rhs);
return lhs;
}
template<std::integral LHS>
[[gnu::always_inline]] constexpr auto &operator>>=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
{
lhs.val >>= static_cast<LHS>(rhs);
return lhs;
}
using uint_np8_t = np_int<std::uint8_t>;
using uint_np16_t = np_int<std::uint16_t>;
using uint_np32_t = np_int<std::uint32_t>;
using uint_np64_t = np_int<std::uint64_t>;
using int_np8_t = np_int<std::int8_t>;
using int_np16_t = np_int<std::int16_t>;
using int_np32_t = np_int<std::int32_t>;
using int_np64_t = np_int<std::int64_t>;
template<typename T>
constexpr bool np_meets_requirements =
sizeof(T) == sizeof(np_int<T>)
&& std::is_trivially_destructible_v<np_int<T>> &&std::is_trivially_move_constructible_v<np_int<T>>
&&std::is_trivially_copy_constructible_v<np_int<T>> &&std::is_trivially_copy_assignable_v<np_int<T>>
&&std::is_trivially_move_assignable_v<np_int<T>>;
static_assert(np_meets_requirements<std::uint8_t>);
static_assert(np_meets_requirements<std::uint16_t>);
static_assert(np_meets_requirements<std::uint32_t>);
static_assert(np_meets_requirements<std::uint64_t>);
static_assert(np_meets_requirements<std::int8_t>);
static_assert(np_meets_requirements<std::int16_t>);
static_assert(np_meets_requirements<std::int32_t>);
static_assert(np_meets_requirements<std::int64_t>);
// ensures that First paired with any of Rest, in any order
// results in the same type as First again
template<std::integral First, std::integral... Rest>
constexpr bool is_same_combinations_v = (std::is_same_v<First, common_int_t<First, Rest>> && ...)
&& (std::is_same_v<First, common_int_t<Rest, First>> && ...);
static_assert(is_same_combinations_v<std::uint8_t, std::uint8_t>);
static_assert(is_same_combinations_v<std::uint16_t, std::uint8_t, std::uint16_t>);
static_assert(is_same_combinations_v<std::uint32_t, std::uint8_t, std::uint16_t, std::uint32_t>);
static_assert(is_same_combinations_v<std::uint64_t, std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t>);
static_assert(is_same_combinations_v<std::int8_t, std::uint8_t>);
static_assert(is_same_combinations_v<std::int16_t, std::uint8_t, std::uint16_t>);
static_assert(is_same_combinations_v<std::int32_t, std::uint8_t, std::uint16_t, std::uint32_t>);
static_assert(is_same_combinations_v<std::int64_t, std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t>);
static_assert(is_same_combinations_v<std::int8_t, std::int8_t>);
static_assert(is_same_combinations_v<std::int16_t, std::int8_t, std::int16_t>);
static_assert(is_same_combinations_v<std::int32_t, std::int8_t, std::int16_t, std::int32_t>);
static_assert(is_same_combinations_v<std::int64_t, std::int8_t, std::int16_t, std::int32_t, std::int64_t>);
auto left_shift(uint_np8_t value, int val) { return value << val; }
auto left_shift(std::uint8_t value, int val) { return static_cast<std::uint8_t>(value << val); }
std::uint8_t unsigned_value();
std::int8_t signed_value();
int main(int argc, const char **)
{
uint_np8_t a{ 15 };
auto value = a + uint_np16_t{ 10 };
value += uint_np16_t{ 16 };
[[maybe_unused]] const auto comparison = value < uint_np8_t{ 3 };
// return static_cast<std::uint16_t>(value);
int i = 1;
[[maybe_unused]] int v = (i << argc);
return std::cmp_less(unsigned_value(), signed_value());
}

View File

@ -262,7 +262,7 @@ bool optimize(std::vector<mos6502> &instructions, [[maybe_unused]] const Persona
if (instructions[op].opcode == mos6502::OpCode::lda || instructions[op].opcode == mos6502::OpCode::bcc
|| instructions[op].opcode == mos6502::OpCode::bcs || instructions[op].opcode == mos6502::OpCode::ldy
|| instructions[op].opcode == mos6502::OpCode::inc || instructions[op].opcode == mos6502::OpCode::clc
|| instructions[op].opcode == mos6502::OpCode::sec) {
|| instructions[op].opcode == mos6502::OpCode::sec || instructions[op].text.starts_with("; Handle N / S")) {
if (instructions[op - 1].text == "; END remove if next is lda, bcc, bcs, ldy, inc, clc, sec"
|| (instructions[op - 2].text == "; END remove if next is lda, bcc, bcs, ldy, inc, clc, sec"
&& instructions[op - 1].type == ASMLine::Type::Directive)) {

View File

@ -0,0 +1,61 @@
#ifndef INC_6502_C_X16_HPP
#define INC_6502_C_X16_HPP
#include "../personality.hpp"
struct X16 : Personality
{
void insert_autostart_sequence(std::vector<mos6502> &new_instructions) const override
{
constexpr static auto start_address = 0x0801;
// first 2 bytes is the load address for a PRG file.
new_instructions.emplace_back(ASMLine::Type::Directive, ".word " + std::to_string(start_address));
new_instructions.emplace_back(ASMLine::Type::Directive, "* = " + std::to_string(start_address));
new_instructions.emplace_back(ASMLine::Type::Directive, "; jmp to start of program with BASIC");
new_instructions.emplace_back(ASMLine::Type::Directive, ".byt $0B,$08,$0A,$00,$9E,$32,$30,$36,$31,$00,$00,$00");
}
[[nodiscard]] Operand get_register(const int reg_num) const override
{
switch (reg_num) {
// based on c64, they are basically the same system, but I'm using a different file so they can fork from each other
case 0: return Operand(Operand::Type::literal, "$4e");// unused, int->fp routine pointer
case 1: return Operand(Operand::Type::literal, "$4f");
case 2: return Operand(Operand::Type::literal, "$50");// unused
case 3: return Operand(Operand::Type::literal, "$51");// unused
case 4: return Operand(Operand::Type::literal, "$52");// bit buffer for rs232
case 5: return Operand(Operand::Type::literal, "$53");// counter for rs232
case 6: return Operand(Operand::Type::literal, "$54");// unused
case 7: return Operand(Operand::Type::literal, "$55");// unused
case 8: return Operand(Operand::Type::literal, "$56");// unused
case 9: return Operand(Operand::Type::literal, "$57");// unused
case 10: return Operand(Operand::Type::literal, "$58");// Current BASIC line number
case 11: return Operand(Operand::Type::literal, "$59");// Current BASIC line number
case 12: return Operand(Operand::Type::literal, "$5a");// arithmetic register #3
case 13: return Operand(Operand::Type::literal, "$5b");
case 14: return Operand(Operand::Type::literal, "$5c");
case 15: return Operand(Operand::Type::literal, "$5d");
case 16: return Operand(Operand::Type::literal, "$5e");
case 17: return Operand(Operand::Type::literal, "$5f");
case 18: return Operand(Operand::Type::literal, "$60");
case 19: return Operand(Operand::Type::literal, "$61");
case 20: return Operand(Operand::Type::literal, "$62");
case 21: return Operand(Operand::Type::literal, "$63");
case 22: return Operand(Operand::Type::literal, "$64");
case 23: return Operand(Operand::Type::literal, "$65");
case 24: return Operand(Operand::Type::literal, "$66");
case 25: return Operand(Operand::Type::literal, "$67");
case 26: return Operand(Operand::Type::literal, "$68");
case 27: return Operand(Operand::Type::literal, "$69");
case 28: return Operand(Operand::Type::literal, "$6a");
case 29: return Operand(Operand::Type::literal, "$6b");
case 30: return Operand(Operand::Type::literal, "$6c");
case 31: return Operand(Operand::Type::literal, "$6d");
}
throw std::runtime_error("Unhandled register number: " + std::to_string(reg_num));
}
};
#endif// INC_6502_C_X16_HPP

View File

@ -19,6 +19,7 @@
#include "include/lib1funcs.hpp"
#include "include/optimizer.hpp"
#include "include/personalities/c64.hpp"
#include "include/personalities/x16.hpp"
int to_int(const std::string_view sv)
{
@ -92,6 +93,7 @@ struct AVR : ASMLine
adiw,
add,
andi,
asr,
breq,
brge,
@ -129,6 +131,7 @@ struct AVR : ASMLine
nop,
OR,
ori,
out,
pop,
@ -182,6 +185,7 @@ struct AVR : ASMLine
if (o == "lds") { return OpCode::lds; }
if (o == "lsr") { return OpCode::lsr; }
if (o == "andi") { return OpCode::andi; }
if (o == "asr") { return OpCode::asr; }
if (o == "eor") { return OpCode::eor; }
if (o == "sbrc") { return OpCode::sbrc; }
if (o == "rjmp") { return OpCode::rjmp; }
@ -213,6 +217,7 @@ struct AVR : ASMLine
if (o == "brge") { return OpCode::brge; }
if (o == "brlt") { return OpCode::brlt; }
if (o == "or") { return OpCode::OR; }
if (o == "ori") { return OpCode::ori; }
}
}
throw std::runtime_error(fmt::format("Unknown opcode: {}", o));
@ -285,6 +290,7 @@ void indirect_store(std::vector<mos6502> &instructions,
std::string s_set = fmt::format("s_set_{}", location);
std::string s_clear = fmt::format("s_clear_{}", location);
instructions.emplace_back(ASMLine::Type::Directive, "; Handle N / S flags from AVR");
instructions.emplace_back(mos6502::OpCode::bmi, Operand(Operand::Type::literal, n_set));
instructions.emplace_back(mos6502::OpCode::bvs, Operand(Operand::Type::literal, s_set));
instructions.emplace_back(mos6502::OpCode::jmp, Operand(Operand::Type::literal, s_clear));
@ -394,6 +400,12 @@ void increment_16_bit(const Personality &personality, std::vector<mos6502> &inst
instructions.emplace_back(ASMLine::Type::Label, skip_high_byte_label);
}
void decrement_16_bit(const Personality &personality, std::vector<mos6502> &instructions, int reg)
{
subtract_16_bit(personality, instructions, reg, 1);
}
void translate_instruction(const Personality &personality,
std::vector<mos6502> &instructions,
const AVR::OpCode op,
@ -420,6 +432,12 @@ void translate_instruction(const Personality &personality,
instructions.emplace_back(mos6502::OpCode::sta, personality.get_register(o1_reg_num));
return;
}
case AVR::OpCode::ori: {
instructions.emplace_back(mos6502::OpCode::lda, personality.get_register(o1_reg_num));
instructions.emplace_back(mos6502::OpCode::ORA, Operand(o2.type, fixup_8bit_literal(o2.value)));
instructions.emplace_back(mos6502::OpCode::sta, personality.get_register(o1_reg_num));
return;
}
case AVR::OpCode::jmp: instructions.emplace_back(mos6502::OpCode::jmp, o1); return;
case AVR::OpCode::tst: {
// just an lda will set the relevant flags that the tst operation sets, so I think this is
@ -513,16 +531,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: {
@ -591,6 +634,13 @@ void translate_instruction(const Personality &personality,
increment_16_bit(personality, instructions, AVR::get_register_number(o1.value[0]));
return;
}
if (o1.value == "-Z" || o1.value == "-Y" || o1.value == "-X") {
decrement_16_bit(personality, instructions, AVR::get_register_number(o1.value[1]));
indirect_store(instructions,
personality.get_register(o2_reg_num).value,
personality.get_register(AVR::get_register_number(o1.value[1])).value);
return;
}
throw std::runtime_error("Unhandled st");
}
case AVR::OpCode::lds: {
@ -602,6 +652,12 @@ void translate_instruction(const Personality &personality,
instructions.emplace_back(mos6502::OpCode::lsr, personality.get_register(o1_reg_num));
return;
}
case AVR::OpCode::asr: {
instructions.emplace_back(mos6502::OpCode::lda, personality.get_register(o1_reg_num));
instructions.emplace_back(mos6502::OpCode::asl);
instructions.emplace_back(mos6502::OpCode::ror, personality.get_register(o1_reg_num));
return;
}
case AVR::OpCode::andi: {
instructions.emplace_back(mos6502::OpCode::lda, personality.get_register(o1_reg_num));
instructions.emplace_back(mos6502::OpCode::AND, Operand(o2.type, fixup_8bit_literal(o2.value)));
@ -644,6 +700,9 @@ void translate_instruction(const Personality &personality,
if (o1.value == "0b") {
instructions.emplace_back(mos6502::OpCode::bne, Operand(Operand::Type::literal, "memcpy_0"));
return;
} else if (o1.value == "1b") {
instructions.emplace_back(mos6502::OpCode::bne, Operand(Operand::Type::literal, "mul2_1"));
return;
} else 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());
@ -843,13 +902,65 @@ void to_mos6502(const Personality &personality, const AVR &from_instruction, std
case ASMLine::Type::Label:
if (from_instruction.text == "0") {
instructions.emplace_back(from_instruction.type, "-memcpy_0");
} else if (from_instruction.text == "1") {
instructions.emplace_back(from_instruction.type, "-mul2_1");
} else {
instructions.emplace_back(from_instruction.type, from_instruction.text);
}
return;
case ASMLine::Type::Directive:
if (from_instruction.text.starts_with(".string")) {
instructions.emplace_back(ASMLine::Type::Directive, ".asc " + from_instruction.text.substr(7));
if (from_instruction.text.starts_with(".string") || from_instruction.text.starts_with(".ascii")) {
const auto &text = from_instruction.text;
const auto start = [=]() -> std::size_t {
if (text.starts_with(".string")) {
return 9;
} else {
return 8;
}
}();
const auto isdigit = [](char c) { return c <= '9' && c >= '0'; };
for (std::size_t pos = start; text[pos] != '"'; ++pos) {
if (text[pos] != '\\') {
instructions.emplace_back(
ASMLine::Type::Directive, fmt::format(".byt ${:02x}", static_cast<std::uint8_t>(text[pos])));
} else {
if (text[pos + 1] == 'f') {
instructions.emplace_back(ASMLine::Type::Directive, fmt::format(".byt ${:02x}", 014));
++pos;
} else if (text[pos + 1] == 'b') {
instructions.emplace_back(ASMLine::Type::Directive, fmt::format(".byt ${:02x}", '\b'));
++pos;
} else if (text[pos + 1] == 't') {
instructions.emplace_back(ASMLine::Type::Directive, fmt::format(".byt ${:02x}", '\t'));
++pos;
} else if (text[pos + 1] == 'r') {
instructions.emplace_back(ASMLine::Type::Directive, fmt::format(".byt ${:02x}", '\r'));
++pos;
} else if (text[pos + 1] == '"') {
instructions.emplace_back(ASMLine::Type::Directive, fmt::format(".byt ${:02x}", '"'));
++pos;
} else if (isdigit(text[pos + 1]) && isdigit(text[pos + 2]) && isdigit(text[pos + 3])) {
std::string octal = "0";
octal += text[pos + 1];
octal += text[pos + 2];
octal += text[pos + 3];
instructions.emplace_back(
ASMLine::Type::Directive, fmt::format(".byt ${:02x}", std::stoi(octal, nullptr, 8)));
pos += 3;
} else {
spdlog::error("[{}]: Unhandled .string escape: '{}': {}",
from_instruction.line_num,
from_instruction.line_text,
text[pos + 1]);
}
}
}
if (text.starts_with(".string")) {
instructions.emplace_back(ASMLine::Type::Directive, ".byt 0");// terminating byte
}
} else if (from_instruction.text.starts_with(".word")) {
const auto matcher = ctre::match<R"(\s*.word\s*(.*))">;
@ -1080,6 +1191,8 @@ std::vector<mos6502> run(const Personality &personality, std::istream &input, co
if (i.type == ASMLine::Type::Label) {
if (i.text == "0") {
i.text = "-memcpy_0";
} else if (i.text == "1") {
i.text = "-mul2_1";
} else {
try {
i.text = new_labels.at(i.text);
@ -1103,15 +1216,32 @@ std::vector<mos6502> run(const Personality &personality, std::istream &input, co
if (i.operand2.value.starts_with("lo8(") || i.operand2.value.starts_with("hi8(")) {
const auto lo_hi_operand = strip_lo_hi(i.operand2.value);
const auto label_matcher = ctre::match<R"(([A-Za-z0-9.]+).*)">;
const auto label_matcher = ctre::match<R"(-?\(?([A-Za-z0-9.]+).*)">;
if (const auto results = label_matcher(lo_hi_operand); results) {
std::string_view potential_label = results.get<1>();
const auto start = std::distance(std::string_view{ i.operand2.value }.begin(), potential_label.begin());
spdlog::trace("Label matched: '{}'", potential_label);
const auto itr1 = new_labels.find(std::string{ potential_label });
if (itr1 != new_labels.end()) { i.operand2.value.replace(4, potential_label.size(), itr1->second); }
if (itr1 != new_labels.end()) {
i.operand2.value.replace(static_cast<std::size_t>(start), potential_label.size(), itr1->second);
}
spdlog::trace("New statement: '{}'", i.operand2.value);
}
}
if (const auto plus = i.operand1.value.find('+'); plus != std::string::npos) {
const auto str = i.operand1.value.substr(0, plus);
const auto itr1 = new_labels.find(str);
if (itr1 != new_labels.end()) { i.operand1.value.replace(0, plus, itr1->second); }
}
if (const auto plus = i.operand2.value.find('+'); plus != std::string::npos) {
const auto str = i.operand2.value.substr(0, plus);
const auto itr1 = new_labels.find(str);
if (itr1 != new_labels.end()) { i.operand2.value.replace(0, plus, itr1->second); }
}
const auto itr1 = new_labels.find(i.operand1.value);
if (itr1 != new_labels.end()) { i.operand1.value = itr1->second; }
@ -1182,35 +1312,42 @@ std::vector<mos6502> run(const Personality &personality, std::istream &input, co
return new_instructions;
}
enum struct Target { C64 };
enum struct Target { C64, X16 };
int main(const int argc, const char **argv)
{
spdlog::set_level(spdlog::level::trace);
const std::map<std::string, Target> targets{ { "C64", Target::C64 } };
spdlog::set_level(spdlog::level::warn);
const std::map<std::string, Target> targets{ { "C64", Target::C64 }, { "X16", Target::X16 } };
CLI::App app{ "C++ Compiler for 6502 processors" };
std::filesystem::path filename{};
Target target{ Target::C64 };
bool optimize{ true };
app.add_option("-f,--file", filename, "C++ file to compile")->required(true);
app.add_option("filename", filename, "C++ file to compile")->required(true);
app.add_option("-t,--target", target, "6502 - based system to target")
->required(true)
->transform(CLI::CheckedTransformer(targets, CLI::ignore_case));
std::string optimization_level;
app.add_option("-O", optimization_level, "Optimization level to pass to GCC instance")
->required(true)
->check(CLI::IsMember({ "s", "0", "1", "2", "3" }));
->required(false)
->check(CLI::IsMember({ "s", "0", "1", "2", "3" }))
->default_val("1");
app.add_flag("--optimize", optimize, "Enable optimization of 6502 generated assembly")->default_val(true);
std::vector<std::string> include_paths;
app.add_option("-I", include_paths, "Extra include paths to pass to GCC instance")
->required(false)
->expected(1)
->take_all();
CLI11_PARSE(app, argc, argv)
const std::string_view include_directories = "-I ~/avr-libstdcpp/include/";
const std::string_view warning_flags = "-Wall -Wextra";
include_paths.insert(include_paths.begin(), "~/avr-libstdcpp/include");
const std::string_view warning_flags = "-Wall -Wextra -Wconversion";
const std::string_view avr = "avr3";
const auto make_output_file_name = [](auto input_filename, const auto &new_extension) {
@ -1242,13 +1379,13 @@ int main(const int argc, const char **argv)
*/
const std::string gcc_command = fmt::format(
"avr-gcc -fverbose-asm -c -o {outfile} -S {warning_flags} -std=c++20 -mtiny-stack "
"-mmcu={avr} -O{optimization} {disabled_optimizations} {include_dirs} {infile}",
"avr-gcc -fverbose-asm -c -o {outfile} -S {warning_flags} -std=c++20 -mtiny-stack -fconstexpr-ops-limit=333554432 "
"-mmcu={avr} -O{optimization} {disabled_optimizations} -I {user_include_dirs} {infile}",
fmt::arg("outfile", avr_output_file.generic_string()),
fmt::arg("warning_flags", warning_flags),
fmt::arg("avr", avr),
fmt::arg("optimization", optimization_level),
fmt::arg("include_dirs", include_directories),
fmt::arg("user_include_dirs", fmt::join(include_paths, " -I ")),
fmt::arg("disabled_optimizations", disabled_optimizations),
fmt::arg("infile", filename.generic_string()));
@ -1265,9 +1402,18 @@ int main(const int argc, const char **argv)
std::ifstream input(avr_output_file);
C64 personality;
const auto new_instructions = run(personality, input, optimize);
const auto new_instructions = [&]() {
switch (target) {
case Target::C64:
return run(C64{}, input, optimize);
case Target::X16:
return run(X16{}, input, optimize);
default:
spdlog::critical("Unhandled target type");
return std::vector<mos6502>{};
}
}();
{
// make sure file is closed before we try to re-open it with xa

View File

@ -80,7 +80,7 @@ quit
REQUIRE(system(fmt::format(
"{} -f {} -t C64 {} {}", mos6502_cpp_executable, source_filename, optimization_level, optimize_6502)
"{} {} -t C64 {} {}", mos6502_cpp_executable, source_filename, optimization_level, optimize_6502)
.c_str())
== EXIT_SUCCESS);
REQUIRE(