#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); } static Uint32 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; } int main(int argc, char** argv) { if (SDL_Init(SDL_INIT_VIDEO) < 0) { return -1; } SDL_Window* window = nullptr; SDL_Renderer* renderer = nullptr; constexpr Uint32 winFlags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE; if (SDL_CreateWindowAndRenderer(640, 400, winFlags, &window, &renderer) < 0) { SDL_Quit(); return -1; } SDL_SetWindowTitle(window, "EnDOOMed"); SDL_RenderSetLogicalSize(renderer, 640, 400); SDL_Texture* codepage = nullptr; { unsigned codepageWidth, codepageHeight; std::vector codepagePixels; if (lodepng::decode(codepagePixels, codepageWidth, codepageHeight, "codepage.png", LCT_RGBA, 8) != 0 || (codepage = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, static_cast(codepageWidth), static_cast(codepageHeight))) == nullptr || SDL_UpdateTexture(codepage, nullptr, codepagePixels.data(), 4 * static_cast(codepageWidth)) != 0 || SDL_SetTextureBlendMode(codepage, SDL_BLENDMODE_BLEND) != 0) { SDL_DestroyTexture(codepage); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return -1; } } std::array display; std::memset(display.data(), 0, sizeof(Cell) * display.size()); FILE* file = fopen("ENDOOM.bin", "rb"); if (file == nullptr) { SDL_DestroyTexture(codepage); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); 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 }; } SDL_AddTimer(400, timerCallback, nullptr); Cell leftBrush = { .character = 219, .bgColour = EColour::BLACK, .fgColour = EColour::LTGREY, .blinking = false }; Cell rightBrush = { .character = ' ', .bgColour = EColour::BLACK, .fgColour = EColour::LTGREY, .blinking = false }; bool mouseLeft = false, mouseRight = false; int mousePrevX = 0, mousePrevY = 0; int mouseX = 0, mouseY = 0; bool mouseInWindow = false; bool running = true, damage = true; bool blinkState = true; do { mousePrevX = mouseX; mousePrevY = mouseY; SDL_Event event; if (SDL_WaitEvent(&event)) { do { switch (event.type) { case SDL_QUIT: running = false; break; case SDL_USEREVENT: blinkState = !blinkState; damage = true; break; case SDL_MOUSEBUTTONDOWN: switch (event.button.button) { case SDL_BUTTON_LEFT: mouseLeft = true; break; case SDL_BUTTON_RIGHT: mouseRight = true; break; } break; case SDL_MOUSEBUTTONUP: switch (event.button.button) { case SDL_BUTTON_LEFT: mouseLeft = false; break; case SDL_BUTTON_RIGHT: mouseRight = false; break; } break; case SDL_MOUSEMOTION: mouseX = event.motion.x; mouseY = event.motion.y; break; case SDL_WINDOWEVENT: switch (event.window.event) { case SDL_WINDOWEVENT_LEAVE: mouseInWindow = false; break; case SDL_WINDOWEVENT_ENTER: mouseInWindow = true; break; }; break; } } while (SDL_PollEvent(&event) > 0); } if (mouseInWindow && (mouseLeft || mouseRight)) { 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; } } } damage = true; } if (damage) { 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); damage = false; } } while (running); fclose(file); SDL_DestroyTexture(codepage); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 0; }