From d2035a1227f0b0836983b3cd4970dc9ac346c86f Mon Sep 17 00:00:00 2001 From: = Date: Fri, 16 Jan 2026 20:01:22 +0100 Subject: [PATCH 01/12] added a getter in `Player` for the snake's segments --- lib/include/chomper/player.hpp | 8 +++++++- lib/src/player.cpp | 4 ---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/include/chomper/player.hpp b/lib/include/chomper/player.hpp index 0005964..f941439 100644 --- a/lib/include/chomper/player.hpp +++ b/lib/include/chomper/player.hpp @@ -2,6 +2,7 @@ #include "chomper/controller.hpp" #include "chomper/debug_inspector.hpp" #include "chomper/snake.hpp" +#include "le2d/render_instance.hpp" #include #include #include @@ -22,7 +23,12 @@ class Player : public IController::IListener, public IDebugInspector, public kli void tick(kvf::Seconds dt); void draw(le::IRenderer& renderer) const; - [[nodiscard]] Info const& getInfo() const; + [[nodiscard]] Info const& getInfo() const { + return m_info; + } + [[nodiscard]] std::span getSegments() const { + return m_snake.getSegments(); + } private: [[nodiscard]] bool isCollidingWithSelf(glm::vec2 targetGrid) const; diff --git a/lib/src/player.cpp b/lib/src/player.cpp index aaf5d0f..635503d 100644 --- a/lib/src/player.cpp +++ b/lib/src/player.cpp @@ -30,10 +30,6 @@ void Player::tick(kvf::Seconds dt) { } } -Player::Info const& Player::getInfo() const { - return m_info; -} - bool Player::isCollidingWithSelf(glm::vec2 const targetGrid) const { if (m_snake.getSegments().empty()) { return false; From 16e98a05f9f9111415d6574784545f831c925970 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 16 Jan 2026 20:26:58 +0100 Subject: [PATCH 02/12] Added necessary changes to support an even world size --- lib/include/chomper/world_size.hpp | 2 +- lib/include/chomper/world_space.hpp | 2 +- lib/src/snake.cpp | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/include/chomper/world_size.hpp b/lib/include/chomper/world_size.hpp index 4d139fa..3339213 100644 --- a/lib/include/chomper/world_size.hpp +++ b/lib/include/chomper/world_size.hpp @@ -3,6 +3,6 @@ #include namespace chomper { -constexpr auto worldSize_v = glm::vec2{15.f}; +constexpr auto worldSize_v = glm::vec2{16.f}; constexpr auto tileSize_v = viewport_v.world_size / worldSize_v; } // namespace chomper diff --git a/lib/include/chomper/world_space.hpp b/lib/include/chomper/world_space.hpp index ef9c337..b845b6a 100644 --- a/lib/include/chomper/world_space.hpp +++ b/lib/include/chomper/world_space.hpp @@ -19,6 +19,6 @@ constexpr auto worldToGrid(glm::vec2 worldPosition) { } constexpr auto isOutOfBounds(glm::vec2 gridPoint) { - return gridPoint.x <= 0 || gridPoint.y <= 0 || gridPoint.x > worldSize_v.x || gridPoint.y > worldSize_v.y; + return gridPoint.x < 0 || gridPoint.y < 0 || gridPoint.x >= worldSize_v.x || gridPoint.y >= worldSize_v.y; } } // namespace chomper::worldSpace \ No newline at end of file diff --git a/lib/src/snake.cpp b/lib/src/snake.cpp index 2538230..5d9bdff 100644 --- a/lib/src/snake.cpp +++ b/lib/src/snake.cpp @@ -1,5 +1,7 @@ #include "chomper/snake.hpp" +#include "chomper/world_size.hpp" #include "chomper/world_space.hpp" +#include "le2d/render_instance.hpp" #include #include @@ -10,6 +12,10 @@ constexpr auto headingToDir_v = klib::EnumArray{glm::vec2{1. } // namespace Snake::Snake() { + le::RenderInstance instance{}; + instance.tint = snakeBodyColor_v; + instance.transform.position = worldSpace::gridToWorld({0, worldSize_v.y / 2}); + m_instances.push_back(instance); while (m_instances.size() < m_baseSize) { grow({}); } From 229368c6faf91d767715bbcb50ef635a617792b6 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 17 Jan 2026 19:59:07 +0100 Subject: [PATCH 03/12] Added `Collectible`s Added `Collectible`s and the ability to eat a `Collectible` --- assets/images/apple.png | Bin 0 -> 337 bytes lib/include/chomper/collectible.hpp | 22 ++++++++ lib/include/chomper/player.hpp | 2 + lib/include/chomper/runtimes/game.hpp | 9 ++++ lib/src/collectible.cpp | 15 ++++++ lib/src/player.cpp | 7 ++- lib/src/runtimes/game.cpp | 70 ++++++++++++++++++++++++++ 7 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 assets/images/apple.png create mode 100644 lib/include/chomper/collectible.hpp create mode 100644 lib/src/collectible.cpp diff --git a/assets/images/apple.png b/assets/images/apple.png new file mode 100644 index 0000000000000000000000000000000000000000..f74d03b2013fde15053aadf94509160312e5a718 GIT binary patch literal 337 zcmV-X0j~auP)Px$3rR#lR9J=WmOT=}AP|LL98W-DMPp~-8LX3wOxysJK)I4YQkY=ESvZyg=futa!jS_Pz#9kELtFC|l z8ABQxBqPs$&U^w9%YytKh|M$x7Xv7<_G2|#e@_w#fGy&BF9#riC|e7RVS_AFj64JZ zh6X^`%|VES()YmFJae7P-2*w9NGJ%19ijEivwSblZ#-`%xzsvC$0&6`;I3SBX j$wDAg #include #include +#include namespace chomper::runtime { // driven by Engine, owner (whether indirectly) of all game things. @@ -27,6 +30,10 @@ class Game : public IRuntime, public klib::Pinned { void bindActions(); void createPlayer(); + void createCollectibleTexture(); + + void spawnCollectible(); + void collideCollectibles(); void onGoBack(); @@ -39,5 +46,7 @@ class Game : public IRuntime, public klib::Pinned { std::unique_ptr m_player{}; std::unique_ptr m_world{}; + std::vector m_collectibles{}; + le::ITexture* m_collectibleTexture{}; }; } // namespace chomper::runtime diff --git a/lib/src/collectible.cpp b/lib/src/collectible.cpp new file mode 100644 index 0000000..6799b07 --- /dev/null +++ b/lib/src/collectible.cpp @@ -0,0 +1,15 @@ +#include "chomper/collectible.hpp" +#include "chomper/world_size.hpp" +#include "le2d/renderer.hpp" + +namespace chomper { +Collectible::Collectible(le::ITexture const& texture, glm::vec2 position) { + m_quad.create(tileSize_v); + m_quad.texture = &texture; + m_quad.transform.position = position; +} + +void Collectible::draw(le::IRenderer& renderer) const { + m_quad.draw(renderer); +} +} // namespace chomper \ No newline at end of file diff --git a/lib/src/player.cpp b/lib/src/player.cpp index 635503d..a42044f 100644 --- a/lib/src/player.cpp +++ b/lib/src/player.cpp @@ -30,6 +30,10 @@ void Player::tick(kvf::Seconds dt) { } } +void Player::shouldPop(bool v) { + m_shouldPop = v; +} + bool Player::isCollidingWithSelf(glm::vec2 const targetGrid) const { if (m_snake.getSegments().empty()) { return false; @@ -74,7 +78,8 @@ void Player::move() { m_snake.popTail(); } - m_graceMove = false; + m_shouldPop = true; // reset shouldPop + m_graceMove = false; // reset graceMove } void Player::draw(le::IRenderer& renderer) const { diff --git a/lib/src/runtimes/game.cpp b/lib/src/runtimes/game.cpp index 740158b..558d6dd 100644 --- a/lib/src/runtimes/game.cpp +++ b/lib/src/runtimes/game.cpp @@ -1,7 +1,16 @@ #include "chomper/runtimes/game.hpp" +#include "chomper/collectible.hpp" #include "chomper/im_util.hpp" #include "chomper/runtimes/entrypoint.hpp" +#include "chomper/world_size.hpp" +#include "chomper/world_space.hpp" +#include "glm/ext/vector_float2.hpp" +#include "le2d/resource/texture.hpp" #include +#include +#include +#include +#include namespace chomper::runtime { using ActionValue = le::input::action::Value; @@ -9,11 +18,15 @@ using ActionValue = le::input::action::Value; Game::Game(gsl::not_null engine) : m_engine(engine), m_mapping(&engine->getInputRouter()) { createPlayer(); m_world = std::make_unique(m_engine); + createCollectibleTexture(); + spawnCollectible(); } void Game::tick(kvf::Seconds const dt) { m_player->tick(dt); + collideCollectibles(); + ImGui::SetNextWindowSize({300.0f, 300.0f}, ImGuiCond_Once); if (ImGui::Begin("Debug Inspect")) { debugInspectWindow(); @@ -29,6 +42,9 @@ void Game::tick(kvf::Seconds const dt) { void Game::render(le::IRenderer& renderer) const { m_world->draw(renderer); m_player->draw(renderer); + for (auto const& collectible : m_collectibles) { + collectible.draw(renderer); + } } void Game::debugInspectWindow() { @@ -59,6 +75,60 @@ void Game::createPlayer() { m_player = std::make_unique(m_mapping, m_engine); } +void Game::createCollectibleTexture() { + m_collectibleTexture = m_engine->getResources().load("images/apple.png"); +} + +void Game::spawnCollectible() { + std::unordered_set occupied; + for (auto const& seg : m_player->getSegments()) { + auto p = worldSpace::worldToGrid(seg.transform.position); + occupied.insert(static_cast((p.y * worldSize_v.x) + p.x)); + } + + for (auto const& c : m_collectibles) { + auto p = c.getGridPosition(); + occupied.insert(static_cast((p.y * worldSize_v.x) + p.x)); + } + + auto emptyTiles = (worldSize_v.x * worldSize_v.y) - (float)occupied.size(); + if (emptyTiles <= 0) { + return; + } + + std::mt19937 rng(std::random_device{}()); + std::uniform_int_distribution dist(0, (int)emptyTiles - 1); + auto target = dist(rng); + + auto count = 0; + for (float y = 0; y < worldSize_v.y; ++y) { + for (float x = 0; x < worldSize_v.x; ++x) { + auto id = static_cast((y * worldSize_v.x) + x); + if (!occupied.contains(id)) { + if (count == target) { + m_collectibles.emplace_back(*m_collectibleTexture, worldSpace::gridToWorld({x, y})); + return; + } + count++; + } + } + } +} + +void Game::collideCollectibles() { + for (auto it = m_collectibles.begin(); it != m_collectibles.end();) { + if (it->getGridPosition() == worldSpace::worldToGrid(m_player->getSegments().back().transform.position)) { + + m_collectibles.erase(it); // erase collided collectible + m_player->shouldPop(false); // tell the player to not pop the tail/grow + spawnCollectible(); // spawn new collectible + + return; + } + it++; + } +} + void Game::onGoBack() { m_log.debug("execute 'go back' action here"); } From 54b8c0e2cf4c8ef513ff416c12595ecd57b48ae6 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 17 Jan 2026 19:59:07 +0100 Subject: [PATCH 04/12] Added `Collectible`s Added `Collectible`s and the ability to eat a `Collectible` --- assets/images/apple.png | Bin 0 -> 337 bytes lib/include/chomper/collectible.hpp | 22 ++++++++ lib/include/chomper/player.hpp | 2 + lib/include/chomper/runtimes/game.hpp | 9 ++++ lib/src/collectible.cpp | 15 ++++++ lib/src/player.cpp | 7 ++- lib/src/runtimes/game.cpp | 70 ++++++++++++++++++++++++++ 7 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 assets/images/apple.png create mode 100644 lib/include/chomper/collectible.hpp create mode 100644 lib/src/collectible.cpp diff --git a/assets/images/apple.png b/assets/images/apple.png new file mode 100644 index 0000000000000000000000000000000000000000..f74d03b2013fde15053aadf94509160312e5a718 GIT binary patch literal 337 zcmV-X0j~auP)Px$3rR#lR9J=WmOT=}AP|LL98W-DMPp~-8LX3wOxysJK)I4YQkY=ESvZyg=futa!jS_Pz#9kELtFC|l z8ABQxBqPs$&U^w9%YytKh|M$x7Xv7<_G2|#e@_w#fGy&BF9#riC|e7RVS_AFj64JZ zh6X^`%|VES()YmFJae7P-2*w9NGJ%19ijEivwSblZ#-`%xzsvC$0&6`;I3SBX j$wDAg #include #include +#include namespace chomper::runtime { // driven by Engine, owner (whether indirectly) of all game things. @@ -27,6 +30,10 @@ class Game : public IRuntime, public klib::Pinned { void bindActions(); void createPlayer(); + void createCollectibleTexture(); + + void spawnCollectible(); + void collideCollectibles(); void onGoBack(); @@ -39,5 +46,7 @@ class Game : public IRuntime, public klib::Pinned { std::unique_ptr m_player{}; std::unique_ptr m_world{}; + std::vector m_collectibles{}; + le::ITexture* m_collectibleTexture{}; }; } // namespace chomper::runtime diff --git a/lib/src/collectible.cpp b/lib/src/collectible.cpp new file mode 100644 index 0000000..6799b07 --- /dev/null +++ b/lib/src/collectible.cpp @@ -0,0 +1,15 @@ +#include "chomper/collectible.hpp" +#include "chomper/world_size.hpp" +#include "le2d/renderer.hpp" + +namespace chomper { +Collectible::Collectible(le::ITexture const& texture, glm::vec2 position) { + m_quad.create(tileSize_v); + m_quad.texture = &texture; + m_quad.transform.position = position; +} + +void Collectible::draw(le::IRenderer& renderer) const { + m_quad.draw(renderer); +} +} // namespace chomper \ No newline at end of file diff --git a/lib/src/player.cpp b/lib/src/player.cpp index 635503d..a42044f 100644 --- a/lib/src/player.cpp +++ b/lib/src/player.cpp @@ -30,6 +30,10 @@ void Player::tick(kvf::Seconds dt) { } } +void Player::shouldPop(bool v) { + m_shouldPop = v; +} + bool Player::isCollidingWithSelf(glm::vec2 const targetGrid) const { if (m_snake.getSegments().empty()) { return false; @@ -74,7 +78,8 @@ void Player::move() { m_snake.popTail(); } - m_graceMove = false; + m_shouldPop = true; // reset shouldPop + m_graceMove = false; // reset graceMove } void Player::draw(le::IRenderer& renderer) const { diff --git a/lib/src/runtimes/game.cpp b/lib/src/runtimes/game.cpp index 740158b..558d6dd 100644 --- a/lib/src/runtimes/game.cpp +++ b/lib/src/runtimes/game.cpp @@ -1,7 +1,16 @@ #include "chomper/runtimes/game.hpp" +#include "chomper/collectible.hpp" #include "chomper/im_util.hpp" #include "chomper/runtimes/entrypoint.hpp" +#include "chomper/world_size.hpp" +#include "chomper/world_space.hpp" +#include "glm/ext/vector_float2.hpp" +#include "le2d/resource/texture.hpp" #include +#include +#include +#include +#include namespace chomper::runtime { using ActionValue = le::input::action::Value; @@ -9,11 +18,15 @@ using ActionValue = le::input::action::Value; Game::Game(gsl::not_null engine) : m_engine(engine), m_mapping(&engine->getInputRouter()) { createPlayer(); m_world = std::make_unique(m_engine); + createCollectibleTexture(); + spawnCollectible(); } void Game::tick(kvf::Seconds const dt) { m_player->tick(dt); + collideCollectibles(); + ImGui::SetNextWindowSize({300.0f, 300.0f}, ImGuiCond_Once); if (ImGui::Begin("Debug Inspect")) { debugInspectWindow(); @@ -29,6 +42,9 @@ void Game::tick(kvf::Seconds const dt) { void Game::render(le::IRenderer& renderer) const { m_world->draw(renderer); m_player->draw(renderer); + for (auto const& collectible : m_collectibles) { + collectible.draw(renderer); + } } void Game::debugInspectWindow() { @@ -59,6 +75,60 @@ void Game::createPlayer() { m_player = std::make_unique(m_mapping, m_engine); } +void Game::createCollectibleTexture() { + m_collectibleTexture = m_engine->getResources().load("images/apple.png"); +} + +void Game::spawnCollectible() { + std::unordered_set occupied; + for (auto const& seg : m_player->getSegments()) { + auto p = worldSpace::worldToGrid(seg.transform.position); + occupied.insert(static_cast((p.y * worldSize_v.x) + p.x)); + } + + for (auto const& c : m_collectibles) { + auto p = c.getGridPosition(); + occupied.insert(static_cast((p.y * worldSize_v.x) + p.x)); + } + + auto emptyTiles = (worldSize_v.x * worldSize_v.y) - (float)occupied.size(); + if (emptyTiles <= 0) { + return; + } + + std::mt19937 rng(std::random_device{}()); + std::uniform_int_distribution dist(0, (int)emptyTiles - 1); + auto target = dist(rng); + + auto count = 0; + for (float y = 0; y < worldSize_v.y; ++y) { + for (float x = 0; x < worldSize_v.x; ++x) { + auto id = static_cast((y * worldSize_v.x) + x); + if (!occupied.contains(id)) { + if (count == target) { + m_collectibles.emplace_back(*m_collectibleTexture, worldSpace::gridToWorld({x, y})); + return; + } + count++; + } + } + } +} + +void Game::collideCollectibles() { + for (auto it = m_collectibles.begin(); it != m_collectibles.end();) { + if (it->getGridPosition() == worldSpace::worldToGrid(m_player->getSegments().back().transform.position)) { + + m_collectibles.erase(it); // erase collided collectible + m_player->shouldPop(false); // tell the player to not pop the tail/grow + spawnCollectible(); // spawn new collectible + + return; + } + it++; + } +} + void Game::onGoBack() { m_log.debug("execute 'go back' action here"); } From ec997b1ffa4c05352bd0e5f13ec573d134f01b32 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 17 Jan 2026 20:03:32 +0100 Subject: [PATCH 05/12] changed `Player::shouldPop(bool v)` to `Player::grow()` --- lib/include/chomper/player.hpp | 2 +- lib/src/player.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/include/chomper/player.hpp b/lib/include/chomper/player.hpp index 0a1504d..0923040 100644 --- a/lib/include/chomper/player.hpp +++ b/lib/include/chomper/player.hpp @@ -23,7 +23,7 @@ class Player : public IController::IListener, public IDebugInspector, public kli void tick(kvf::Seconds dt); void draw(le::IRenderer& renderer) const; - void shouldPop(bool v); + void grow(); [[nodiscard]] Info const& getInfo() const { return m_info; diff --git a/lib/src/player.cpp b/lib/src/player.cpp index a42044f..7da95c0 100644 --- a/lib/src/player.cpp +++ b/lib/src/player.cpp @@ -30,8 +30,8 @@ void Player::tick(kvf::Seconds dt) { } } -void Player::shouldPop(bool v) { - m_shouldPop = v; +void Player::grow() { + m_shouldPop = false; } bool Player::isCollidingWithSelf(glm::vec2 const targetGrid) const { From caeab210d4a129e40f0511dbd45af865b10fbb98 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 17 Jan 2026 20:32:39 +0100 Subject: [PATCH 06/12] Added a score text --- lib/include/chomper/player.hpp | 4 ++++ lib/src/player.cpp | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/include/chomper/player.hpp b/lib/include/chomper/player.hpp index 0923040..a25fba7 100644 --- a/lib/include/chomper/player.hpp +++ b/lib/include/chomper/player.hpp @@ -2,6 +2,7 @@ #include "chomper/controller.hpp" #include "chomper/debug_inspector.hpp" #include "chomper/snake.hpp" +#include "le2d/drawable/text.hpp" #include "le2d/render_instance.hpp" #include #include @@ -16,6 +17,7 @@ class Player : public IController::IListener, public IDebugInspector, public kli public: struct Info { bool alive = true; + size_t score{}; }; explicit Player(le::input::ScopedActionMapping& mapping, gsl::not_null engine); @@ -36,6 +38,7 @@ class Player : public IController::IListener, public IDebugInspector, public kli [[nodiscard]] bool isCollidingWithSelf(glm::vec2 targetGrid) const; [[nodiscard]] bool isCollidingWithWall(glm::vec2 targetGrid) const; void move(); + void updateScoreText(); // IController::IListener void onSetHeading(Heading heading) final; @@ -52,6 +55,7 @@ class Player : public IController::IListener, public IDebugInspector, public kli std::unique_ptr m_controller{}; Snake m_snake{}; + le::drawable::Text m_scoreText{}; Info m_info{}; diff --git a/lib/src/player.cpp b/lib/src/player.cpp index 7da95c0..ac3a698 100644 --- a/lib/src/player.cpp +++ b/lib/src/player.cpp @@ -1,6 +1,7 @@ #include "chomper/player.hpp" #include "chomper/controllers/player_controller.hpp" #include "chomper/engine.hpp" +#include "chomper/world_size.hpp" #include "chomper/world_space.hpp" #include @@ -13,6 +14,7 @@ constexpr auto headingToDir_v = klib::EnumArray{glm::vec2{1. Player::Player(le::input::ScopedActionMapping& mapping, gsl::not_null engine) : m_engine(engine) { createController(mapping); + updateScoreText(); } void Player::tick(kvf::Seconds dt) { @@ -31,6 +33,8 @@ void Player::tick(kvf::Seconds dt) { } void Player::grow() { + m_info.score++; + updateScoreText(); m_shouldPop = false; } @@ -82,8 +86,18 @@ void Player::move() { m_graceMove = false; // reset graceMove } +void Player::updateScoreText() { + static constexpr auto textParams_v = le::drawable::Text::Params{ + .height = le::TextHeight{16}, + }; + + m_scoreText.set_string(m_engine->getResources().getMainFont(), std::format("Score: {}", m_info.score), textParams_v); + m_scoreText.transform.position = worldSpace::gridToWorld({0, worldSize_v.y - 1}) + glm::vec2{m_scoreText.get_size().x / 2, 0}; +} + void Player::draw(le::IRenderer& renderer) const { m_snake.draw(renderer); + m_scoreText.draw(renderer); } void Player::debugInspect() { From cc9dd515282b84be5629d0708b0c70b89828b202 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 17 Jan 2026 20:39:18 +0100 Subject: [PATCH 07/12] Added a initial amount of collectibles --- lib/include/chomper/runtimes/game.hpp | 2 ++ lib/src/runtimes/game.cpp | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/include/chomper/runtimes/game.hpp b/lib/include/chomper/runtimes/game.hpp index 060232c..23ca75a 100644 --- a/lib/include/chomper/runtimes/game.hpp +++ b/lib/include/chomper/runtimes/game.hpp @@ -48,5 +48,7 @@ class Game : public IRuntime, public klib::Pinned { std::unique_ptr m_world{}; std::vector m_collectibles{}; le::ITexture* m_collectibleTexture{}; + + size_t m_collectibleAmount = 10; }; } // namespace chomper::runtime diff --git a/lib/src/runtimes/game.cpp b/lib/src/runtimes/game.cpp index 59e9909..66d79bc 100644 --- a/lib/src/runtimes/game.cpp +++ b/lib/src/runtimes/game.cpp @@ -19,7 +19,10 @@ Game::Game(gsl::not_null engine) : m_engine(engine), m_mapping(&engine- createPlayer(); m_world = std::make_unique(m_engine); createCollectibleTexture(); - spawnCollectible(); + + for (size_t i = 0; i < m_collectibleAmount; ++i) { + spawnCollectible(); + } } void Game::tick(kvf::Seconds const dt) { From dd39dc90ae59da9dcc9a6034d9e746d563b9f43c Mon Sep 17 00:00:00 2001 From: = Date: Sat, 17 Jan 2026 23:59:26 +0100 Subject: [PATCH 08/12] added requested changes --- lib/include/chomper/collectible.hpp | 6 +-- lib/include/chomper/player.hpp | 2 +- lib/include/chomper/runtimes/game.hpp | 8 +++- lib/src/collectible.cpp | 8 ++-- lib/src/runtimes/game.cpp | 56 +++++++++++---------------- 5 files changed, 36 insertions(+), 44 deletions(-) diff --git a/lib/include/chomper/collectible.hpp b/lib/include/chomper/collectible.hpp index 06dab42..a7cedb4 100644 --- a/lib/include/chomper/collectible.hpp +++ b/lib/include/chomper/collectible.hpp @@ -1,7 +1,7 @@ #pragma once #include "chomper/world_space.hpp" #include "glm/ext/vector_float2.hpp" -#include "le2d/drawable/shape.hpp" +#include "le2d/drawable/sprite.hpp" #include "le2d/renderer.hpp" #include "le2d/resource/texture.hpp" @@ -13,10 +13,10 @@ class Collectible { void draw(le::IRenderer& renderer) const; [[nodiscard]] glm::vec2 getGridPosition() const { - return worldSpace::worldToGrid(m_quad.transform.position); + return worldSpace::worldToGrid(m_sprite.transform.position); } private: - le::drawable::Quad m_quad{}; + le::drawable::Sprite m_sprite{}; }; } // namespace chomper \ No newline at end of file diff --git a/lib/include/chomper/player.hpp b/lib/include/chomper/player.hpp index a25fba7..610ca02 100644 --- a/lib/include/chomper/player.hpp +++ b/lib/include/chomper/player.hpp @@ -17,7 +17,7 @@ class Player : public IController::IListener, public IDebugInspector, public kli public: struct Info { bool alive = true; - size_t score{}; + std::size_t score{}; }; explicit Player(le::input::ScopedActionMapping& mapping, gsl::not_null engine); diff --git a/lib/include/chomper/runtimes/game.hpp b/lib/include/chomper/runtimes/game.hpp index 23ca75a..bc81fe1 100644 --- a/lib/include/chomper/runtimes/game.hpp +++ b/lib/include/chomper/runtimes/game.hpp @@ -4,11 +4,12 @@ #include "chomper/player.hpp" #include "chomper/runtime.hpp" #include "chomper/world.hpp" +#include "le2d/random.hpp" #include "le2d/resource/texture.hpp" #include #include #include -#include +#include namespace chomper::runtime { // driven by Engine, owner (whether indirectly) of all game things. @@ -44,10 +45,13 @@ class Game : public IRuntime, public klib::Pinned { le::input::ScopedActionMapping m_mapping; Actions m_actions{}; + le::Random m_random{}; + std::unordered_set m_occupied; + std::unique_ptr m_player{}; std::unique_ptr m_world{}; std::vector m_collectibles{}; - le::ITexture* m_collectibleTexture{}; + klib::Ptr m_collectibleTexture{}; size_t m_collectibleAmount = 10; }; diff --git a/lib/src/collectible.cpp b/lib/src/collectible.cpp index 6799b07..3573d9c 100644 --- a/lib/src/collectible.cpp +++ b/lib/src/collectible.cpp @@ -4,12 +4,12 @@ namespace chomper { Collectible::Collectible(le::ITexture const& texture, glm::vec2 position) { - m_quad.create(tileSize_v); - m_quad.texture = &texture; - m_quad.transform.position = position; + m_sprite.set_base_size(tileSize_v); + m_sprite.set_texture(&texture); + m_sprite.transform.position = position; } void Collectible::draw(le::IRenderer& renderer) const { - m_quad.draw(renderer); + m_sprite.draw(renderer); } } // namespace chomper \ No newline at end of file diff --git a/lib/src/runtimes/game.cpp b/lib/src/runtimes/game.cpp index 66d79bc..4dc4e7f 100644 --- a/lib/src/runtimes/game.cpp +++ b/lib/src/runtimes/game.cpp @@ -4,13 +4,8 @@ #include "chomper/runtimes/entrypoint.hpp" #include "chomper/world_size.hpp" #include "chomper/world_space.hpp" -#include "glm/ext/vector_float2.hpp" -#include "le2d/resource/texture.hpp" -#include -#include -#include -#include -#include +#include +#include namespace chomper::runtime { using ActionValue = le::input::action::Value; @@ -83,52 +78,45 @@ void Game::createCollectibleTexture() { } void Game::spawnCollectible() { - std::unordered_set occupied; + m_occupied.clear(); for (auto const& seg : m_player->getSegments()) { auto p = worldSpace::worldToGrid(seg.transform.position); - occupied.insert(static_cast((p.y * worldSize_v.x) + p.x)); + m_occupied.insert(static_cast((p.y * worldSize_v.x) + p.x)); } for (auto const& c : m_collectibles) { auto p = c.getGridPosition(); - occupied.insert(static_cast((p.y * worldSize_v.x) + p.x)); + m_occupied.insert(static_cast((p.y * worldSize_v.x) + p.x)); } - auto emptyTiles = (worldSize_v.x * worldSize_v.y) - (float)occupied.size(); + auto emptyTiles = (worldSize_v.x * worldSize_v.y) - static_cast(m_occupied.size()); if (emptyTiles <= 0) { return; } - std::mt19937 rng(std::random_device{}()); - std::uniform_int_distribution dist(0, (int)emptyTiles - 1); - auto target = dist(rng); + auto random = m_random.next_int(0, static_cast(emptyTiles - 1)); auto count = 0; - for (float y = 0; y < worldSize_v.y; ++y) { - for (float x = 0; x < worldSize_v.x; ++x) { - auto id = static_cast((y * worldSize_v.x) + x); - if (!occupied.contains(id)) { - if (count == target) { - m_collectibles.emplace_back(*m_collectibleTexture, worldSpace::gridToWorld({x, y})); - return; - } - count++; - } + auto const width = static_cast(worldSize_v.x); + for (int index = 0; index < static_cast(worldSize_v.x * worldSize_v.y); index++) { + if (m_occupied.contains(index)) { + continue; + } + if (count++ == random) { + m_collectibles.emplace_back(*m_collectibleTexture, worldSpace::gridToWorld({index % width, index / width})); + return; } } } void Game::collideCollectibles() { - for (auto it = m_collectibles.begin(); it != m_collectibles.end();) { - if (it->getGridPosition() == worldSpace::worldToGrid(m_player->getSegments().back().transform.position)) { - - m_collectibles.erase(it); // erase collided collectible - m_player->grow(); // tell the player to not pop the tail/grow - spawnCollectible(); // spawn new collectible - - return; - } - it++; + auto it = std::ranges::find_if(m_collectibles, [&](auto const& collectible) { + return collectible.getGridPosition() == worldSpace::worldToGrid(m_player->getSegments().back().transform.position); + }); + if (it != m_collectibles.end()) { + m_collectibles.erase(it); + m_player->grow(); + spawnCollectible(); } } From 51d758eac56f264d151a8460eb6e896ce593d2f3 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 18 Jan 2026 20:55:59 +0100 Subject: [PATCH 09/12] Fixed IMPORTANT heading bug --- lib/src/player.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/player.cpp b/lib/src/player.cpp index ac3a698..d9f7e48 100644 --- a/lib/src/player.cpp +++ b/lib/src/player.cpp @@ -117,12 +117,12 @@ void Player::createController(le::input::ScopedActionMapping& mapping) { void Player::onSetHeading(Heading const heading) { auto lastHeading = m_headingQueue.empty() ? m_heading : m_headingQueue.back(); - if (heading == m_heading || heading == oppositeHeading_v[lastHeading]) { + if (heading == lastHeading || heading == oppositeHeading_v[lastHeading]) { return; } if (m_headingQueue.size() < 3) { - m_log.debug("changing heading from {} to {}", headingName_v[m_heading], headingName_v[heading]); + m_log.debug("changing heading from {} to {}", headingName_v[lastHeading], headingName_v[heading]); m_headingQueue.push_back(heading); } } From 616a692eee2cf51f6e9b4e328d558a447fcea772 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 18 Jan 2026 21:03:02 +0100 Subject: [PATCH 10/12] Fixed for merge --- lib/src/runtimes/game.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/src/runtimes/game.cpp b/lib/src/runtimes/game.cpp index 8ff6b9c..9279cb7 100644 --- a/lib/src/runtimes/game.cpp +++ b/lib/src/runtimes/game.cpp @@ -24,14 +24,8 @@ Game::Game(gsl::not_null engine) : m_engine(engine), m_mapping(&engine- for (size_t i = 0; i < m_collectibleAmount; ++i) { spawnCollectible(); } - - m_countdownText.set_string(engine->getResources().getMainFont(), "3", countdownParams_v); -} - -void Game::tick(kvf::Seconds const dt) { - m_player->tick(dt); - collideCollectibles(); + m_countdownText.set_string(engine->getResources().getMainFont(), "3", countdownParams_v); } void Game::tick(kvf::Seconds const dt) { @@ -49,6 +43,8 @@ void Game::tick(kvf::Seconds const dt) { m_player->tick(dt); + collideCollectibles(); + // On death if (!m_player->getInfo().alive) { m_engine->setNextRuntime(); @@ -60,7 +56,7 @@ void Game::render(le::IRenderer& renderer) const { m_player->draw(renderer); for (auto const& collectible : m_collectibles) { collectible.draw(renderer); - } + } if (m_countdown.count() > 0) { m_countdownText.draw(renderer); } From fff125cba32d9783ea5c0e133f9994fc34fdc5e6 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 19 Jan 2026 18:12:10 +0100 Subject: [PATCH 11/12] refactor --- lib/include/chomper/player.hpp | 2 +- lib/include/chomper/runtimes/game.hpp | 5 ++- lib/src/runtimes/game.cpp | 58 ++++++++++++++++----------- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/lib/include/chomper/player.hpp b/lib/include/chomper/player.hpp index 610ca02..b7d4122 100644 --- a/lib/include/chomper/player.hpp +++ b/lib/include/chomper/player.hpp @@ -17,7 +17,7 @@ class Player : public IController::IListener, public IDebugInspector, public kli public: struct Info { bool alive = true; - std::size_t score{}; + int score{}; }; explicit Player(le::input::ScopedActionMapping& mapping, gsl::not_null engine); diff --git a/lib/include/chomper/runtimes/game.hpp b/lib/include/chomper/runtimes/game.hpp index c74ab7a..7e12e03 100644 --- a/lib/include/chomper/runtimes/game.hpp +++ b/lib/include/chomper/runtimes/game.hpp @@ -34,7 +34,8 @@ class Game : public IRuntime, public klib::Pinned { void createPlayer(); void createCollectibleTexture(); - void spawnCollectible(); + void findEmptyTiles(); + void spawnCollectibles(); void collideCollectibles(); void onGoBack(); @@ -54,7 +55,7 @@ class Game : public IRuntime, public klib::Pinned { std::vector m_collectibles{}; klib::Ptr m_collectibleTexture{}; - size_t m_collectibleAmount = 10; + std::vector m_emptyTiles{}; le::drawable::Text m_countdownText{}; kvf::Seconds m_countdown{3}; diff --git a/lib/src/runtimes/game.cpp b/lib/src/runtimes/game.cpp index 9279cb7..f33efce 100644 --- a/lib/src/runtimes/game.cpp +++ b/lib/src/runtimes/game.cpp @@ -12,7 +12,8 @@ namespace { constexpr auto countdownParams_v = le::drawable::Text::Params{ .height = le::TextHeight{60}, }; -} +constexpr auto collectibleAmount_v = 10; +} // namespace using ActionValue = le::input::action::Value; Game::Game(gsl::not_null engine) : m_engine(engine), m_mapping(&engine->getInputRouter()) { @@ -21,9 +22,7 @@ Game::Game(gsl::not_null engine) : m_engine(engine), m_mapping(&engine- createCollectibleTexture(); - for (size_t i = 0; i < m_collectibleAmount; ++i) { - spawnCollectible(); - } + spawnCollectibles(); m_countdownText.set_string(engine->getResources().getMainFont(), "3", countdownParams_v); } @@ -94,35 +93,48 @@ void Game::createCollectibleTexture() { m_collectibleTexture = m_engine->getResources().load("images/apple.png"); } -void Game::spawnCollectible() { - m_occupied.clear(); +void Game::findEmptyTiles() { + m_emptyTiles.clear(); + m_emptyTiles.reserve(static_cast(worldSize_v.x * worldSize_v.y)); + for (auto i = 0; i < static_cast(worldSize_v.x * worldSize_v.y); i++) { + m_emptyTiles.push_back(i); + } + + auto const removeTile = [this](int tile) { + auto it = std::ranges::find(m_emptyTiles, tile); + if (it != m_emptyTiles.end()) { + *it = m_emptyTiles.back(); + m_emptyTiles.pop_back(); + } + }; + for (auto const& seg : m_player->getSegments()) { auto p = worldSpace::worldToGrid(seg.transform.position); - m_occupied.insert(static_cast((p.y * worldSize_v.x) + p.x)); + removeTile(static_cast((p.y * worldSize_v.x) + p.x)); } for (auto const& c : m_collectibles) { auto p = c.getGridPosition(); - m_occupied.insert(static_cast((p.y * worldSize_v.x) + p.x)); - } - - auto emptyTiles = (worldSize_v.x * worldSize_v.y) - static_cast(m_occupied.size()); - if (emptyTiles <= 0) { - return; + removeTile(static_cast((p.y * worldSize_v.x) + p.x)); } +} - auto random = m_random.next_int(0, static_cast(emptyTiles - 1)); +void Game::spawnCollectibles() { + findEmptyTiles(); - auto count = 0; - auto const width = static_cast(worldSize_v.x); - for (int index = 0; index < static_cast(worldSize_v.x * worldSize_v.y); index++) { - if (m_occupied.contains(index)) { - continue; - } - if (count++ == random) { - m_collectibles.emplace_back(*m_collectibleTexture, worldSpace::gridToWorld({index % width, index / width})); + for (auto i = m_collectibles.size(); i < collectibleAmount_v; i++) { + if (m_emptyTiles.empty()) { return; } + // find a random tile + auto random = m_random.next_index(m_emptyTiles.size()); + auto tile = m_emptyTiles[random]; + // remove said tile from the vector + m_emptyTiles[random] = m_emptyTiles.back(); + m_emptyTiles.pop_back(); + // place the collectible on the tile + auto width = static_cast(worldSize_v.x); + m_collectibles.emplace_back(*m_collectibleTexture, worldSpace::gridToWorld({tile % width, tile / width})); } } @@ -133,7 +145,7 @@ void Game::collideCollectibles() { if (it != m_collectibles.end()) { m_collectibles.erase(it); m_player->grow(); - spawnCollectible(); + spawnCollectibles(); } } From 235628991917b76a78c3995ee423eac96ded1723 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 19 Jan 2026 18:24:19 +0100 Subject: [PATCH 12/12] added requested changes --- lib/src/runtimes/game.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/src/runtimes/game.cpp b/lib/src/runtimes/game.cpp index f33efce..16666f8 100644 --- a/lib/src/runtimes/game.cpp +++ b/lib/src/runtimes/game.cpp @@ -130,8 +130,9 @@ void Game::spawnCollectibles() { auto random = m_random.next_index(m_emptyTiles.size()); auto tile = m_emptyTiles[random]; // remove said tile from the vector - m_emptyTiles[random] = m_emptyTiles.back(); - m_emptyTiles.pop_back(); + std::erase_if(m_emptyTiles, [&](auto const& v) { + return v == m_emptyTiles[random]; + }); // place the collectible on the tile auto width = static_cast(worldSize_v.x); m_collectibles.emplace_back(*m_collectibleTexture, worldSpace::gridToWorld({tile % width, tile / width})); @@ -142,11 +143,13 @@ void Game::collideCollectibles() { auto it = std::ranges::find_if(m_collectibles, [&](auto const& collectible) { return collectible.getGridPosition() == worldSpace::worldToGrid(m_player->getSegments().back().transform.position); }); - if (it != m_collectibles.end()) { - m_collectibles.erase(it); - m_player->grow(); - spawnCollectibles(); + if (it == m_collectibles.end()) { + return; } + + m_collectibles.erase(it); + m_player->grow(); + spawnCollectibles(); } void Game::onGoBack() {