using System.Diagnostics; namespace laba3.Subprograms { /// /// TUI classic game "Snake"; /// The player controls the snake. /// The player has to eat apples to increase the length of the snake /// and try not to crash into the wall or themselves; /// internal class SnakeGame { int size_x; int size_y; int score; int size_xy; Level difficulty; Tiles[,] world; Snake snake; Random rnd; private bool gameover; /// /// Game world constructor; /// /// Game speed /// Game world size dim x /// Game world size dim y public SnakeGame(Level difficulty, int size_x, int size_y) { this.size_x = size_x; this.size_y = size_y; this.difficulty = difficulty; world = new Tiles[size_x, size_y]; rnd = new Random(); score = 0; size_xy = size_x * size_y; } /// /// Start game; /// Console will be clean!; /// public void start() { int msPerTick = difficulty switch { Level.Low => 500, Level.Medium => 250, Level.High => 100, _ => 500, }; gameover = false; Console.Clear(); Console.CursorVisible = false; snake = new Snake(new Point(size_x / 2, size_y / 2, this), 1, Direction.Right, this); GenerateFood(); DrawBackground(); DrawWorld(); Stopwatch tickTimer = new Stopwatch(); while (!gameover) { tickTimer.Restart(); if (Console.KeyAvailable) { ConsoleKeyInfo key = Console.ReadKey(false); snake.HandleKey(key.Key); while (Console.KeyAvailable) Console.ReadKey(false); } snake.Move(); long delta = tickTimer.ElapsedMilliseconds; Console.SetCursorPosition(1, 0); if (delta > msPerTick) Console.ForegroundColor = ConsoleColor.Red; Console.Write(delta); Thread.Sleep((int)Math.Max(0, msPerTick - delta)); } onStop(); } /// /// Stop game; /// public void stop() { gameover = true; } private void onStop() { string gameOverTitle = @" ### ## # ## #### # # # # # # ## # ## # # # # # ### # # # # ### # # # # # # # # # # # # # # ### ## # # # ## ## # ## # "; Console.Clear(); printArt(gameOverTitle, (Console.WindowWidth - 36) / 2, 4, ConsoleColor.DarkYellow, ConsoleColor.DarkGray); Console.ResetColor(); string scoreString = $"Score: {score}"; Console.SetCursorPosition((Console.WindowWidth - scoreString.Length) / 2, 11); Console.Write(scoreString); Console.SetCursorPosition((Console.WindowWidth - "Press Enter to continue".Length) / 2, 14); Console.CursorVisible = true; } private void printArt(string art, int x, int y, ConsoleColor primaryColor, ConsoleColor shadowColor) { int row = 0; Console.SetCursorPosition(x, y); char shadow = ' '; foreach (char ch in art) { if (ch == '\n') { if (shadow == '#') { Console.BackgroundColor = shadowColor; Console.Write(' '); } Console.SetCursorPosition(x, y++ + row); Thread.Sleep(25); shadow = ch; } else if (ch == ' ') { if (shadow == '#') { Console.BackgroundColor = shadowColor; Console.Write(' '); } else Console.CursorLeft++; shadow = ch; } else if (ch == '#') { Console.BackgroundColor = primaryColor; Console.Write(' '); shadow = ch; } } } private void addScore() { score += difficulty switch { Level.Low => 10, Level.Medium => 50, Level.High => 100, _ => 1, }; } /// /// Generate food in random place; /// public void GenerateFood() { int x; int y; do { x = rnd.Next(size_x); y = rnd.Next(size_y); } while (world[x, y] != Tiles.Void); world[x, y] = Tiles.Food; DrawTile(x, y); } private void DrawBackground() { for (int x = 1; x < (size_x + 1) * 2 + 1; x++) { Console.SetCursorPosition(x, 1); Console.Write('#'); Console.SetCursorPosition(x, size_y + 2); Console.Write('#'); } for (int y = 1; y < size_y + 2; y++) { Console.SetCursorPosition(1, y); Console.Write('#'); Console.SetCursorPosition(size_x * 2 + 1, y); Console.Write('#'); } } private void DrawTile(int x, int y) { char symbol; switch (world[x, y]) { case Tiles.Void: symbol = ' '; break; case Tiles.Snake: symbol = '*'; Console.ForegroundColor = ConsoleColor.DarkRed; break; case Tiles.Food: symbol = '@'; Console.ForegroundColor = ConsoleColor.Red; break; default: symbol = '?'; break; }; //Console.BackgroundColor = ConsoleColor.DarkGreen; Console.SetCursorPosition((x + 1) * 2, y + 2); Console.Write(symbol); Console.BackgroundColor = ConsoleColor.Black; Console.ForegroundColor = ConsoleColor.White; } private void DrawWorld() { for (int x = 0; x < size_x; x++) { for (int y = 0; y < size_y; y++) { DrawTile(x, y); } } } private enum Tiles { Void, Snake, Food } public enum Level { Low, Medium, High } private class Snake { private SnakeGame game; private List body; private Direction direction; public Snake(Point tail, int length, Direction initialDirection, SnakeGame game) { this.game = game; body = new List(); direction = initialDirection; for (int i = 0; i < length; i++) { Point p = new Point(tail.X, tail.Y, game); body.Add(p); } } public void Move() { Point head = GetNextPoint(); Point tail = body.First(); if (head.X < 0 || head.Y < 0 || head.X >= game.size_x || head.Y >= game.size_y) { game.stop(); return; } if (game.world[head.X, head.Y] == Tiles.Snake) { game.stop(); return; } body.Add(head); if (game.world[head.X, head.Y] != Tiles.Food) { body.Remove(tail); tail.UpdateWorld(Tiles.Void); } else { game.addScore(); game.GenerateFood(); if (body.Count >= game.size_xy) game.stop(); } head.UpdateWorld(Tiles.Snake); } public Point GetNextPoint() { Point head = body.Last(); Point nextPoint = new Point(head.X, head.Y, game); switch (direction) { case Direction.Right: nextPoint.X++; break; case Direction.Left: nextPoint.X--; break; case Direction.Up: nextPoint.Y--; break; case Direction.Down: nextPoint.Y++; break; } return nextPoint; } public void HandleKey(ConsoleKey key) { switch (key) { case ConsoleKey.LeftArrow: if (direction != Direction.Right) direction = Direction.Left; break; case ConsoleKey.RightArrow: if (direction != Direction.Left) direction = Direction.Right; break; case ConsoleKey.UpArrow: if (direction != Direction.Down) direction = Direction.Up; break; case ConsoleKey.DownArrow: if (direction != Direction.Up) direction = Direction.Down; break; } } } enum Direction { Left, Right, Up, Down } private class Point { private SnakeGame game { get; set; } public int X { get; set; } public int Y { get; set; } public Point(int x, int y, SnakeGame game) { this.game = game; X = x; Y = y; } public void UpdateWorld(Tiles tile) { game.world[X, Y] = tile; game.DrawTile(X, Y); } } } }