Как написать змейку на java
Перейти к содержимому

Как написать змейку на java

  • автор:

Как написать свою змейку на Java за 15 минут

Обложка поста Как написать свою змейку на Java за 15 минут

В предыдущей статье мы писали сапёра за 15 минут, теперь займёмся классической змейкой.

В этот раз нам снова понадобятся:

  • 15 минут свободного времени;
  • Настроенная рабочая среда, т.е. JDK и IDE (например Eclipse);
  • Библиотека LWJGL (версии 2.x.x) для работы с Open GL. Обратите внимание, что для LWJGL версий выше 3 потребуется написать код, отличающийся от того, что приведён в статье;
  • Спрайты, т.е. картинки самой змеи и фрукта, который она будет есть. Можно чисто символически нарисовать самому, или скачать использовавшиеся при написании статьи.

Подключение библиотек

В прошлый раз у многих возникли с этим вопросом проблемы, поэтому мне показалось уместным посвятить этому немного времени. Во-первых, выше я дал ссылку на скачивание архива с библиотеками, которые использую я, чтобы не было путаницы с версиями и вопросов, где найти что-то. Папку из архива требуется поместить в папку проекта и подключить через вашу IDE.

Во-вторых, у многих пользователей InteliJ IDEA возникли проблемы как раз с их подключением. Я нашёл в сети следующий видеогайд:

После того, как я сделал всё в точности по нему, у меня библиотеки подключились корректно и всё заработало.

Работа с графикой

С этой стороны наша задача мало отличается от той, что мы выполняли при написании Сапёра. Снова создаём класс GUI, который будет хранить и обновлять состояние всех графических элементов. Если точнее:

  • Класс будет выполнять инициализацию OpenGL:

initializeOpenGL()

///Class GUI private static void initializeOpenGL() < try < //Задаём размер будущего окна Display.setDisplayMode(new DisplayMode(SCREEN_WIDTH, SCREEN_HEIGHT)); //Задаём имя будущего окна Display.setTitle(SCREEN_NAME); //Создаём окно Display.create(); >catch (LWJGLException e) < e.printStackTrace(); >glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0,SCREEN_WIDTH,0,SCREEN_HEIGHT,1,-1); glMatrixMode(GL_MODELVIEW); /* * Для поддержки текстур */ glEnable(GL_TEXTURE_2D); /* * Для поддержки прозрачности */ glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); /* * Белый фоновый цвет */ glClearColor(1,1,1,1); > 

Как вы можете видеть, здесь я уже использовал несколько констант. Для них был создан отдельный класс Constants с public static полями. Вот он целиком:

public class Constants < ///Размер игровой ячейки public static final int CELL_SIZE = 32; ///Размеры игрового поля в ячейках public static final int CELLS_COUNT_X = 20; public static final int CELLS_COUNT_Y = 20; ///Шанс появления ягод на старте в процентах. ///При выставленном значении спавнится 3-5 ягод. ///Не беспокойтесь, что значение слишком низкое, как минимум одна ягода создаётся отдельно. public static final int INITIAL_SPAWN_CHANCE = 1;//% ///В нашем случае змея проходит одну клетку за один фрейм. ///Значение 5 мне показалось оптимальным, но вы можете экспериментировать. public static final int FPS = 5; ///Константы для создания окна, названия достаточно говорящие. public static final int SCREEN_WIDTH =CELLS_COUNT_X*CELL_SIZE; public static final int SCREEN_HEIGHT = CELLS_COUNT_Y*CELL_SIZE; public static final String SCREEN_NAME = "Tproger's Snake"; > 

Enum Sprite , который отвечает за подгрузку текстур, полностью идентичен тому, что мы писали для Сапёра, за исключением того, что нам нужно только две текстуры — для змеи и для ягод. Вот код:

public enum Sprite < ///Файлы с именами circle и cherries должны лежать по адресу /// %папка проекта%/res/ в расширении .png BODY("circle"), CHERRIES("cherries"); private Texture texture; private Sprite(String texturename)< try < this.texture = TextureLoader.getTexture("PNG", new FileInputStream(new File("res/"+texturename+".png"))); >catch (IOException e) < e.printStackTrace(); >> public Texture getTexture() < return this.texture; >> 

