Back to Home

Collect the Dots V2

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.

SetPlayerAndTarget() to Entity Registry CHANGED
@@ -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<Dot *> dots;
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 player/target pointers...
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.

Collect the Dots V2 CHANGED
@@ -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<Dot *> dots;
73
+ std::vector<DotEntry> dots;
62
74
  std::function<void()> onScoreIncrement;
63
- Dot *player = nullptr;
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
- // between the player and target
101
+ // Score increment if player and target collide
102
+ if (onScoreIncrement &&
93
- if (onScoreIncrement && ((dotA == player && dotB == target) ||
103
+ ((typeA == DotType::Player && typeB == DotType::Target) ||
94
- (dotA == target && dotB == player))) {
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 (Dot *dot : dots) {
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 Dot *dot : dots) {
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
- return player ? player->GetPosition() : Vector2{0, 0};
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
- // Delegate all updates to the PositionManager
293
+ // Delegate all updates to the PositionManager
241
- positionManager.Update(deltaTime);
294
+ positionManager.Update(deltaTime);
295
+ }
242
296
  }
243
297
 
244
- void Render() const {
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
- DrawText("Catch the moving dot!", 10, 10, 20, DARKGRAY);
308
+ DrawText("Catch the moving dot!", 10, 10, 20, DARKGRAY);
250
- DrawText(TextFormat("Score: %d", score), 10, 40, 20, DARKGRAY);
309
+ DrawText(TextFormat("Score: %d", score), 10, 40, 20, DARKGRAY);
251
-
252
- player->Draw();
310
+ player->Draw();
253
- target->Draw();
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
Next: Structural Entropy, Collect the Dots V3 Previous: Collect the Dots V1