Public Access
The rules of the three Games (issue #101), with no screen and no keyboard in them: time and keys go in, the new state and what happened come out, and a seed decides the dice. With a player for a tune a note at a time, and Korobeiniki's notes. Host-tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0162FokPdvY2KsS4NBfwyWPk
93 lines
2.6 KiB
C++
93 lines
2.6 KiB
C++
#include "snake.h"
|
|
|
|
#include <algorithm>
|
|
|
|
namespace roro::games {
|
|
|
|
namespace {
|
|
bool opposite(Snake::Dir a, Snake::Dir b) { return (static_cast<int>(a) + 2) % 4 == static_cast<int>(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<int8_t>(kCols / 2 - i), static_cast<int8_t>(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<uint32_t>(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<int>(body_.size());
|
|
if (free <= 0) return;
|
|
int pick = rng_.below(free);
|
|
for (int i = 0; i < kCols * kRows; i++) {
|
|
if (taken_[static_cast<size_t>(i)]) continue;
|
|
if (pick-- == 0) {
|
|
food_ = {static_cast<int8_t>(i % kCols), static_cast<int8_t>(i / kCols)};
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8_t Snake::step(uint32_t ms) {
|
|
uint8_t events = 0;
|
|
if (!alive_) return 0;
|
|
sinceMs_ += std::min<uint32_t>(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<int8_t>(body_.front().col + dc[static_cast<int>(dir_)]),
|
|
static_cast<int8_t>(body_.front().row + dr[static_cast<int>(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
|