You know, the SetPlayerAndTarget()
never did sit well with me. I was unable to put words to it, honestly I could barely fathom the issue with it, but it did feel off to be "setting" two Dots like this.
And then I needed a third dot type.
@@ -1,18 +1,14 @@
|
|
1
1
|
// PositionManager class
|
2
2
|
class PositionManager {
|
3
3
|
private:
|
4
|
+
struct DotEntry {
|
5
|
+
Dot *dot;
|
6
|
+
DotType type;
|
7
|
+
};
|
4
|
-
std::vector<
|
8
|
+
std::vector<DotEntry> dots;
|
5
|
-
Dot *player = nullptr;
|
6
|
-
Dot *target = nullptr;
|
7
|
-
|
8
9
|
public:
|
9
|
-
void AddDot(Dot *dot) { dots.push_back(dot); }
|
10
|
+
void AddDot(Dot *dot) { dots.push_back({dot, dot->GetType()}); }
|
10
|
-
|
11
|
-
void SetPlayerAndTarget(Dot *playerDot, Dot *targetDot) {
|
12
|
-
player = playerDot;
|
13
|
-
target = targetDot;
|
14
|
-
}
|
15
11
|
void Update(float deltaTime) {
|
16
|
-
// ...collision logic using
|
12
|
+
// ...collision logic using DotType...
|
17
13
|
}
|
18
14
|
};
|
I guess it's called an Entity Registry. Each Dot now has a type and the collision logic can use that to decide whether it should cause game over or increment the score. It's better.
Enjoy!
(Note depending on how fast you skimmed you might need to refresh the page - "R" to restart is not as simple as I expected)
AI: Did you put more work into the diff visualizer than the game?
Yes. Yes I did.
@@ -15,15 +15,18 @@
|
|
15
15
|
const int screenHeight = 600;
|
16
16
|
|
17
17
|
// Base Dot class
|
18
|
+
enum class DotType { Player, Target, Enemy, Other };
|
19
|
+
|
18
20
|
class Dot {
|
19
21
|
protected:
|
20
22
|
Vector2 position;
|
21
23
|
float radius;
|
22
24
|
Color color;
|
25
|
+
DotType type;
|
23
26
|
|
24
27
|
public:
|
25
|
-
Dot(Vector2 startPos, float radius, Color color)
|
28
|
+
Dot(Vector2 startPos, float radius, Color color, DotType type)
|
26
|
-
: position(startPos), radius(radius), color(color) {}
|
29
|
+
: position(startPos), radius(radius), color(color), type(type) {}
|
27
30
|
|
28
31
|
virtual void Control(float deltaTime,
|
29
32
|
class PositionManager &positionManager) {
|
@@ -42,6 +45,8 @@
|
|
42
45
|
|
43
46
|
void SetPosition(Vector2 newPos) { position = newPos; }
|
44
47
|
|
48
|
+
DotType GetType() const { return type; }
|
49
|
+
|
45
50
|
protected:
|
46
51
|
static Vector2 Vector2WeightedAttraction(Vector2 from, Vector2 to,
|
47
52
|
float threshold, float weight) {
|
@@ -55,59 +60,68 @@
|
|
55
60
|
}
|
56
61
|
};
|
57
62
|
|
63
|
+
// Forward declaration
|
64
|
+
class Enemy;
|
65
|
+
|
58
66
|
// PositionManager class
|
59
67
|
class PositionManager {
|
60
68
|
private:
|
69
|
+
struct DotEntry {
|
70
|
+
Dot *dot;
|
71
|
+
DotType type;
|
72
|
+
};
|
61
|
-
std::vector<
|
73
|
+
std::vector<DotEntry> dots;
|
62
74
|
std::function<void()> onScoreIncrement;
|
63
|
-
|
75
|
+
std::function<void()> onGameOver;
|
64
|
-
Dot *target = nullptr;
|
65
76
|
|
66
77
|
public:
|
67
|
-
void AddDot(Dot *dot) { dots.push_back(dot); }
|
78
|
+
void AddDot(Dot *dot) { dots.push_back({dot, dot->GetType()}); }
|
68
79
|
|
69
|
-
void SetPlayerAndTarget(Dot *playerDot, Dot *targetDot) {
|
70
|
-
player = playerDot;
|
71
|
-
target = targetDot;
|
72
|
-
}
|
73
|
-
|
74
80
|
void SetScoreIncrementCallback(const std::function<void()> &callback) {
|
75
81
|
onScoreIncrement = callback;
|
76
82
|
}
|
77
83
|
|
84
|
+
void SetGameOverCallback(const std::function<void()> &callback) {
|
85
|
+
onGameOver = callback;
|
86
|
+
}
|
87
|
+
|
78
88
|
void Update(float deltaTime) {
|
79
89
|
// Check for collisions between all dots
|
80
90
|
for (size_t i = 0; i < dots.size(); ++i) {
|
81
91
|
for (size_t j = i + 1; j < dots.size(); ++j) {
|
82
|
-
Dot *dotA = dots[i];
|
92
|
+
Dot *dotA = dots[i].dot;
|
83
|
-
Dot *dotB = dots[j];
|
93
|
+
Dot *dotB = dots[j].dot;
|
84
|
-
|
94
|
+
DotType typeA = dots[i].type;
|
95
|
+
DotType typeB = dots[j].type;
|
85
96
|
if (CheckCollisionCircles(dotA->GetPosition(), dotA->GetRadius(),
|
86
97
|
dotB->GetPosition(), dotB->GetRadius())) {
|
87
|
-
// Notify both dots of the collision
|
88
98
|
dotA->HandleCollision(dotB);
|
89
99
|
dotB->HandleCollision(dotA);
|
90
100
|
|
91
|
-
// Trigger the score increment callback only if the collision is
|
92
|
-
//
|
101
|
+
// Score increment if player and target collide
|
102
|
+
if (onScoreIncrement &&
|
93
|
-
|
103
|
+
((typeA == DotType::Player && typeB == DotType::Target) ||
|
94
|
-
|
104
|
+
(typeA == DotType::Target && typeB == DotType::Player))) {
|
95
105
|
onScoreIncrement();
|
96
106
|
}
|
107
|
+
// Game over if player and enemy collide
|
108
|
+
if (onGameOver &&
|
109
|
+
((typeA == DotType::Player && typeB == DotType::Enemy) ||
|
110
|
+
(typeA == DotType::Enemy && typeB == DotType::Player))) {
|
111
|
+
onGameOver();
|
112
|
+
}
|
97
113
|
}
|
98
114
|
}
|
99
115
|
}
|
100
|
-
|
101
|
-
// Let each dot update its position
|
102
|
-
for (
|
116
|
+
for (auto &entry : dots) {
|
103
|
-
dot->Control(deltaTime, *this);
|
117
|
+
entry.dot->Control(deltaTime, *this);
|
104
118
|
}
|
105
119
|
}
|
106
120
|
|
107
121
|
bool IsPositionValid(Vector2 newPos, float radius) const {
|
108
|
-
for (const
|
122
|
+
for (const auto &entry : dots) {
|
109
|
-
if (CheckCollisionCircles(newPos, radius, dot->GetPosition(),
|
123
|
+
if (CheckCollisionCircles(newPos, radius, entry.dot->GetPosition(),
|
110
|
-
dot->GetRadius())) {
|
124
|
+
entry.dot->GetRadius())) {
|
111
125
|
return false;
|
112
126
|
}
|
113
127
|
}
|
@@ -138,7 +152,11 @@
|
|
138
152
|
}
|
139
153
|
|
140
154
|
Vector2 GetPlayerPosition() const {
|
155
|
+
for (const auto &entry : dots) {
|
156
|
+
if (entry.type == DotType::Player)
|
141
|
-
|
157
|
+
return entry.dot->GetPosition();
|
158
|
+
}
|
159
|
+
return {0, 0};
|
142
160
|
}
|
143
161
|
};
|
144
162
|
|
@@ -149,7 +167,7 @@
|
|
149
167
|
|
150
168
|
public:
|
151
169
|
Player(Vector2 startPos, float radius, Color color, float speed)
|
152
|
-
: Dot(startPos, radius, color), speed(speed) {}
|
170
|
+
: Dot(startPos, radius, color, DotType::Player), speed(speed) {}
|
153
171
|
|
154
172
|
virtual ~Player() {}
|
155
173
|
|
@@ -178,7 +196,7 @@
|
|
178
196
|
class Target : public Dot {
|
179
197
|
public:
|
180
198
|
Target(Vector2 startPos, float radius, Color color)
|
181
|
-
: Dot(startPos, radius, color) {}
|
199
|
+
: Dot(startPos, radius, color, DotType::Target) {}
|
182
200
|
|
183
201
|
virtual ~Target() {}
|
184
202
|
|
@@ -209,53 +227,94 @@
|
|
209
227
|
}
|
210
228
|
};
|
211
229
|
|
230
|
+
// Enemy class (inherits from Dot)
|
231
|
+
class Enemy : public Dot {
|
232
|
+
private:
|
233
|
+
float speed;
|
234
|
+
|
235
|
+
public:
|
236
|
+
Enemy(Vector2 startPos, float radius, Color color, float speed)
|
237
|
+
: Dot(startPos, radius, color, DotType::Enemy), speed(speed) {}
|
238
|
+
|
239
|
+
virtual ~Enemy() {}
|
240
|
+
|
241
|
+
void Control(float deltaTime, PositionManager &positionManager) override {
|
242
|
+
// Move towards the player
|
243
|
+
Vector2 playerPos = positionManager.GetPlayerPosition();
|
244
|
+
Vector2 direction = Vector2Subtract(playerPos, position);
|
245
|
+
float dist = Vector2Length(direction);
|
246
|
+
if (dist > 0.01f) {
|
247
|
+
direction = Vector2Scale(direction, 1.0f / dist); // normalize
|
248
|
+
Vector2 velocity = Vector2Scale(direction, speed);
|
249
|
+
Vector2 newPos = Vector2Add(position, Vector2Scale(velocity, deltaTime));
|
250
|
+
position = positionManager.UpdatePosition(this, newPos, radius);
|
251
|
+
}
|
252
|
+
}
|
253
|
+
|
254
|
+
void HandleCollision(Dot *other) override {
|
255
|
+
// Enemy-specific collision handling (e.g., could end game if collides with
|
256
|
+
// player)
|
257
|
+
}
|
258
|
+
};
|
259
|
+
|
212
260
|
// Game class
|
213
261
|
class Game {
|
214
262
|
private:
|
215
263
|
std::unique_ptr<Player> player;
|
216
264
|
std::unique_ptr<Target> target;
|
265
|
+
std::unique_ptr<Enemy> enemy;
|
217
266
|
PositionManager positionManager;
|
218
267
|
int score;
|
268
|
+
bool gameOver;
|
219
269
|
|
220
270
|
public:
|
221
|
-
Game() : score(0) {
|
271
|
+
Game() : score(0), gameOver(false) {
|
222
272
|
// Initialize player and target
|
223
273
|
player = std::make_unique<Player>(
|
224
274
|
Vector2{screenWidth / 2.0f, screenHeight / 2.0f}, 15.0f, BLUE, 200.0f);
|
225
275
|
target = std::make_unique<Target>(positionManager.GetValidPosition(10.0f),
|
226
276
|
10.0f, RED);
|
277
|
+
enemy = std::make_unique<Enemy>(positionManager.GetValidPosition(12.0f),
|
278
|
+
12.0f, DARKGREEN, 120.0f);
|
227
279
|
|
228
280
|
// Register dots with the position manager
|
229
281
|
positionManager.AddDot(player.get());
|
230
282
|
positionManager.AddDot(target.get());
|
283
|
+
positionManager.AddDot(enemy.get());
|
231
284
|
|
232
|
-
// Set player and target in the position manager
|
233
|
-
positionManager.SetPlayerAndTarget(player.get(), target.get());
|
234
|
-
|
235
285
|
// Set the score increment callback
|
236
286
|
positionManager.SetScoreIncrementCallback([this]() { score++; });
|
287
|
+
// Set the game over callback
|
288
|
+
positionManager.SetGameOverCallback([this]() { gameOver = true; });
|
237
289
|
}
|
238
290
|
|
239
291
|
void Update(float deltaTime) {
|
292
|
+
if (!gameOver) {
|
240
|
-
|
293
|
+
// Delegate all updates to the PositionManager
|
241
|
-
|
294
|
+
positionManager.Update(deltaTime);
|
295
|
+
}
|
242
296
|
}
|
243
297
|
|
244
|
-
void Render()
|
298
|
+
void Render() {
|
245
|
-
// Draw game elements
|
246
299
|
BeginDrawing();
|
247
300
|
ClearBackground(RAYWHITE);
|
248
301
|
|
302
|
+
if (gameOver) {
|
303
|
+
DrawText("Game Over!", screenWidth / 2 - 100, screenHeight / 2 - 40, 40,
|
304
|
+
RED);
|
305
|
+
DrawText(TextFormat("Final Score: %d", score), screenWidth / 2 - 100,
|
306
|
+
screenHeight / 2 + 10, 30, DARKGRAY);
|
307
|
+
} else {
|
249
|
-
|
308
|
+
DrawText("Catch the moving dot!", 10, 10, 20, DARKGRAY);
|
250
|
-
|
309
|
+
DrawText(TextFormat("Score: %d", score), 10, 40, 20, DARKGRAY);
|
251
|
-
|
252
|
-
|
310
|
+
player->Draw();
|
253
|
-
|
311
|
+
target->Draw();
|
254
|
-
|
312
|
+
enemy->Draw();
|
313
|
+
}
|
255
314
|
EndDrawing();
|
256
315
|
}
|
257
316
|
|
258
|
-
bool IsRunning() const { return !WindowShouldClose(); }
|
317
|
+
bool IsRunning() const { return !WindowShouldClose() && !gameOver; }
|
259
318
|
};
|
260
319
|
|
261
320
|
Game *gameInstance = nullptr;
|
@@ -275,7 +334,7 @@
|
|
275
334
|
std::srand(std::time(nullptr));
|
276
335
|
|
277
336
|
// Create game object
|
278
|
-
Game game;
|
337
|
+
static Game game;
|
279
338
|
gameInstance = &game;
|
280
339
|
|
281
340
|
#ifdef PLATFORM_WEB
|