using System.Diagnostics; using System.Text; if (Console.IsInputRedirected || Console.IsOutputRedirected) { Console.WriteLine("Snake Lab wymaga interaktywnej konsoli."); return; } var game = new SnakeGame(width: 36, height: 18); game.Run(); internal enum Direction { Up, Right, Down, Left } internal readonly record struct Position(int X, int Y) { public static Position operator +(Position left, Position right) => new(left.X + right.X, left.Y + right.Y); } internal sealed class SnakeGame { private readonly int _width; private readonly int _height; private readonly Random _random = new(); private readonly LinkedList _snake = new(); private readonly HashSet _occupied = new(); private Direction _direction = Direction.Right; private Direction _nextDirection = Direction.Right; private Position _food; private bool _running = true; private int _score; private int _tickMilliseconds = 115; public SnakeGame(int width, int height) { if (width < 16 || height < 10) throw new ArgumentOutOfRangeException(nameof(width), "Plansza jest zbyt mała."); _width = width; _height = height; var start = new Position(width / 2, height / 2); AddHead(start); AddTail(new Position(start.X - 1, start.Y)); AddTail(new Position(start.X - 2, start.Y)); SpawnFood(); } public void Run() { Console.CursorVisible = false; Console.Clear(); try { while (_running) { var frame = Stopwatch.StartNew(); ReadInput(); Update(); Render(); var remaining = _tickMilliseconds - (int)frame.ElapsedMilliseconds; if (remaining > 0) Thread.Sleep(remaining); } Console.SetCursorPosition(0, _height + 4); Console.WriteLine($"Koniec gry. Wynik: {_score}. Naciśnij dowolny klawisz."); Console.ReadKey(intercept: true); } finally { Console.CursorVisible = true; Console.ResetColor(); } } private void ReadInput() { while (Console.KeyAvailable) { var key = Console.ReadKey(intercept: true).Key; if (key == ConsoleKey.Escape) { _running = false; return; } var candidate = key switch { ConsoleKey.UpArrow or ConsoleKey.W => Direction.Up, ConsoleKey.RightArrow or ConsoleKey.D => Direction.Right, ConsoleKey.DownArrow or ConsoleKey.S => Direction.Down, ConsoleKey.LeftArrow or ConsoleKey.A => Direction.Left, _ => _nextDirection }; if (!IsOpposite(_direction, candidate)) _nextDirection = candidate; } } private void Update() { if (!_running) return; _direction = _nextDirection; var head = _snake.First!.Value; var next = head + Delta(_direction); var grows = next == _food; var tail = _snake.Last!.Value; var hitsWall = next.X <= 0 || next.X >= _width - 1 || next.Y <= 0 || next.Y >= _height - 1; var hitsBody = _occupied.Contains(next) && (grows || next != tail); if (hitsWall || hitsBody) { _running = false; return; } AddHead(next); if (grows) { _score += 10; _tickMilliseconds = Math.Max(55, _tickMilliseconds - 3); SpawnFood(); return; } var removedTail = _snake.Last!.Value; _snake.RemoveLast(); _occupied.Remove(removedTail); } private void Render() { var head = _snake.First!.Value; var frame = new StringBuilder((_width + 1) * (_height + 3)); for (var y = 0; y < _height; y++) { for (var x = 0; x < _width; x++) { var position = new Position(x, y); var symbol = x == 0 || x == _width - 1 || y == 0 || y == _height - 1 ? '#' : position == head ? 'O' : position == _food ? '*' : _occupied.Contains(position) ? 'o' : ' '; frame.Append(symbol); } frame.AppendLine(); } frame.Append("Wynik: ").Append(_score) .Append(" Sterowanie: strzałki / WASD Wyjście: Esc") .AppendLine(); Console.SetCursorPosition(0, 0); Console.Write(frame); } private void SpawnFood() { do { _food = new Position( _random.Next(1, _width - 1), _random.Next(1, _height - 1)); } while (_occupied.Contains(_food)); } private void AddHead(Position position) { _snake.AddFirst(position); _occupied.Add(position); } private void AddTail(Position position) { _snake.AddLast(position); _occupied.Add(position); } private static Position Delta(Direction direction) => direction switch { Direction.Up => new Position(0, -1), Direction.Right => new Position(1, 0), Direction.Down => new Position(0, 1), Direction.Left => new Position(-1, 0), _ => throw new ArgumentOutOfRangeException(nameof(direction)) }; private static bool IsOpposite(Direction current, Direction candidate) => (current, candidate) is (Direction.Up, Direction.Down) or (Direction.Down, Direction.Up) or (Direction.Left, Direction.Right) or (Direction.Right, Direction.Left); }