Механика игры

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

Несложно подсчитать, что каждая лампочка должна гореть столько тиков, какова длина “змеи”. Значит, мы должны сообщить клетке, в которую попадает змея, что она должна гореть определённое количество секунд, а каждый тик уменьшать это число у каждой клетки с ненулевым таймером, и менять спрайт, если змея из клетки уже выползла (т.е. таймер стал равен нулю). В случае же необходимости удлинить цепочку, достаточно просто не уменьшать время “горения” клеток на каком-то тике. Именно поэтому метод update() у классов Cell и GUI принимает параметр — если он равен false , значит, змея что-то съела.

Пишем класс клетки

public class Cell < private int x; private int y; private int state;/* 0 ->ячейка пуста >0 -> в ячейке тело змеи, которое будет там ещё N фреймов Что-то необычное: -1: Ягоды */ ///Конструктор просто выставляет начальные значения координат и состояния public Cell (int x, int y, int state) < this.x=x; this.y=y; this.state=state; >///==== Ничем не примечательные геттеры и сеттеры public int getX() < return x; >public int getY() < return y; >public int getHeight() < return CELL_SIZE; >public int getWidth() < return CELL_SIZE; >public int getState() < return this.state; >public void setState(int state) < this.state = state; >///==== ///Метод обновления клетки. Уменьшаем время "горения", если это необходимо public void update(boolean have_to_decrease) < if (have_to_decrease && this.state >0) < this.state--; >> ///Ячейка "думает" как она должна выглядеть public Sprite getSprite() < if(this.state >0)< ///Если в ней тело змеи -- как змея return Sprite.BODY; >else if(this.state==0)< ///Если в ней нет ничего -- никак выглядеть и не должна return null; >else < ///Иначе проходимся свитчем по возможным объектам. ///Так как это демо -- я добавил только ягоды switch(this.state)< default: return Sprite.CHERRIES; >> > > 

Добавляем геттер и сеттер для состояния клетки поля в GUI

getState(x,y) < return cells[x][y].getState(); >setState(x,y,state)

Добавляем метод, создающий начальное поле в GUI

Просто инициализируем OpenGL, затем массив Cell[][] cells и заполняем последний клетками со случайным полем state .

