Files
EnDOOMed/Endoomed/src/main.cpp
2024-04-13 23:18:52 +10:00

419 lines
9.0 KiB
C++

#include "lodepng.h"
#include <SDL2/SDL.h>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstdio>
#include <array>
#include <utility>
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<SDL_Colour, 16> 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 <typename T> 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<Cell, 2000> 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<unsigned char> 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<int>(pixelWidth), textureHeight = static_cast<int>(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<int>(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<unsigned char>(currentCharacter.bgColour)];
auto currentFgColour = colourTable[static_cast<unsigned char>(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<const char**>(argv));
return app.run();
}