blob: ad4c90810c87a68d5c8dea8ba77f53fb5954f5e0 [file] [log] [blame]
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "snake/snake.h"
#include "pw_assert/check.h"
namespace snake {
namespace {
constexpr size_t CyclicDecrement(size_t value, size_t max_value) {
return value >= 1 ? value - 1 : max_value;
}
constexpr size_t CyclicIncrement(size_t value, size_t max_value) {
return (value + 1) % (max_value + 1);
}
constexpr bool DoBlocksCollide(const Block& block, const Block& other_block) {
return block.x == other_block.x && block.y == other_block.y;
}
} // namespace
Snake::Snake(int32_t max_x,
int32_t max_y,
size_t initial_size,
Direction direction)
: max_x_(max_x), max_y_(max_y), direction_(direction) {
// Initial size cannot be wider than screen width.
PW_CHECK(static_cast<int>(initial_size) < max_x_);
PW_CHECK(initial_size > 0);
const int32_t y = max_y_ / 2;
int32_t x = (max_x_ - static_cast<int>(initial_size)) / 2;
for (size_t i = 0; i < initial_size; ++i) {
Block block{.x = x, .y = y};
blocks_.push_back(std::move(block));
x = CyclicIncrement(x, max_x_);
}
}
Snake::~Snake() {}
void Snake::ChangeDirection(Direction new_direction) {
if (direction_ == new_direction ||
(direction_ == Direction::kUp && new_direction == Direction::kDown) ||
(direction_ == Direction::kDown && new_direction == Direction::kUp) ||
(direction_ == Direction::kLeft && new_direction == Direction::kRight) ||
(direction_ == Direction::kRight && new_direction == Direction::kLeft)) {
return;
}
direction_ = new_direction;
}
void Snake::Advance(const Block& fruit, bool& ate_fruit, bool& crashed) {
crashed = false;
ate_fruit = false;
Block next_head{.x = AdvanceX(blocks_.front().x),
.y = AdvanceY(blocks_.front().y)};
if (CollidesWithBody(next_head)) {
// Snake crashed!
crashed = true;
return;
}
ate_fruit = DoBlocksCollide(fruit, next_head);
// Advance head.
blocks_.push_front(next_head);
// Advance tail if the object was not eaten or the snake is its max size.
if (!ate_fruit || blocks_.full()) {
blocks_.pop_back();
}
}
int32_t Snake::AdvanceX(int32_t x) const {
switch (direction_) {
case kLeft:
return CyclicDecrement(x, max_x_);
case kRight:
return CyclicIncrement(x, max_x_);
default:
return x;
};
}
int32_t Snake::AdvanceY(int32_t y) const {
switch (direction_) {
case kUp:
return CyclicDecrement(y, max_y_);
case kDown:
return CyclicIncrement(y, max_y_);
default:
return y;
};
}
bool Snake::CollidesWithBody(const Block& other_block) {
for (const auto& block : blocks_) {
if (DoBlocksCollide(block, other_block)) {
return true;
}
}
return false;
}
void Snake::Draw(const pw::Function<void(const Block&)>& draw_callback) {
for (const auto& block : blocks_) {
// Draw block given x and y;
draw_callback(block);
}
}
} // namespace snake