#include "snake.h" #include namespace roro::games { namespace { bool opposite(Snake::Dir a, Snake::Dir b) { return (static_cast(a) + 2) % 4 == static_cast(b); } } // namespace void Snake::start() { body_.clear(); taken_.fill(false); for (int i = 0; i < 3; i++) { // three cells in the middle, heading right Cell c{static_cast(kCols / 2 - i), static_cast(kRows / 2)}; body_.push_back(c); mark(c, true); } dir_ = Dir::Right; turnCount_ = 0; score_ = 0; sinceMs_ = 0; alive_ = true; placeFood(); } uint32_t Snake::intervalMs() const { uint32_t off = static_cast(score_ / kFoodsAStep) * kFasterMs; return off >= kSlowestMs - kFastestMs ? kFastestMs : kSlowestMs - off; } void Snake::turn(Dir dir) { Dir last = turnCount_ ? turns_[turnCount_ - 1] : dir_; if (dir == last || opposite(dir, last) || turnCount_ >= 2) return; turns_[turnCount_++] = dir; } // On a free cell, each as likely as another. void Snake::placeFood() { int free = kCols * kRows - static_cast(body_.size()); if (free <= 0) return; int pick = rng_.below(free); for (int i = 0; i < kCols * kRows; i++) { if (taken_[static_cast(i)]) continue; if (pick-- == 0) { food_ = {static_cast(i % kCols), static_cast(i / kCols)}; return; } } } uint8_t Snake::step(uint32_t ms) { uint8_t events = 0; if (!alive_) return 0; sinceMs_ += std::min(ms, 500); while (alive_ && sinceMs_ >= intervalMs()) { sinceMs_ -= intervalMs(); events |= move(); } return events; } uint8_t Snake::move() { if (turnCount_) { dir_ = turns_[0]; turns_[0] = turns_[1]; turnCount_--; } static const int8_t dc[4] = {0, 1, 0, -1}, dr[4] = {-1, 0, 1, 0}; Cell head{static_cast(body_.front().col + dc[static_cast(dir_)]), static_cast(body_.front().row + dr[static_cast(dir_)])}; bool eats = head == food_; // The tail moves on as the head arrives: the head may go where the tail was. A snake that // dies stays as it was, whole. bool ontoTail = !eats && head == body_.back(); if (head.col < 0 || head.col >= kCols || head.row < 0 || head.row >= kRows || (taken(head) && !ontoTail)) { alive_ = false; return Died; } if (!eats) { mark(body_.back(), false); body_.pop_back(); } body_.push_front(head); mark(head, true); if (!eats) return Moved; score_++; placeFood(); return Moved | Ate; } } // namespace roro::games