World updates

Update World API and implementation:
rename size fields to sizeX_/sizeY_/sizeZ_, add helpers such as
mineAllPositive, positiveAverage, topPositiveSum and a sort helper,
and fix related surface/mine semantics for dynamic columns.
This commit is contained in:
2026-04-26 17:48:28 +02:00
parent 94ceebb50a
commit 2a79d42bd6
4 changed files with 3443 additions and 4 deletions
File diff suppressed because it is too large Load Diff
+965
View File
@@ -0,0 +1,965 @@
# SDL2 GUI Extension Guide: Parallel Deep Miner
> **Scope:** optional SDL2/SDL2_ttf renderer for the parallel Deep Miner project. This guide assumes the core implementation from `PARALLEL_DEEP_MINER_GUIDE.md`.
>
> The GUI is intentionally separated from the terminal/threading guide so the base project remains buildable without SDL2.
---
## Table of Contents
1. [Purpose](#1-purpose)
2. [Design Goals](#2-design-goals)
3. [Prerequisites](#3-prerequisites)
4. [Renderer Architecture](#4-renderer-architecture)
5. [Renderer Public Interface](#5-renderer-public-interface)
6. [Implementation Notes](#6-implementation-notes)
7. [Integrating Renderer with Game](#7-integrating-renderer-with-game)
8. [GUI CMake Target](#8-gui-cmake-target)
9. [GUI Entry Point](#9-gui-entry-point)
10. [Common Pitfalls](#10-common-pitfalls)
11. [Appendix A: Complete GUI Source Code](#appendix-a-complete-gui-source-code)
---
## 1. Purpose
The SDL2 renderer provides a live window beside the terminal output. It visualizes:
- the 5×5 grid
- surface values and effects
- column depth bars
- all 510 robots, not just two robots
- live HP and scores
- recent game messages
- optional step-by-step or auto-advance playback
This guide replaces the old two-robot GUI design that referenced `player_`, `computer_`, `autoMode_`, and `play(Robot&, bool)`. The parallel architecture uses `std::vector<std::unique_ptr<Robot>>`, `robotLoop(idx)`, and a complete-turn mutex.
---
## 2. Design Goals
1. **Optional dependency**: the terminal build must not require SDL2 headers or libraries.
2. **Parallel-compatible**: rendering works with the robot vector and does not assume exactly two robots.
3. **No ownership confusion**: `Game` owns the robots; `Renderer` only observes them.
4. **Stable snapshots**: render calls happen while the game state is stable.
5. **Simple build split**: `deep_miner` remains terminal-only; `deep_miner_gui` links SDL2.
---
## 3. Prerequisites
### Arch Linux
```bash
sudo pacman -S sdl2 sdl2_ttf
```
### Ubuntu / Debian
```bash
sudo apt-get install libsdl2-dev libsdl2-ttf-dev
```
### Fedora
```bash
sudo dnf install SDL2-devel SDL2_ttf-devel
```
The code can probe common system font paths at runtime. Do not bundle font files unless your project explicitly permits it.
---
## 4. Renderer Architecture
Use the pimpl idiom so SDL2 headers stay out of normal project headers.
```text
include/Renderer.h src/Renderer.cpp
------------------ ----------------
class Renderer { #include <SDL2/SDL.h>
struct Impl; #include <SDL2/SDL_ttf.h>
std::unique_ptr<Impl> impl_; struct Renderer::Impl { ... };
};
```
Only `src/Renderer.cpp` includes SDL2 headers. `Game.h` can forward-declare `Renderer`.
---
## 5. Renderer Public Interface
```cpp
// include/Renderer.h
#pragma once
#include <memory>
#include <string>
#include <vector>
class World;
class Robot;
class Renderer {
public:
Renderer();
~Renderer();
Renderer(const Renderer&) = delete;
Renderer& operator=(const Renderer&) = delete;
bool init();
void render(const World& world,
const std::vector<const Robot*>& robots,
int round,
const std::vector<std::string>& log);
bool waitForStep();
void setAutoAdvanceMs(int ms);
bool isOpen() const;
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
```
### Why pass `std::vector<const Robot*>`?
`Game` owns robots as `std::unique_ptr<Robot>`. The renderer should not know or care about ownership. A vector of raw `const Robot*` is a lightweight read-only view.
---
## 6. Implementation Notes
### Layout
A practical 900×680 window layout:
```text
+-------------------------------+----------------------------+
| grid title / round | scores and HP |
| | |
| 5×5 grid | legend |
| each cell shows surface value | message log |
| and robot markers | controls |
+-------------------------------+----------------------------+
```
Recommended constants:
```cpp
static constexpr int WIN_W = 900;
static constexpr int WIN_H = 680;
static constexpr int HDR_H = 56;
static constexpr int GRID_OFF_X = 32;
static constexpr int GRID_OFF_Y = 82;
static constexpr int CELL_W = 96;
static constexpr int CELL_H = 86;
static constexpr int PANEL_X = 536;
static constexpr int PANEL_PAD = 10;
```
### Drawing multiple robots in one cell
A single cell may contain several robots. Draw small indexed markers instead of assuming one `P` and one `C`.
```cpp
std::vector<int> robotsInCell;
for (int i = 0; i < static_cast<int>(robots.size()); ++i) {
if (robots[i]->isAlive()
&& robots[i]->getX() == x
&& robots[i]->getY() == y) {
robotsInCell.push_back(i);
}
}
for (int k = 0; k < static_cast<int>(robotsInCell.size()); ++k) {
const int robotIndex = robotsInCell[k];
const int cx = cellX + 18 + (k % 3) * 22;
const int cy = cellY + 20 + (k / 3) * 22;
fillCircle(cx, cy, 9, robotColor(robotIndex));
drawText(std::to_string(robotIndex + 1), cx - 4, cy - 7, white, false);
}
```
### Controls
| Key / action | Effect |
|---|---|
| `SPACE` / `ENTER` | advance immediately |
| mouse click | advance immediately |
| `ESC` | close the GUI window |
| window close button | close the GUI window |
| auto-advance | continue after configured milliseconds |
### waitForStep()
```cpp
bool Renderer::waitForStep() {
const Uint32 start = SDL_GetTicks();
while (impl_->open) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
impl_->open = false;
return false;
}
if (event.type == SDL_KEYDOWN) {
const auto key = event.key.keysym.sym;
if (key == SDLK_ESCAPE) {
impl_->open = false;
return false;
}
if (key == SDLK_SPACE || key == SDLK_RETURN) {
return true;
}
}
if (event.type == SDL_MOUSEBUTTONDOWN) {
return true;
}
}
if (impl_->autoAdvanceMs > 0
&& static_cast<int>(SDL_GetTicks() - start) >= impl_->autoAdvanceMs) {
return true;
}
SDL_Delay(8);
}
return false;
}
```
---
## 7. Integrating Renderer with Game
### Game.h additions
```cpp
// include/Game.h
class Renderer;
class Game {
public:
Game();
void run();
#ifdef WITH_SDL2_GUI
void setRenderer(Renderer* renderer) { renderer_ = renderer; }
#endif
private:
// existing members
#ifdef WITH_SDL2_GUI
Renderer* renderer_ = nullptr; // non-owning
std::vector<std::string> msgLog_; // capped message buffer
int currentRound_ = 0;
void pushMsg(const std::string& msg);
void renderFrameLocked();
#endif
};
```
The pointer is non-owning. `main_gui.cpp` creates the renderer and passes its address to `Game`.
### Game.cpp includes
```cpp
#ifdef WITH_SDL2_GUI
#include "../include/Renderer.h"
#endif
```
### Message logging
```cpp
void Game::log(const std::string& msg) {
std::cout << msg << '\n';
#ifdef WITH_SDL2_GUI
if (renderer_) {
msgLog_.push_back(msg);
if (msgLog_.size() > 120) {
msgLog_.erase(msgLog_.begin());
}
}
#endif
}
```
If you prefer to keep `log()` terminal-only, use a separate `pushMsg()` helper. The important point is that the GUI log must be updated from the same serialized turn path.
### Build a read-only robot view
```cpp
#ifdef WITH_SDL2_GUI
std::vector<const Robot*> Game::robotView() const {
std::vector<const Robot*> view;
view.reserve(robots_.size());
for (const auto& robot : robots_) {
view.push_back(robot.get());
}
return view;
}
#endif
```
If you add this helper, declare it in `Game.h` under the same `#ifdef`.
### renderFrameLocked()
```cpp
#ifdef WITH_SDL2_GUI
void Game::renderFrameLocked() {
if (!renderer_ || !renderer_->isOpen()) return;
std::vector<const Robot*> view;
view.reserve(robots_.size());
for (const auto& robot : robots_) {
view.push_back(robot.get());
}
renderer_->render(world_, view, currentRound_, msgLog_);
renderer_->waitForStep();
}
#endif
```
The name `renderFrameLocked()` is deliberate: call it only while `turnMutex_` is held. This guarantees the renderer sees a consistent snapshot. In GUI step mode, the game intentionally pauses while the user views the frame.
### Calling the renderer from robotLoop()
```cpp
void Game::robotLoop(int idx) {
ScopedTimer threadTimer(threadTimes_[idx]);
Robot& robot = *robots_[idx];
while (true) {
{
std::lock_guard<std::mutex> lk(turnMutex_);
if (!robot.isAlive()) {
recordDeath(idx, "killed earlier");
#ifdef WITH_SDL2_GUI
renderFrameLocked();
#endif
break;
}
if (isGameOver()) {
break;
}
++currentRound_;
const int dir = robot.decideNextMove(world_);
robot.move(dir, world_);
const int effect = world_.checkEffects(robot.getX(), robot.getY());
if (effect < 0) applyEffect(robot, effect);
if (!robot.isAlive()) {
recordDeath(idx, "killed by effect");
#ifdef WITH_SDL2_GUI
renderFrameLocked();
#endif
break;
}
fightNearby(robot);
if (effect != -1 && robot.isAlive()) {
const int mined = robot.mine(world_);
log(robot.getName() + " mined " + std::to_string(mined) + " points.");
}
checkRearrange(robot);
#ifdef WITH_SDL2_GUI
renderFrameLocked();
#endif
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
```
This preserves the base thread model. The GUI mode slows the simulation because it pauses after turns, but it does not reintroduce `player_`, `computer_`, or a sequential `play()` loop.
### Initial and final frames
Render once after setup and once after all threads join. Both should happen while no robot thread is mutating the game state.
```cpp
void Game::run() {
setup();
#ifdef WITH_SDL2_GUI
{
std::lock_guard<std::mutex> lk(turnMutex_);
renderFrameLocked();
}
#endif
// launch and join threads
#ifdef WITH_SDL2_GUI
if (renderer_ && renderer_->isOpen()) {
renderer_->setAutoAdvanceMs(0);
std::lock_guard<std::mutex> lk(turnMutex_);
log("=== GAME OVER ===");
renderFrameLocked();
}
#endif
}
```
---
## 8. GUI CMake Target
Keep the terminal target independent from SDL2.
```cmake
option(BUILD_GUI "Build SDL2 GUI target" OFF)
if (BUILD_GUI)
find_package(SDL2 REQUIRED)
find_package(SDL2_ttf REQUIRED)
add_executable(deep_miner_gui
${SOURCES}
src/Renderer.cpp
main_gui.cpp
)
target_compile_definitions(deep_miner_gui PRIVATE WITH_SDL2_GUI)
target_include_directories(deep_miner_gui PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries(deep_miner_gui
Threads::Threads
SDL2::SDL2
SDL2_ttf::SDL2_ttf
)
endif()
```
Depending on your platform's CMake package files, the imported targets may be named differently. If `SDL2::SDL2` or `SDL2_ttf::SDL2_ttf` is unavailable, use the variables exported by your SDL2 CMake package.
Build:
```bash
mkdir build
cd build
cmake -DBUILD_GUI=ON ..
cmake --build .
./deep_miner_gui
```
---
## 9. GUI Entry Point
```cpp
// main_gui.cpp
#include "include/Game.h"
#include "include/Renderer.h"
#include <exception>
#include <iostream>
int main() {
try {
Renderer renderer;
if (!renderer.init()) {
std::cerr << "Could not initialise SDL2 renderer.\n";
return 1;
}
renderer.setAutoAdvanceMs(900);
Game game;
game.setRenderer(&renderer);
game.run();
return 0;
} catch (const std::exception& e) {
std::cerr << "Fatal error: " << e.what() << "\n";
return 1;
}
}
```
The normal terminal `main.cpp` remains unchanged and does not include `Renderer.h`.
---
## 10. Common Pitfalls
### Pitfall 1: Including SDL2 in Game.h
Do not include `<SDL2/SDL.h>` in `Game.h`. Forward-declare `Renderer` and include SDL2 only in `Renderer.cpp`.
### Pitfall 2: Rendering two hard-coded robots
Do not use `player_`, `computer_`, `P`, or `C` as architectural concepts. The game has 510 robots. Render indexed markers or abbreviated robot names.
### Pitfall 3: Waiting for input without a stable snapshot
If the renderer waits while other threads mutate the world, the displayed state can become inconsistent. The simple solution is to call `renderFrameLocked()` while `turnMutex_` is held. For a more advanced solution, copy a lightweight snapshot under the lock and let the renderer wait after the lock is released.
### Pitfall 4: Linking SDL2 into the terminal target
Only `deep_miner_gui` should link SDL2. The base `deep_miner` target should link only `Threads::Threads`.
### Pitfall 5: GUI timing changes thread measurements
Step mode and auto-advance delays are part of the measured runtime if you call the renderer inside `robotLoop()`. This is acceptable for visualization. For benchmark runs, use the terminal target.
---
## Minimal Project Additions
```text
deep_miner/
|-- main_gui.cpp
|-- include/
| `-- Renderer.h
`-- src/
`-- Renderer.cpp
```
The GUI guide deliberately stays separate from the base programming guide. The core project remains a clean terminal-based C++17 threading assignment, while this extension adds visualization without changing the ownership model or robot architecture.
---
## Appendix A: Complete GUI Source Code
This appendix contains the complete GUI-specific files plus the small `Game` integration points required by the renderer. The core project files remain in `PARALLEL_DEEP_MINER_GUIDE.md`.
### `main_gui.cpp`
```cpp
#include "Game.h"
#include "Renderer.h"
#include <exception>
#include <iostream>
int main() {
try {
Renderer renderer;
if (!renderer.init()) {
std::cerr << "Could not initialise SDL2 renderer.\n";
return 1;
}
renderer.setAutoAdvanceMs(900);
Game game;
game.setRenderer(&renderer);
game.run();
return 0;
} catch (const std::exception& e) {
std::cerr << "Fatal error: " << e.what() << "\n";
return 1;
}
}
```
### `include/Renderer.h`
```cpp
#pragma once
#include <memory>
#include <string>
#include <vector>
class World;
class Robot;
class Renderer {
public:
Renderer();
~Renderer();
bool init();
void render(const World& world,
const std::vector<Robot*>& robots,
int round,
const std::vector<std::string>& log);
bool waitForStep();
void setAutoAdvanceMs(int ms);
bool isOpen() const;
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
```
### `src/Renderer.cpp`
```cpp
#include "Renderer.h"
#include "Robot.h"
#include "World.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <algorithm>
#include <cmath>
#include <sstream>
#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(245, 245, 245), true);
int depth = world.getSurfaceLevel(x, y) + 1;
int barW = static_cast<int>((CELL_W - 12) * (static_cast<double>(depth) / world.getSizeZ()));
fillRect(px, py + CELL_H - 15, barW, 5, rgba(240, 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; }
```
### `CMakeLists.txt` GUI additions
```cmake
option(BUILD_GUI "Build SDL2 GUI target" OFF)
if (BUILD_GUI)
find_package(SDL2 REQUIRED)
find_package(SDL2_ttf REQUIRED)
add_executable(deep_miner_gui
${SOURCES}
src/Renderer.cpp
main_gui.cpp
)
target_compile_definitions(deep_miner_gui PRIVATE WITH_SDL2_GUI)
target_include_directories(deep_miner_gui PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries(deep_miner_gui
Threads::Threads
SDL2::SDL2
SDL2_ttf::SDL2_ttf
)
endif()
```
### `include/Game.h` GUI integration additions
Add a forward declaration before the `Game` class:
```cpp
class Renderer;
```
Add this public method:
```cpp
#ifdef WITH_SDL2_GUI
void setRenderer(Renderer* renderer);
#endif
```
Add these private members and helper declarations:
```cpp
#ifdef WITH_SDL2_GUI
Renderer* renderer_ = nullptr;
std::vector<std::string> guiLog_;
void renderFrameLocked();
std::vector<Robot*> robotPointers() const;
#endif
```
### `src/Game.cpp` GUI integration additions
Include the renderer only for the GUI build:
```cpp
#ifdef WITH_SDL2_GUI
#include "Renderer.h"
#endif
```
Add these methods:
```cpp
#ifdef WITH_SDL2_GUI
void Game::setRenderer(Renderer* renderer) {
renderer_ = renderer;
}
std::vector<Robot*> Game::robotPointers() const {
std::vector<Robot*> out;
out.reserve(robots_.size());
for (const auto& robot : robots_) out.push_back(robot.get());
return out;
}
void Game::renderFrameLocked() {
if (!renderer_ || !renderer_->isOpen()) return;
renderer_->render(world_, robotPointers(), round_, guiLog_);
renderer_->waitForStep();
}
#endif
```
Modify `Game::log` so GUI messages are recorded while terminal output remains unchanged:
```cpp
void Game::log(const std::string& message) const {
std::cout << message << "\n";
#ifdef WITH_SDL2_GUI
auto* self = const_cast<Game*>(this);
self->guiLog_.push_back(message);
if (self->guiLog_.size() > 120) self->guiLog_.erase(self->guiLog_.begin());
#endif
}
```
Call `renderFrameLocked()` after each atomic turn, before releasing the mutex:
```cpp
// near the end of Game::robotLoop(), still inside the lock_guard scope
checkRearrange(robot);
#ifdef WITH_SDL2_GUI
renderFrameLocked();
#endif
```
Render an initial and final frame in `Game::run()`:
```cpp
#ifdef WITH_SDL2_GUI
{
std::lock_guard<std::mutex> lock(turnMutex_);
renderFrameLocked();
}
#endif
// launch and join threads here
#ifdef WITH_SDL2_GUI
if (renderer_ && renderer_->isOpen()) {
renderer_->setAutoAdvanceMs(0);
std::lock_guard<std::mutex> lock(turnMutex_);
guiLog_.push_back("=== GAME OVER ===");
renderFrameLocked();
}
#endif
```
+4 -4
View File
@@ -10,9 +10,9 @@ class World {
public: public:
explicit World ( int x = 5, int y = 5, int z = 10 ); explicit World ( int x = 5, int y = 5, int z = 10 );
int getSizeX () const { return _sizeX; } int getSizeX () const { return sizeX_; }
int getSizeY () const { return _sizeY; } int getSizeY () const { return sizeY_; }
int getSizeZ () const { return _sizeZ; } int getSizeZ () const { return sizeZ_; }
int getValue ( int x, int y, int z ) const; int getValue ( int x, int y, int z ) const;
void setValue ( int x, int y, int z, int value ); void setValue ( int x, int y, int z, int value );
@@ -30,7 +30,7 @@ class World {
int checkEffects ( int x, int y ); int checkEffects ( int x, int y );
int mineAllPositive ( int x, int y ); int mineAllPositive ( int x, int y );
double posiveAverage ( int x, int y ) const; double positiveAverage ( int x, int y ) const;
int topPositiveSum ( int x, int y, int blocks ) const; int topPositiveSum ( int x, int y, int blocks ) const;
void sortPositiveValuesInColumnAscending ( int x, int y ); void sortPositiveValuesInColumnAscending ( int x, int y );
void rearrange (); void rearrange ();
+48
View File
@@ -143,6 +143,54 @@ int World::mine ( int x, int y ) {
return value > 0 ? value : 0; return value > 0 ? value : 0;
} }
int World::mineAllPositive( int x, int y ) {
validateXY(x, y );
auto& col = grid_[x][y];
int total = 0;
std::vector <int> kept;
kept.reserve( col.size() );
for ( int v : col ) {
if ( v > 0 ) {
total += v;
} else {
kept.push_back( v );
}
}
col = std::move(kept );
return total;
}
double World::positiveAverage(int x, int y ) const {
validateXY(x, y );
int sum = 0, count = 0;
for ( int v : grid_[x][y] ) {
if ( v > 0 ) {
sum += v;
count++;
}
}
return count ==0 ? 0.0f : static_cast<double> (sum) / count;
}
int World::topPositiveSum(int x, int y, int blocks ) const {
validateXY( x, y );
int sum = 0, count =0;
const auto& col = grid_[x][z];
for ( auto it = col.rbegin(); it != col.rend() && count < blocks; ++it ) {
if ( *it > 0 ) {
sum += *it;
++count;
}
}
return sum;
}
void World::sortPositiveValuesInColumnAscending( int x, int y ) {
validateXY ( x, y );
auto
}
/* Print a 2D surface view with optional player/computer markers. /* Print a 2D surface view with optional player/computer markers.
Marker legend is shown when at least one robot position is provided. Marker legend is shown when at least one robot position is provided.
Complexity: O(x × y × z) due to per-cell surface lookup */ Complexity: O(x × y × z) due to per-cell surface lookup */