Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion lib/include/chomper/player.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@
#include <le2d/renderer.hpp>

namespace chomper {
class Engine;

class Player : public IController::IListener, public IDebugInspector, public klib::Pinned {
public:
explicit Player(le::input::ScopedActionMapping& mapping);
explicit Player(le::input::ScopedActionMapping& mapping, gsl::not_null<Engine*> engine);

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

private:
bool selfCollides() const;
void move();

// IController::IListener
void onSetHeading(Heading heading) final;

Expand All @@ -27,8 +32,18 @@ class Player : public IController::IListener, public IDebugInspector, public kli

klib::TypedLogger<Player> m_log{};

gsl::not_null<Engine*> m_engine;

std::unique_ptr<IController> m_controller{};

Snake m_snake{};

Heading m_heading{};
std::vector<Heading> m_headingQueue{};

kvf::Seconds m_moveTimer{};
// bool to decide wether to remove the tail, turn false if the snake has eaten
bool m_shouldPop = true;
bool m_graceMove{};
};
} // namespace chomper
19 changes: 7 additions & 12 deletions lib/include/chomper/snake.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,22 @@ namespace chomper {
class Snake {
public:
explicit Snake();
void tick(kvf::Seconds dt);
void draw(le::IRenderer& renderer) const;

void setHeading(Heading heading);
void debugInspect();

private:
void grow(Heading heading);
void popTail();
void grow();

klib::TypedLogger<Snake> m_log{};
[[nodiscard]] std::span<le::RenderInstance const> getSegments() const {
return m_quads.instances;
}

Heading m_heading{};
std::vector<Heading> m_headingQueue{};
private:
void syncQuads();

std::deque<le::RenderInstance> m_instances{};
le::drawable::InstancedQuad m_quads{tileSize_v};

kvf::Seconds m_moveTimer{};
// bool to decide wether to remove the tail, turn false if the snake has eaten
bool m_shouldPop = true;
size_t m_baseSize{6};
};

} // namespace chomper
2 changes: 1 addition & 1 deletion lib/include/chomper/world_space.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace chomper::worldSpace {
namespace {
constexpr auto halfGridSize = worldSize_v * 0.5f;
auto const halfGridSize = glm::ceil(worldSize_v * 0.5f);
// only add half the tilesize when that axis is even
constexpr auto tileOffset =
glm::vec2{(static_cast<int>(worldSize_v.x) % 2 == 0) ? tileSize_v.x * 0.5f : 0.0f, (static_cast<int>(worldSize_v.y) % 2 == 0) ? tileSize_v.y * 0.5f : 0.0f};
Expand Down
76 changes: 72 additions & 4 deletions lib/src/player.cpp
Original file line number Diff line number Diff line change
@@ -1,30 +1,98 @@
#include "chomper/player.hpp"
#include "chomper/controllers/player_controller.hpp"
#include "chomper/engine.hpp"
#include "chomper/runtimes/entrypoint.hpp"
#include "chomper/world_space.hpp"
#include <algorithm>

namespace chomper {
Player::Player(le::input::ScopedActionMapping& mapping) {
namespace {
constexpr auto moveSpeed_v = kvf::Seconds{0.135f};
constexpr auto oppositeHeading_v = klib::EnumArray<Heading, Heading>{Heading::West, Heading::South, Heading::East, Heading::North};
constexpr auto headingToDir_v = klib::EnumArray<Heading, glm::vec2>{glm::vec2{1.f, 0.f}, glm::vec2{0.f, 1.f}, glm::vec2{-1.f, 0.f}, glm::vec2{0.f, -1.f}};
} // namespace

Player::Player(le::input::ScopedActionMapping& mapping, gsl::not_null<Engine*> engine) : m_engine(engine) {
createController(mapping);
}

void Player::tick(kvf::Seconds dt) {
m_controller->tick(dt);
m_snake.tick(dt);

m_moveTimer += dt;

if (m_moveTimer >= moveSpeed_v) {
m_moveTimer = {};
move();
}
}

bool Player::selfCollides() const {
auto targetGrid = worldSpace::worldToGrid(m_snake.getSegments().back().transform.position) + headingToDir_v[m_heading];
return std::ranges::any_of(m_snake.getSegments(), [targetGrid](le::RenderInstance const& s) {
return worldSpace::worldToGrid(s.transform.position) == targetGrid;
});
}

void Player::move() {
// no body, no movement
if (m_snake.getSegments().empty()) {
return;
}

if (!m_headingQueue.empty()) {
m_heading = m_headingQueue.front();
m_headingQueue.erase(m_headingQueue.begin());
}

if (selfCollides()) {
if (m_graceMove) {
m_engine->setNextRuntime<runtime::Entrypoint>();
} else {
m_graceMove = true;
}

return; // return so you don't move when you collide
}

m_snake.grow(m_heading);

if (m_shouldPop) {
m_snake.popTail();
}

m_graceMove = false;
}

void Player::draw(le::IRenderer& renderer) const {
m_snake.draw(renderer);
}

void Player::debugInspect() {
m_snake.debugInspect();
ImGui::TextUnformatted(klib::FixedString{"Heading: {}", headingName_v[m_heading]}.c_str());

if (ImGui::TreeNode("heading queue")) {
for (auto const heading : m_headingQueue) {
ImGui::TextUnformatted(headingName_v[heading].data());
}
ImGui::TreePop();
}
}

void Player::createController(le::input::ScopedActionMapping& mapping) {
m_controller = std::make_unique<PlayerController>(this, mapping);
}

void Player::onSetHeading(Heading const heading) {
m_snake.setHeading(heading);
auto lastHeading = m_headingQueue.empty() ? m_heading : m_headingQueue.back();
if (heading == m_heading || 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_headingQueue.push_back(heading);
}
}

} // namespace chomper
2 changes: 1 addition & 1 deletion lib/src/runtimes/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ void Game::createPlayer() {
// rebind game actions.
bindActions();
// create the player, passing a reference of the logger and a reference of the input mapping to create its PlayerController.
m_player = std::make_unique<Player>(m_mapping);
m_player = std::make_unique<Player>(m_mapping, m_engine);
}

void Game::onGoBack() {
Expand Down
73 changes: 15 additions & 58 deletions lib/src/snake.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,88 +5,45 @@

namespace chomper {
namespace {
constexpr auto moveSpeed_v = kvf::Seconds{0.135f};
constexpr auto snakeBodyColor_v = kvf::Color(glm::vec4{0.f, 0.6f, 1.f, 1.f});
constexpr auto oppositeHeading_v = klib::EnumArray<Heading, Heading>{Heading::West, Heading::South, Heading::East, Heading::North};
constexpr auto headingToDir_v = klib::EnumArray<Heading, glm::vec2>{glm::vec2{1.f, 0.f}, glm::vec2{0.f, 1.f}, glm::vec2{-1.f, 0.f}, glm::vec2{0.f, -1.f}};
} // namespace

Snake::Snake() {
// initialize snake body
grow();
}

void Snake::tick(kvf::Seconds dt) {
m_moveTimer += dt;

if (m_moveTimer >= moveSpeed_v) {
m_moveTimer = {};

// no body, no movement
if (m_instances.empty()) {
return;
}

if (!m_headingQueue.empty()) {
m_heading = m_headingQueue.front();
m_headingQueue.erase(m_headingQueue.begin());
}

grow();

if (m_shouldPop) {
popTail();
}
while (m_instances.size() < m_baseSize) {
grow({});
}

m_quads.instances.clear();
m_quads.instances.reserve(m_instances.size());
std::ranges::copy(m_instances, std::back_inserter(m_quads.instances));
}

void Snake::draw(le::IRenderer& renderer) const {
m_quads.draw(renderer);
}

void Snake::popTail() {
if (!m_instances.empty()) {
m_instances.erase(m_instances.begin());
}
}

void Snake::grow() {
void Snake::grow(Heading heading) {
le::RenderInstance instance{};
instance.tint = snakeBodyColor_v;
// no reason to move on initialization
if (!m_instances.empty()) {
instance.transform.position = worldSpace::gridToWorld(worldSpace::worldToGrid(m_instances.back().transform.position) + headingToDir_v[m_heading]);
instance.transform.position = worldSpace::gridToWorld(worldSpace::worldToGrid(m_instances.back().transform.position) + headingToDir_v[heading]);
}

m_instances.push_back(instance);

syncQuads();
}

void Snake::setHeading(Heading heading) {
auto lastHeading = m_headingQueue.empty() ? m_heading : m_headingQueue.back();
if (heading == m_heading || heading == oppositeHeading_v[lastHeading]) {
void Snake::popTail() {
if (m_instances.empty()) {
return;
}

if (m_headingQueue.size() < 3) {
m_log.debug("changing heading from {} to {}", headingName_v[m_heading], headingName_v[heading]);
m_headingQueue.push_back(heading);
}
m_instances.erase(m_instances.begin());
syncQuads();
}

void Snake::debugInspect() {
ImGui::TextUnformatted("snake");
ImGui::Separator();
ImGui::TextUnformatted(klib::FixedString{"Heading: {}", headingName_v[m_heading]}.c_str());

if (ImGui::TreeNode("heading queue")) {
for (auto const heading : m_headingQueue) {
ImGui::TextUnformatted(headingName_v[heading].data());
}
ImGui::TreePop();
}
void Snake::syncQuads() {
m_quads.instances.clear();
m_quads.instances.reserve(m_instances.size());
std::ranges::copy(m_instances, std::back_inserter(m_quads.instances));
}

} // namespace chomper