public static void init() < initializeOpenGL(); cells = new Cell[CELLS_COUNT_X][CELLS_COUNT_Y]; Random rnd = new Random(); for(int i=0; i

Главный управляющий класс

class Main < ///Переменная, при обращении которой в true приложение закрывается private static boolean isExitRequested=false; ///Данные о нашей змее. Выползать она будет из нижнего левого угла, ///Вправо (направления посчитаны по часовой стрелки от севера, т.е. /// 0 -- вверх, 1 -- вправо, 2 -- вниз, 3 -- влево private static int x=-1,y=0, direction=1, length=3; ///Флаг, который обращается в false, если на данном тике змея что-то съела private static boolean have_to_decrease = true; ///Входной класс public static void main(String[] args) < ///Инициализируем графический интерфейс GUI.init(); ///Создаём ягодку в случайном месте generate_new_obj(); ///Пока не получим сигнал на закрытие, в цикле. while(!isExitRequested)< ///Проверяем ввод данных input(); ///Двигаем змею move(); ///Обновляем и рисуем графические элементы GUI.draw(); GUI.update(have_to_decrease); >> private static void move() < /// Если на прошлом тике мы что-то съели, то на этом должны вернуть значение на true have_to_decrease=true; ///Меняем координаты змеи в зависимости от направления switch(direction)< case 0: y++; break; case 1: x++; break; case 2: y--; break; case 3: x--; break; >///Проверяем, не вышла ли змея за границы if(x < 0 || x >= CELLS_COUNT_X || y < 0 || y >= CELLS_COUNT_Y) < //TODO gameover System.exit(1); >///Смотрим состояние ячейки, куда зашла змея int next_cell_state = GUI.getState(x,y); ///Если там змея, то это проигрыш if(next_cell_state>0)< //TODO gameover System.exit(1); >else < ///Если там еда, то if(next_cell_state < 0)< length++; ///Увеличиваем длину на единицу generate_new_obj(); ///Создаём новую еду have_to_decrease=false; ///Выставляем флаг того, что мы съели что-то >///"Зажигаем" клетку GUI.setState(x,y,length); > > /*Алгоритм генерации новой еды следующий. Мы высчитываем количество клеток, которые не заполнены змеёй, по формуле: CELLS_COUNT_X*CELLS_COUNT_Y-length И выбираем случайную такую клетку (сохраняем её номер в point). Потом проходимся по всем клеткам, и, если в клетке не змея, уменьшаем счётчик. Как только счётчик равен нулю, создаём в этой клетке еду и выходим из цикла. ВНИМАНИЕ! При таком методе ягоды могут создаваться поверх других ягод, т.е. Их общее количество будет уменьшаться со временем. Чтобы избежать этого можно при уничтожении одной ягоды создавать случайное число (1-3) ягод. */ private static void generate_new_obj() < int point = new Random().nextInt(CELLS_COUNT_X*CELLS_COUNT_Y-length); for(int i=0; i

Готово!
P.S. Исходники можно скачать здесь (архив всей папки проекта).

Змейка

Длинное змееобразное существо ползает по ограниченному полю, подчиняясь приказам игрока (право, лево, вниз, вверх). И нет никакой возможности остановить его движение. Только бы не коснуться краев поля, иначе гибель! Только бы не укусить себя, иначе… ну вы поняли. На пути у существа попадается еда, от которой оно становится ещё длиннее. От роста ему становиться всё теснее, и теснее, и теснее… Стоп машина! Вы, вероятно, узнали в описанной выше шуточной драме сценарий знаменитой игры «Змейка». Эта игрушка берёт своё начало в 70-х годах прошлого столетия. Именно тогда она впервые появилась на аркадных автоматах. А в 21-м столетии её популяризатором выступила компания Nokia, которая предустанавливала «Змейку» едва ли не во все свои мобильные телефоны. Между этими двумя вехами были версии для Commodore VIC-20, MS-DOS, ZX Spectrum, Windows, Mac OS… Если вы залезете в Play Маркет или Apple Store, то, скорее всего, найдёте пару десятков вариантов различных «Змеек». Сложно представить себе платформу, на которой не было бы собственного варианта «Змейки». Почему так? Дело в том, что с одной стороны, это интересная игра, которая отлично помогает скоротать время. С другой стороны, «Змейку» достаточно просто создать самостоятельно. Давайте убедимся в этом сами, и напишем собственную версию приключений растущего пресмыкающегося на Java. Мы на JavaRush уже расписали все шаги, которые помогут начинающему игроку справиться с такой задачей без проблем. Вперёд!

  • Посмотреть опубликованные игры других пользователей Посмотреть опубликованные игры пользователей Посмотреть игры пользователей
  • Написать своё решение

Создание игры "Змейка" на чистом JavaScript и HTML5

Создание игры

Змейка - классическая игра, которую мы знаем еще с давних времен. Мы представляем вам статью, в ходе которой мы создадим полноценную игру «Змейка» на чистом JavaScript и HTML5.

Для создания веб игр на языке JavaScript используется технология Canvas , которая позволяет выполнять JavaScript код в HTML5 документе. Вы можете более детально ознакомиться с этой технологией посмотрев видео ниже:

HTML страница может содержать классическую разметку, в которую необходимо вписать canvas для отображения игры внутри него. Пример кода:

      Игра на JavaScript    

Внутри этого файла мы подключаем скрипт "game.js", который будет описывать весь функционал нашей игры.

JavaScript файл

Внутри JavaScript файла добавьте выборку канваса, а также укажите контекст игры.

var cvs = document.getElementById("canvas"); var ctx = cvs.getContext("2d");

Добавление изображений и аудио

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

Код добавления изображений и аудио в игру:

const ground = new Image(); // Создание объекта ground.src = "img/ground.png"; // Указываем нужное изображение const foodImg = new Image(); // Создание объекта foodImg.src = "img/food.png"; // Указываем нужное изображение

Рисование объектов

Чтобы нарисовать объекты, а также добавить функционал к игре необходимо прописать функцию, которая будет постоянно вызываться. Такую функцию вы можете назвать как вам будет угодно. Чтобы функция работала постоянно, вы можете запустите её выполнение через setInterval() .

function draw() < // Какой-либо код >let game = setInterval(draw, 100); // Вызов функции из вне

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

Чтобы отследить нажатие игрока на какую-либо клавишу, необходимо использовать отслеживание событий - addEventListener . К примеру, чтобы отследить нажатие на любую клавишу на клавиатуре надо прописать следующий код:

// При нажатии на какую-либо кнопку document.addEventListener("keydown", someMethod); // Вызывается метод someMethod function someMethod() < // Изменяем что-то в коде >

Видео урок

Это были лишь небольшие азы перед созданием самой игры. Предлагаем вам ознакомиться с большим видео уроком, в ходе которого вы создадите 2D игру "Змейка" на чистом JavaScript'е.

Полезные ссылки из видео:

  • Текстовый редактор Atom.io ;
  • Подбор иконок IconFinder ;
  • Хостинг компания Reg.ru .

Весь JS код игры

Ниже вы можете посмотреть на полностью весь код JavaScript файла, который был создан в ходе видео урока выше:

const canvas = document.getElementById("game"); const ctx = canvas.getContext("2d"); const ground = new Image(); ground.src = "img/ground.png"; const foodImg = new Image(); foodImg.src = "img/food.png"; let box = 32; let score = 0; let food = < x: Math.floor((Math.random() * 17 + 1)) * box, y: Math.floor((Math.random() * 15 + 3)) * box, >; let snake = []; snake[0] = < x: 9 * box, y: 10 * box >; document.addEventListener("keydown", direction); let dir; function direction(event) < if(event.keyCode == 37 && dir != "right") dir = "left"; else if(event.keyCode == 38 && dir != "down") dir = "up"; else if(event.keyCode == 39 && dir != "left") dir = "right"; else if(event.keyCode == 40 && dir != "up") dir = "down"; >function eatTail(head, arr) < for(let i = 0; i < arr.length; i++) < if(head.x == arr[i].x && head.y == arr[i].y) clearInterval(game); >> function drawGame() < ctx.drawImage(ground, 0, 0); ctx.drawImage(foodImg, food.x, food.y); for(let i = 0; i < snake.length; i++) < ctx.fillStyle = i == 0 ? "green" : "red"; ctx.fillRect(snake[i].x, snake[i].y, box, box); >ctx.fillStyle = "white"; ctx.font = "50px Arial"; ctx.fillText(score, box * 2.5, box * 1.7); let snakeX = snake[0].x; let snakeY = snake[0].y; if(snakeX == food.x && snakeY == food.y) < score++; food = < x: Math.floor((Math.random() * 17 + 1)) * box, y: Math.floor((Math.random() * 15 + 3)) * box, >; > else snake.pop(); if(snakeX < box || snakeX >box * 17 || snakeY < 3 * box || snakeY >box * 17) clearInterval(game); if(dir == "left") snakeX -= box; if(dir == "right") snakeX += box; if(dir == "up") snakeY -= box; if(dir == "down") snakeY += box; let newHead = < x: snakeX, y: snakeY >; eatTail(newHead, snake); snake.unshift(newHead); > let game = setInterval(drawGame, 100);

Также вы можете скачать весь проект целиком по этой ссылке .

Больше интересных новостей

6 способов монетизировать мобильное приложение

6 способов монетизировать мобильное приложение

Раздражающие программерские фичи / ТОП 7

Раздражающие программерские фичи / ТОП 7

Подборка: Что можно купить в сети Даркнет?

Подборка: Что можно купить в сети Даркнет?

Начало пути: как стать разработчиком, если вы совсем далеки от айти

Начало пути: как стать разработчиком, если вы совсем далеки от айти

Комментарии (4)

Саня 20 ноября 2023 в 09:47

А как сделать так, чтобы игра начиналась заново после проигрыша?

Михаил 27 марта 2023 в 11:53

а где файл со стилями

миша 05 мая 2023 в 18:18

зажми контрл и нажми на этот текст

Елена 03 августа 2022 в 13:28

Кирилл Михайлович 17 мая 2022 в 20:33

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

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

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

Для начала нам ничего лишнего не понадобится, только блокнот (или ваш любимый редактор), и компилятор C#, он присутствует по умолчанию в Windows, находится он в С:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe. Можно использовать компилятор последней версии который поставляется с visual studio, он находится Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\Roslyn\csc.exe.

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

@echo off :Start set /p name= Enter program name: echo. С:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe "%name%.cs" echo. goto Start

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

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

Для тех кто сразу хочет увидеть весь код.

Скрытый текст

using System; using System.Threading; using System.Collections.Generic; using System.Linq; namespace SnakeGame < class Game < static readonly int x = 80; static readonly int y = 26; static Walls walls; static Snake snake; static FoodFactory foodFactory; static Timer time; static void Main() < Console.SetWindowSize(x + 1, y + 1); Console.SetBufferSize(x + 1, y + 1); Console.CursorVisible = false; walls = new Walls(x, y, '#'); snake = new Snake(x / 2, y / 2, 3); foodFactory = new FoodFactory(x, y, '@'); foodFactory.CreateFood(); time = new Timer(Loop, null, 0, 200); while (true) < if (Console.KeyAvailable) < ConsoleKeyInfo key = Console.ReadKey(); snake.Rotation(key.Key); >> >// Main() 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(); >>// Loop() >// class Game struct Point < public int x < get; set; >public int y < get; set; >public char ch < get; set; >public static implicit operator Point((int, int, char) value) => new 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; public void Draw() < DrawPoint(ch); >public void Clear() < DrawPoint(' '); >private void DrawPoint(char _ch) < Console.SetCursorPosition(x, y); Console.Write(_ch); >> class Walls < private char ch; private Listwall = new List(); 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); >> public bool IsHit(Point p) < foreach (var w in wall) < if (p == w) < return true; >> return false; > >// class Walls enum Direction < LEFT, RIGHT, UP, DOWN >class Snake < private Listsnake; 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(); 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 bool Eat(Point p) < head = GetNextPoint(); if (head == p) < snake.Add(head); head.Draw(); return true; >return false; > 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; > > public bool IsHit(Point p) < for (int i = snake.Count - 2; i >0; i--) < if (snake[i] == p) < return true; >> return false; > >//class Snake 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(); >> > 
Глава 2. Первые шаги

Подготовим поле нашей игры, начиная с точки входа в нашу программу. Задаем переменные X и Y, размер и буфер окна консоли, и скроем отображение курсора.

using System; using System.Collections.Generic; using System.Linq; class Game< static readonly int x = 80; static readonly int y = 26; static void Main()< Console.SetWindowSize(x + 1, y + 1); Console.SetBufferSize(x + 1, y + 1); Console.CursorVisible = false; >// Main() >// class Game 

Для вывода на экран нашей «графики» создадим свой тип данных — точка. Он будет содержать координаты и символ, который будет выводится на экран. Также сделаем методы для вывода на экран точки и ее «стирания».

struct Point < public int x < get; set; >public int y < get; set; >public char ch < get; set; >public static implicit operator Point((int, int, char) value) => new Point ; public void Draw() < DrawPoint(ch); >public void Clear() < DrawPoint(' '); >private void DrawPoint(char _ch) < Console.SetCursorPosition(x, y); Console.Write(_ch); >>

Это интересно!
Оператор => называется лямбда-оператор, он используется в качестве определения анонимных лямбда выражений, и в качестве тела, состоящего из одного выражения, синтаксический сахар, заменяющий оператор return. Приведенный выше метод переопределения оператора (про его назначение чуть ниже) можно переписать так:

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

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

class Walls < private char ch; private Listwall = new List(); 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

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

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

Вернемся к классу 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

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

class Snake < private Listsnake; 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(); 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.
Всем удачи!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *