Реализация третьего уровня сложности игры Змейка

Третий уровень сложности –это полный вариант игры в соответствии с техническим заданием.

Внимание!!!Прежде чем приступать к изучению третьего уровня сложности – необходимо хорошо разобраться с первым и вторым уровнем сложности. Нужно хорошо понимать программный код и картину происходящего.

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

Вместе с техническим заданием по разработке игры Змейка был предложен алгоритм реализации этой игры. Речь идет об алгоритме перемещения тела змейки по игровому полю. Все пустые ячейки игрового поля в двумерном массиве отмечены нулями(см. рис. 3),объект для поедания отмечен минус единицей: -1 (см. рис. 3), голова змейки отмечена значением единица: 1 (см. рис. 3), тело змейки будет помечено числами от 2 и более (см. рис. 3).

Реализация третьего уровня сложности игры Змейка - student2.ru

Рис. 3

В третьем уровне сложности нам предстоит добавить тело змейки. После поедания объекта длина змейки увеличивается на единицу,увеличение происходит с конца. Для рассмотренного на рисунке примера к змейке из трех клеток добавится четвертая и будет отмечена числом: 4 (см. рис.4)

Реализация третьего уровня сложности игры Змейка - student2.ru

Рис. 4

При следующем поедании объекта к змейке из четырех клеток добавится пятая и будет отмечена числом:5 (см. рис. 5).Таким образом, в хвосте змейки всегда будет стоять число, равное длине змейки. Если длина змейки пять, то и в хвосте будет пять клеток (см. рис. 5). Почему мы не сделали проще, не отметили все тело змейки числом, например два?

Реализация третьего уровня сложности игры Змейка - student2.ru

Рис. 5

Дело в том, что когда змейка будет сильно изогнута, и части ее тела будут прилегать друг к другу, то клетки ее тела будут как бы перемешиваться,и какая из них будет являться хвостом неизвестно (см. рис. 6).

Реализация третьего уровня сложности игры Змейка - student2.ru

Рис. 6

Безусловно, данный алгоритм не является единственным! Можно придумать другие варианты!


Начнем с доработки файла zmeika.java,дополним программный код в классе myPanel. Найдем место, где у нас реализован таймер для изменения логики игры –tmUpdate (см. рис. 7)

Реализация третьего уровня сложности игры Змейка - student2.ru

Рис. 7

Нам нужно, чтобы перемещалась не только голова змейки, но и ее тело. Для этого в классе gameмы добавим метод perem(), который будет отвечать за перемещение змейки. Поэтому удалим вызов метода peremGolova(), а вместо него добавим следующий программный код:

//Создаем, настраиваем и запускаем таймер для

//изменениялогикиигры

tmUpdate = new Timer(100,new ActionListener() {

@Override

public void actionPerformed(ActionEvent arg0) {

// Если не конец игры, то перемещаем змейку

if (myGame.endg==false)

{

myGame.perem();

}

// Выводим информацию о количестве очков

lb.setText("Счет: "+myGame.kol);

}

});

tmUpdate.start();

Переменная endgтакже будет добавлена в классе game. Эта переменная логического типа будет хранить признак "конца игры". В конце игры ее значение будет: true. Другими словами, мы будем вызывать метод перемещения змейки пока еще не конец игры. Метод perem() будет изменять массив. Следующие доработки коснутся изменения направления движения змейки при нажатии на стрелки клавиатуры. Вместо изменения переменной naprмы будем изменять переменнуюnew_napr, которую добавим в классе game:

// Если нажатие одной из четырех стрелочек,то

// изменение направления змейки

if (key==KeyEvent.VK_LEFT) myGame.new_napr = 0;

else if (key==KeyEvent.VK_UP) myGame.new_napr = 1;

else if (key==KeyEvent.VK_RIGHT) myGame.new_napr = 2;

else if (key==KeyEvent.VK_DOWN) myGame.new_napr = 3;

Теперь перейдем к методу paintComponent(). В самом конце метода добавим вывод изображения окончания игры:

//Вывод изображения конца игры - при окончании игры

if (myGame.endg==true)

{

gr.drawImage(endg,250,200,200,100,null);

}

Если признак окончания игры равен true,то будет выводиться картинка, загруженная в переменную endg. Так как строки, описывающие этот алгоритм, будут находиться в самом конце метода paintComponent(), то изображение, обозначающее конец игры, будет выводиться самым последним, и будет находиться поверх остальных изображений (см. рис. 8):

Реализация третьего уровня сложности игры Змейка - student2.ru

Рис. 8

