381 lines
11 KiB
C#
381 lines
11 KiB
C#
using System.Diagnostics;
|
|
|
|
namespace laba3.Subprograms
|
|
{
|
|
/// <summary>
|
|
/// 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;
|
|
/// </summary>
|
|
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;
|
|
/// <summary>
|
|
/// Game world constructor;
|
|
/// </summary>
|
|
/// <param name="difficulty">Game speed</param>
|
|
/// <param name="size_x">Game world size dim x</param>
|
|
/// <param name="size_y">Game world size dim y</param>
|
|
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;
|
|
}
|
|
/// <summary>
|
|
/// Start game;
|
|
/// Console will be clean!;
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop game;
|
|
/// </summary>
|
|
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,
|
|
};
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Generate food in random place;
|
|
/// </summary>
|
|
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<Point> body;
|
|
private Direction direction;
|
|
|
|
public Snake(Point tail, int length, Direction initialDirection, SnakeGame game)
|
|
{
|
|
this.game = game;
|
|
body = new List<Point>();
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|