From 2366c9976edb3c917549d54a9a265f2d73d7b3ba Mon Sep 17 00:00:00 2001 From: diademoff Date: Tue, 18 May 2021 15:37:54 +0300 Subject: [PATCH] add sln --- .gitignore | 3 + LICENSE | 25 ++++ README.md | 43 ++++++ games-cli.sln | 18 +++ games-cli/Apple.cs | 63 ++++++++ games-cli/Display.cs | 170 ++++++++++++++++++++++ games-cli/Drawer/Border.cs | 57 ++++++++ games-cli/Drawer/DrawableChar.cs | 20 +++ games-cli/Drawer/Drawer.cs | 130 +++++++++++++++++ games-cli/Drawer/IDrawable.cs | 11 ++ games-cli/Drawer/IDrawableElement.cs | 11 ++ games-cli/Drawer/IInteractive.cs | 10 ++ games-cli/Drawer/Line.cs | 45 ++++++ games-cli/Drawer/MessageBox.cs | 61 ++++++++ games-cli/Drawer/Padding.cs | 22 +++ games-cli/Drawer/TextField.cs | 49 +++++++ games-cli/Program.cs | 67 +++++++++ games-cli/Progress.cs | 34 +++++ games-cli/Snake.cs | 207 +++++++++++++++++++++++++++ games-cli/games-cli.csproj | 9 ++ makefile | 24 ++++ 21 files changed, 1079 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 games-cli.sln create mode 100644 games-cli/Apple.cs create mode 100644 games-cli/Display.cs create mode 100644 games-cli/Drawer/Border.cs create mode 100644 games-cli/Drawer/DrawableChar.cs create mode 100644 games-cli/Drawer/Drawer.cs create mode 100644 games-cli/Drawer/IDrawable.cs create mode 100644 games-cli/Drawer/IDrawableElement.cs create mode 100644 games-cli/Drawer/IInteractive.cs create mode 100644 games-cli/Drawer/Line.cs create mode 100644 games-cli/Drawer/MessageBox.cs create mode 100644 games-cli/Drawer/Padding.cs create mode 100644 games-cli/Drawer/TextField.cs create mode 100644 games-cli/Program.cs create mode 100644 games-cli/Progress.cs create mode 100644 games-cli/Snake.cs create mode 100644 games-cli/games-cli.csproj create mode 100644 makefile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5137440 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode +bin +obj \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4c7b401 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2021, Dmitry +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e9661d --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# About +![GitHub repo size](https://img.shields.io/github/repo-size/diademoff/snake-cli) +![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/diademoff/snake-cli) +![GitHub](https://img.shields.io/github/license/diademoff/snake-cli) + +Play your favorite game in console. Install `dotnet-runtime` to run app. + +drawing + +Platforms: +* Windows +* Gnu Linux + +Features: +* Use `W`, `A`, `S`, `D` or arrows for moving +* Use `Escape` for pause +* Use `Space` for speedup + +[Download](https://github.com/diademoff/snake-cli/releases) + +# Build +Install `dotnet-sdk` to build. + +## Linux + +``` +git clone https://github.com/diademoff/snake-cli && cd snake-cli +``` + +Build and install +``` +make && sudo make install +``` + +Run +``` +snake-cli +``` + +## Windows +``` +dotnet publish -r win-x64 -p:PublishSingleFile=true --self-contained true +``` diff --git a/games-cli.sln b/games-cli.sln new file mode 100644 index 0000000..6f16465 --- /dev/null +++ b/games-cli.sln @@ -0,0 +1,18 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.6.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/games-cli/Apple.cs b/games-cli/Apple.cs new file mode 100644 index 0000000..119ecf7 --- /dev/null +++ b/games-cli/Apple.cs @@ -0,0 +1,63 @@ +/* +Яблоко, которое змейка должна съесть +*/ + +using System; +using System.Drawing; +using System.Linq; + +class Apple : IDrawable +{ + public Point Location { get; private set; } + public char Char { get; set; } = '☼'; + + // Создать яблоко в поле с случайным расположением + public Apple(AppleGen applegen, ref Random rnd) + { + this.Location = applegen.GetRandomPoint(ref rnd); + } +} + +struct AppleGen +{ + public int Field_width; + public int Field_height; + public Point[] Avoid; // Не генерировать в заданных точках + public Padding Padding; + + public AppleGen(int field_width, int field_height, Snake snake) + { + Field_width = field_width; + Field_height = field_height; + Padding = new Padding(0, 0, 0, 0); + + Avoid = new Point[snake.Blocks.Count]; + + for (int i = 0; i < snake.Blocks.Count; i++) + { + Avoid[i] = snake.Blocks[i].Location; // не генерировать на змейке + } + } + + public AppleGen(int field_width, int field_height, Snake snake, Padding p) : this(field_width, field_height, snake) + { + this.Padding = p; + } + + /* + Сгенерировать место в соответствии с заданными параметрами. + */ + public Point GetRandomPoint(ref Random rnd) + { + Point point; + + do + { + int x = rnd.Next(Padding.Left + 1, this.Field_width - Padding.Right - 1); + int y = rnd.Next(Padding.Top + 1, this.Field_height - Padding.Buttom - 1); + point = new Point(x, y); + } while (Avoid.ToList().Contains(point)); + + return point; + } +} \ No newline at end of file diff --git a/games-cli/Display.cs b/games-cli/Display.cs new file mode 100644 index 0000000..a9c0469 --- /dev/null +++ b/games-cli/Display.cs @@ -0,0 +1,170 @@ +/* +Управление содержимым экрана. +*/ + +using System; + +class Display : IInteractive +{ + /* + Находится ли голова змейки на яблоке. + */ + public bool IsAppleEaten => snake.IsEaten(apple); + + /* + столкнулась ли змейка с собой или с краем. + */ + public bool IsSnakeIntersect => snake.SelfIntersect() || snake.BorderIntersect(FIELD_SIZE_WIDTH, FIELD_SIZE_HEIGHT, p); + + /* + Ускорение + */ + bool speedUp = false; + /* + Высчитать задержку между кадрами исходя из текущего прогресса и + ускорения. + */ + public int FrameDelay => frameDelay(); + int frameDelay() + { + if (speedUp) + { + speedUp = false; + return progress.Delay / 4; + } + return progress.Delay; + } + int FIELD_SIZE_WIDTH; + int FIELD_SIZE_HEIGHT; + int INIT_DELAY = 100; + + Snake snake; + /* + Для отрисовки используется класс Drawer. Он предоставляет + возможность отрисовать IDrawable. + */ + Drawer drawer; + Apple apple; + /* + Прогресс игрока + */ + Progress progress; + Random rnd = new Random(); + + Padding p = new Padding(1, 1, 3, 5); + MessageBox info_paused; + + public Display() + { + FIELD_SIZE_WIDTH = Console.WindowWidth; + FIELD_SIZE_HEIGHT = Console.WindowHeight; + + drawer = new Drawer(FIELD_SIZE_WIDTH, FIELD_SIZE_HEIGHT); + progress = new Progress(INIT_DELAY, FIELD_SIZE_WIDTH, FIELD_SIZE_HEIGHT, 1); + + Console.Title = "snake-cli"; + Console.CursorVisible = false; + + info_paused = new MessageBox("Press ESC to resume", 30, 5, + FIELD_SIZE_WIDTH, FIELD_SIZE_HEIGHT, p); + + snake = new Snake('*', p); + drawer.CreateBorder('·', p); + + RegenerateApple(p); + + drawer.RedrawAll(); + } + + // Отрисовать окно Game over + public void GameOver() + { + MessageBox box = new MessageBox("GAME OVER", 50, 7, + FIELD_SIZE_WIDTH, FIELD_SIZE_HEIGHT, p); + + drawer.Create(box); + Flush(); + drawer.RedrawAll(); + + Console.CursorVisible = true; + } + + // Отрисовать окна паузы + public void Paused() + { + drawer.Create(info_paused); + Flush(); + } + + /* + Удовлетворить запросы на отрисовку + */ + public void Flush() + { + drawer.DrawToConsole(); + } + + public void MoveSnake() + { + UnPause(); // стереть надпись паузы + RemoveSnake(); // Удалить старую змейку + + snake.Move(); + + if (IsAppleEaten) + { + AddBlock(); + } + + Draw(); // добавить в очередь на отрисовку всё содержимое + + Flush(); // отрисовать очередь + } + + public bool IsFocused { get => true; set => throw new NotImplementedException(); } + + public void HandleKey(ConsoleKey key) + { + if (key == ConsoleKey.Spacebar) + { + speedUp = true; + return; + } + snake.HandleKey(key); + } + + /* + Добавить в очередь запросы на отрисовку компонентов + */ + void Draw() + { + drawer.Create(snake); + drawer.Create(apple); + drawer.Create(progress.StatusBar); + } + + void AddBlock() + { + snake.AddBlock(); + drawer.Remove(apple); // удалить старое яблоко + RegenerateApple(p); + progress.AppleEaten(); + } + + // Запрос на удаление окна паузы + void UnPause() + { + drawer.Remove(info_paused); + } + + // Запрос на удаление змейки + void RemoveSnake() + { + drawer.Remove(snake); // запрос на удаление змейки + } + + void RegenerateApple(Padding p) + { + apple = new Apple(new AppleGen(FIELD_SIZE_WIDTH, FIELD_SIZE_HEIGHT, snake, p), ref rnd); + } +} \ No newline at end of file diff --git a/games-cli/Drawer/Border.cs b/games-cli/Drawer/Border.cs new file mode 100644 index 0000000..8519cd9 --- /dev/null +++ b/games-cli/Drawer/Border.cs @@ -0,0 +1,57 @@ +/* +Граница/обводка чего-либо +*/ + +using System.Collections.Generic; +using System.Drawing; + +class Border : IDrawableElement +{ + public IDrawable[] ElementContent => getContent(); + List border_lines = new List(); + + public Border(char c, Point lt, Point rt, Point lb, Point rb) + { + fromPoints(c, lt, rt, lb, rb); + } + + public Border(char c, int width, int height, Padding p) + { + /* + Создать ключевые точки с учетом отступов + */ + + var lt = new Point(p.Left, p.Top); // left top + var rt = new Point(width - p.Right - 1, p.Top); // right top + var lb = new Point(p.Left, height - p.Buttom - 1); // left buttom + var rb = new Point(width - p.Right - 1, height - p.Buttom - 1); // right buttom + + fromPoints(c, lt, rt, lb, rb); + } + + private void fromPoints(char c, Point lt, Point rt, Point lb, Point rb) + { + Line top = new Line(c, lt, rt); + Line left = new Line(c, lt, lb); + Line right = new Line(c, rt, rb); + Line button = new Line(c, lb, rb); + + border_lines.Add(top); + border_lines.Add(left); + border_lines.Add(right); + border_lines.Add(button); + } + + private IDrawable[] getContent() + { + List r = new List(); + foreach (Line line in border_lines) + { + foreach (DrawableChar c in line.ElementContent) + { + r.Add(c); + } + } + return r.ToArray(); + } +} \ No newline at end of file diff --git a/games-cli/Drawer/DrawableChar.cs b/games-cli/Drawer/DrawableChar.cs new file mode 100644 index 0000000..c695baf --- /dev/null +++ b/games-cli/Drawer/DrawableChar.cs @@ -0,0 +1,20 @@ +/* +Простая реализация IDrawable - символ +*/ + +using System.Drawing; + +class DrawableChar : IDrawable +{ + public char Char => c; + char c; + + public Point Location => loc; + Point loc; + + public DrawableChar(char c, Point loc) + { + this.c = c; + this.loc = loc; + } +} \ No newline at end of file diff --git a/games-cli/Drawer/Drawer.cs b/games-cli/Drawer/Drawer.cs new file mode 100644 index 0000000..f1f473c --- /dev/null +++ b/games-cli/Drawer/Drawer.cs @@ -0,0 +1,130 @@ +/* +Класс для рисования в консоли. Добавляете в очередь +элементы и выводите их в консоль. +*/ + +using System; +using System.Collections.Generic; +using System.Drawing; + +class Drawer +{ + /* + Массив содержит симолы которые отображены в консоли. Вместо + изменения массива используйте очередь на отрисовку. + */ + public char[,] Content { get; private set; } + + /* + В целях оптимизации вместо полной переотрисовки всего массива Content + используется очередь на отрисовку. При следующей отрисовке символы + отобразятся в консоли, а список будет очищен. + */ + private List drawQueue = new List(); + + /* + Высота и ширина. Этими значениями ограничится поле на + котором можно рисовать. + */ + public int Width { get; private set; } + public int Height { get; private set; } + + public Drawer(int width, int height) + { + this.Content = new char[width, height]; + this.Width = width; + this.Height = height; + } + + /* + Отрисовать очередь + */ + public void DrawToConsole() + { + foreach (var p in drawQueue) + { + Content[p.Location.X, p.Location.Y] = p.Char; + DrawCharToConsole(p.Char, p.Location); + } + drawQueue.Clear(); + } + + // Перерисовать всё содержимое (High CPU usage) + public void RedrawAll() + { + for (int i = 0; i < Content.GetLength(0); i++) + { + for (int j = 0; j < Content.GetLength(1); j++) + { + var c = Content[i, j]; + var p = new Point(i, j); + DrawCharToConsole(c, p); + } + } + } + + // Отрисовать один символ в консоль + private void DrawCharToConsole(char c, Point p) + { + c = c == (char)0 ? ' ' : c; // заменить пустой символ пробелом + Content[p.X, p.Y] = c; + Console.SetCursorPosition(p.X, p.Y); + Console.Write(c); + } + + // Нарисовать границу из символов по краям с отступами + public void CreateBorder(char c, Padding p) + { + Border b = new Border(c, Width, Height, p); + Create(b); + } + + /* + Добавить элемент в очередь на отрисовку + */ + public void Create(IDrawableElement element) + { + foreach (IDrawable d in element.ElementContent) + { + Create(d); + } + } + + /* + Добавить символ в очередь на отрисовку + */ + public void Create(char c, int x, int y) + { + drawQueue.Add(new DrawableChar(c, new Point(x, y))); + } + + /* + Добавить в очередь на отрисовку. + */ + public void Create(IDrawable drawable) + { + Create(drawable.Char, drawable.Location.X, drawable.Location.Y); + } + + /* + Запросить затирание пробелами элемента. Запрос будет + добавлен в очередь и удовлетворен во время следующей отрисовки. + */ + public void Remove(IDrawableElement element) + { + foreach (IDrawable d in element.ElementContent) + { + Remove(d.Location.X, d.Location.Y); + } + } + + public void Remove(IDrawable drawable) + { + Remove(drawable.Location.X, drawable.Location.Y); + } + + public void Remove(int x, int y) + { + drawQueue.Add(new DrawableChar(' ', new Point(x, y))); + } +} \ No newline at end of file diff --git a/games-cli/Drawer/IDrawable.cs b/games-cli/Drawer/IDrawable.cs new file mode 100644 index 0000000..d0110e9 --- /dev/null +++ b/games-cli/Drawer/IDrawable.cs @@ -0,0 +1,11 @@ +/* +Символ, который можно вывеси в консоль +*/ + +using System.Drawing; + +interface IDrawable +{ + char Char { get; } + Point Location { get; } +} \ No newline at end of file diff --git a/games-cli/Drawer/IDrawableElement.cs b/games-cli/Drawer/IDrawableElement.cs new file mode 100644 index 0000000..00de865 --- /dev/null +++ b/games-cli/Drawer/IDrawableElement.cs @@ -0,0 +1,11 @@ +/* +Элемент, который можно вывести в консоль +*/ +interface IDrawableElement +{ + /* + Любой элемент это просто массив из символов. + Here it is. + */ + IDrawable[] ElementContent { get; } +} \ No newline at end of file diff --git a/games-cli/Drawer/IInteractive.cs b/games-cli/Drawer/IInteractive.cs new file mode 100644 index 0000000..b63de65 --- /dev/null +++ b/games-cli/Drawer/IInteractive.cs @@ -0,0 +1,10 @@ +/* +Интерактивный - значит может обрабатывать нажатия клавиш +*/ +using System; + +interface IInteractive +{ + bool IsFocused {get; set; } + void HandleKey(ConsoleKey key); +} \ No newline at end of file diff --git a/games-cli/Drawer/Line.cs b/games-cli/Drawer/Line.cs new file mode 100644 index 0000000..5e2ceb2 --- /dev/null +++ b/games-cli/Drawer/Line.cs @@ -0,0 +1,45 @@ +/* +Реализация отрисовки линии +*/ + +using System; +using System.Collections.Generic; +using System.Drawing; + +class Line : IDrawableElement +{ + public IDrawable[] ElementContent => line_chars.ToArray(); + List line_chars = new List(); + + public Line(char c, Point p1, Point p2) + { + if (!(p1.X == p2.X || p1.Y == p2.Y)) + { + throw new Exception("Можно нарисовать только вертикальную или горизонтальную линию. " + + $"Координаты переданы: {p1.X} {p1.Y} и {p2.X} {p2.Y}"); + } + + if (p1.X == p2.X) + { + // Вертикальная прямая + int from = Math.Min(p1.Y, p2.Y); + int to = Math.Max(p1.Y, p2.Y); + + for (int i = from; i <= to; i++) + { + line_chars.Add(new DrawableChar(c, new Point(p1.X, i))); + } + } + else + { + // Горизонтальная прямая + int from = Math.Min(p1.X, p2.X); + int to = Math.Max(p1.X, p2.X); + + for (int i = from; i <= to; i++) + { + line_chars.Add(new DrawableChar(c, new Point(i, p1.Y))); + } + } + } +} \ No newline at end of file diff --git a/games-cli/Drawer/MessageBox.cs b/games-cli/Drawer/MessageBox.cs new file mode 100644 index 0000000..f957817 --- /dev/null +++ b/games-cli/Drawer/MessageBox.cs @@ -0,0 +1,61 @@ +/* +Текстовое сообщение в рамке. +*/ + +using System.Collections.Generic; +using System.Drawing; + +class MessageBox : IDrawableElement +{ + public IDrawable[] ElementContent => getContent(); + /* + Состоит из рамки (Border) и текстового поля (TextField) + */ + Border border; + TextField textField; + + public string Text { get; set; } + + public MessageBox(string text, int width, int height, int field_width, int field_height, Padding p) + { + field_width -= (p.Left + p.Right); + field_height -= (p.Buttom + p.Top); + + this.Text = text; + int padding_topbuttom = (field_height - height) / 2; + int padding_leftright = (field_width - width) / 2; + + /* + Расчитать отступы таким образом сообщение было по середине + */ + Padding p_border = new Padding(padding_leftright, padding_leftright, + padding_topbuttom, padding_topbuttom); + + /* + Расчитать координаты текста так чтобы он был в центе обводки + */ + Point textStartLocation = new Point((field_width / 2) - (text.Length / 2), + padding_topbuttom + (height / 2)); + + this.border = new Border('+', field_width, field_height, p_border); + this.textField = new TextField(textStartLocation, text.Length); + textField.Text = text; + } + + private IDrawable[] getContent() + { + List r = new List(); + + foreach (var c in border.ElementContent) + { + r.Add(c); + } + + foreach (var c in textField.ElementContent) + { + r.Add(c); + } + + return r.ToArray(); + } +} diff --git a/games-cli/Drawer/Padding.cs b/games-cli/Drawer/Padding.cs new file mode 100644 index 0000000..e4af4b7 --- /dev/null +++ b/games-cli/Drawer/Padding.cs @@ -0,0 +1,22 @@ +/* +Класс задет отступы, которые можно передать в качестве аргумента +*/ +struct Padding +{ + // Отступ слева + public int Left { get; set; } + // Отступ справа + public int Right { get; set; } + // Отступ сверху + public int Top { get; set; } + // Отступ снизу + public int Buttom { get; set; } + + public Padding(int left, int right, int top, int buttom) + { + Left = left; + Right = right; + Top = top; + Buttom = buttom; + } +} \ No newline at end of file diff --git a/games-cli/Drawer/TextField.cs b/games-cli/Drawer/TextField.cs new file mode 100644 index 0000000..e3894f5 --- /dev/null +++ b/games-cli/Drawer/TextField.cs @@ -0,0 +1,49 @@ +/* +Реализация IDrawableElement для вывода теста в консоль +*/ +using System.Drawing; + +class TextField : IDrawableElement +{ + public string Text { get; set; } + public IDrawable[] ElementContent => getContent(); + + /* + Позиция, начиная с которой будет напечатан текст слева направо + */ + private Point startLocation; + public int length { get; private set; } + + public TextField(Point startLocation, int length) + { + // позиция с которой начинать писать + this.startLocation = startLocation; + this.length = length; + this.Text = ""; + } + + private IDrawable[] getContent() + { + IDrawable[] r = new IDrawable[length]; + for (int i = 0; i < length; i++) + { + Point location = new Point(startLocation.X + i, startLocation.Y); + char c; + if (Text.Length - 1 >= i) + { + // Добавить символ из строки + c = Text[i]; + } + else + { + /* + Если строка закончилась заполнить оставшееся + место пробелами + */ + c = ' '; + } + r[i] = new DrawableChar(c, location); + } + return r; + } +} \ No newline at end of file diff --git a/games-cli/Program.cs b/games-cli/Program.cs new file mode 100644 index 0000000..558725f --- /dev/null +++ b/games-cli/Program.cs @@ -0,0 +1,67 @@ +using System; +using System.Threading; + +namespace snake_cli +{ + class Program + { + static bool isPaused = false; + static Display display; + static void Main(string[] args) + { + display = new Display(); + InitKeyReading(); + + while (true) + { + if (isPaused) + { + display.Paused(); + Thread.Sleep(100); + continue; + } + + if (display.IsSnakeIntersect) + { + break; + } + + display.MoveSnake(); + + Thread.Sleep(display.FrameDelay); + } + + /* + Выход из цикла означает что игра окончена. + */ + display.GameOver(); + } + + /* + Запустить поток, который читает нажатые клавиши + */ + static void InitKeyReading() + { + Thread keyReading = new Thread(ReadKeysThread); + keyReading.IsBackground = true; + keyReading.Start(); + } + + /* + Бесконечный цикл, который читает нажатые клавиши + */ + static void ReadKeysThread() + { + while (true) + { + ConsoleKey keyPressed = Console.ReadKey(true).Key; + if (keyPressed == ConsoleKey.Escape) + { + isPaused = !isPaused; + continue; + } + display.HandleKey(keyPressed); + } + } + } +} diff --git a/games-cli/Progress.cs b/games-cli/Progress.cs new file mode 100644 index 0000000..4b32c3a --- /dev/null +++ b/games-cli/Progress.cs @@ -0,0 +1,34 @@ +/* +Класс для отслеживания прогресса игрока +*/ +class Progress +{ + public TextField StatusBar { get; } + public int Delay { get; set; } + public int Score { get; set; } + + public Progress(int delay, int FIELD_SIZE_WIDTH, int FIELD_SIZE_HEIGHT, int buttonShift) + { + this.Score = 0; + this.Delay = delay; + + this.StatusBar = new TextField(new System.Drawing.Point(0, FIELD_SIZE_HEIGHT - buttonShift), + FIELD_SIZE_WIDTH); + ChangeStatusBarText(); + } + + public void AppleEaten() + { + Score += 1; + ChangeStatusBarText(); + if (Delay > 50) + { + Delay -= 5; + } + } + + private void ChangeStatusBarText() + { + StatusBar.Text = $"Score: {Score}"; + } +} \ No newline at end of file diff --git a/games-cli/Snake.cs b/games-cli/Snake.cs new file mode 100644 index 0000000..70e2cf7 --- /dev/null +++ b/games-cli/Snake.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Drawing; + +struct SnakeBlock : IDrawable +{ + public Point Location { get; private set; } + public char Char { get; private set; } + + public SnakeBlock(char c, Point p) + { + this.Location = p; + this.Char = c; + } +} + +enum Direction +{ + Up, + Down, + Left, + Right, + None +} + +class Snake : IDrawableElement, IInteractive +{ + public List Blocks { get; private set; } = new List(); + public Direction Direction { get; set; } + public char SnakeChar { get; set; } + + public Point Position => new Point(1, 1); + public IDrawable[] ElementContent => getContent(); + + /* + Добавление нового блока к змейки происходит во время движения. Значение + переменной показывает сколько еще блоков нужно добавить к змейке. + */ + private int addBlockQueue = 0; + + public bool IsFocused { get => isFocused; set => isFocused = value; } + bool isFocused = true; + /* + В каком направлении фактически было сделано движение последний раз. Так как за одну итерацию + направление может сменится два раза. + */ + Direction actual_direction; + + public Snake(char c, Padding p) + { + this.SnakeChar = c; + this.Direction = Direction.Right; + this.actual_direction = Direction.Right; + this.Blocks.Add(new SnakeBlock(c, new Point(p.Left + 1, p.Top + 1))); + } + + // Сдвинуть змейку по направлению + public void Move() + { + // Первый (ведущий) блок змейки + SnakeBlock head = Blocks[0]; + + if (addBlockQueue > 0) + { + // Вместо добавления, не удаляется + addBlockQueue -= 1; + } + else + { + Blocks.RemoveAt(Blocks.Count - 1); + } + + /* + Для смещения последий блок перемещается в начало, перед текущим + ведущим. + */ + + Point newBlockLocation = head.Location; // Координаты нового ведущего блока + + newBlockLocation = GetPosFollowingDirection(newBlockLocation, this.Direction); + actual_direction = this.Direction; // зафиксировать в каком направлении фактически двигается змейка + + SnakeBlock blockToAdd = new SnakeBlock(SnakeChar, newBlockLocation); + Blocks.Insert(0, blockToAdd); + } + + public void HandleKey(ConsoleKey key) + { + if (key == ConsoleKey.W || key == ConsoleKey.UpArrow) + { + if (actual_direction != Direction.Down) + { + this.Direction = Direction.Up; + } + } + else if (key == ConsoleKey.A || key == ConsoleKey.LeftArrow) + { + if (actual_direction != Direction.Right) + { + this.Direction = Direction.Left; + } + } + else if (key == ConsoleKey.D || key == ConsoleKey.RightArrow) + { + if (actual_direction != Direction.Left) + { + this.Direction = Direction.Right; + } + } + else if (key == ConsoleKey.S || key == ConsoleKey.DownArrow) + { + if (actual_direction != Direction.Up) + { + this.Direction = Direction.Down; + } + } + } + + /* + Добавить блок к змейке + */ + public void AddBlock() + { + this.addBlockQueue += 1; + } + + // Находится ли голова змейки на яблоке + public bool IsEaten(Apple apple) + { + return Blocks[0].Location.X == apple.Location.X && + Blocks[0].Location.Y == apple.Location.Y; + } + + /* + Пересекает ли змейка сама себя в данный момент + */ + public bool SelfIntersect() + { + for (int i = 0; i < Blocks.Count - 1; i++) + { + for (int j = i + 1; j < Blocks.Count; j++) + { + if (Blocks[i].Location == Blocks[j].Location) + { + return true; + } + } + } + + return false; + } + + /* + Пересекает ли змейка границы + */ + public bool BorderIntersect(int field_width, int field_height, Padding p) + { + var h = this.Blocks[0].Location; + + if (h.X <= p.Left || h.X >= field_width - p.Right - 1 || + h.Y <= p.Top || h.Y >= field_height - p.Buttom - 1) + { + return true; + } + return false; + } + + /* + Получить координаты левее/правее/ниже/выше на 1 чем заданная + */ + private Point GetPosFollowingDirection(Point point, Direction dir) + { + switch (dir) + { + case Direction.Up: + point.Y -= 1; + break; + case Direction.Down: + point.Y += 1; + break; + case Direction.Left: + point.X -= 1; + break; + case Direction.Right: + point.X += 1; + break; + case Direction.None: + // ничего не делать + break; + } + return point; + } + + /* + Ковертировать блоки в IDrawable, чтобы реализовать интерфейс + IDrawableElement + */ + private IDrawable[] getContent() + { + IDrawable[] r = new IDrawable[Blocks.Count]; + for (int i = 0; i < Blocks.Count; i++) + { + r[i] = (IDrawable)Blocks[i]; + } + return r; + } +} \ No newline at end of file diff --git a/games-cli/games-cli.csproj b/games-cli/games-cli.csproj new file mode 100644 index 0000000..49fd202 --- /dev/null +++ b/games-cli/games-cli.csproj @@ -0,0 +1,9 @@ + + + + Exe + net5.0 + games_cli + + + diff --git a/makefile b/makefile new file mode 100644 index 0000000..28664dc --- /dev/null +++ b/makefile @@ -0,0 +1,24 @@ +DC=dotnet + +all: + dotnet build + +clean: + rm -rf bin/ + rm -rf obj/ + +install: + install ./bin/Debug/net5.0/snake-cli /usr/local/bin + install ./bin/Debug/net5.0/snake-cli.deps.json /usr/local/bin + install ./bin/Debug/net5.0/snake-cli.dll /usr/local/bin + install ./bin/Debug/net5.0/snake-cli.pdb /usr/local/bin + install ./bin/Debug/net5.0/snake-cli.runtimeconfig.dev.json /usr/local/bin + install ./bin/Debug/net5.0/snake-cli.runtimeconfig.json /usr/local/bin + +uninstall: + rm -f /usr/local/bin/snake-cli + rm -f /usr/local/bin/snake-cli.deps.json + rm -f /usr/local/bin/snake-cli.dll + rm -f /usr/local/bin/snake-cli.pdb + rm -f /usr/local/bin/snake-cli.runtimeconfig.dev.json + rm -f /usr/local/bin/snake-cli.runtimeconfig.json