Files
roro9stack/lib/games/src/snake.cpp
T
twislaandClaude Opus 5.5 68d50b320f Games: Pong, Snake and Tetris as seeded state machines, and a tune
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
2026-10-13 22:59:05 +02:00

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