1
0
mirror of https://github.com/lefticus/6502-cpp.git synced 2024-12-21 10:30:35 +00:00
6502-cpp/examples/graphics.cpp

607 lines
16 KiB
C++
Raw Normal View History

#include <array>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <span>
2021-05-26 04:30:03 +00:00
#include <string_view>
#include <variant>
#include "chargen.hpp"
enum Colors : uint8_t { WHITE = 0x01 };
2021-05-26 04:30:03 +00:00
static volatile uint8_t &memory_loc(const uint16_t loc) { return *reinterpret_cast<volatile uint8_t *>(loc); }
2021-05-26 04:30:03 +00:00
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{};
2021-05-26 04:30:03 +00:00
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; }
};
2021-05-26 04:30:03 +00:00
static bool joystick_down()
{
uint8_t joystick_state = peek(0xDC00);
return (joystick_state & 2) == 0;
}
void use_data(std::array<char, 1024> &data);
2021-05-26 04:30:03 +00:00
static void puts(uint8_t x, uint8_t y, std::string_view str)
{
const auto start = 0x400 + (y * 40 + x);
2021-05-27 14:31:02 +00:00
std::copy(str.begin(), str.end(), &memory_loc(start));
// std::memcpy(const_cast<uint8_t *>(&memory_loc(start)), str.data(), str.size());
}
2021-05-26 04:30:03 +00:00
template<std::uint8_t Width, std::uint8_t Height> struct Graphic
{
std::array<std::uint8_t, Width * Height> data{};
2021-05-26 04:30:03 +00:00
static constexpr auto width() noexcept { return Width; }
2021-05-26 04:30:03 +00:00
static constexpr auto height() noexcept { return Height; }
constexpr Graphic() = default;
constexpr Graphic(std::array<uint8_t, Width * Height> data_) noexcept : data(data_) {}
2021-05-26 04:30:03 +00:00
constexpr Graphic(std::initializer_list<uint8_t> data_) noexcept { std::copy(begin(data_), end(data_), begin(data)); }
2021-05-26 04:30:03 +00:00
constexpr auto &operator()(const std::uint8_t x, const std::uint8_t y) noexcept { return data[y * Width + x]; }
2021-05-26 04:30:03 +00:00
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) {
2021-05-26 04:30:03 +00:00
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());
}
};
2021-05-26 04:30:03 +00:00
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) {
2021-05-26 04:30:03 +00:00
Graphic<8, 8> glyph{};
for (std::uint8_t row = 0; row < 8; ++row) {
2021-05-26 04:30:03 +00:00
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;
}
2021-05-26 04:30:03 +00:00
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{};
2021-05-26 04:30:03 +00:00
constexpr auto charset = load_charset(uppercase);
2021-05-26 04:30:03 +00:00
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;
2021-05-26 04:30:03 +00:00
std::uint8_t cur_char = 0;
for (const auto &glyph : charset) {
2021-05-26 04:30:03 +00:00
const auto count = pixels.match_count(glyph, x, y);
if (count > match_count) {
best_match = cur_char;
match_count = count;
}
2021-05-26 04:30:03 +00:00
++cur_char;
}
2021-05-26 04:30:03 +00:00
result(x / 8, y / 8) = best_match;
}
}
2021-05-26 04:30:03 +00:00
return result;
}
2021-05-26 04:30:03 +00:00
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
}
}
}
}
2021-05-26 04:30:03 +00:00
return result;
}
static void putc(uint8_t x, uint8_t y, uint8_t c)
{
const auto start = 0x400 + (y * 40 + x);
poke(start, c);
}
2021-05-26 04:30:03 +00:00
static std::uint8_t loadc(uint8_t x, uint8_t y)
{
const auto start = 0x400 + (y * 40 + x);
return peek(start);
}
2021-05-26 04:30:03 +00:00
static void put_hex(uint8_t x, uint8_t y, uint8_t value)
{
const auto put_nibble = [](auto x, auto y, uint8_t nibble) {
if (nibble <= 9) {
putc(x, y, nibble + 48);
} else {
putc(x, y, nibble - 9);
}
};
put_nibble(x + 1, y, 0xF & value);
put_nibble(x, y, 0xF & (value >> 4));
}
2021-05-26 04:30:03 +00:00
static void put_hex(uint8_t x, uint8_t y, uint16_t value)
{
put_hex(x + 2, y, static_cast<std::uint8_t>(0xFF & value));
put_hex(x, y, static_cast<std::uint8_t>(0xFF & (value >> 8)));
}
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) {
2021-05-26 04:30:03 +00:00
for (uint8_t cur_x = 0; cur_x < graphic.width(); ++cur_x) { putc(cur_x + x, cur_y + y, graphic(cur_x, cur_y)); }
}
}
2021-05-26 04:30:03 +00:00
struct Clock
{
using milliseconds = std::chrono::duration<std::uint16_t, std::milli>;
// return elapsed time since last restart
2021-05-26 04:30:03 +00:00
[[nodiscard]] milliseconds restart() noexcept
{
// stop Timer A
poke(0xDC0E, 0b00000000);
// last value
2021-05-26 04:30:03 +00:00
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);
2021-05-26 04:30:03 +00:00
return milliseconds{ 0xFFFF - previous_value };
}
Clock() noexcept { [[maybe_unused]] const auto value = restart(); }
};
2021-05-26 04:30:03 +00:00
static void cls()
{
for (std::uint16_t i = 0x400; i < 0x400 + 1000; ++i) { poke(i, 32); }
}
2021-05-26 04:30:03 +00:00
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{};
2021-05-26 04:30:03 +00:00
constexpr SimpleSprite(std::initializer_list<uint8_t> data_) noexcept : graphic(data_) {}
};
// helper type for the visitor #4
2021-05-26 04:30:03 +00:00
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
{
2021-05-26 04:30:03 +00:00
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) {
2021-05-26 04:30:03 +00:00
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); }
}
}
2021-05-27 14:31:02 +00:00
void hide(auto &s) {
if (s.is_shown) {
put_graphic(s.x, s.y, s.saved_background);
s.is_shown = false;
}
}
2021-05-26 04:30:03 +00:00
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);
2021-05-26 04:30:03 +00:00
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;
2021-05-27 14:31:02 +00:00
bool redraw = true;
2021-05-26 04:30:03 +00:00
Clock game_clock{};
Map<10, 5> const *current_map = nullptr;
2021-05-27 14:31:02 +00:00
constexpr void set_current_map(const Map<10, 5> &new_map) {
current_map = &new_map;
redraw = true;
}
2021-05-26 04:30:03 +00:00
constexpr void execute_actions(const auto &character) noexcept
{
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() };
}
};
2021-05-26 04:30:03 +00:00
struct Map_Action
{
std::uint8_t x{};
std::uint8_t y{};
2021-05-26 04:30:03 +00:00
std::uint8_t width{};
std::uint8_t height{};
2021-05-26 04:30:03 +00:00
using Action_Func = void (*)(GameState &);
Action_Func action = nullptr;
2021-05-26 04:30:03 +00:00
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;
}
2021-05-26 04:30:03 +00:00
const std::uint8_t rect1_x1 = x;
2021-05-27 14:31:02 +00:00
const std::uint8_t rect1_x2 = x + width;
2021-05-26 04:30:03 +00:00
const std::uint8_t rect1_y1 = y;
2021-05-27 14:31:02 +00:00
const std::uint8_t rect1_y2 = y + height;
2021-05-26 04:30:03 +00:00
const std::uint8_t rect2_x1 = obj_x;
2021-05-27 14:31:02 +00:00
const std::uint8_t rect2_x2 = obj_x + obj_width;
2021-05-26 04:30:03 +00:00
const std::uint8_t rect2_y1 = obj_y;
2021-05-27 14:31:02 +00:00
const std::uint8_t rect2_y2 = obj_y + obj_height;
2021-05-26 04:30:03 +00:00
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 {
2021-05-27 14:31:02 +00:00
std::string_view name;
2021-05-26 04:30:03 +00:00
Graphic<Width, Height> layout;
std::span<const Map_Action> actions;
};
int main()
{
// static constexpr auto charset = load_charset(uppercase);
2021-05-27 14:31:02 +00:00
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,
};
2021-05-26 04:30:03 +00:00
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 mountain = Graphic<4, 4>{
32, 78, 77, 32,
32, 32, 78, 77,
78, 77, 32, 32,
32, 78, 77, 32 };
auto character = SimpleSprite<2, 3>{
32, 87,
78, 79,
78, 77 };
2021-05-27 14:31:02 +00:00
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>{}
};
2021-05-26 04:30:03 +00:00
static constexpr auto overview_actions = std::array {
2021-05-27 14:31:02 +00:00
Map_Action { 16,0,4,4, [](GameState &g) { g.set_current_map(city_map); } }
2021-05-26 04:30:03 +00:00
};
static constexpr auto overview_map = Map<10, 5>{
2021-05-27 14:31:02 +00:00
"the world",
2021-05-26 04:30:03 +00:00
{
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))
};
2021-05-27 14:31:02 +00:00
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 */
},
2021-05-26 04:30:03 +00:00
[](std::uint8_t x, std::uint8_t y) { put_graphic(x, y, mountain); },
[](std::uint8_t x, std::uint8_t y) {
/* do nothing for 2 */
},
2021-05-26 04:30:03 +00:00
[](std::uint8_t x, std::uint8_t y) { put_graphic(x, y, town); },
2021-05-27 14:31:02 +00:00
[](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) {
2021-05-26 04:30:03 +00:00
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) {
2021-05-26 04:30:03 +00:00
if (map(map_large_x, map_large_y) == tile) { tile_types[tile](map_large_x * 4, map_large_y * 4); }
}
}
}
};
constexpr auto draw_box = [](uint8_t x, uint8_t y, uint8_t width, uint8_t height) {
2021-05-26 04:30:03 +00:00
putc(x, y, 85);
putc(x + width - 1, y, 73);
putc(x + width - 1, y + height - 1, 75);
putc(x, y + height - 1, 74);
2021-05-26 04:30:03 +00:00
for (uint8_t cur_x = x + 1; cur_x < x + width - 1; ++cur_x) {
putc(cur_x, y, 67);
2021-05-26 04:30:03 +00:00
putc(cur_x, y + height - 1, 67);
}
2021-05-26 04:30:03 +00:00
for (uint8_t cur_y = y + 1; cur_y < y + height - 1; ++cur_y) {
putc(x, cur_y, 93);
2021-05-26 04:30:03 +00:00
putc(x + width - 1, cur_y, 93);
}
};
GameState game;
2021-05-26 04:30:03 +00:00
game.current_map = &overview_map;
constexpr auto show_stats = [](const auto &cur_game) {
puts(1, 21, "stamina:");
put_hex(12, 21, cur_game.stamina);
put_hex(15, 21, cur_game.max_stamina());
2021-05-26 04:30:03 +00:00
puts(14, 21, "/");
puts(1, 22, "endurance:");
2021-05-26 04:30:03 +00:00
put_hex(12, 22, cur_game.endurance);
puts(1, 23, "cash: ");
put_hex(12, 23, cur_game.cash);
};
Screen screen;
2021-05-26 04:30:03 +00:00
auto eventHandler = overloaded{ [&](const GameState::JoyStick2StateChanged &e) {
if (e.state.up()) { --game.y; }
if (e.state.down()) { ++game.y; }
if (e.state.left()) { --game.x; }
if (e.state.right()) { ++game.x; }
2021-05-26 04:30:03 +00:00
game.execute_actions(character.graphic);
2021-05-26 04:30:03 +00:00
screen.show(game.x, game.y, character);
put_hex(36, 1, e.state.state);
},
[](const GameState::TimeElapsed &e) { put_hex(36, 0, e.us.count()); } };
while (true) {
std::visit(eventHandler, game.next_event());
2021-05-27 14:31:02 +00:00
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);
puts(10, 20, game.current_map->name);
show_stats(game);
screen.show(game.x, game.y, character);
}
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();
}
}
*/
}