» » » Своя змейка, или пишем первый проект. Часть 0

 

Своя змейка, или пишем первый проект. Часть 0

Автор: admin от 4-02-2018, 16:50, посмотрело: 199

Предисловие


Привет Хабр! Меня зовут Евгений «Nage», и я начал заниматься программированием около года назад, в свободное от работы время. Просмотрев множество различных туториалов по программированию задаешься вопросом «а что же делать дальше?», ведь в основном все рассказывают про самые основы и дальше как правило не заходят. Вот после продолжительного времени за просмотром разных роликов про одно и тоже я решил что стоит двигаться дальше, и браться за первый проект. И так, сейчас мы разберем как можно написать игру «Змейка» в консоли со своими начальными знаниями.

Глава 1. Итак, с чего начнем?


Для начала нам ничего лишнего не понадобится, только блокнот (или ваш любимый редактор), и компилятор C#, он присутствует по умолчанию в Windows, находится он в С:WindowsMicrosoft.NETFrameworkv4.0.30319csc.exe. Можно использовать компилятор последней версии который поставляется с visual studio, он находится Microsoft Visual Studio2017CommunityMSBuild15.0BinRoslyncsc.exe.

Создадим файл для быстрой компиляции нашего кода, сохранил файл с расширением .bat со следующим содержимым:

@echo off
:Start
set /p name= Enter program name: 
echo.
С:WindowsMicrosoft.NETFrameworkv4.0.30319csc.exe "%name%.cs"
echo.
goto Start

"@echo off" отключает отображение команд в консоли. С помощью команды goto получаем бесконечный цикл. Задаем переменную name, а с модификатором /p в переменную записывается значение введенное пользователем в консоль. «echo.» просто оставляет пустую строчку в консоли. Далее вызываем компилятор и передаем ему файл нашего кода, который он скомпилирует.

Таким способом мы можем скомпилировать только один файл, поэтому мы будем писать все классы в одном документе (я не разобрался еще как компилировать несколько файлов в один .exe через консоль, да и это не тема нашей статьи, может кто нибудь расскажет в комментариях).
определение текста выражения. Приведенный выше метод переопределения оператора (про его назначение чуть ниже) можно переписать так:


public static bool operator ==(Point a, Point b){
if (a.x == b.x && a.y == b.y){
return true;
}
else{
return false;
}
}

[/quote]
Создадим класс стен, границы игрового поля. Напишем 2 метода на создание вертикальных и горизонтальных линий, и в конструкторе вызываем отрисовку всех 4х сторон заданным символом. Список всех точек в стенке нам пригодится позже.

class Walls{
    private char ch;
    private List<Point> wall = new List<Point>();

    public Walls(int x, int y, char ch){
        this.ch = ch;
        DrawHorizontal(x, 0);
        DrawHorizontal(x, y);
        DrawVertical(0, y);
        DrawVertical(x, y);
    }

    private void DrawHorizontal(int x, int y){
        for (int i = 0; i < x; i++){
            Point p = (i, y, ch);
            p.Draw();
            wall.Add(p);
        }
    }
    private void DrawVertical(int x, int y) {
        for (int i = 0; i < y; i++) {
            Point p = (x, i, ch);
            p.Draw();
            wall.Add(p);
        }
    }
}// class Walls

[quote]Это интересно!

Как вы могли заметить для инициализации типа данных Point используется форма Point p = (x, y, ch); как и у встроенных типов, это становится возможным при переопределении оператора implicit, в котором описывается как задаются переменные.
[/quote]
[quote]Важно!

Конструкция (int, int, char) называется кортежем, и работает только с .net 4.7+, по этому если у вас не установлен visual studio, то в вашем распоряжении только компилятор v4.0.30319 и нужно использовать стандартную инициализацию через оператор new.
[/quote]
Вернемся к классу Game и объявим поле walls, а в методе Main инициализируем ее.

