Skip to content

Commit 5ff0720

Browse files
committed
feat: cycle detection
1 parent b05d5ab commit 5ff0720

File tree

3 files changed

+163
-5
lines changed

3 files changed

+163
-5
lines changed

src/apps/2024day6/src/entities/GameObject.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,17 @@ struct GameObject {
3838
);
3939
SDL_RenderFillRect(renderer.Get().get(), &rect);
4040
}
41+
42+
void RenderHollow(Core::Renderer& renderer) const {
43+
SDL_Rect rect =
44+
{(int)this->x + 15, (int)this->y + 15, (int)this->width, (int)this->height};
45+
SDL_SetRenderDrawColor(
46+
renderer.Get().get(),
47+
color[0],
48+
color[1],
49+
color[2],
50+
255
51+
);
52+
SDL_RenderDrawRect(renderer.Get().get(), &rect);
53+
}
4154
};

src/apps/2024day6/src/main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
int main() {
77
MinimalLoopStrategy strategy;
88

9-
auto loop = Core::Loop({&strategy});
9+
auto loop = Core::Loop({&strategy}, 1400, 3000);
1010
loop.Run();
1111
}

src/apps/2024day6/src/strategies/MinimalLoopStrategy.h

Lines changed: 149 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#pragma once
2+
#include <algorithm>
23
#include <fstream>
34
#include <iostream>
45

@@ -25,19 +26,108 @@ bool areOverlapping(GameObject& guard, GameObject& b) {
2526
guard.y < b.y + b.height && guard.y + guard.height > b.y;
2627
}
2728

29+
bool isCornerPosition(GameObject& distinctPosition) {
30+
if (distinctPosition.color[0] == 255 && distinctPosition.color[1] == 0 &&
31+
distinctPosition.color[2] == 255) {
32+
return true;
33+
}
34+
return false;
35+
}
36+
37+
std::optional<GameObject> doLast4CornerDistinctPositionsFormRectangle(
38+
std::vector<GameObject>& distinctPositions
39+
) {
40+
// find last 4 corner distinct positions
41+
std::vector<GameObject> corners;
42+
for (int i = distinctPositions.size() - 1; i >= 0; i--) {
43+
if (isCornerPosition(distinctPositions[i])) {
44+
corners.push_back(distinctPositions[i]);
45+
}
46+
if (corners.size() == 4) {
47+
break;
48+
}
49+
}
50+
51+
if (corners.size() != 4) {
52+
return std::nullopt;
53+
}
54+
55+
// Initialize with first corner
56+
GameObject topLeft = corners[0];
57+
GameObject topRight = corners[0];
58+
GameObject bottomLeft = corners[0];
59+
GameObject bottomRight = corners[0];
60+
61+
// Find the extreme corners
62+
for (const auto& corner : corners) {
63+
// Top-left has minimum x and y
64+
if (corner.x + corner.y < topLeft.x + topLeft.y) {
65+
topLeft = corner;
66+
}
67+
// Top-right has maximum x and minimum y
68+
if (corner.x - corner.y > topRight.x - topRight.y) {
69+
topRight = corner;
70+
}
71+
// Bottom-left has minimum x and maximum y
72+
if (corner.x - corner.y < bottomLeft.x - bottomLeft.y) {
73+
bottomLeft = corner;
74+
}
75+
// Bottom-right has maximum x and y
76+
if (corner.x + corner.y > bottomRight.x + bottomRight.y) {
77+
bottomRight = corner;
78+
}
79+
}
80+
81+
// Verify we have a rectangle by checking:
82+
// 1. Width equality (top and bottom)
83+
// 2. Height equality (left and right)
84+
// 3. Diagonal equality
85+
double topWidth = std::abs(topRight.x - topLeft.x);
86+
double bottomWidth = std::abs(bottomRight.x - bottomLeft.x);
87+
double leftHeight = std::abs(bottomLeft.y - topLeft.y);
88+
double rightHeight = std::abs(bottomRight.y - topRight.y);
89+
90+
// Allow for small floating-point differences
91+
const double epsilon = 0.001;
92+
93+
bool widthsEqual = std::abs(topWidth - bottomWidth) < epsilon;
94+
bool heightsEqual = std::abs(leftHeight - rightHeight) < epsilon;
95+
96+
if (widthsEqual && heightsEqual) {
97+
return GameObject(
98+
topWidth,
99+
leftHeight,
100+
topLeft.x,
101+
topLeft.y,
102+
0,
103+
0,
104+
{255, 0, 255}
105+
);
106+
}
107+
108+
return std::nullopt;
109+
}
110+
111+
struct Cycle {
112+
GameObject rectangle;
113+
GameObject corner;
114+
};
115+
28116
class MinimalLoopStrategy : public Core::IStrategy {
29117
public:
30118
std::vector<GameObject> obstructions;
31119
GameObject guard;
32120
std::vector<GameObject> path;
33121
std::vector<GameObject> distinctPositions;
34122
std::vector<std::string> recentObstructionCoords;
123+
GameObject distinctPathPieceNext;
124+
std::vector<Cycle> cycles;
35125

36126
int boundaryHeight;
37127
int boundaryWidth;
38128

39-
int scale = 10;
40-
int defaultVelocity = 100;
129+
int scale = 30;
130+
int defaultVelocity = 30;
41131

42132
double totalTime = 0;
43133

@@ -46,7 +136,7 @@ class MinimalLoopStrategy : public Core::IStrategy {
46136

47137
auto size = 1 * scale;
48138

49-
const std::string filePath = "assets/input.txt";
139+
const std::string filePath = "assets/input-example-1.txt";
50140
auto input = ParseInput(filePath);
51141

52142
boundaryWidth = input[0].size() * size;
@@ -83,6 +173,9 @@ class MinimalLoopStrategy : public Core::IStrategy {
83173
vel_y,
84174
{255, 0, 0}
85175
);
176+
177+
distinctPathPieceNext =
178+
GameObject(size, size, guard.x, guard.y, 0, 0, {0, 0, 255});
86179
}
87180
}
88181
}
@@ -109,6 +202,8 @@ class MinimalLoopStrategy : public Core::IStrategy {
109202
guard.x += guard.velX * deltaTime;
110203
guard.y += guard.velY * deltaTime;
111204

205+
bool hasChangedDirection = false;
206+
112207
for (auto& obstruction : obstructions) {
113208
auto obstructionCoords =
114209
std::to_string(obstruction.x) + "-" + std::to_string(obstruction.y);
@@ -149,6 +244,7 @@ class MinimalLoopStrategy : public Core::IStrategy {
149244
if (recentObstructionCoords.size() > 2) {
150245
recentObstructionCoords.erase(recentObstructionCoords.begin());
151246
}
247+
hasChangedDirection = true;
152248
}
153249
}
154250

@@ -199,14 +295,56 @@ class MinimalLoopStrategy : public Core::IStrategy {
199295
}
200296
}
201297

298+
// move distinct piece next in the direction of the guard
299+
distinctPathPieceNext.x = guard.velX > 0 ? distinctPathPiece.x + scale
300+
: guard.velX < 0 ? distinctPathPiece.x - scale
301+
: distinctPathPiece.x;
302+
distinctPathPieceNext.y = guard.velY > 0 ? distinctPathPiece.y + scale
303+
: guard.velY < 0 ? distinctPathPiece.y - scale
304+
: distinctPathPiece.y;
305+
202306
if (!isOverlapping) {
203307
distinctPositions.push_back(distinctPathPiece);
308+
} else {
309+
if (hasChangedDirection) {
310+
distinctPositions.back().color = {255, 0, 255};
311+
}
312+
}
313+
314+
// if distinctPathPieceNext overlapping with distinct path
315+
for (auto& distinctPiece : distinctPositions) {
316+
if (areOverlapping(distinctPiece, distinctPathPieceNext)) {
317+
distinctPiece.color = {255, 0, 255};
318+
319+
auto rectangle =
320+
doLast4CornerDistinctPositionsFormRectangle(distinctPositions);
321+
322+
if (rectangle.has_value()) {
323+
// if rectangle doesn't exist in cycles
324+
auto isRectangleInCycles = false;
325+
for (auto& cycle : cycles) {
326+
if (cycle.rectangle.x == rectangle.value().x &&
327+
cycle.rectangle.y == rectangle.value().y &&
328+
cycle.rectangle.width == rectangle.value().width &&
329+
cycle.rectangle.height == rectangle.value().height
330+
331+
) {
332+
isRectangleInCycles = true;
333+
break;
334+
}
335+
}
336+
337+
if (!isRectangleInCycles) {
338+
std::cout << "Rectangle found" << std::endl;
339+
cycles.push_back(Cycle{rectangle.value(), distinctPathPieceNext});
340+
}
341+
}
342+
}
204343
}
205344
}
206345
}
207346

208347
void OnRender(Core::Window& window, Core::Renderer& renderer) override {
209-
guard.Render(renderer);
210348
for (auto& obstruction : obstructions) {
211349
obstruction.Render(renderer);
212350
}
@@ -217,6 +355,13 @@ class MinimalLoopStrategy : public Core::IStrategy {
217355
distinctPiece.Render(renderer);
218356
}
219357

358+
for (auto& cycle : cycles) {
359+
cycle.rectangle.RenderHollow(renderer);
360+
}
361+
362+
distinctPathPieceNext.Render(renderer);
363+
guard.Render(renderer);
364+
220365
SDL_Rect rect = {0, 0, boundaryWidth, boundaryHeight};
221366
SDL_SetRenderDrawColor(renderer.Get().get(), 255, 255, 255, 255);
222367
SDL_RenderDrawRect(renderer.Get().get(), &rect);

0 commit comments

Comments
 (0)