#include "lodepng.h" #include #include #include #include #include #include #include constexpr int cellWidth = 8, cellHeight = 16; enum class EColour : unsigned char { BLACK, BLUE, GREEN, CYAN, RED, MEGENTA, BROWN, LTGREY, DKGREY, LTBLUE, LTGREEN, LTCYAN, LTRED, LTMEGENTA, YELLOW, WHITE }; static constexpr std::array colourTable = { /* EColour::BLACK */ SDL_Colour{ 0x00, 0x00, 0x00, 0xFF }, /* EColour::BLUE */ SDL_Colour{ 0x00, 0x00, 0xAA, 0xFF }, /* EColour::GREEN */ SDL_Colour{ 0x00, 0xAA, 0x00, 0xFF }, /* EColour::CYAN */ SDL_Colour{ 0x00, 0xAA, 0xAA, 0xFF }, /* EColour::RED */ SDL_Colour{ 0xAA, 0x00, 0x00, 0xFF }, /* EColour::MEGENTA */ SDL_Colour{ 0xAA, 0x00, 0xAA, 0xFF }, /* EColour::BROWN */ SDL_Colour{ 0xAA, 0x55, 0x00, 0xFF }, /* EColour::LTGREY */ SDL_Colour{ 0xAA, 0xAA, 0xAA, 0xFF }, /* EColour::DKGREY */ SDL_Colour{ 0x55, 0x55, 0x55, 0xFF }, /* EColour::LTBLUE */ SDL_Colour{ 0x55, 0x55, 0xFF, 0xFF }, /* EColour::LTGREEN */ SDL_Colour{ 0x55, 0xFF, 0x55, 0xFF }, /* EColour::LTCYAN */ SDL_Colour{ 0x55, 0xFF, 0xFF, 0xFF }, /* EColour::LTRED */ SDL_Colour{ 0xFF, 0x55, 0x55, 0xFF }, /* EColour::LTMEGENTA */ SDL_Colour{ 0xFF, 0x55, 0xFF, 0xFF }, /* EColour::YELLOW */ SDL_Colour{ 0xFF, 0xFF, 0x55, 0xFF }, /* EColour::WHITE */ SDL_Colour{ 0xFF, 0xFF, 0xFF, 0xFF } }; struct Cell { unsigned char character; EColour bgColour, fgColour; bool blinking; }; template constexpr T clamp(T x, T min, T max) { return x < min ? min : (x > max ? max : x); } class Application final { int argc; const char** argv; SDL_Window* window; SDL_Renderer* renderer; SDL_Texture* codepage; SDL_TimerID blinkTimer; bool needRepaint; bool mouseLeft, mouseRight; int mousePrevX, mousePrevY, mouseX, mouseY; bool mouseInWindow; bool drawing, blinkState; std::array display; int create(); int handleEvent(const SDL_Event& event) noexcept; int update(); int repaint(); static Uint32 timerCallback(Uint32 interval, void* param); inline void damage() noexcept { needRepaint = true; } SDL_Texture* loadTexture(const std::string& path); public: Application(int argc, const char* argv[]); ~Application(); int run(); }; Uint32 Application::timerCallback(Uint32 interval, void* param) { SDL_Event event = { .type = SDL_USEREVENT, .user = { .type = SDL_USEREVENT, .timestamp = SDL_GetTicks(), .code = 0, .data1 = nullptr, .data2 = nullptr } }; SDL_PushEvent(&event); return interval; } Application::Application(int aArgc, const char* aArgv[]) : argc(aArgc), argv(aArgv), window(nullptr), renderer(nullptr), codepage(nullptr), blinkTimer(0), needRepaint(true), mouseLeft(false), mouseRight(false), mousePrevX(0), mousePrevY(0), mouseX(0), mouseY(0), mouseInWindow(false), drawing(false), blinkState(true) {} Application::~Application() { SDL_RemoveTimer(blinkTimer); SDL_DestroyTexture(codepage); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); } SDL_Texture* Application::loadTexture(const std::string& path) { unsigned pixelWidth, pixelHeight; std::vector pixels; if (lodepng::decode(pixels, pixelWidth, pixelHeight, path, LCT_RGBA, 8) != 0) { return nullptr; } constexpr Uint32 format = SDL_PIXELFORMAT_RGBA8888; constexpr int access = SDL_TEXTUREACCESS_STATIC; const int textureWidth = static_cast(pixelWidth), textureHeight = static_cast(pixelHeight); SDL_Texture* texture = SDL_CreateTexture(renderer, format, access, textureWidth, textureHeight); if (texture == nullptr) { return nullptr; } if (SDL_UpdateTexture(texture, nullptr, pixels.data(), 4 * static_cast(pixelWidth)) != 0 || SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND) != 0) { SDL_DestroyTexture(texture); } return texture; } int Application::create() { if (SDL_Init(SDL_INIT_VIDEO) < 0) { return -1; } constexpr int screenWidth = cellWidth * 80; constexpr int screenHeight = cellHeight * 25; constexpr Uint32 winFlags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE; if (SDL_CreateWindowAndRenderer(screenWidth * 2, screenHeight * 2, winFlags, &window, &renderer) < 0) { return -1; } SDL_SetWindowTitle(window, "EnDOOMed"); SDL_RenderSetLogicalSize(renderer, screenWidth, screenHeight); if ((codepage = Application::loadTexture("codepage.png")) == nullptr) { return -1; } std::memset(display.data(), 0, sizeof(Cell) * display.size()); FILE* file = fopen("ENDOOM.bin", "rb"); if (file == nullptr) { return -1; } for (Cell& currentCharacter : display) { unsigned char character[2]; fread(character, sizeof(unsigned char), 2, file); currentCharacter = { .character = character[0], .bgColour = EColour((character[1] & 0x70) >> 4), .fgColour = EColour( character[1] & 0x0F), .blinking = (character[1] & (0x80 > 1)) ? true : false }; } fclose(file); blinkTimer = SDL_AddTimer(400, Application::timerCallback, nullptr); if (blinkTimer == 0) { return -1; } return 0; } int Application::handleEvent(const SDL_Event& event) noexcept { switch (event.type) { case SDL_QUIT: return 1; case SDL_USEREVENT: blinkState = !blinkState; damage(); return 0; case SDL_MOUSEBUTTONDOWN: switch (event.button.button) { case SDL_BUTTON_LEFT: mouseLeft = true; break; case SDL_BUTTON_RIGHT: mouseRight = true; break; } return 0; case SDL_MOUSEBUTTONUP: switch (event.button.button) { case SDL_BUTTON_LEFT: mouseLeft = false; break; case SDL_BUTTON_RIGHT: mouseRight = false; break; } return 0; case SDL_MOUSEMOTION: mouseX = event.motion.x; mouseY = event.motion.y; return 0; case SDL_WINDOWEVENT: switch (event.window.event) { case SDL_WINDOWEVENT_LEAVE: mouseInWindow = false; break; case SDL_WINDOWEVENT_ENTER: mouseInWindow = true; break; }; return 0; default: return 0; } } int Application::update() { Cell leftBrush = { .character = 219, .bgColour = EColour::BLACK, .fgColour = EColour::LTGREY, .blinking = false }; Cell rightBrush = { .character = ' ', .bgColour = EColour::BLACK, .fgColour = EColour::LTGREY, .blinking = false }; if (mouseInWindow && (mouseLeft || mouseRight)) { if (!drawing) { mousePrevX = mouseX; mousePrevY = mouseY; drawing = true; } int fromX = clamp(mouseX / cellWidth, 0, 79); int fromY = clamp(mouseY / cellHeight, 0, 24); int toX = clamp(mousePrevX / cellWidth, 0, 79); int toY = clamp(mousePrevY / cellHeight, 0, 24); auto srcChar = mouseLeft ? leftBrush : rightBrush; if (toX == fromX && toY == fromY) { display[toX + toY * 80] = srcChar; } else { bool steep = std::abs(toY - fromY) > std::abs(toX - fromX); if (steep) { std::swap(fromY, fromX); std::swap(toY, toX); } if (fromX > toX) { std::swap(toX, fromX); std::swap(toY, fromY); } int deltaX = toX - fromX; int deltaY = std::abs(toY - fromY); int error = deltaX / 2; int y = fromY; int yStep = (fromY < toY) ? 1 : -1; for (int x = fromX; x <= toX; ++x) { int idx = steep ? y + x * 80 : x + y * 80; display[idx] = srcChar; error -= deltaY; if (error < 0) { y += yStep; error += deltaX; } } } mousePrevX = mouseX; mousePrevY = mouseY; damage(); } else if (drawing && !mouseLeft && !mouseRight) { drawing = false; } return 0; } int Application::repaint() { SDL_SetRenderDrawColor(renderer, 48, 48, 48, 255); SDL_RenderClear(renderer); SDL_Rect src, dst; src.w = cellWidth; src.h = cellHeight; dst.w = cellWidth; dst.h = cellHeight; int x = 0, y = 0; for (const auto& currentCharacter : display) { auto currentBgColour = colourTable[static_cast(currentCharacter.bgColour)]; auto currentFgColour = colourTable[static_cast(currentCharacter.fgColour)]; src.x = (currentCharacter.character % 32) * cellWidth; src.y = (currentCharacter.character / 32) * cellHeight; dst.x = x * cellWidth; dst.y = y * cellHeight; SDL_SetRenderDrawColor(renderer, currentBgColour.r, currentBgColour.g, currentBgColour.b, currentBgColour.a); SDL_RenderFillRect(renderer, &dst); SDL_SetRenderDrawColor(renderer, 0, 0, 0, currentFgColour.a); if (blinkState || !currentCharacter.blinking) { SDL_SetTextureColorMod(codepage, currentFgColour.r, currentFgColour.g, currentFgColour.b); SDL_RenderCopy(renderer, codepage, &src, &dst); } if (++x >= 80) { x = 0; ++y; } } SDL_RenderPresent(renderer); return 0; } int Application::run() { int res = create(); while (res == 0) { SDL_Event event; if (SDL_WaitEvent(&event)) { do { res = handleEvent(event); } while (res == 0 && SDL_PollEvent(&event) > 0); } if (res == 0) { res = update(); } if (res == 0 && needRepaint) { res = repaint(); needRepaint = false; } } return res < 0 ? 1 : 0; }; int main(int argc, char* argv[]) { Application app(argc, const_cast(argv)); return app.run(); }