1680388189
- 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
215 lines
8.1 KiB
C++
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; }
|