Skip to content

Commit ec91759

Browse files
authored
Added Collectibles (#24)
1 parent 575658a commit ec91759

10 files changed

Lines changed: 173 additions & 10 deletions

File tree

assets/images/apple.png

337 Bytes
Loading
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
#include "chomper/world_space.hpp"
3+
#include "glm/ext/vector_float2.hpp"
4+
#include "le2d/drawable/sprite.hpp"
5+
#include "le2d/renderer.hpp"
6+
#include "le2d/resource/texture.hpp"
7+
8+
namespace chomper {
9+
class Collectible {
10+
public:
11+
explicit Collectible(le::ITexture const& texture, glm::vec2 position);
12+
13+
void draw(le::IRenderer& renderer) const;
14+
15+
[[nodiscard]] glm::vec2 getGridPosition() const {
16+
return worldSpace::worldToGrid(m_sprite.transform.position);
17+
}
18+
19+
private:
20+
le::drawable::Sprite m_sprite{};
21+
};
22+
} // namespace chomper

lib/include/chomper/player.hpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#include "chomper/controller.hpp"
33
#include "chomper/debug_inspector.hpp"
44
#include "chomper/snake.hpp"
5+
#include "le2d/drawable/text.hpp"
6+
#include "le2d/render_instance.hpp"
57
#include <imgui.h>
68
#include <klib/log.hpp>
79
#include <le2d/input/action.hpp>
@@ -15,19 +17,28 @@ class Player : public IController::IListener, public IDebugInspector, public kli
1517
public:
1618
struct Info {
1719
bool alive = true;
20+
int score{};
1821
};
1922

2023
explicit Player(le::input::ScopedActionMapping& mapping, gsl::not_null<Engine const*> engine);
2124

2225
void tick(kvf::Seconds dt);
2326
void draw(le::IRenderer& renderer) const;
2427

25-
[[nodiscard]] Info const& getInfo() const;
28+
void grow();
29+
30+
[[nodiscard]] Info const& getInfo() const {
31+
return m_info;
32+
}
33+
[[nodiscard]] std::span<le::RenderInstance const> getSegments() const {
34+
return m_snake.getSegments();
35+
}
2636

2737
private:
2838
[[nodiscard]] bool isCollidingWithSelf(glm::vec2 targetGrid) const;
2939
[[nodiscard]] bool isCollidingWithWall(glm::vec2 targetGrid) const;
3040
void move();
41+
void updateScoreText();
3142

3243
// IController::IListener
3344
void onSetHeading(Heading heading) final;
@@ -44,6 +55,7 @@ class Player : public IController::IListener, public IDebugInspector, public kli
4455
std::unique_ptr<IController> m_controller{};
4556

4657
Snake m_snake{};
58+
le::drawable::Text m_scoreText{};
4759

4860
Info m_info{};
4961

lib/include/chomper/runtimes/game.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
#pragma once
2+
#include "chomper/collectible.hpp"
23
#include "chomper/engine.hpp"
34
#include "chomper/player.hpp"
45
#include "chomper/runtime.hpp"
56
#include "chomper/world.hpp"
7+
#include "le2d/random.hpp"
8+
#include "le2d/resource/texture.hpp"
69
#include <klib/ptr.hpp>
710
#include <le2d/drawable/text.hpp>
811
#include <le2d/input/action.hpp>
912
#include <le2d/input/scoped_mapping.hpp>
13+
#include <unordered_set>
1014

1115
namespace chomper::runtime {
1216
// driven by Engine, owner (whether indirectly) of all game things.
@@ -28,6 +32,11 @@ class Game : public IRuntime, public klib::Pinned {
2832

2933
void bindActions();
3034
void createPlayer();
35+
void createCollectibleTexture();
36+
37+
void findEmptyTiles();
38+
void spawnCollectibles();
39+
void collideCollectibles();
3140

3241
void onGoBack();
3342

@@ -38,8 +47,15 @@ class Game : public IRuntime, public klib::Pinned {
3847
le::input::ScopedActionMapping m_mapping;
3948
Actions m_actions{};
4049

50+
le::Random m_random{};
51+
std::unordered_set<int> m_occupied;
52+
4153
std::unique_ptr<Player> m_player{};
4254
std::unique_ptr<World> m_world{};
55+
std::vector<Collectible> m_collectibles{};
56+
klib::Ptr<le::ITexture const> m_collectibleTexture{};
57+
58+
std::vector<int> m_emptyTiles{};
4359

4460
le::drawable::Text m_countdownText{};
4561
kvf::Seconds m_countdown{3};

lib/include/chomper/world_size.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
#include <glm/vec2.hpp>
44

55
namespace chomper {
6-
constexpr auto worldSize_v = glm::vec2{15.f};
6+
constexpr auto worldSize_v = glm::vec2{16.f};
77
constexpr auto tileSize_v = viewport_v.world_size / worldSize_v;
88
} // namespace chomper

lib/include/chomper/world_space.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ constexpr auto worldToGrid(glm::vec2 worldPosition) {
1919
}
2020

2121
constexpr auto isOutOfBounds(glm::vec2 gridPoint) {
22-
return gridPoint.x <= 0 || gridPoint.y <= 0 || gridPoint.x > worldSize_v.x || gridPoint.y > worldSize_v.y;
22+
return gridPoint.x < 0 || gridPoint.y < 0 || gridPoint.x >= worldSize_v.x || gridPoint.y >= worldSize_v.y;
2323
}
2424
} // namespace chomper::worldSpace

lib/src/collectible.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include "chomper/collectible.hpp"
2+
#include "chomper/world_size.hpp"
3+
#include "le2d/renderer.hpp"
4+
5+
namespace chomper {
6+
Collectible::Collectible(le::ITexture const& texture, glm::vec2 position) {
7+
m_sprite.set_base_size(tileSize_v);
8+
m_sprite.set_texture(&texture);
9+
m_sprite.transform.position = position;
10+
}
11+
12+
void Collectible::draw(le::IRenderer& renderer) const {
13+
m_sprite.draw(renderer);
14+
}
15+
} // namespace chomper

lib/src/player.cpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "chomper/player.hpp"
22
#include "chomper/controllers/player_controller.hpp"
33
#include "chomper/engine.hpp"
4+
#include "chomper/world_size.hpp"
45
#include "chomper/world_space.hpp"
56
#include <algorithm>
67

@@ -13,6 +14,7 @@ constexpr auto headingToDir_v = klib::EnumArray<Heading, glm::vec2>{glm::vec2{1.
1314

1415
Player::Player(le::input::ScopedActionMapping& mapping, gsl::not_null<Engine const*> engine) : m_engine(engine) {
1516
createController(mapping);
17+
updateScoreText();
1618
}
1719

1820
void Player::tick(kvf::Seconds dt) {
@@ -30,8 +32,10 @@ void Player::tick(kvf::Seconds dt) {
3032
}
3133
}
3234

33-
Player::Info const& Player::getInfo() const {
34-
return m_info;
35+
void Player::grow() {
36+
m_info.score++;
37+
updateScoreText();
38+
m_shouldPop = false;
3539
}
3640

3741
bool Player::isCollidingWithSelf(glm::vec2 const targetGrid) const {
@@ -78,11 +82,22 @@ void Player::move() {
7882
m_snake.popTail();
7983
}
8084

81-
m_graceMove = false;
85+
m_shouldPop = true; // reset shouldPop
86+
m_graceMove = false; // reset graceMove
87+
}
88+
89+
void Player::updateScoreText() {
90+
static constexpr auto textParams_v = le::drawable::Text::Params{
91+
.height = le::TextHeight{16},
92+
};
93+
94+
m_scoreText.set_string(m_engine->getResources().getMainFont(), std::format("Score: {}", m_info.score), textParams_v);
95+
m_scoreText.transform.position = worldSpace::gridToWorld({0, worldSize_v.y - 1}) + glm::vec2{m_scoreText.get_size().x / 2, 0};
8296
}
8397

8498
void Player::draw(le::IRenderer& renderer) const {
8599
m_snake.draw(renderer);
100+
m_scoreText.draw(renderer);
86101
}
87102

88103
void Player::debugInspect() {
@@ -102,12 +117,12 @@ void Player::createController(le::input::ScopedActionMapping& mapping) {
102117

103118
void Player::onSetHeading(Heading const heading) {
104119
auto lastHeading = m_headingQueue.empty() ? m_heading : m_headingQueue.back();
105-
if (heading == m_heading || heading == oppositeHeading_v[lastHeading]) {
120+
if (heading == lastHeading || heading == oppositeHeading_v[lastHeading]) {
106121
return;
107122
}
108123

109124
if (m_headingQueue.size() < 3) {
110-
m_log.debug("changing heading from {} to {}", headingName_v[m_heading], headingName_v[heading]);
125+
m_log.debug("changing heading from {} to {}", headingName_v[lastHeading], headingName_v[heading]);
111126
m_headingQueue.push_back(heading);
112127
}
113128
}

lib/src/runtimes/game.cpp

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
11
#include "chomper/runtimes/game.hpp"
2+
#include "chomper/collectible.hpp"
23
#include "chomper/im_util.hpp"
34
#include "chomper/runtimes/entrypoint.hpp"
4-
#include <array>
5+
#include "chomper/world_size.hpp"
6+
#include "chomper/world_space.hpp"
7+
#include <le2d/random.hpp>
8+
#include <algorithm>
59

610
namespace chomper::runtime {
711
namespace {
812
constexpr auto countdownParams_v = le::drawable::Text::Params{
913
.height = le::TextHeight{60},
1014
};
11-
}
15+
constexpr auto collectibleAmount_v = 10;
16+
} // namespace
1217
using ActionValue = le::input::action::Value;
1318

1419
Game::Game(gsl::not_null<Engine*> engine) : m_engine(engine), m_mapping(&engine->getInputRouter()) {
1520
createPlayer();
1621
m_world = std::make_unique<World>(m_engine);
1722

23+
createCollectibleTexture();
24+
25+
spawnCollectibles();
26+
1827
m_countdownText.set_string(engine->getResources().getMainFont(), "3", countdownParams_v);
1928
}
2029

@@ -33,6 +42,8 @@ void Game::tick(kvf::Seconds const dt) {
3342

3443
m_player->tick(dt);
3544

45+
collideCollectibles();
46+
3647
// On death
3748
if (!m_player->getInfo().alive) {
3849
m_engine->setNextRuntime<runtime::Entrypoint>();
@@ -42,6 +53,9 @@ void Game::tick(kvf::Seconds const dt) {
4253
void Game::render(le::IRenderer& renderer) const {
4354
m_world->draw(renderer);
4455
m_player->draw(renderer);
56+
for (auto const& collectible : m_collectibles) {
57+
collectible.draw(renderer);
58+
}
4559
if (m_countdown.count() > 0) {
4660
m_countdownText.draw(renderer);
4761
}
@@ -75,6 +89,69 @@ void Game::createPlayer() {
7589
m_player = std::make_unique<Player>(m_mapping, m_engine);
7690
}
7791

92+
void Game::createCollectibleTexture() {
93+
m_collectibleTexture = m_engine->getResources().load<le::ITexture>("images/apple.png");
94+
}
95+
96+
void Game::findEmptyTiles() {
97+
m_emptyTiles.clear();
98+
m_emptyTiles.reserve(static_cast<int>(worldSize_v.x * worldSize_v.y));
99+
for (auto i = 0; i < static_cast<int>(worldSize_v.x * worldSize_v.y); i++) {
100+
m_emptyTiles.push_back(i);
101+
}
102+
103+
auto const removeTile = [this](int tile) {
104+
auto it = std::ranges::find(m_emptyTiles, tile);
105+
if (it != m_emptyTiles.end()) {
106+
*it = m_emptyTiles.back();
107+
m_emptyTiles.pop_back();
108+
}
109+
};
110+
111+
for (auto const& seg : m_player->getSegments()) {
112+
auto p = worldSpace::worldToGrid(seg.transform.position);
113+
removeTile(static_cast<int>((p.y * worldSize_v.x) + p.x));
114+
}
115+
116+
for (auto const& c : m_collectibles) {
117+
auto p = c.getGridPosition();
118+
removeTile(static_cast<int>((p.y * worldSize_v.x) + p.x));
119+
}
120+
}
121+
122+
void Game::spawnCollectibles() {
123+
findEmptyTiles();
124+
125+
for (auto i = m_collectibles.size(); i < collectibleAmount_v; i++) {
126+
if (m_emptyTiles.empty()) {
127+
return;
128+
}
129+
// find a random tile
130+
auto random = m_random.next_index(m_emptyTiles.size());
131+
auto tile = m_emptyTiles[random];
132+
// remove said tile from the vector
133+
std::erase_if(m_emptyTiles, [&](auto const& v) {
134+
return v == m_emptyTiles[random];
135+
});
136+
// place the collectible on the tile
137+
auto width = static_cast<int>(worldSize_v.x);
138+
m_collectibles.emplace_back(*m_collectibleTexture, worldSpace::gridToWorld({tile % width, tile / width}));
139+
}
140+
}
141+
142+
void Game::collideCollectibles() {
143+
auto it = std::ranges::find_if(m_collectibles, [&](auto const& collectible) {
144+
return collectible.getGridPosition() == worldSpace::worldToGrid(m_player->getSegments().back().transform.position);
145+
});
146+
if (it == m_collectibles.end()) {
147+
return;
148+
}
149+
150+
m_collectibles.erase(it);
151+
m_player->grow();
152+
spawnCollectibles();
153+
}
154+
78155
void Game::onGoBack() {
79156
m_log.debug("execute 'go back' action here");
80157
}

lib/src/snake.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include "chomper/snake.hpp"
2+
#include "chomper/world_size.hpp"
23
#include "chomper/world_space.hpp"
4+
#include "le2d/render_instance.hpp"
35
#include <imgui.h>
46
#include <klib/fixed_string.hpp>
57

@@ -10,6 +12,10 @@ constexpr auto headingToDir_v = klib::EnumArray<Heading, glm::vec2>{glm::vec2{1.
1012
} // namespace
1113

1214
Snake::Snake() {
15+
le::RenderInstance instance{};
16+
instance.tint = snakeBodyColor_v;
17+
instance.transform.position = worldSpace::gridToWorld({0, worldSize_v.y / 2});
18+
m_instances.push_back(instance);
1319
while (m_instances.size() < m_baseSize) {
1420
grow({});
1521
}

0 commit comments

Comments
 (0)