class Game{
static Walls walls;
    static void Main(){
        walls = new Walls(x, y, '#');
...

Все! Можно скомпилировать код и посмотреть, что наше поле построилось, и самая легкая часть позади.

Глава 3. А что сегодня на завтрак?


Добавим генерацию еды на нашем поле, для этого создадим класс FoodFactory, который и будет заниматься созданием еды внутри границ.

class FoodFactory
{
    int x;
    int y;
    char ch;
    public Point food { get; private set; }

    Random random = new Random();

    public FoodFactory(int x, int y, char ch)
    {
        this.x = x;
        this.y = y;
        this.ch = ch;
    }

    public void CreateFood()
    {
        food = (random.Next(2, x - 2), random.Next(2, y - 2), ch);
        food.Draw();
    }
}

Добавляем инициализацию фабрики и создадим еду на поле
class Game{
    static FoodFactory foodFactory;

    static void Main(){
        foodFactory = new FoodFactory(x, y, '@');
        foodFactory.CreateFood();
...

Кушать подано!

Глава 4. Время главного героя


Перейдем к созданию самой змеи, и для начала определим перечисление направления движения змейки.

enum Direction{
    LEFT,
    RIGHT,
    UP,
    DOWN
}

Теперь можем создать класс змейки, где опишем как она будет ползать, поворачивать. Определим список точек змеи, наше перечисление, шаг на сколько будет перемещаться за ход, и ссылки на хвостовую и головную точки, и конструктор, в котором рисуем змею в заданных координатах и заданной длинны при старте игры.

class Snake{
    private List<Point> snake;
    private Direction direction;
    private int step = 1;
    private Point tail;
    private Point head;
    bool rotate = true;
    public Snake(int x, int y, int length){
        direction = Direction.RIGHT;
        snake = new List<Point>();
        for (int i = x - length; i < x; i++)        {
            Point p = (i, y, '*');
            snake.Add(p);
            p.Draw();
        }
    }
//Методы движения и поворота в зависимости он направления движения змейки.
    public Point GetHead() => snake.Last();
    public void Move(){
        head = GetNextPoint();
        snake.Add(head);
        tail = snake.First();
        snake.Remove(tail);
        tail.Clear();
        head.Draw();
        rotate = true;
    }
    public Point GetNextPoint() {
        Point p = GetHead();
        switch (direction) {
            case Direction.LEFT:
                p.x -= step;
                break;
            case Direction.RIGHT:
                p.x += step;
                break;
            case Direction.UP:
                p.y -= step;
                break;
            case Direction.DOWN:
                p.y += step;
                break;
        }
    return p;
    }
    public void Rotation(ConsoleKey key) {
        if (rotate) {
            switch (direction) {
                case Direction.LEFT:
                case Direction.RIGHT:
                    if (key == ConsoleKey.DownArrow)
                        direction = Direction.DOWN;
                    else if (key == ConsoleKey.UpArrow)
                        direction = Direction.UP;
                    break;
                case Direction.UP:
                case Direction.DOWN:
                    if (key == ConsoleKey.LeftArrow)
                        direction = Direction.LEFT;
                    else if (key == ConsoleKey.RightArrow)
                        direction = Direction.RIGHT;
                    break;
            }
            rotate = false;
        }
    }
}//class Snake

В методе поворота, что бы избежать возможности повернуть сразу на 180 градусов, просто указываем, что в каждом направлении мы можем повернуть только в 2 стороны. А проблему поворота на 180 градусов двумя нажатиями — поставив «переключатель», отключаем возможность поворачивать после первого нажатия, и включаем после очередного хода.

Осталось вывести ее на экран.

class Game{
    static Snake snake;
    static void Main(){
        snake = new Snake(x / 2, y / 2, 3);
...

Готово! теперь у нас есть все что нужно, поле огороженное стенами, рандомно появляющаяся еда, и змейка. Пришла пора заставить все это взаимодействовать друг с другом.

Глава 5. Л-логика


Заставим нашу змейку двигаться, напишем бесконечный цикл для считывания клавиш нажатых на клавиатуре, и передаем клавишу в метод поворота змеи

class Game {
    static void Main () {
        while (true) {
            if (Console.KeyAvailable) {
                ConsoleKeyInfo key = Console.ReadKey ();
                snake.Rotation(key.Key);
            }
...

для движения змеи воспользуемся классом .net который будет запускать метод Loop через определенные промежутки времени.

using System.Threading;
class Game {
    static Timer time;
    static void Main () {
        time = new Timer (Loop, null, 0, 200);
...

Теперь, перед тем как написать метод движения змейки, надо реализовать взаимодействие головы с едой, стенками и хвостом змеи. Для этого надо написать метод, позволяющий сравнивать две точки на совпадение координат. Переопределим оператор равенства и не равенства, их обязательно нужно переопределять в паре.

struct Point {
    public static bool operator == (Point a, Point b) => 
        (a.x == b.x && a.y == b.y) ? true : false;
    public static bool operator != (Point a, Point b) => 
        (a.x != b.x || a.y != b.y) ? true : false;
...

Теперь можно написать метод, который будет проверять совпадает ли интересующая нас точка с какой нибудь из массива стен.
class Walls {
    public bool IsHit (Point p) {
        foreach (var w in wall) {
            if (p == w) {
                return true;
            }
        }
        return false;
    }
...

И похожий метод проверяющий не совпадает ли точка с хвостом.

class Snake {
    public bool IsHit (Point p) {
        for (int i = snake.Count - 2; i > 0; i--) {
            if (snake[i] == p) {
                return true;
            }
        }
        return false;
    }
...

И методом проверки съела ли еду наша змейка, и сразу делаем ее длиннее.

class Snake {
    public bool Eat (Point p) {
        head = GetNextPoint ();
        if (head == p) {
            snake.Add (head);
            head.Draw ();
            return true;
        }
        return false;
    }
...

теперь можно написать метод движения, со всеми нужными проверками.

class Snake {
    static void Loop (object obj) {
        if (walls.IsHit (snake.GetHead ()) || snake.IsHit (snake.GetHead ())) {
            time.Change (0, Timeout.Infinite);
        } else if (snake.Eat (foodFactory.food)) {
            foodFactory.CreateFood ();
        } else {
            snake.Move ();
        }
    }
...

Вот и все! Наша змейка в консоли закончена и можно поиграть.

Заключение


Мы посмотрели как можно реализовать первую простенькую игру с небольшим использованием ООП, научились перегружать операторы, посмотрели на кортежи и лямбда оператор, надеюсь это было полезно!

Это была пилотная статья, и если вам понравилось, я напишу про реализацию змейки на Unity.
Всем удачи!

Источник: Хабрахабр

Категория: Программирование » Game Development

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *