namespace Snake.Core; public sealed class Snake { private readonly LinkedList _segments = new(); private Direction _pendingDirection; public Snake(Position start, int length, Direction direction) { if (length < 1) throw new ArgumentOutOfRangeException(nameof(length), "Length must be at least 1."); Direction = direction; _pendingDirection = direction; var offset = DirectionToOffset(direction); for (var i = 0; i < length; i++) { _segments.AddLast(new Position( start.X - offset.X * i, start.Y - offset.Y * i)); } } public Direction Direction { get; private set; } public Position Head => _segments.First!.Value; public IReadOnlyCollection Segments => _segments; public void SetDirection(Direction direction) { if (direction == _pendingDirection) return; if (AreOpposite(Direction, direction)) return; if (_pendingDirection != Direction && AreOpposite(_pendingDirection, direction)) return; _pendingDirection = direction; } public Position PeekNextHead() { var offset = DirectionToOffset(_pendingDirection); return new Position(Head.X + offset.X, Head.Y + offset.Y); } public Position Move(bool grow = false) { Direction = _pendingDirection; var newHead = PeekNextHead(); _segments.AddFirst(newHead); if (!grow) _segments.RemoveLast(); return newHead; } public bool Occupies(Position position, bool excludeTail = false) { if (excludeTail && _segments.Last is { } last && last.Value == position) return false; return _segments.Contains(position); } private static Position DirectionToOffset(Direction direction) => direction switch { Direction.Up => new Position(0, -1), Direction.Down => new Position(0, 1), Direction.Left => new Position(-1, 0), Direction.Right => new Position(1, 0), _ => throw new ArgumentOutOfRangeException(nameof(direction)) }; private static bool AreOpposite(Direction current, Direction next) => (current, next) switch { (Direction.Up, Direction.Down) => true, (Direction.Down, Direction.Up) => true, (Direction.Left, Direction.Right) => true, (Direction.Right, Direction.Left) => true, _ => false }; }