Теперь в методе paintComponent() намнужно найти два вложенных цикла для отрисовки змейки(см. рис. 9). Сейчас этот цикл рисует голову змейки и объект для поедания. Добавим фрагмент кода, который будет прорисовывать тело змейки.

Реализация третьего уровня сложности игры Змейка - student2.ru

Рис. 9

// Отрисовка игрового поля на основании массива

for (int i = 0; i < 30; i++) {

for (int j = 0; j < 30; j++) {

if (myGame.mas[i][j]!=0)

{

if (myGame.mas[i][j]==1)

{

// Выводим голову змейки в ячейку игрового поля

gr.drawImage(golova,10+j*20, 10+i*20,20,20,null);

}

else if (myGame.mas[i][j]==-1)

{

// Выводим объект для поедания в ячейку игрового поля

gr.drawImage(ob,10+j*20, 10+i*20,20,20,null);

}

else if (myGame.mas[i][j]>=2)

{

// Выводимтелозмейки

gr.drawImage(telo,10+j*20, 10+i*20,20,20,null);

}

}

}

}

Мыдобавилиблок:

else if (myGame.mas[i][j]>=2)

{

// Выводимтелозмейки

gr.drawImage(telo,10+j*20, 10+i*20,20,20,null);

}

Если значение элемента массива больше или равно двум, то выводится изображение из переменной telo, в которую мы загрузили картинку элемента тела змейки.Как было рассмотрено выше –тело змейки обозначено числами от двух и больше. На этом доработки в классе myPanelи файле zmeika.javaдля третьего уровня сложности закончены. Полный программный код файла zmeika.javaвыглядит так:

// Для обработки событий

import java.awt.event.*;

// Для работы с окнами

import javax.swing.*;

// Для работы с графикой

import java.awt.*;

// Для работы с изображениями

import javax.imageio.*;

// Для работы с файлами

import java.io.*;

// Главный класс программы

public class zmeika

{

// Методзапускаприложения

public static void main(String[] args)

{

//Создание объекта окна игрового поля

myFrame okno = new myFrame();

}

}

// Класс окна игрового поля

class myFrame extends JFrame

{

// Конструктор класса

public myFrame()

{

//Создание объекта панели и подключения ее к окну

myPanel pan = new myPanel();

Container cont = getContentPane();

cont.add(pan);

//Заголовококна

setTitle("Игра \"Змейка\"");

//Границы окна: расположение и размеры

setBounds(0, 0, 800, 650);

//Операция при закрытии окна - завершение приложения

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

//Запрет изменения размеров окна

setResizable(false);

//Отображение (показ) окна

setVisible(true);

}

}

// Класс панели игрового поля

class myPanel extends JPanel

