<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Circles im Labyrinth]]></title><description><![CDATA[<p>Hallo!</p>
<p>&quot;Can Circles solve a Maze?&quot;. Diese Frage wollte ich vor langer Zeit mal beantworten und es ist mir so lala geworden.</p>
<p>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.</p>
<p>Es ist etwas längerer Code, insgesamt drei Klassen, ein Maze Generator durch Backtracking, die CirclePhysics und das Hauptprogramm 'MazeAndCircles'.</p>
<p>Als erstes der Maze Generator</p>
<pre><code class="language-cpp">#pragma once
#include &lt;zp/Engine/SDLPixelEngine.h&gt;
#include &lt;zp/Math/Random.h&gt;

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&lt;i32&gt; m_MazeArray;

		i32 m_VisitedCells = 0;
		std::stack&lt;Coord&gt; 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&lt;i32&gt; array() const { return m_MazeArray; }

		constexpr ivec2 position() const { return m_MazePosition; }
		constexpr ivec2&amp; 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&amp; generationDelay() { return m_GenerationDelay; }
		constexpr bool isGenerating() const { return m_IsGenerating; }
		constexpr bool&amp; isGenerating() { return m_IsGenerating; }

		constexpr MazeColor color() const { return m_Color; }
		constexpr MazeColor&amp; color() { return m_Color; }

	public:
		inline void init(
			SDLPixelEngine&amp; 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&lt;i32&gt;() / m_CellStride;
				const i32 h = e.screenHeight&lt;i32&gt;() / 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&lt;i32&gt;(m_MazeArray.width() - 1);
			const i32 y = Random().uniformInt&lt;i32&gt;(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&amp; e)
		{
			if (e.delayThis(m_GenerationDelay))
			{
				// Do Maze Algorithm
				if (m_VisitedCells &lt; m_MazeArray.size())
				{
					// Create a set of unvisted neighbours
					std::vector&lt;i32&gt; neighbours;

					// North neighbour
					if (m_CellStack.top().Y &gt; 0 &amp;&amp; (cellOffset(0, -1) &amp; CELL_VISITED) == 0)
						neighbours.push_back(0);
					// East neighbour
					if (m_CellStack.top().X &lt; m_MazeArray.width() - 1 &amp;&amp; (cellOffset(1, 0) &amp; CELL_VISITED) == 0)
						neighbours.push_back(1);
					// South neighbour
					if (m_CellStack.top().Y &lt; m_MazeArray.height() - 1 &amp;&amp; (cellOffset(0, 1) &amp; CELL_VISITED) == 0)
						neighbours.push_back(2);
					// West neighbour
					if (m_CellStack.top().X &gt; 0 &amp;&amp; (cellOffset(-1, 0) &amp; 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&lt;size_t&gt;(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&amp; 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 &lt; m_MazeArray.height() - 1; y++)
			{
				for (int x = 0; x &lt; 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) &amp; 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) &amp; 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) &amp; 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() &amp;&amp; m_IsGenerating) // Draw only if stack is not empty &amp;&amp; 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&amp; cellOffset(const i32 x, const i32 y)
		{
			return m_MazeArray(m_CellStack.top().X + x, m_CellStack.top().Y + y);
		}
	};
}

</code></pre>
<p>Circle Physics</p>
<pre><code class="language-cpp">#pragma once
#include &lt;zp/Engine/SDLPixelEngine.h&gt;

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 &gt; 0.0f);

	private:
		std::vector&lt;Circle&gt; m_CircleList;
		std::vector&lt;Wall&gt; m_WallList;

	public:
		constexpr const std::vector&lt;Circle&gt;&amp; circles() const { return m_CircleList; }
		constexpr const std::vector&lt;Wall&gt;&amp; walls() const { return m_WallList; }

	public:
		inline void createCircle(const fvec2&amp; pos, const f32 radius, const RGB&amp; fill, const RGB&amp; frame)
		{
			if (!(radius &gt; 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&amp; start, const fvec2&amp; end, const f32 radius, const RGB&amp; color)
		{
			if ((end - start).mag2() &lt; EPSILON)
				return;

			if (!(radius &gt; 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&amp; e)
		{
			const f32 dt = std::min(e.deltaTime(), 1.0f / 30.0f);
			
			for (ui32 step = 0; step &lt; SUBSTEPS; ++step)
			{
				integrate(dt);
				solveCircleCollisions(dt);
			}

			removeOffscreen(e);
		}

		inline void reset()
		{
			m_CircleList.clear();
			m_WallList.clear();
		}

		constexpr void drawCircle(SDLPixelEngine&amp; e, const Circle&amp; 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 &gt; 2)
				drawVelocityLine(e, circle, circle.Color.Frame);
		}

		constexpr void drawWalls(SDLPixelEngine&amp; e) const
		{
			for (const auto&amp; 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&lt;f32&gt; 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&amp; circle : m_CircleList)
			{
				circle.Vel += GRAVITY * dt;
				circle.Vel *= frameDamping;

				const f32 speed = circle.Vel.mag();
				if (speed &gt; 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 &lt; ITERATIONS; ++iteration)
			{
				/* Circle vs circle */

				for (size_t i = 0; i &lt; m_CircleList.size(); ++i)
				{
					for (size_t j = i + 1; j &lt; m_CircleList.size(); ++j)
					{
						Circle&amp; a = m_CircleList[i];
						Circle&amp; 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 &gt;= radiusSum * radiusSum)
							continue;

						f32 distance = std::sqrt(distanceSq);
						fvec2 normal;

						if (distance &lt; EPSILON)
						{
							// deterministic, but different for each pair
							const f32 theta = static_cast&lt;f32&gt;((i * 31 + j * 17) % 360) * math::TAU&lt;f32&gt; / 360.0f;
							normal = math::toCartesian&lt;f32&gt;(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 &lt; m_CircleList.size(); ++i)
				{
					Circle&amp; circle = m_CircleList[i];

					for (const auto&amp; wall : m_WallList)
					{
						const fvec2 wallVector = wall.End - wall.Start;
						const f32 wallLengthSq = wallVector.mag2();

						if (wallLengthSq &lt; 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 &gt;= 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 &lt; ITERATIONS; ++iteration)
			{
				for (size_t i = 0; i &lt; m_CircleList.size(); ++i)
				{
					for (size_t j = i + 1; j &lt; m_CircleList.size(); ++j)
					{
						Circle&amp; a = m_CircleList[i];
						Circle&amp; 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 &gt; radiusSum * radiusSum)
							continue;

						const f32 distance = std::sqrt(distanceSq);
						if (distance &lt; EPSILON)
							continue;

						const fvec2 normal = delta / distance;
						const fvec2 relativeVelocity = b.Vel - a.Vel;
						const f32 velocityAlongNormal = relativeVelocity.dot(normal);

						// Moving apart.
						if (velocityAlongNormal &gt;= 0.0f)
							continue;

						const f32 e = (-velocityAlongNormal &lt; 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 &lt; m_CircleList.size(); ++i)
				{
					Circle&amp; circle = m_CircleList[i];

					for (const auto&amp; wall : m_WallList)
					{
						const fvec2 wallVector = wall.End - wall.Start;
						const f32 wallLengthSq = wallVector.mag2();

						if (wallLengthSq &lt; 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 &gt; 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 &gt;= 0.0f)
							continue; // moving apart

						const f32 e = (-velocityAlongNormal &lt; restThreshold) ? 0.0f : WALL_RESTITUTION;
						circle.Vel -= *normal * ((1 + e) * velocityAlongNormal);
					}
				}
			}
		}

		inline void removeOffscreen(SDLPixelEngine&amp; e)
		{
			const auto screenRect = e.screenRect&lt;f32&gt;();
			std::erase_if(m_CircleList, [&amp;](const Circle&amp; c) {
				return !utils::isPointInsideBox(screenRect, c.Pos, c.Radius);
				});
		}

		constexpr void drawVelocityLine(SDLPixelEngine&amp; e, const Circle&amp; circle, const RGB&amp; color) const
		{
			const f32 speedSq = circle.Vel.mag2();

			if (speedSq &gt; 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&lt;fvec2&gt; wallContactNormal(const fvec2&amp; delta, const f32 distance, const fvec2&amp; wallVector)
		{
			// Standard case: The circle center does not lie on the wall segment.
			if (distance &gt; 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 &lt; EPSILON)
				return std::nullopt;

			return perpendicular / length;
		}
	};
}

</code></pre>
<p>Maz and Circles .h</p>
<pre><code class="language-cpp">#pragma once
#include &lt;zp/Engine/SDLPixelEngine.h&gt;
#include &quot;MazeGenerator.h&quot;
#include &quot;CirclePhysics.h&quot;

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&amp; 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&lt;frect, WallType&gt;;
        std::vector&lt;Wall&gt; MazeWallList;
        f32 WallWeight = 0;
        RGB WallColor;

        std::pair&lt;Coord, i32&gt; 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&amp; pos, const i32 N);
        void updateCircles();
        void drawCircles();
        
        // Common
        void loadBackground(const std::string&amp; fileName);
        void setBackground();
        void reset();
        void drawInfo(const fvec2&amp; pos);

    private:
        void createOuterWalls();
        void exportMazeWallsToPhysics();

    
    };
}

</code></pre>
<p>Maze and Circles .cpp</p>
<pre><code class="language-cpp">#include &quot;MazeAndCircles.h&quot;
#include &lt;zp/Math/Random.h&gt;

zp::MazeAndCircles::MazeAndCircles(const i32 width, const i32 height) :
    SDLPixelEngine(&quot;SDLPixelEngine&quot;, width, height)
{
}

zp::MazeAndCircles::MazeAndCircles(const std::string&amp; title, const i32 width, const i32 height) :
    SDLPixelEngine(title, width, height)
{
}

bool zp::MazeAndCircles::onCreate()
{
	screenColor() = LIGHT_BLUE;
	loadBackground(&quot;why_so_alone_.png&quot;);
	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() &lt; 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&lt;int&gt;(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() &amp;&amp; !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&amp; pos, const i32 N)
{
	for (i32 i = 0; i &lt; N; i++)
		Physic.createCircle(pos, CircleRadius, CircleFill, CircleFrame);
}

void zp::MazeAndCircles::updateCircles()
{
	if (DoMazePhysics)
		Physic.update(*this);
}

void zp::MazeAndCircles::drawCircles()
{
	for (auto&amp; 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 &lt; Maze.height() - 1; y++)
	{
		for (i32 x = 0; x &lt; 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&lt;WallType&gt;(Maze.array()(x, y));
			MazeWallList.push_back(std::make_pair(cellRect, wall));
		}
	}

	exportMazeWallsToPhysics();
}

void zp::MazeAndCircles::loadBackground(const std::string&amp; fileName)
{
	Background = { getRenderer() };
	Background.load(fileName);
}

void zp::MazeAndCircles::setBackground()
{
	Background.fillWindow();
}


void zp::MazeAndCircles::drawInfo(const fvec2&amp; 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, &quot;Maze Size:&quot;); p.x += tabX;
	drawString(p, std::to_string(Maze.width()) + &quot;, &quot; + std::to_string(Maze.height())); p.x = pos.x; p.y += tabY;
	drawString(p, &quot;Maze Cells:&quot;); p.x += tabX;
	drawString(p, std::to_string(Maze.size())); p.x = pos.x; p.y += tabY;

	if (Maze.isGenerating())
	{
		drawString(p, &quot;Generation Delay:&quot;); 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, &quot;Generating Maze&quot;);

	if (!Maze.isGenerating())
		drawString(p, &quot;Drop Circles with Mouse&quot;);

	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, &quot;Circle count: &quot;); 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&amp; 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 &gt; x1) std::swap(x0, x1);
		if (y0 &gt; 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;
		}	
	}
}

</code></pre>
<p>Danke fürs Lesen und viele Grüße!</p>
<p>PS: Ganz vergessen; wer möchte, kann sich das Ergebnis dieses Code auch anschauen:</p>
<p><a href="https://www.youtube.com/watch?v=fTxjSNfpqC8" rel="nofollow">Circles in a Maze</a></p>
]]></description><link>https://www.c-plusplus.net/forum/topic/355499/circles-im-labyrinth</link><generator>RSS for Node</generator><lastBuildDate>Tue, 06 Oct 2026 17:57:09 GMT</lastBuildDate><atom:link href="https://www.c-plusplus.net/forum/topic/355499.rss" rel="self" type="application/rss+xml"/><pubDate>Tue, 06 Oct 2026 16:04:30 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Circles im Labyrinth on Tue, 06 Oct 2026 16:09:30 GMT]]></title><description><![CDATA[<p>Hallo!</p>
<p>&quot;Can Circles solve a Maze?&quot;. Diese Frage wollte ich vor langer Zeit mal beantworten und es ist mir so lala geworden.</p>
<p>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.</p>
<p>Es ist etwas längerer Code, insgesamt drei Klassen, ein Maze Generator durch Backtracking, die CirclePhysics und das Hauptprogramm 'MazeAndCircles'.</p>
<p>Als erstes der Maze Generator</p>
<pre><code class="language-cpp">#pragma once
#include &lt;zp/Engine/SDLPixelEngine.h&gt;
#include &lt;zp/Math/Random.h&gt;

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&lt;i32&gt; m_MazeArray;

		i32 m_VisitedCells = 0;
		std::stack&lt;Coord&gt; 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&lt;i32&gt; array() const { return m_MazeArray; }

		constexpr ivec2 position() const { return m_MazePosition; }
		constexpr ivec2&amp; 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&amp; generationDelay() { return m_GenerationDelay; }
		constexpr bool isGenerating() const { return m_IsGenerating; }
		constexpr bool&amp; isGenerating() { return m_IsGenerating; }

		constexpr MazeColor color() const { return m_Color; }
		constexpr MazeColor&amp; color() { return m_Color; }

	public:
		inline void init(
			SDLPixelEngine&amp; 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&lt;i32&gt;() / m_CellStride;
				const i32 h = e.screenHeight&lt;i32&gt;() / 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&lt;i32&gt;(m_MazeArray.width() - 1);
			const i32 y = Random().uniformInt&lt;i32&gt;(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&amp; e)
		{
			if (e.delayThis(m_GenerationDelay))
			{
				// Do Maze Algorithm
				if (m_VisitedCells &lt; m_MazeArray.size())
				{
					// Create a set of unvisted neighbours
					std::vector&lt;i32&gt; neighbours;

					// North neighbour
					if (m_CellStack.top().Y &gt; 0 &amp;&amp; (cellOffset(0, -1) &amp; CELL_VISITED) == 0)
						neighbours.push_back(0);
					// East neighbour
					if (m_CellStack.top().X &lt; m_MazeArray.width() - 1 &amp;&amp; (cellOffset(1, 0) &amp; CELL_VISITED) == 0)
						neighbours.push_back(1);
					// South neighbour
					if (m_CellStack.top().Y &lt; m_MazeArray.height() - 1 &amp;&amp; (cellOffset(0, 1) &amp; CELL_VISITED) == 0)
						neighbours.push_back(2);
					// West neighbour
					if (m_CellStack.top().X &gt; 0 &amp;&amp; (cellOffset(-1, 0) &amp; 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&lt;size_t&gt;(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&amp; 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 &lt; m_MazeArray.height() - 1; y++)
			{
				for (int x = 0; x &lt; 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) &amp; 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) &amp; 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) &amp; 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() &amp;&amp; m_IsGenerating) // Draw only if stack is not empty &amp;&amp; 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&amp; cellOffset(const i32 x, const i32 y)
		{
			return m_MazeArray(m_CellStack.top().X + x, m_CellStack.top().Y + y);
		}
	};
}

</code></pre>
<p>Circle Physics</p>
<pre><code class="language-cpp">#pragma once
#include &lt;zp/Engine/SDLPixelEngine.h&gt;

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 &gt; 0.0f);

	private:
		std::vector&lt;Circle&gt; m_CircleList;
		std::vector&lt;Wall&gt; m_WallList;

	public:
		constexpr const std::vector&lt;Circle&gt;&amp; circles() const { return m_CircleList; }
		constexpr const std::vector&lt;Wall&gt;&amp; walls() const { return m_WallList; }

	public:
		inline void createCircle(const fvec2&amp; pos, const f32 radius, const RGB&amp; fill, const RGB&amp; frame)
		{
			if (!(radius &gt; 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&amp; start, const fvec2&amp; end, const f32 radius, const RGB&amp; color)
		{
			if ((end - start).mag2() &lt; EPSILON)
				return;

			if (!(radius &gt; 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&amp; e)
		{
			const f32 dt = std::min(e.deltaTime(), 1.0f / 30.0f);
			
			for (ui32 step = 0; step &lt; SUBSTEPS; ++step)
			{
				integrate(dt);
				solveCircleCollisions(dt);
			}

			removeOffscreen(e);
		}

		inline void reset()
		{
			m_CircleList.clear();
			m_WallList.clear();
		}

		constexpr void drawCircle(SDLPixelEngine&amp; e, const Circle&amp; 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 &gt; 2)
				drawVelocityLine(e, circle, circle.Color.Frame);
		}

		constexpr void drawWalls(SDLPixelEngine&amp; e) const
		{
			for (const auto&amp; 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&lt;f32&gt; 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&amp; circle : m_CircleList)
			{
				circle.Vel += GRAVITY * dt;
				circle.Vel *= frameDamping;

				const f32 speed = circle.Vel.mag();
				if (speed &gt; 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 &lt; ITERATIONS; ++iteration)
			{
				/* Circle vs circle */

				for (size_t i = 0; i &lt; m_CircleList.size(); ++i)
				{
					for (size_t j = i + 1; j &lt; m_CircleList.size(); ++j)
					{
						Circle&amp; a = m_CircleList[i];
						Circle&amp; 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 &gt;= radiusSum * radiusSum)
							continue;

						f32 distance = std::sqrt(distanceSq);
						fvec2 normal;

						if (distance &lt; EPSILON)
						{
							// deterministic, but different for each pair
							const f32 theta = static_cast&lt;f32&gt;((i * 31 + j * 17) % 360) * math::TAU&lt;f32&gt; / 360.0f;
							normal = math::toCartesian&lt;f32&gt;(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 &lt; m_CircleList.size(); ++i)
				{
					Circle&amp; circle = m_CircleList[i];

					for (const auto&amp; wall : m_WallList)
					{
						const fvec2 wallVector = wall.End - wall.Start;
						const f32 wallLengthSq = wallVector.mag2();

						if (wallLengthSq &lt; 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 &gt;= 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 &lt; ITERATIONS; ++iteration)
			{
				for (size_t i = 0; i &lt; m_CircleList.size(); ++i)
				{
					for (size_t j = i + 1; j &lt; m_CircleList.size(); ++j)
					{
						Circle&amp; a = m_CircleList[i];
						Circle&amp; 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 &gt; radiusSum * radiusSum)
							continue;

						const f32 distance = std::sqrt(distanceSq);
						if (distance &lt; EPSILON)
							continue;

						const fvec2 normal = delta / distance;
						const fvec2 relativeVelocity = b.Vel - a.Vel;
						const f32 velocityAlongNormal = relativeVelocity.dot(normal);

						// Moving apart.
						if (velocityAlongNormal &gt;= 0.0f)
							continue;

						const f32 e = (-velocityAlongNormal &lt; 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 &lt; m_CircleList.size(); ++i)
				{
					Circle&amp; circle = m_CircleList[i];

					for (const auto&amp; wall : m_WallList)
					{
						const fvec2 wallVector = wall.End - wall.Start;
						const f32 wallLengthSq = wallVector.mag2();

						if (wallLengthSq &lt; 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 &gt; 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 &gt;= 0.0f)
							continue; // moving apart

						const f32 e = (-velocityAlongNormal &lt; restThreshold) ? 0.0f : WALL_RESTITUTION;
						circle.Vel -= *normal * ((1 + e) * velocityAlongNormal);
					}
				}
			}
		}

		inline void removeOffscreen(SDLPixelEngine&amp; e)
		{
			const auto screenRect = e.screenRect&lt;f32&gt;();
			std::erase_if(m_CircleList, [&amp;](const Circle&amp; c) {
				return !utils::isPointInsideBox(screenRect, c.Pos, c.Radius);
				});
		}

		constexpr void drawVelocityLine(SDLPixelEngine&amp; e, const Circle&amp; circle, const RGB&amp; color) const
		{
			const f32 speedSq = circle.Vel.mag2();

			if (speedSq &gt; 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&lt;fvec2&gt; wallContactNormal(const fvec2&amp; delta, const f32 distance, const fvec2&amp; wallVector)
		{
			// Standard case: The circle center does not lie on the wall segment.
			if (distance &gt; 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 &lt; EPSILON)
				return std::nullopt;

			return perpendicular / length;
		}
	};
}

</code></pre>
<p>Maz and Circles .h</p>
<pre><code class="language-cpp">#pragma once
#include &lt;zp/Engine/SDLPixelEngine.h&gt;
#include &quot;MazeGenerator.h&quot;
#include &quot;CirclePhysics.h&quot;

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&amp; 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&lt;frect, WallType&gt;;
        std::vector&lt;Wall&gt; MazeWallList;
        f32 WallWeight = 0;
        RGB WallColor;

        std::pair&lt;Coord, i32&gt; 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&amp; pos, const i32 N);
        void updateCircles();
        void drawCircles();
        
        // Common
        void loadBackground(const std::string&amp; fileName);
        void setBackground();
        void reset();
        void drawInfo(const fvec2&amp; pos);

    private:
        void createOuterWalls();
        void exportMazeWallsToPhysics();

    
    };
}

</code></pre>
<p>Maze and Circles .cpp</p>
<pre><code class="language-cpp">#include &quot;MazeAndCircles.h&quot;
#include &lt;zp/Math/Random.h&gt;

zp::MazeAndCircles::MazeAndCircles(const i32 width, const i32 height) :
    SDLPixelEngine(&quot;SDLPixelEngine&quot;, width, height)
{
}

zp::MazeAndCircles::MazeAndCircles(const std::string&amp; title, const i32 width, const i32 height) :
    SDLPixelEngine(title, width, height)
{
}

bool zp::MazeAndCircles::onCreate()
{
	screenColor() = LIGHT_BLUE;
	loadBackground(&quot;why_so_alone_.png&quot;);
	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() &lt; 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&lt;int&gt;(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() &amp;&amp; !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&amp; pos, const i32 N)
{
	for (i32 i = 0; i &lt; N; i++)
		Physic.createCircle(pos, CircleRadius, CircleFill, CircleFrame);
}

void zp::MazeAndCircles::updateCircles()
{
	if (DoMazePhysics)
		Physic.update(*this);
}

void zp::MazeAndCircles::drawCircles()
{
	for (auto&amp; 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 &lt; Maze.height() - 1; y++)
	{
		for (i32 x = 0; x &lt; 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&lt;WallType&gt;(Maze.array()(x, y));
			MazeWallList.push_back(std::make_pair(cellRect, wall));
		}
	}

	exportMazeWallsToPhysics();
}

void zp::MazeAndCircles::loadBackground(const std::string&amp; fileName)
{
	Background = { getRenderer() };
	Background.load(fileName);
}

void zp::MazeAndCircles::setBackground()
{
	Background.fillWindow();
}


void zp::MazeAndCircles::drawInfo(const fvec2&amp; 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, &quot;Maze Size:&quot;); p.x += tabX;
	drawString(p, std::to_string(Maze.width()) + &quot;, &quot; + std::to_string(Maze.height())); p.x = pos.x; p.y += tabY;
	drawString(p, &quot;Maze Cells:&quot;); p.x += tabX;
	drawString(p, std::to_string(Maze.size())); p.x = pos.x; p.y += tabY;

	if (Maze.isGenerating())
	{
		drawString(p, &quot;Generation Delay:&quot;); 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, &quot;Generating Maze&quot;);

	if (!Maze.isGenerating())
		drawString(p, &quot;Drop Circles with Mouse&quot;);

	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, &quot;Circle count: &quot;); 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&amp; 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 &gt; x1) std::swap(x0, x1);
		if (y0 &gt; 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;
		}	
	}
}

</code></pre>
<p>Danke fürs Lesen und viele Grüße!</p>
<p>PS: Ganz vergessen; wer möchte, kann sich das Ergebnis dieses Code auch anschauen:</p>
<p><a href="https://www.youtube.com/watch?v=fTxjSNfpqC8" rel="nofollow">Circles in a Maze</a></p>
]]></description><link>https://www.c-plusplus.net/forum/post/2625286</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2625286</guid><dc:creator><![CDATA[zeropage]]></dc:creator><pubDate>Tue, 06 Oct 2026 16:09:30 GMT</pubDate></item><item><title><![CDATA[Reply to Circles im Labyrinth on Tue, 06 Oct 2026 17:21:01 GMT]]></title><description><![CDATA[<p>Hm, die Ausgänge (Senken) müssen aber immer unten sein, oder? Bspw. ein W-Labyrinth würde nicht funktionieren, oder?</p>
<p>Na ja, ist ja auch logisch, Wasser fließt nicht bergauf...</p>
<p>Cool wäre, wenn sich die Kreise so weit stauen könnten, dass sich das ganze Labyrinth füllt... ähnlich wie das Fluten eines Mäuse- oder Hamsterbaus.</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2625287</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2625287</guid><dc:creator><![CDATA[Anon-81275]]></dc:creator><pubDate>Tue, 06 Oct 2026 17:21:01 GMT</pubDate></item><item><title><![CDATA[Reply to Circles im Labyrinth on Tue, 06 Oct 2026 17:45:43 GMT]]></title><description><![CDATA[<p>Ach, sorry, ich bin doof... <img
      src="https://www.c-plusplus.net/forum/plugins/nodebb-plugin-emoji/emoji/emoji-one/1f629.png?v=ab1pehoraso"
      class="not-responsive emoji emoji-emoji-one emoji--weary_face"
      title=":weary_face:"
      alt="😩"
    /></p>
<p>Du hast den Kapillareffekt ja bereits perfekt umgesetzt... danke.</p>
]]></description><link>https://www.c-plusplus.net/forum/post/2625288</link><guid isPermaLink="true">https://www.c-plusplus.net/forum/post/2625288</guid><dc:creator><![CDATA[Anon-81275]]></dc:creator><pubDate>Tue, 06 Oct 2026 17:45:43 GMT</pubDate></item></channel></rss>