Files
deepMiner/src/Renderer.cpp
T
fegger 1680388189 Add SDL2 GUI support and fix World surface logic
- Integrate SDL2 GUI rendering into the Game class
- Update World::getSurfaceLevel to correctly identify surface height
- Improve ASSERT_EQ macro to avoid multiple evaluation of arguments
- Fix typo in main_gui.cpp include
- Update CMakeLists.txt to include Threads package
2026-04-29 02:57:41 +02:00

215 lines
8.1 KiB
C++

#include "../include/Renderer.h"
#include "../include/Robot.h"
#include "../include/World.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_ttf.h>
#include <SDL2/SDL_video.h>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <sstream>
#include <string>
#include <string>
#include <vector>
namespace {
SDL_Color rgba( Uint8 r, Uint8 g, Uint8 b, Uint8 a = 255 ) {return SDL_Color{ r, g, b, a}; }
}
struct Renderer::Impl {
SDL_Window* window = nullptr;
SDL_Renderer* sdl = nullptr;
TTF_Font* fontLg = nullptr;
TTF_Font* fontSm = nullptr;
bool open = false;
int autoAdvanceMs = 900;
static constexpr int WIN_W = 920;
static constexpr int WIN_H = 700;
static constexpr int GRID_X = 32;
static constexpr int GRID_Y = 84;
static constexpr int CELL_W = 92;
static constexpr int CELL_H = 82;
static constexpr int PANEL_X = 535;
bool init() {
if (SDL_Init( SDL_INIT_VIDEO ) != 0 ) return false;
if ( TTF_Init() != 0 ) return false;
window = SDL_CreateWindow( "Parallel Deep Miner",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
WIN_W, WIN_H,
SDL_WINDOW_SHOWN );
if ( !window ) return false;
sdl = SDL_CreateRenderer( window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC );
if ( !sdl ) return false;
const char* candidates[] = {
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/dejavu/DejaVuSans.ttf",
"/Library/Fonts/Arial.ttf",
"C:/Windows/Fonts/arial.ttf"
};
for ( const char* path :candidates ) {
if ( !fontLg ) fontLg = TTF_OpenFont( path, 20 );
if ( !fontSm ) fontSm = TTF_OpenFont( path, 13 );
if ( fontLg && fontSm ) break;
}
open = true;
return true;
}
void shutdown() {
if ( fontLg ) TTF_CloseFont( fontLg );
if ( fontSm ) TTF_CloseFont( fontSm );
if ( sdl ) SDL_DestroyRenderer( sdl );
if ( window ) SDL_DestroyWindow( window );
fontLg = nullptr;
fontSm = nullptr;
sdl = nullptr;
window = nullptr;
TTF_Quit();
SDL_Quit();
}
void setColor( SDL_Color c) {
SDL_SetRenderDrawColor( sdl, c.r, c.g, c.b, c.a );
SDL_SetRenderDrawBlendMode( sdl, c.a < 255 ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE );
}
void fillRect( int x, int y, int w, int h, SDL_Color c ) {
setColor( c );
SDL_Rect r{ x, y, w, h };
SDL_RenderFillRect( sdl, &r );
}
void strokeRect( int x, int y, int w, int h, SDL_Color c ) {
setColor( c );
SDL_Rect r{ x, y, w, h };
SDL_RenderDrawRect( sdl, &r );
}
void fillCircle( int cx, int cy, int radius, SDL_Color c ) {
setColor( c );
for ( int dy = -radius; dy <= radius; ++dy ) {
int dx = static_cast<int> ( std::sqrt( radius * radius - dy * dy ));
SDL_RenderDrawLine(sdl, cx - dx, cy - dy, cx + dx, cy + dy);
}
}
void drawText( const std::string& text, int x, int y, SDL_Color color, bool large = false ) {
TTF_Font* font = large ? fontLg : fontSm;
if ( !font || text.empty() ) return;
SDL_Surface* surface = TTF_RenderUTF8_Blended( font, text.c_str(), color );
if ( !surface ) return;
SDL_Texture* texture = SDL_CreateTextureFromSurface( sdl, surface );
SDL_Rect dst{ x, y, surface->w, surface->h };
SDL_FreeSurface( surface );
if ( !texture ) return;
SDL_RenderCopy( sdl, texture, nullptr, &dst );
SDL_DestroyTexture( texture );
}
SDL_Color cellColor( int value ) const {
if (value == 0) return rgba( 28, 31, 35 );
if (value == -1) return rgba( 176, 96, 28 );
if (value == -2) return rgba( 88, 55, 140 );
if (value == -3) return rgba( 170, 40, 55 );
int clamped = std::clamp( value, 1, 9 );
Uint8 g = static_cast<Uint8>( 70 + clamped * 15 );
Uint8 r = static_cast<Uint8>( 20 + clamped * 22 );
return rgba( r, g, 55 );
}
void drawGrid( const World& world, const std::vector<Robot*>& robots ) {
for ( int y = 0; y < world.getSizeY(); ++y ) {
for ( int x = 0; x < world.getSizeX(); ++x ) {
int px = GRID_X + x * CELL_W;
int py = GRID_Y + y * CELL_H;
int value = world.getSurfaceValue( x, y );
fillRect( px, py, CELL_W -8, CELL_H - 8, cellColor( value ) );
strokeRect( px, py, CELL_W -8, CELL_H - 8, rgba( 230, 230, 230, 90 ) );
std::string label = value == 0 ? "--" : std::to_string( value );
drawText( label, px + 32, py + 24, rgba( 240, 240, 150 ));
}
}
for ( std::size_t i = 0; i < robots.size(); ++i ) {
Robot* r = robots[i];
if ( !r || !r->isAlive() ) continue;
int px = GRID_X + r->getX() * CELL_W + 18 + static_cast<int> (( i % 3 ) * 18 );
int py = GRID_Y + r->getY() * CELL_H + 14 + static_cast<int> (( i / 3 ) * 14 );
fillCircle( px, py, 10, rgba( 80, 190, 240 ));
drawText( std::to_string( i + 1 ), px -4, py - 8, rgba( 10, 10, 10 ));
}
}
void drawPanel(const std::vector<Robot*>& robots, int round, const std::vector<std::string>& log) {
fillRect(PANEL_X, 0, WIN_W - PANEL_X, WIN_H, rgba(20, 22, 26));
drawText("Round " + std::to_string(round), PANEL_X + 18, 24, rgba(245, 245, 245), true);
drawText("Robots", PANEL_X + 18, 70, rgba(230, 230, 230), true);
int y = 105;
for (std::size_t i = 0; i < robots.size(); ++i) {
Robot* r = robots[i];
if (!r) continue;
std::ostringstream oss;
oss << (i + 1) << ". " << r->getName() << " score=" << r->getScore() << " hp=" << r->getHp();
drawText(oss.str(), PANEL_X + 18, y, r->isAlive() ? rgba(235, 235, 235) : rgba(160, 160, 160));
y += 22;
}
y += 18;
drawText("Messages", PANEL_X + 18, y, rgba(230, 230, 230), true);
y += 34;
int start = std::max(0, static_cast<int>(log.size()) - 15);
for (int i = start; i < static_cast<int>(log.size()); ++i) {
drawText(log[i], PANEL_X + 18, y, rgba(210, 210, 210));
y += 20;
}
}
void render(const World& world, const std::vector<Robot*>& robots, int round, const std::vector<std::string>& log) {
if (!open) return;
setColor(rgba(12, 14, 18));
SDL_RenderClear(sdl);
drawText("DEEP MINER", 32, 24, rgba(245, 245, 245), true);
drawGrid(world, robots);
drawPanel(robots, round, log);
drawText("SPACE/click: step ESC/window close: quit", 32, WIN_H - 36, rgba(210, 210, 210));
SDL_RenderPresent(sdl);
}
bool waitForStep() {
if (!open) return false;
Uint32 start = SDL_GetTicks();
SDL_Event event;
while (open) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) { open = false; return false; }
if (event.type == SDL_KEYDOWN) {
if (event.key.keysym.sym == SDLK_ESCAPE) { open = false; return false; }
if (event.key.keysym.sym == SDLK_SPACE || event.key.keysym.sym == SDLK_RETURN) return true;
}
if (event.type == SDL_MOUSEBUTTONDOWN) return true;
}
if (autoAdvanceMs > 0 && SDL_GetTicks() - start >= static_cast<Uint32>(autoAdvanceMs)) return true;
SDL_Delay(10);
}
return false;
}
};
Renderer::Renderer() : impl_(std::make_unique<Impl>()) {}
Renderer::~Renderer() { impl_->shutdown(); }
bool Renderer::init() { return impl_->init(); }
void Renderer::render(const World& world, const std::vector<Robot*>& robots, int round, const std::vector<std::string>& log) { impl_->render(world, robots, round, log); }
bool Renderer::waitForStep() { return impl_->waitForStep(); }
void Renderer::setAutoAdvanceMs(int ms) { impl_->autoAdvanceMs = ms; }
bool Renderer::isOpen() const { return impl_->open; }