{

// Переменная для реализации логики игры

private game myGame;

// Два таймера: отрисовки и изменения логики игры

private Timer tmDraw, tmUpdate;

// Изображения, используемые в игре

private Image fon,telo,golova,ob,endg;

// Надпись для количества очков

private JLabel lb;

// Двекнопки

private JButton btn1,btn2;

// Ссылканапанель

private myPanel pan;

// Класс для обработки событий от клавиатуры

private class myKey implements KeyListener

{

//Методпринажатиинаклавишу

public void keyPressed(KeyEvent e)

{

// Получениекоданажатойклавиши

int key = e.getKeyCode();

// Если нажатие одной из четырех стрелочек, то

// изменение направления змейки

if (key==KeyEvent.VK_LEFT) myGame. new_napr = 0;

else if (key==KeyEvent.VK_UP) myGame. new_napr = 1;

else if (key==KeyEvent.VK_RIGHT) myGame. new_napr = 2;

else if (key==KeyEvent.VK_DOWN) myGame. new_napr = 3;

}

public void keyReleased(KeyEvent e) {}

public void keyTyped(KeyEvent e) {}

}

// Конструктор класса

public myPanel()

{

//Помещаем ссылку на саму панель в переменную

pan = this;

//Подключение обработчика события для клавиатуры к панели

this.addKeyListener(new myKey());

// Делаем панель в фокусе - для приема событий от клавиатуры

this.setFocusable(true);

//Попытка загрузки всех изображений для игры

try

{

fon = ImageIO.read(new File("c:\\fon.png"));

telo = ImageIO.read(new File("c:\\telo.png"));

golova = ImageIO.read(new File("c:\\golova.png"));

ob = ImageIO.read(new File("c:\\ob.png"));

endg = ImageIO.read(new File("c:\\endg.png"));

}

catch (Exception ex) {}

//Создаем объект новой игры

myGame = new game();

myGame.start();

//Создаем, настраиваем и запускаем таймер

// для отрисовки игрового поля

tmDraw = new Timer(20,new ActionListener() {

@Override

public void actionPerformed(ActionEvent arg0) {

// Вызываемперерисовку -paintComponent()

repaint();

}

});

tmDraw.start();

//Создаем, настраиваем и запускаем таймер

// для изменения логики игры

tmUpdate = new Timer(100,new ActionListener() {

@Override

public void actionPerformed(ActionEvent arg0) {

// Если не конец игры, то перемещаем змейку

if (myGame.endg==false)

{

myGame.perem();

}

// Выводим информацию о количестве очков

lb.setText("Счет: "+myGame.kol);

}

});

tmUpdate.start();

//Включаем возможность произвольного размещения

//элементов интерфейса на панели

setLayout(null);

//Создаем текстовую надпись

lb = new JLabel("Счет: 0");

lb.setForeground(Color.WHITE);

lb.setFont(new Font("serif",0,30));

lb.setBounds(630, 200, 150, 50);

add(lb);

//СоздаемкнопкуНоваяигра

btn1 = new JButton();

btn1.setText("Новаяигра");

btn1.setForeground(Color.BLUE);

btn1.setFont(new Font("serif",0,20));

btn1.setBounds(630, 30, 150, 50);

btn1.addActionListener(new ActionListener() {

// Обработчик события при нажатии на кнопку Новая игра

public void actionPerformed(ActionEvent arg0) {

// Запуск игры

myGame.start();

// Забираем фокус у кнопки Новая игры

btn1.setFocusable(false);

// Забираем фокус у кнопки Выход

btn2.setFocusable(false);

// Отдаем фокус панели

pan.setFocusable(true);

}

});

add(btn1);

//Создаем кнопку Выход

btn2 = new JButton();

btn2.setText("Выход");

btn2.setForeground(Color.RED);

btn2.setFont(new Font("serif",0,20));

btn2.setBounds(630, 100, 150, 50);

btn2.addActionListener(new ActionListener() {

// Обработчик события при нажатии на кнопку Новая игра

public void actionPerformed(ActionEvent arg0) {

// Выход их игры -завершение работы приложения

System.exit(0);

}

});

add(btn2);

}

// Метод отрисовки

public void paintComponent(Graphics gr)

{

//Очищениеигровогополя

super.paintComponent(gr);

//Отрисовкафона

gr.drawImage(fon,0,0,800,650,null);

//Отрисовка игрового поля на основании массива

for (int i = 0; i < 30; i++) {

for (int j = 0; j < 30; j++) {

if (myGame.mas[i][j]!=0)

{

if (myGame.mas[i][j]==1)

{

// Выводим голову змейки в ячейку игрового поля

gr.drawImage(golova,10+j*20, 10+i*20,20,20,null);

}

else if (myGame.mas[i][j]==-1)

{

// Выводим объект для поедания в ячейку игрового поля

gr.drawImage(ob,10+j*20, 10+i*20,20,20,null);

}

else if (myGame.mas[i][j]>=2)

{

// Выводимтелозмейки

gr.drawImage(telo,10+j*20, 10+i*20,20,20,null);

}

}

}

}

//Отрисовка сетки игрового поля из синих линий

gr.setColor(Color.BLUE);

for (int i = 0; i <= 30; i++)

{

// Рисование линий сетки

gr.drawLine(10+i*20, 10, 10+i*20, 610);

gr.drawLine(10, 10+i*20, 610, 10+i*20);

}

//Вывод изображения конца игры - при окончании игры

if (myGame.endg==true)

{

gr.drawImage(endg,250,200,200,100,null);

}

}

}

Теперь перейдем в файлgame.javaи приступим к доработкам класса game. В первую очередь добавим свойства (переменные) класса,которые нам понадобятся.

public class game

{

// Двухмерный массив для хранения игрового поля

public int[][] mas;

// Текущее направление движения

public int napr;

// Координаты головы змейки

private int gX, gY;

// Количествоочков

public int kol;

// Новое направление движения

public int new_napr;

// Длиназмейки

private int dlina;

// Признак окончания игры

public boolean endg;

Мы добавили три новые переменные.

1. Переменная new_napr–это направление движения змейки, которое выбирает пользователь.

Когда змейка будет иметь не только голову, но и тело, мы заблокируем возможность изменять направление на противоположное. А в случае,когда направление выбирается тоже самое–нет смысла изменять его вообще. Другими словами,направление будет меняться, если пользователь выбирает движение влево или вправо от текущего. Сначала мы будем проверять выбор пользователя по переменной new_napr, и если оно нам подходит, то будем изменять значение переменной napr.

2. Переменная dlina будет хранить текущую длину змейки.

3. Переменная endg –это признак окончания игры,переменная логического типа.

После блока объявления переменных добавим новый метод:

// Изменение направления движения змейки (поворот)

private void povorot()

{

if (Math.abs((new_napr-napr))!=2)

{

napr=new_napr;

}

}

Метод povorot()будет анализировать выбираемое направление пользователем, хранящееся в переменнойnew_napr, и, если возможно изменить направление, переменнаяnaprбудет получать новое значение.

Math –это класс математических операций, метод abs() –вычисляет модуль числа –отбрасывает знак минус, если он есть. Если разница между текущим и выбираемым направлением не равна двум, то мы изменяем значение переменнойnapr на новое.

Теперь перейдем в методstart() изаменим две строки(см. рис. 10):

Реализация третьего уровня сложности игры Змейка - student2.ru

Рис. 10

Вместо двух строк,выделенных на рис. 10, добавим следующие:

//Создаем начальную змейку с длиной три ячейки

mas[14][14] = 1;

mas[14][15] = 2;

mas[14][16] = 3;

dlina = 3;

//Записываем в переменные координаты головы змейки

gX = gY = 14;

//Признак окончания игры

endg=false;

Начальная змейка состоит из трех клеток. Поэтому вписываем в массив три цифры от 1 до 3.

//Создаем начальную змейку с длиной три ячейки

mas[14][14] = 1;

mas[14][15] = 2;

mas[14][16] = 3;

При такой записи змейка будет горизонтальной и расположенной по центру игрового поля.

Укажем длину змейки:

dlina = 3;

В переменные gXи gYзаписываем число 14. Это координаты головы змейки.

// Признак окончания игры

endg=false;

Признак окончания игры устанавливаем в значение false, т.к. игра только начинается.

Перейдем к методуperemGolova(), этот метод мы несколько изменим. После доработки этот метод будет возвращать некоторое значение –число целого типа. Укажем тип возвращаемого значения:

// Перемещение головы змейки

public intperemGolova()

{

После перемещения головы змейки –метод будет возвращать тот или иной результат перемещения в зависимости от условия:

· если съеден объект, то 1

· если перемещение в пустую ячейку, то2

· если перемещение в тело змейки, то3

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

Удалим первую строку метода peremGolova() (см. рис.11)

Реализация третьего уровня сложности игры Змейка - student2.ru

Рис. 11

Удалять голову змейки из текущей ячейки игрового поля мы сразу не будем - блок анализа выхода за пределы экрана останется без изменений. А в конце метода peremGolova()вместо имеющегося программного кода(см. рис. 12) добавим следующий:

Реализация третьего уровня сложности игры Змейка - student2.ru

Рис. 12

//Результат

int rez=0;

// Если съеден объект

if (mas[gY][gX]==-1) rez=1;

// Если перемещение в пустую ячейку

else if (mas[gY][gX]==0) rez=2;

// Если попадание в туловище змейки

else if (mas[gY][gX]>0) rez=3;

// В место перемещения головы

//установим значение - минус два

mas[gY][gX] = -2;

//Возвращаем результат

return rez;

В переменной rezбудет находиться результат, возвращаемый методом. В зависимости от ситуации он будет равен1или 2или 3. В новую позицию для перемещения головы помещаем значение минус два:

//установим значение - минус два

mas[gY][gX] = -2;

И возвращаем результат:

//Возвращаем результат

return rez;

В новом виде методperemGolova() будет выглядеть так:

// Перемещение головы змейки

public int peremGolova()

{

// Если текущее направление влево

if (napr == 0)

{

if ((gX - 1) >= 0)

gX--;

else

gX = 29;

}

// Если текущее направление вверх

else if (napr == 1)

{

if ((gY - 1) >= 0)

gY--;

else

gY = 29;

}

// Если текущее направление вправо

else if (napr == 2)

{

if ((gX + 1) <= 29)

gX++;

else

gX = 0;

}

// Если текущее направление вниз

else if (napr == 3)

{

if ((gY + 1) <= 29)

gY++;

else

gY = 0;

}

//Результат

int rez=0;

// Если съеден объект

if (mas[gY][gX]==-1) rez=1;

// Если перемещение в пустую ячейку

else if (mas[gY][gX]==0) rez=2;

// Если попадание в туловище змейки

else if (mas[gY][gX]>0) rez=3;

// В место перемещения головы

//установим значение - минус два

mas[gY][gX] = -2;

//Возвращаем результат

return rez;

}

Нам осталось добавить последний метод, который будет перемещать тело змейки. Метод будет называться perem(), именно этот метод вызывается таймером изменения логики игры.

Наши рекомендации