namespace Snake.Core; public sealed class SnakeGame { public SnakeGame(int width, int height, Random? random = null) { _random = random ?? Random.Shared; Board = new Board(width, height); var start = new Position(width / 2, height / 2); Snake = new Snake(start, length: 3, Direction.Right); Food = new Food(Board, Snake.Segments, _random); } private readonly Random _random; public Board Board { get; } public Snake Snake { get; } public Food Food { get; } public int Score { get; private set; } public bool IsGameOver { get; private set; } public const int PointsPerLevel = 5; public int Level => 1 + Score / PointsPerLevel; public static int GetTickIntervalMs(int score, int baseIntervalMs = 120, int minIntervalMs = 50) => Math.Max(minIntervalMs, baseIntervalMs - (1 + score / PointsPerLevel - 1) * 10); public void SetDirection(Direction direction) => Snake.SetDirection(direction); public GameTickResult Tick() { if (IsGameOver) return GameTickResult.GameOver; var newHead = Snake.PeekNextHead(); var ateFood = newHead == Food.Position; if (!Board.IsWithinBounds(newHead) || Snake.Occupies(newHead, excludeTail: !ateFood)) { IsGameOver = true; return GameTickResult.GameOver; } Snake.Move(grow: ateFood); if (ateFood) { Score++; if (Snake.Segments.Count >= Board.Width * Board.Height) { IsGameOver = true; return GameTickResult.AteFood; } Food.Respawn(Snake.Segments); return GameTickResult.AteFood; } return GameTickResult.Moved; } }