Circles im Labyrinth
-
Hallo!
"Can Circles solve a Maze?". Diese Frage wollte ich vor langer Zeit mal beantworten und es ist mir so lala geworden.
Für die Physik habe ich mir Hilfe von der KI geholt. Mein erster Ansatz war erbärmlich, dann habe ich ChatGBT nach Hilfestellung gefragt, die hat sich aber ständig widersprochen und sie hatte auch nur begrenzt Zeit. Dann Claude, das lief schon besser. Sind aber noch einige Sachen zu fixen. Manchmal tunneln noch einige Kreise und die Performance lässt ab 1000 Kreisen zu wünschen übrig.
Es ist etwas längerer Code, insgesamt drei Klassen, ein Maze Generator durch Backtracking, die CirclePhysics und das Hauptprogramm 'MazeAndCircles'.
Als erstes der Maze Generator
#pragma once #include <zp/Engine/SDLPixelEngine.h> #include <zp/Math/Random.h> namespace zp { // Some bit fields for convenience enum { CELL_PATH_N = 0x01, CELL_PATH_E = 0x02, CELL_PATH_S = 0x04, CELL_PATH_W = 0x08, CELL_VISITED = 0x10, }; // Colors of the maze struct MazeColor { RGB Background; RGB Visited; RGB Free; RGB TopOfStack; }; class MazeGenerator { public: constexpr MazeGenerator() = default; private: Array2D<i32> m_MazeArray; i32 m_VisitedCells = 0; std::stack<Coord> m_CellStack; i32 m_PathWidth = 0; // Path thickness i32 m_PathGap = 0; // Passage width, additional gap i32 m_CellStride = 0; // Distance from one cell to the next. ivec2 m_MazePosition; // top left pos of the rendered maze f32 m_GenerationDelay = 0; bool m_IsGenerating = false; MazeColor m_Color; public: constexpr Array2D<i32> array() const { return m_MazeArray; } constexpr ivec2 position() const { return m_MazePosition; } constexpr ivec2& position() { return m_MazePosition; } constexpr i32 width() const { return m_MazeArray.width(); } constexpr i32 height() const { return m_MazeArray.height(); } constexpr i32 totalWidth() const { return m_MazeArray.width() * m_CellStride; } constexpr i32 totalHeight() const { return m_MazeArray.height() * m_CellStride ; } constexpr size_t size() const { return m_MazeArray.size(); } constexpr i32 pathWidth() const { return m_PathWidth; } constexpr i32 pathGap() const { return m_PathGap; } constexpr i32 cellStride() const { return m_CellStride; } constexpr f32 generationDelay() const { return m_GenerationDelay; } constexpr f32& generationDelay() { return m_GenerationDelay; } constexpr bool isGenerating() const { return m_IsGenerating; } constexpr bool& isGenerating() { return m_IsGenerating; } constexpr MazeColor color() const { return m_Color; } constexpr MazeColor& color() { return m_Color; } public: inline void init( SDLPixelEngine& e, const i32 width, const i32 height, const i32 pathWidth, const i32 pathGap, const bool fullScreen) { // Maze parameters m_PathWidth = pathWidth; m_PathGap = pathGap; m_CellStride = m_PathWidth + m_PathGap; // Distance from one cell to the next. // Maze dimensions if (fullScreen) { const i32 w = e.screenWidth<i32>() / m_CellStride; const i32 h = e.screenHeight<i32>() / m_CellStride; m_MazeArray = { w, h }; } else { m_MazeArray = { width, height }; } m_Color.Background = GREY; m_Color.Visited = LIGHT_BLUE; m_Color.TopOfStack = WHITE; // reset maze reset(); } inline void reset() { // Clear stack while (!m_CellStack.empty()) m_CellStack.pop(); // Clear array m_MazeArray.clear(); // Choose a starting cell const i32 x = Random().uniformInt<i32>(m_MazeArray.width() - 1); const i32 y = Random().uniformInt<i32>(m_MazeArray.height() - 1); m_CellStack.push(Coord(x, y)); m_MazeArray(x, y) = CELL_VISITED; m_VisitedCells = 1; m_IsGenerating = true; } constexpr void generate(SDLPixelEngine& e) { if (e.delayThis(m_GenerationDelay)) { // Do Maze Algorithm if (m_VisitedCells < m_MazeArray.size()) { // Create a set of unvisted neighbours std::vector<i32> neighbours; // North neighbour if (m_CellStack.top().Y > 0 && (cellOffset(0, -1) & CELL_VISITED) == 0) neighbours.push_back(0); // East neighbour if (m_CellStack.top().X < m_MazeArray.width() - 1 && (cellOffset(1, 0) & CELL_VISITED) == 0) neighbours.push_back(1); // South neighbour if (m_CellStack.top().Y < m_MazeArray.height() - 1 && (cellOffset(0, 1) & CELL_VISITED) == 0) neighbours.push_back(2); // West neighbour if (m_CellStack.top().X > 0 && (cellOffset(-1, 0) & CELL_VISITED) == 0) neighbours.push_back(3); // Are there any neighbours available? if (!neighbours.empty()) { // Choose one available neighbour at random const i32 next_cell_dir = neighbours[Random().uniformInt<size_t>(neighbours.size() - 1)]; // Create a path between the neighbour and the current cell switch (next_cell_dir) { case 0: // North cellOffset(0, -1) |= CELL_VISITED | CELL_PATH_S; cellOffset(0, 0) |= CELL_PATH_N; m_CellStack.push(Coord(m_CellStack.top().X + 0, m_CellStack.top().Y - 1)); break; case 1: // East cellOffset(+1, 0) |= CELL_VISITED | CELL_PATH_W; cellOffset(0, 0) |= CELL_PATH_E; m_CellStack.push(Coord(m_CellStack.top().X + 1, m_CellStack.top().Y + 0)); break; case 2: // South cellOffset(0, +1) |= CELL_VISITED | CELL_PATH_N; cellOffset(0, 0) |= CELL_PATH_S; m_CellStack.push(Coord(m_CellStack.top().X + 0, m_CellStack.top().Y + 1)); break; case 3: // West cellOffset(-1, 0) |= CELL_VISITED | CELL_PATH_E; cellOffset(0, 0) |= CELL_PATH_W; m_CellStack.push(Coord(m_CellStack.top().X - 1, m_CellStack.top().Y + 0)); break; } m_VisitedCells++; } else { // No available neighbours so backtrack! m_CellStack.pop(); } } else { // m_MazeArray generation is done, set generate flag m_IsGenerating = false; } } } constexpr void draw(SDLPixelEngine& e) { // Draw background const frect background = { m_MazePosition, ivec2(m_MazeArray.width() * m_CellStride, (m_MazeArray.height() - 1) * m_CellStride) - ivec2(m_PathGap, m_PathGap) }; e.draw().fillRectangle(background, m_Color.Background); // Draw Maze for (int y = 0; y < m_MazeArray.height() - 1; y++) { for (int x = 0; x < m_MazeArray.width(); x++) { const ivec2 cellPos( x * m_CellStride + m_MazePosition.x, y * m_CellStride + m_MazePosition.y); // Draw Cell if (m_MazeArray(x, y) & CELL_VISITED) e.draw().fillRectangle(cellPos, ivec2(m_PathWidth, m_PathWidth), m_Color.Visited); else e.draw().fillRectangle(cellPos, ivec2(m_PathWidth, m_PathWidth), m_Color.Free); // Draw passageways between cells if (m_MazeArray(x, y) & CELL_PATH_S) // South e.draw().fillRectangle( ivec2(cellPos.x, cellPos.y + m_PathWidth), ivec2(m_PathWidth, m_CellStride - m_PathWidth), m_Color.Visited); if (m_MazeArray(x, y) & CELL_PATH_E) // East e.draw().fillRectangle( ivec2(cellPos.x + m_PathWidth, cellPos.y), ivec2(m_CellStride - m_PathWidth, m_PathWidth), m_Color.Visited); // Current cell / top of stack if (!m_CellStack.empty() && m_IsGenerating) // Draw only if stack is not empty && maze is generating { const ivec2 stackPos( m_CellStack.top().X * m_CellStride + m_MazePosition.x, m_CellStack.top().Y * m_CellStride + m_MazePosition.y); e.draw().fillRectangle(stackPos, ivec2(m_PathWidth, m_PathWidth), m_Color.TopOfStack); } } } } private: constexpr i32 cellOffset(const i32 x, const i32 y) const { return m_MazeArray(m_CellStack.top().X + x, m_CellStack.top().Y + y); } constexpr i32& cellOffset(const i32 x, const i32 y) { return m_MazeArray(m_CellStack.top().X + x, m_CellStack.top().Y + y); } }; }Circle Physics
#pragma once #include <zp/Engine/SDLPixelEngine.h> namespace zp::physics { inline constexpr f32 EPSILON = 0.0001f; inline constexpr fvec2 GRAVITY = fvec2(0.0f, 9.81f) * 2.0f; inline constexpr ui32 ITERATIONS = 2; inline constexpr ui32 SUBSTEPS = 6; inline constexpr f32 MAX_STEP_DISPLACEMENT = 3.0f; inline constexpr f32 MAX_CORRECTION = 2.0f; } namespace zp { using namespace physics; // Coefficient of restitution: // // 1.0 = perfectly elastic // 0.0 = completely inelastic inline constexpr f32 WALL_RESTITUTION = 0.25f; inline constexpr f32 CIRCLE_RESTITUTION = 0.20f; inline constexpr f32 CIRCLE_MASS = 10.0f; inline constexpr f32 CIRCLE_DAMPING = 0.5f; // between [0, 1] struct Circle { fvec2 Pos, Vel; f32 Radius = 1; struct { RGB Fill = WHITE; RGB Frame = DARK_GREY; } Color; }; struct Wall { fvec2 Start, End; f32 Radius = 1; RGB Color = BLACK; }; class CirclePhysics { public: CirclePhysics() = default; static_assert(CIRCLE_MASS > 0.0f); private: std::vector<Circle> m_CircleList; std::vector<Wall> m_WallList; public: constexpr const std::vector<Circle>& circles() const { return m_CircleList; } constexpr const std::vector<Wall>& walls() const { return m_WallList; } public: inline void createCircle(const fvec2& pos, const f32 radius, const RGB& fill, const RGB& frame) { if (!(radius > 0.0f)) return; Circle circle; circle.Pos = pos; circle.Radius = radius; circle.Color = { fill, frame }; m_CircleList.push_back(circle); } inline void createWall(const fvec2& start, const fvec2& end, const f32 radius, const RGB& color) { if ((end - start).mag2() < EPSILON) return; if (!(radius > 0.0f)) return; Wall wall; wall.Start = start; wall.End = end; wall.Radius = radius; wall.Color = color; m_WallList.push_back(wall); } inline void update(SDLPixelEngine& e) { const f32 dt = std::min(e.deltaTime(), 1.0f / 30.0f); for (ui32 step = 0; step < SUBSTEPS; ++step) { integrate(dt); solveCircleCollisions(dt); } removeOffscreen(e); } inline void reset() { m_CircleList.clear(); m_WallList.clear(); } constexpr void drawCircle(SDLPixelEngine& e, const Circle& circle) const { e.draw().fillCircle(circle.Pos, circle.Radius, circle.Color.Fill); e.draw().circle(circle.Pos, circle.Radius, circle.Color.Frame); if (circle.Radius > 2) drawVelocityLine(e, circle, circle.Color.Frame); } constexpr void drawWalls(SDLPixelEngine& e) const { for (const auto& wall : m_WallList) { f32 nx = -(wall.End.y - wall.Start.y); f32 ny = (wall.End.x - wall.Start.x); const f32 d = std::sqrt(nx * nx + ny * ny); nx /= d; ny /= d; const fvec2 v0{ wall.Start.x + nx * wall.Radius, wall.Start.y + ny * wall.Radius }; const fvec2 v1{ wall.End.x + nx * wall.Radius, wall.End.y + ny * wall.Radius }; const fvec2 v2{ wall.End.x - nx * wall.Radius, wall.End.y - ny * wall.Radius }; const fvec2 v3{ wall.Start.x - nx * wall.Radius, wall.Start.y - ny * wall.Radius }; // render wall Box<f32> box = { v0, v1, v2, v3 }; box.color() = wall.Color; e.draw().fillBox(box); e.draw().fillCircle(wall.Start, wall.Radius, wall.Color); e.draw().fillCircle(wall.End, wall.Radius, wall.Color); } } private: inline void integrate(const f32 dt) { const f32 frameDamping = std::pow(CIRCLE_DAMPING, dt); const f32 maxSpeed = MAX_STEP_DISPLACEMENT / dt; for (auto& circle : m_CircleList) { circle.Vel += GRAVITY * dt; circle.Vel *= frameDamping; const f32 speed = circle.Vel.mag(); if (speed > maxSpeed) circle.Vel *= maxSpeed / speed; circle.Pos += circle.Vel * dt; } } inline void solveCircleCollisions(const f32 dt) { constexpr f32 SLOP = 0.01f; constexpr f32 PERCENT = 1.0f; constexpr f32 InvMass = 1 / CIRCLE_MASS; constexpr f32 INV_MASS_SUM = InvMass + InvMass; const f32 restThreshold = 2.0f * GRAVITY.mag() * dt; // ------------------------- // Position solver // ------------------------- for (ui32 iteration = 0; iteration < ITERATIONS; ++iteration) { /* Circle vs circle */ for (size_t i = 0; i < m_CircleList.size(); ++i) { for (size_t j = i + 1; j < m_CircleList.size(); ++j) { Circle& a = m_CircleList[i]; Circle& b = m_CircleList[j]; const fvec2 delta = b.Pos - a.Pos; const f32 radiusSum = a.Radius + b.Radius; const f32 distanceSq = delta.mag2(); if (distanceSq >= radiusSum * radiusSum) continue; f32 distance = std::sqrt(distanceSq); fvec2 normal; if (distance < EPSILON) { // deterministic, but different for each pair const f32 theta = static_cast<f32>((i * 31 + j * 17) % 360) * math::TAU<f32> / 360.0f; normal = math::toCartesian<f32>(1.0f, theta); distance = 0.0f; } else normal = delta / distance; const f32 penetration = radiusSum - distance; const f32 correction = std::min(std::max(penetration - SLOP, 0.0f), MAX_CORRECTION) * PERCENT / INV_MASS_SUM; a.Pos -= normal * correction * InvMass; b.Pos += normal * correction * InvMass; } } /* Circle vs wall */ for (size_t i = 0; i < m_CircleList.size(); ++i) { Circle& circle = m_CircleList[i]; for (const auto& wall : m_WallList) { const fvec2 wallVector = wall.End - wall.Start; const f32 wallLengthSq = wallVector.mag2(); if (wallLengthSq < EPSILON) continue; const fvec2 toCircle = circle.Pos - wall.Start; f32 t = wallVector.dot(toCircle) / wallLengthSq; t = std::max(0.0f, std::min(1.0f, t)); const fvec2 closestPoint = wall.Start + wallVector * t; const fvec2 delta = circle.Pos - closestPoint; const f32 distanceSq = delta.mag2(); const f32 collisionRadius = circle.Radius + wall.Radius; if (distanceSq >= collisionRadius * collisionRadius) continue; const f32 distance = std::sqrt(distanceSq); const auto normal = wallContactNormal(delta, distance, wallVector); if (!normal) continue; const f32 penetration = collisionRadius - distance; circle.Pos += *normal * std::max(penetration - SLOP, 0.0f); } } } // ------------------------- // Velocity Solver // ------------------------- /* Circle vs circle */ for (ui32 iteration = 0; iteration < ITERATIONS; ++iteration) { for (size_t i = 0; i < m_CircleList.size(); ++i) { for (size_t j = i + 1; j < m_CircleList.size(); ++j) { Circle& a = m_CircleList[i]; Circle& b = m_CircleList[j]; const fvec2 delta = b.Pos - a.Pos; const f32 distanceSq = delta.mag2(); const f32 radiusSum = a.Radius + b.Radius; // Position solver should already have // separated these, but allow a small // tolerance. if (distanceSq > radiusSum * radiusSum) continue; const f32 distance = std::sqrt(distanceSq); if (distance < EPSILON) continue; const fvec2 normal = delta / distance; const fvec2 relativeVelocity = b.Vel - a.Vel; const f32 velocityAlongNormal = relativeVelocity.dot(normal); // Moving apart. if (velocityAlongNormal >= 0.0f) continue; const f32 e = (-velocityAlongNormal < restThreshold) ? 0.0f : CIRCLE_RESTITUTION; const f32 impulseMagnitude = -(1 + e) * velocityAlongNormal / INV_MASS_SUM; const fvec2 impulse = normal * impulseMagnitude; a.Vel -= impulse * InvMass; b.Vel += impulse * InvMass; } } /* Circle vs wall */ for (size_t i = 0; i < m_CircleList.size(); ++i) { Circle& circle = m_CircleList[i]; for (const auto& wall : m_WallList) { const fvec2 wallVector = wall.End - wall.Start; const f32 wallLengthSq = wallVector.mag2(); if (wallLengthSq < EPSILON) continue; const fvec2 toCircle = circle.Pos - wall.Start; f32 t = wallVector.dot(toCircle) / wallLengthSq; t = std::max(0.0f, std::min(1.0f, t)); const fvec2 closestPoint = wall.Start + wallVector * t; const fvec2 delta = circle.Pos - closestPoint; const f32 distanceSq = delta.mag2(); const f32 collisionRadius = circle.Radius + wall.Radius; if (distanceSq > collisionRadius * collisionRadius) continue; const f32 distance = std::sqrt(distanceSq); const auto normal = wallContactNormal(delta, distance, wallVector); if (!normal) continue; const f32 velocityAlongNormal = circle.Vel.dot(*normal); if (velocityAlongNormal >= 0.0f) continue; // moving apart const f32 e = (-velocityAlongNormal < restThreshold) ? 0.0f : WALL_RESTITUTION; circle.Vel -= *normal * ((1 + e) * velocityAlongNormal); } } } } inline void removeOffscreen(SDLPixelEngine& e) { const auto screenRect = e.screenRect<f32>(); std::erase_if(m_CircleList, [&](const Circle& c) { return !utils::isPointInsideBox(screenRect, c.Pos, c.Radius); }); } constexpr void drawVelocityLine(SDLPixelEngine& e, const Circle& circle, const RGB& color) const { const f32 speedSq = circle.Vel.mag2(); if (speedSq > EPSILON * EPSILON) { const fvec2 dir = circle.Vel / std::sqrt(speedSq); e.draw().line(circle.Pos, circle.Pos + dir * circle.Radius, color); } else e.draw().point(circle.Pos, color); } inline static std::optional<fvec2> wallContactNormal(const fvec2& delta, const f32 distance, const fvec2& wallVector) { // Standard case: The circle center does not lie on the wall segment. if (distance > EPSILON) return delta / distance; // Fallback: The midpoint lies (almost) exactly on the wall line; // in this case, the perpendicular to the wall is the only sensible direction. const fvec2 perpendicular(-wallVector.y, wallVector.x); const f32 length = perpendicular.mag(); if (length < EPSILON) return std::nullopt; return perpendicular / length; } }; }Maz and Circles .h
#pragma once #include <zp/Engine/SDLPixelEngine.h> #include "MazeGenerator.h" #include "CirclePhysics.h" namespace zp { enum class WallType { CLOSED = 0, CLOSED_RIGHT = 1, CLOSED_LEFT_RIGHT = 2, CLOSED_BOTTOM_LEFT_1 = 17, CLOSED_BOTTOM_LEFT_2 = 18, CLOSED_BOTTOM_LEFT_3 = 19, CLOSED_LEFT_1 = 20, CLOSED_LEFT_2 = 21, CLOSED_LEFT_3 = 22, CLOSED_LEFT_4 = 23, CLOSED_BOTTOM_1 = 24, CLOSED_BOTTOM_2 = 25, CLOSED_BOTTOM_3 = 26, CLOSED_BOTTOM_4 = 27, CLEAR_1 = 28, CLEAR_2 = 29, CLEAR_3 = 30, }; class MazeAndCircles : public SDLPixelEngine { public: MazeAndCircles() = default; MazeAndCircles(const i32 width, const i32 height); MazeAndCircles(const std::string& title, const i32 width, const i32 height); private: virtual bool onCreate() override; virtual bool onUpdate() override; virtual bool onInput() override; private: // Maze MazeGenerator Maze; using Wall = std::pair<frect, WallType>; std::vector<Wall> MazeWallList; f32 WallWeight = 0; RGB WallColor; std::pair<Coord, i32> CurrentCell; // circles CirclePhysics Physic; f32 CircleRadius = 0; RGB CircleFill; RGB CircleFrame; bool DoMazePhysics = false; // common SDLImage Background; public: void getMazeWalls(); public: // Maze void initMaze(); void generateMaze(); void drawMaze(); void drawMazeWalls(); // Circles void createCircles(const fvec2& pos, const i32 N); void updateCircles(); void drawCircles(); // Common void loadBackground(const std::string& fileName); void setBackground(); void reset(); void drawInfo(const fvec2& pos); private: void createOuterWalls(); void exportMazeWallsToPhysics(); }; }Maze and Circles .cpp
#include "MazeAndCircles.h" #include <zp/Math/Random.h> zp::MazeAndCircles::MazeAndCircles(const i32 width, const i32 height) : SDLPixelEngine("SDLPixelEngine", width, height) { } zp::MazeAndCircles::MazeAndCircles(const std::string& title, const i32 width, const i32 height) : SDLPixelEngine(title, width, height) { } bool zp::MazeAndCircles::onCreate() { screenColor() = LIGHT_BLUE; loadBackground("why_so_alone_.png"); initMaze(); return true; } bool zp::MazeAndCircles::onUpdate() { setBackground(); generateMaze(); drawMaze(); drawMazeWalls(); updateCircles(); drawCircles(); const i32 posX = Maze.position().x + Maze.totalWidth() + 40; const i32 posY = Maze.position().y + 30; drawInfo({ posX, posY }); return true; } bool zp::MazeAndCircles::onInput() { // Maze if (getKey(Key::LEFT).Held) Maze.generationDelay() -= 1.0f * deltaTime(); if (getKey(Key::RIGHT).Held) Maze.generationDelay() += 1.0f * deltaTime(); if (Maze.generationDelay() < 0) Maze.generationDelay() = 0; if (getKey(Key::RETURN).Pressed) reset(); // circles if (!Maze.isGenerating()) { if (getMouse(Button::LEFT).Held) { if (delayThis(3)) createCircles(getMousePos(), 3); } } return true; } void zp::MazeAndCircles::initMaze() { // Maze WallWeight = 1; WallColor = RGB(BLACK, 200); CircleRadius = 4; CircleFill = ORANGE; CircleFrame = BLACK; Maze.init(*this, 12, 16, 35, static_cast<int>(WallWeight) + 1, false); Maze.position() = { 170, 100 }; MazeWallList.clear(); Maze.color().Free = RGB(); Maze.color().Visited = RGB(LIGHT_BLUE, 150); Maze.color().Background = RGB(); Maze.generationDelay() = 0.0f; // physics DoMazePhysics = false; Physic.reset(); createOuterWalls(); } void zp::MazeAndCircles::generateMaze() { Maze.generate(*this); if (!Maze.isGenerating() && !DoMazePhysics) { getMazeWalls(); DoMazePhysics = true; } } void zp::MazeAndCircles::drawMaze() { Maze.draw(*this); } void zp::MazeAndCircles::drawMazeWalls() { if (!Maze.isGenerating()) Physic.drawWalls(*this); } void zp::MazeAndCircles::createCircles(const fvec2& pos, const i32 N) { for (i32 i = 0; i < N; i++) Physic.createCircle(pos, CircleRadius, CircleFill, CircleFrame); } void zp::MazeAndCircles::updateCircles() { if (DoMazePhysics) Physic.update(*this); } void zp::MazeAndCircles::drawCircles() { for (auto& circle : Physic.circles()) Physic.drawCircle(*this, circle); } void zp::MazeAndCircles::reset() { initMaze(); } void zp::MazeAndCircles::createOuterWalls() { // set Dimensions constexpr i32 outerGap = 50; fvec2 startA = { Maze.position().x - 1, Maze.position().y - outerGap }; fvec2 endA = { Maze.position().x - 1, Maze.position().y }; fvec2 startB = { Maze.position().x - 1 + Maze.totalWidth(), Maze.position().y - outerGap }; fvec2 endB = { Maze.position().x - 1 + Maze.totalWidth(), Maze.totalHeight() + outerGap + 11 }; // create walls Physic.createWall(startA, endA, WallWeight, WallColor); Physic.createWall(startB, endB, WallWeight, WallColor); } void zp::MazeAndCircles::getMazeWalls() { for (i32 y = 0; y < Maze.height() - 1; y++) { for (i32 x = 0; x < Maze.width(); x++) { const fvec2 origin = { x * Maze.cellStride() + Maze.position().x - Maze.pathGap() / 2, y * Maze.cellStride() + Maze.position().y - Maze.pathGap() / 2 }; const fvec2 size = { Maze.cellStride(), Maze.cellStride() }; const frect cellRect = { origin, size }; const WallType wall = static_cast<WallType>(Maze.array()(x, y)); MazeWallList.push_back(std::make_pair(cellRect, wall)); } } exportMazeWallsToPhysics(); } void zp::MazeAndCircles::loadBackground(const std::string& fileName) { Background = { getRenderer() }; Background.load(fileName); } void zp::MazeAndCircles::setBackground() { Background.fillWindow(); } void zp::MazeAndCircles::drawInfo(const fvec2& pos) { fvec2 p = pos; f32 tabX = 0, tabY = 0, tabX2 = 0; const RGB colorA = RGB(RED, 200); const RGB colorB = RGB(DARK_RED, 200); screenFont().setSize(18); screenFont().setColor(colorA); tabY = fontSize() * 1.0f; tabX = 190.0f; // Maze info drawString(p, "Maze Size:"); p.x += tabX; drawString(p, std::to_string(Maze.width()) + ", " + std::to_string(Maze.height())); p.x = pos.x; p.y += tabY; drawString(p, "Maze Cells:"); p.x += tabX; drawString(p, std::to_string(Maze.size())); p.x = pos.x; p.y += tabY; if (Maze.isGenerating()) { drawString(p, "Generation Delay:"); p.x += tabX; drawString(p, utils::toString(Maze.generationDelay(), 1, 2)); p.x = pos.x; } // active status screenFont().setSize(24); screenFont().setColor(colorB); tabY = fontSize() * 1.0f; tabX = 200; p.y += tabY; if (Maze.isGenerating()) drawString(p, "Generating Maze"); if (!Maze.isGenerating()) drawString(p, "Drop Circles with Mouse"); p.y += tabY * 2; // debug info screenFont().setSize(18); screenFont().setColor(colorA); tabY = fontSize() * 1.0f; tabX = 200; tabX2 = 75; // active circles count drawString(p, "Circle count: "); p.x += tabX; drawString(p, std::to_string(std::size(Physic.circles()))); p.x = pos.x; p.y += tabY; } void zp::MazeAndCircles::exportMazeWallsToPhysics() { for (const auto& mazeWall : MazeWallList) { f32 x0 = mazeWall.first.origin().x; f32 y0 = mazeWall.first.origin().y; f32 x1 = x0 + mazeWall.first.size().x; f32 y1 = y0 + mazeWall.first.size().y; if (x0 > x1) std::swap(x0, x1); if (y0 > y1) std::swap(y0, y1); switch (mazeWall.second) { case WallType::CLOSED: // bottom side Physic.createWall(fvec2(x0, y1), fvec2(x1, y1), WallWeight, WallColor); // left and right side Physic.createWall(fvec2(x0, y0), fvec2(x0, y1), WallWeight, WallColor); Physic.createWall(fvec2(x1, y0), fvec2(x1, y1), WallWeight, WallColor); break; case WallType::CLOSED_RIGHT: // right side Physic.createWall(fvec2(x1, y0), fvec2(x1, y1), WallWeight, WallColor); break; case WallType::CLOSED_LEFT_RIGHT: // left and right side Physic.createWall(fvec2(x0, y0), fvec2(x0, y1), WallWeight, WallColor); Physic.createWall(fvec2(x1, y0), fvec2(x1, y1), WallWeight, WallColor); break; case WallType::CLOSED_BOTTOM_LEFT_1: // bottom side Physic.createWall(fvec2(x0, y1), fvec2(x1, y1), WallWeight, WallColor); // left side Physic.createWall(fvec2(x0, y0), fvec2(x0, y1), WallWeight, WallColor); break; case WallType::CLOSED_BOTTOM_LEFT_2: // bottom side Physic.createWall(fvec2(x0, y1), fvec2(x1, y1), WallWeight, WallColor); // left side Physic.createWall(fvec2(x0, y0), fvec2(x0, y1), WallWeight, WallColor); break; case WallType::CLOSED_BOTTOM_LEFT_3: // bottom side Physic.createWall(fvec2(x0, y1), fvec2(x1, y1), WallWeight, WallColor); // left side Physic.createWall(fvec2(x0, y0), fvec2(x0, y1), WallWeight, WallColor); break; case WallType::CLOSED_LEFT_1: // left Physic.createWall(fvec2(x0, y0), fvec2(x0, y1), WallWeight, WallColor); break; case WallType::CLOSED_LEFT_2: // left Physic.createWall(fvec2(x0, y0), fvec2(x0, y1), WallWeight, WallColor); break; case WallType::CLOSED_LEFT_3: // left Physic.createWall(fvec2(x0, y0), fvec2(x0, y1), WallWeight, WallColor); break; case WallType::CLOSED_LEFT_4: // left Physic.createWall(fvec2(x0, y0), fvec2(x0, y1), WallWeight, WallColor); break; case WallType::CLOSED_BOTTOM_1: // bottom side Physic.createWall(fvec2(x0, y1), fvec2(x1, y1), WallWeight, WallColor); break; case WallType::CLOSED_BOTTOM_2: // bottom side Physic.createWall(fvec2(x0, y1), fvec2(x1, y1), WallWeight, WallColor); break; case WallType::CLOSED_BOTTOM_3: // bottom side Physic.createWall(fvec2(x0, y1), fvec2(x1, y1), WallWeight, WallColor); break; case WallType::CLOSED_BOTTOM_4: // bottom side Physic.createWall(fvec2(x0, y1), fvec2(x1, y1), WallWeight, WallColor); break; default: break; } } }Danke fürs Lesen und viele Grüße!
PS: Ganz vergessen; wer möchte, kann sich das Ergebnis dieses Code auch anschauen: