Как написать игру на Java (для PC)?

Доброго времени суток всем!
В этой статье будет описываться создание 2D игры на Java. Сразу предупреждаю, вы должны хотя бы базово знать язык Java, поскольку на подробное объяснение каждой строки у меня нету времени. И очень прошу вас, не списывать просто код, а пытаться понять что означает каждая строка, и писать со смыслом. И еще, я использую Eclipse, но вы можете использовать любой IDE.

Задача:

Я планирую создать игру, напоминающую шутер с видом от 3 лица.

Начало:

Ну что, приступим!
Для начала создадим проект. Назовем его «Just game». И сразу создаем класс Display.java. В него пишем:
public static void main(String args) { JFrame frame = new JFrame(/* название нашей игры */); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setExtendedState(JFrame.MAXIMIZED_BOTH); frame.setUndecorated(true); frame.setVisible(true); }
Теперь разберемся, что мы сделали.
JFrame frame = new JFrame(/*название нашей игры*/);
мы создаем рамку, которая и будет отображаться при запуске нашей игры
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
устанавливаем операцию, которая будет происходить при нажатии на крестик. EXIT_ON_CLOSE — выйти из программы
frame.setExtendedState(JFrame.MAXIMIZED_BOTH); frame.setUndecorated(true);
устанавливаем нашей рамке максимальные размеры, убираем декорации(кнопки свернуть, закрыть, уменьшить/увеличить и т.п.), т.е. делаем игру на весь экран. Если вы хотите, чтобы игра не была бы на весь экран, то используйте:
frame.setSize(/*ширина*/,/*высота*/); frame.setResizable(false); //false чтобы нельзя было бы поменять размеры рамки, true -можно
frame.setVisble(true); — делаем рамку видимой
Только не забудьте, все настройки рамки надо писать до того, как вы сделаете её видимой
Ну чтож, теперь нажимаем «Run» и пробуем запустить нашу игру. Если все написано правильно, у вас не должны возникать ошибки и должно появиться пустое, серое окно.
Серое окно… Как скучно… Давайте создадим что-нибудь поинтереснее.
Создадим новый класс, под названием «Main». Main класс у нас будет являться панелью, которую мы вставим в рамку, по этому он должен расширять JPanel. (Для тех, кто не знает, расширять пишется как extends после названия класса)
Возвращаемся в класс Display и после настроек рамки, но перед установлением её видимости, пишем:
frame.add(new Main());
Вы спросите — «Ну и зачем мы это сделали?». Представьте себе картину. Эта картина и является конечная наша игра. А теперь представьте рамку. Без ничего внутри, просто пустую рамку. На ней ничего нельзя нарисовать, она бесполезна. Для этого, мы вставили в картину пустой лист, на котором программа в дальнейшем может рисовать картину. На этом закончим наше лирическое отступление и вернемся к классу Main.
Нам нужно осуществить отрисовку, по этому мы должны добавить метод paint. Для этого пишем:
public void paint(Graphics g) { //отрисовка всех объектов }
Ну и для начала, можем написать внутри этого метода отрисовку линии. Для этого пишем:
g.drawLine(20, 20, 100, 100);
Теперь запускаем программу, и видим:

Даааааа, не густо…

Давайте отрисуем какую-нибудь картинку. Например эту:

Для начала, нам нужно указать путь к картинке. Для этого не в методе paint, пишем:
Image img = new ImageIcon(«2.png»).getImage();
(предварительно надо в наш проект скинуть картинку и назвать ее 2.png)
После этого удаляем строчку отрисовки линии, а вместо нее в метод paint пишем:
g.drawImage(img, 0, 0, null);
Разберемся поближе с методом drawImage, так как мы будем часто его затрагивать.
drawImage(картинка которую мы будем рисовать, которую мы объявили раннее, координата X с которой будет рисоваться картинка, координата Y с которой будет рисоваться картинка, paint);
Отдельно хочу поговорить о параметре paint. Лучше всего оставляйте его null. Я только однажды сталкивался, когда мне нужно было использовать paint. Это было когда я отрисовывал текст, и задавал ему размер шрифта. Но советую не лезть туда и использовать null.
Теперь запускаем программу, и видим:

Чего-то она маленькая, не правда ли? Давайте научимся увеличивать её размеры. Добавляем к drawImage() параметры так, чтобы вышло:
g.drawImage(img, 0, 0, 1920, 1080, null);
Что мы сейчас добавили? Эти два параметра растягивают картинку, до координат 1920 и 1080. Получается, что картинка на весь экран. Давайте запустим программу и это проверим.
Получается:

Ну наконец-то. Теперь мы умеем любые картинки растягивать на весь экран. Но вот проблема. Метод paint вызывается только один раз. И как же его обновлять постоянно? Для этого существует очень полезная вещь — таймер. Давайте создадим его.
Для этого пишем:
Timer timer = new Timer(20, this);
(20 это частота с которой обновляется таймер, this- где выполнять метод при обновлении таймера
Это мы должны вписать сразу после строки определения класса, т.е. после:
public class Main extends JPanel{
Также, надо дополнить строку определения класса таким образом:
public class Main extends JPanel implements ActionListener{
После прописывания этой строки, у вас название класса должно подчеркнуться красным. Чтобы это исправить, в самом конце класса добавьте метод:
@Override public void actionPerformed(ActionEvent e) { // TODO Auto-generated method stub }
Этот метод будет выполняться при обновлении таймера. В него мы должны написать repaint(); чтобы при каждом обновлении таймера у нас все элементы бы стирались, и нарисовывались заново.
Дальше, мы должны запустить таймер. Для этого, создаем конструктор класса Main и в него пишем:
timer.start();
После этого, можете не запускать программу, ведь в ней ничего не изменится. Давайте заменим текстуру домика на нормальную текстуру карты. Её вы можете нарисовать сами, либо скопировать у меня пробную:

Размер картинки может быть любой, все равно её размер будет подгоняться прямо в программе. Ах да, разрешения компьютеров могут быть разные, так что добавим-ка в конструктор такие вещи:
public Main(JFrame frame) { timer.start(); this.frame = frame; }
И перед конструктором добавим:
JFrame frame;
И сходим еще в класс Display.java и там немного изменяем метод frame.add:
frame.add(new Main(frame));
Таким образом, наша рамка будет передаваться в класс Main.java. Переходим в этот класс, и там где у нас метод paint() меняем строку drawImage() на:

g.drawImage(img, 0, 0,frame.getWidth(), frame.getHeight(), null);
Таким образом, теперь наша игра будет отрисовывать картинку на весь экран, в независимости от его разрешения. Запускаем:

На сегодня все. Оставляю код, для тех, кто запутался:

Display.java

import javax.swing.JFrame; public class Display { public static void main(String args) { JFrame frame = new JFrame(«JustGame»); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setExtendedState(JFrame.MAXIMIZED_BOTH); frame.setUndecorated(true); frame.add(new Main(frame)); frame.setVisible(true); } }

Main.java

import java.awt.Graphics; import java.awt.Image; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; public class Main extends JPanel implements ActionListener{ Image img = new ImageIcon(«2.png»).getImage(); Timer timer = new Timer(20, this); JFrame frame; public Main(JFrame frame) { this.frame = frame; } public void paint(Graphics g) { g.drawImage(img, 0, 0,frame.getWidth(), frame.getHeight(), null); } @Override public void actionPerformed(ActionEvent e) { // TODO Auto-generated method stub repaint(); } }
Спасибо за внимание!

Эта статья рассчитана на новичков в программировании. Её цель — объяснить азы языка Java (классы, методы, компиляция, байт-код, JVM, переменные, условия, циклы, импорт). Этот материал поможет:

  • научиться писать код, компилировать и запускать байт-код;
  • познакомиться с переменными;
  • узнать об условных операторах и циклах;
  • освоить использование классов из стандартных пакетов;
  • написать простую консольную игру «Угадай число».

Java Development Kit (JDK)

В строку поиска Google вводим запрос: “jdk 8 download”, первая же строка поисковой выдачи даст нам ссылку на страницу загрузки на сайте oracle.com. Листаем вниз, щёлкаем по радио-кнопке “Accept License Agreement”, соглашаясь с лицензией, и выбираем пакет в колонке “Download”, который соответствует вашей операционной системе и её разрядности (32/64). Для скачивания потребуется регистрация на сайте (нужно будет указать логин и e-mail). Скачиваем и устанавливаем. На этапе выбора пакетов и папки JDK рекомендуется отказаться от Public JRE и сократить имя папки до “jdk1.8”.

Счастливым обладателям Windows потребуется также настройка системных переменных. Как их найти: Панель управления → Система → Дополнительные параметры системы → Вкладка «Дополнительно» → кнопка «Переменные среды», второе окошко сверху. В нем надо создать переменную JAVA_HOME и изменить (дополнить) Path (для этого используются кнопки «Создать» и «Изменить». Значение для переменной JAVA_HOME содержит путь к папке JDK. Переменную Path дополняем значением %JAVA_HOME%\bin. Обратите внимание, что список путей разделяется точкой с запятой (;). Инструкция есть также на сайте Oracle.

JDK содержит много утилит, но нам потребуется только компилятор (javac) и виртуальная машина Java — JVM (java).

Проверить правильность установки и настройки можно в командной строке (на Windows: Win + R → cmd) или в терминале (Linux, Mac), набрав следующие команды:

javac -version java -version

В качестве альтернативы можно использовать онлайн-JDK вместе с редактором, например на сайте Repl.it.

Написание кода

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

Для Windows хорошим выбором будет Notepad++ с плагином NppExec. Для Linux и Мас существуют другие текстовые редакторы, например Atom или Geany. Подробно об их настройке можно прочитать в статье «Пишем код на Java и Go в текстовом редакторе».

Hello, world!

При изучении нового языка писать программу, выводящую «Hello, world!», — это традиция. Она возникла в 1978 году, с выходом в свет книги «Язык программирования С» Брайана Кернигана и Денниса Ричи.

class HelloWorld { public static void main(String args) { System.out.println(«Hello, world!»); } }

Код начинается с ключевого слова class. Java-программы состоят из классов — это «кирпичики» приложения. В реальных приложениях их могут быть десятки, сотни и тысячи. Затем пишется имя класса — HelloWorld. Имена классов принято писать в стиле CamelCase (все слова слитно, каждое с заглавной буквы). Классам, как и другим сущностям — методам, полям и переменным — рекомендуется давать мнемонические, «говорящие» имена.

После имени в фигурных скобках записывается тело класса. Оно может содержать поля, методы и другие классы. Поля хранят данные. Они подобны переменным, однако в Java это несколько иная сущность. Методы содержат набор команд или код. Имена методов (как и полей, переменных) принято писать в стиле camelCase, со строчной буквы.

В нашем классе только один метод — main(). Он особенный, его часто называют стартовым. Когда виртуальная машина Java получает класс для исполнения, она ищет в нём метод main. Если находит — выполняет код, который содержит метод. Если нет — выдаёт сообщение об ошибке. Важно, чтобы метод main() имел строго определённую сигнатуру: был публичным (public), статическим (static), не возвращающим никакого значения (void), с параметром в виде массива строк (String args). В противном случае виртуальная машина его просто не найдёт.

Наш метод main() содержит лишь одну строку, которая выводит в консоль текст «Hello, world!».

Перед компиляцией сохраняем написанный код в файл. Имя файла должно соответствовать имени класса с точностью до регистра символов (Java — регистрозависимый язык). Затем открываем командную строку (или терминал) и вызываем сначала компилятор (javac), а потом виртуальную машину JVM (java).

C:\Users\User>javac HelloWorld.java C:\Users\User>java HelloWorld Hello, world!

После компиляции возникнет бинарный файл HelloWorld.class, содержащий байт-код. Имя этого файла (без расширения) передаётся как параметр при вызове виртуальной машины. В результате видим строку «Hello, world!» — это результат выполнения программы.

Переменные

Переменные хранят данные (числа, строки, логические значения и другие). Они похожи на коробочки: надписи на них — это имя и тип переменной, а внутри находится значение. При этом возможны две операции:

  • запись значения в переменную (или присвоение значения);
  • чтение значения из переменной.

В Java перед использованием переменную нужно обязательно объявить, указав тип и имя, поскольку Java — язык со строгой и статической типизацией. Попытка использовать переменную без объявления приведёт к ошибке компиляции. По типу переменные могут быть простыми (числа, символы, логические значения) или ссылочными (массивы, строки, любые другие объекты). В Java есть восемь простых типов (byte, int, short, long, float, double, boolean, char) и бесконечное количество ссылочных.

В примере ниже мы видим объявление двух переменных целого типа (int). Причём переменная second инициируется при объявлении. Затем переменной first присваивается результат выражения, в котором присутствует переменная second. В результате выполнения программы в консоль будут выведены значения обеих переменных: 35 и 10.

class Variables { public static void main(String args) { int first; int second = 10; first = second * 3 + 5; System.out.println(first); System.out.println(second); } }

Имя переменной может содержать буквы и цифры, знак подчёркивания и $. Имя не должно начинаться с цифры. В профессиональной разработке в именах переменных используют только буквы. Имя должно явно указывать на назначение переменной. Например: countString — счётчик строк, errorSign — признак ошибки. Рекомендуется избегать однобуквенных имён — за исключением временных переменных (существующих в рамках цикла или небольшого кодового блока).

Проверка условий

Любой язык программирования высокого уровня позволяет изменять порядок выполнения программы в зависимости от заданного условия. В Java для этого используется конструкция if. После этого ключевого слова в скобках записывается логическое выражение, затем в фигурных скобках — группа команд. Если результат логического выражения — true (истина), то эта группа команд выполняется, если false (ложь) — нет.

class ComparisonShort { public static void main(String args) { int i = 10; if (i == 10) { System.out.println(«i = 10»); } } }

Результат этого примера — строка “i = 10” в консоли, так как результат логического выражения (проверка на равенство) будет true.

В качестве знаков сравнения допустимы: <, <=, >, >=, ==, !=. Добавив ключевое слово else, получаем полную форму оператора сравнения.

class ComparisonFull { public static void main(String args) { int i = 10 + 2; if (i == 10) { System.out.println(«i = 10»); } else { System.out.println(«i != 10»); } } }

Выполнив эту программу, мы увидим в консоли сообщение “i != 10”, так как результат сравнения значения переменной i и цифры 10 будет false. Поэтому выполнится набор команд в фигурных скобках после слова else.

Циклы

Циклы предназначены для повторения группы команд. Java позволяет использовать два вида циклов:

  • с определённым количеством итераций: for;
  • без определенного количества итераций: while и do-while.

class CycleFor { public static void main(String args) { for (int i = 0; i < 5; i++) { System.out.println(i); } } }

Запустив этот пример, мы увидим в консоли колонку чисел от 0 до 4. Это результат работы классического цикла for со счётчиком. В круглых скобках три параметра цикла, разделённые точкой с запятой: объявление и инициализация счётчика, проверка условия продолжения цикла, действие после каждой итерации. В фигурных скобках — тело цикла. В нашем случае это команда, выводящая значение счётчика i в консоль.

Когда будем писать игру, используем циклы while и do-while — там и увидим их работу на практике. Пока достаточно сказать, что их синтаксис проще for, а тело цикла повторяется до тех пор, пока логическое выражение в круглых скобках после while возвращает true.

Пример ниже иллюстрирует вторую форму цикла for — без счётчика. Такой вариант удобен при переборе элементов массива или списка.

class CycleForeach { public static void main(String args) { int arr = {5, 4, 3, 2, 1}; for (int a : arr) { System.out.println(a); } } }

Импорт и использование классов

Один из важных принципов программирования — DRY: Don’t Repeat Youself (не повторяйся). Разработчик должен уметь использовать то, уже создано, отлажено и работает.

Java поставляется с набором packages (пакетов). Они содержат скомпилированные классы, сгруппированные по тематике. На неё указывает имя пакета: java.io, java.util, java.net и так далее.

В игре нам потребуется случайное число от 0 до 9. Писать генератор случайных чисел незачем, так как в пакете java.util есть готовый класс Random. Чтобы использовать класс из пакета, его нужно импортировать. Строки с директивами импорта располагаются в самом начале файла класса.

import java.util.Random;

Создадим объект на основании класса, чтобы можно было вызвать нужный метод. Слева от знака присваивания (=) объявляем ссылочную переменную (объект) random типа Random. Справа создаём объект с помощью директивы new.

Random random = new Random();

Вызываем метод через точку после имени объекта: random.nextInt(10). Цифра 10 ограничивает диапазон генерируемых чисел (от 0 до 9). В результате получим строку из 30 случайных чисел. Запустите программу несколько раз и обратите внимание, что последовательность чисел не повторяется.

import java.util.Random; class RandomInt { public static void main(String args) { Random random = new Random(); for (int i = 0; i < 30; i++) { System.out.print(random.nextInt(10) + » «); } } }

Игра «Угадай число»

Мы узнали достаточно, чтобы написать игру «Угадай число». В ней компьютер «загадывает» целое число от 0 до 9, а человек должен угадать его — есть три попытки.

Создадим класс GuessTheNumber с методом main(), импортируем нужные нам классы и добавим соответствующие объекты. Класс Scanner обеспечит чтение чисел с консоли (System.in — консоль).

import java.util.Scanner; import java.util.Random; class GuessTheNumber { public static void main(String args) { Random random = new Random(); Scanner sc = new Scanner(System.in); } }

Объявим и инициируем три переменные: счётчик попыток count, для чтения числа с консоли — guess, для хранения загаданного числа — number.

int count = 0; int guess = -1; int number = random.nextInt(10);

Опишем основной игровой цикл, используя while. В цикле запрашиваем число с консоли, сравниваем с загаданным. В случае несовпадения выводим подсказку и увеличиваем счётчик попыток на 1.

while (count < 3 && guess != number) { System.out.print(«Guess the number (0..9): «); guess = sc.nextInt(); if (number != guess) { System.out.println(«Your number is » + ((guess > number)? «greater» : «less»)); count++; } }

Тело цикла while повторяется, пока логическое значение в скобках возвращает true. Таким образом наш цикл будет повторяться, пока значение счётчика попыток меньше 3 и число не отгадано. Обратите внимание на конструкцию, которая позволяет выбрать одно из двух значений согласно условию в скобках. Это тернарный оператор. Если условие в скобках true, то возвращается значение слева от двоеточия, если false — справа.

(guess > number)? «greater» : «less»

Остаётся добавить строку с выводом результата игры (победа или проигрыш) после цикла — тут снова используется тернарный оператор. Текст работающей программы выглядит так:

import java.util.Scanner; import java.util.Random; class GuessTheNumber { public static void main(String args) { Random random = new Random(); Scanner sc = new Scanner(System.in); int count = 0; int guess = -1; int number = random.nextInt(10); while (count < 3 && guess != number) { System.out.print(«Guess the number (0..9): «); guess = sc.nextInt(); if (number != guess) { System.out.println(«Your number is » + ((guess > number)? «greater» : «less»)); count++; } } System.out.println(«You » + ((guess == number)? «WIN!» : «Lose: » + number)); } }

Сохраняем программу в файл GuessTheNumber.java, компилируем (javac), вызываем JVM (java) — и пробуем выиграть у компьютера.

Добавим заключительный штрих: используя цикл do-while, позволим пользователю повторять сеанс игры без перезапуска программы. В приведённом ниже коде комментарий нужно заменить на соответствующий фрагмент из предыдущей программы. Попробуйте сделать это самостоятельно.

import java.util.Random; import java.util.Scanner; class GuessTheNumber { public static void main(String args) { Random random = new Random(); Scanner sc = new Scanner(System.in); do { // // Вставьте фрагмент, который должен повторяться // System.out.println(«Repeat game? Yes — 1, No — 0»); } while (sc.nextInt() == 1); } }

Обратите внимание, что тело цикла do-while повторяется на тех же условиях, что и тело цикла while: логическое выражение в скобках должно быть true.

На всякий случай прилагаю мой telegram — @biblelamp. Если вас заинтересовала тема, рекомендую почитать «Java-программирование для начинающих» Майка МакГрата и «Изучаем Java» Кэти Сьерра и Берт Бейтс. В следующих статьях мы продолжим начальное изучение Java на примере создания других игр. Следите за блогом!

Другие статьи из серии «Быстрый старт с Java»:

  • «Быстрый старт с Java: крестики-нолики»
  • «Быстрый старт с Java: «лопни шарик»

Если язык Java вас заинтересовал — приглашаем на факультет Java-разработки. Если ещё не совсем уверены — посмотрите истории успеха наших Java-выпускников:

  • Приключения бравого джависта в поисках пропитания. Как начать карьеру программиста Java, если в твоём городе пять вакансий и везде требуют опыт от года
  • «Иногда за сутки я спал один час в метро перед работой». Из белорусской типографии — в московские тимлиды.
  • Первая работа в IT с переездом в Москву: как это бывает. Опыт собеседований, тестовых заданий, учебных проектов и трудоустройства в международную компанию.
  • Взгляд изнутри: как работается в «Альфа-Банке». Рассказывает Михаил Степнов, выпускник GeekUniversity и программист банка.

Привет, друзья и будущие коллеги! Совсем недавно я проходил тестирование для участия в реальном проекте, прошел его, но так уж сложилось, что я по личным обстоятельствам не смог принять участие в самом РП. После таких интересных задач, как тест на РП, обычные задачи курса стали менее привлекательным времяпрепровождением, тем более, большую часть я уже решил. Поэтому чтобы талант не пропадал зря продолжать обучение, я решил создать многопользовательскую веб-игру. Ссылки на другие игры:

  1. продолжение этой статьи
  2. 2048

Самой простой игрой мне показались крестики нолики, я решил разбить задачу на ряд подзадач:

  1. Консольное приложение для отработки игровой логики
  2. Мультиплеер
  3. Прикручивание базы данных игроков к консольному приложению
  4. Создание дизайна фронтенда, написание шаблонов страниц, игрового интерфейса
  5. Сборка «всего» воедино

Есть вероятность, что меня поругают, за такую последовательность, и скорей всего все серьезные проекты строятся в совсем иной последовательности, отвечу сразу, напишите об этом пост «для начинающих», чтобы все (и я в том числе) научились этому 🙂 Чтож.. приступим к написанию консольного приложения! Я пойду по тем же шагам, что и в больших задачах 20-х уровней. Что есть в игре крестики-нолики?!

  1. поле
  2. два игрока, которые ходят по очереди, один ставит крестик, второй нолик. всё просто.

Поле делаем стандартное поле 3х3. В чем можно хранить такое поле? первый вариант — двумерный массив. Какие элементы должны содержаться в этом массиве? ответ – нужно подумать, что мы будем делать с этими элементами, это вывод на экран и сравнение для поиска победителя. Если бы мы их только выводили на экран, то логично было бы держать их в виде строки, тогда сам массив и вывод на экран в цикле выглядели бы как-то так: String strings = {{«O», «O», «_»}, {«_», «X», «O»}, {«X», «X», «X»}, for (String ss : strings){ for (String s : ss) System.out.print(s + » «); System.out.println(); //для перевода строки } на экране бы отобразилось: O O _ _ X O X X X Но кроме отображения, у нас есть еще сравнение значений, а тут уже возможны варианты. Можно сравнивать строки, можно создать специальный класс-перечисление (enum), но я предпочел бы сравнивать числа, а на «Х» и «О» заменять их только при выводе на экран. Пусть будет, например, 1 — «Х», 2 — «О», 0 — «_». итак, как же проверять поле на тройное совпадение Х или О?
Самый первый алгоритм — это проверка всего поля int canvas = {{00, 01, 02}, {10, 11, 12}, {20, 21, 22}} Комбинации для выигрыша: 00-01-02, 10-11-12, 20-21-22, 00-10-20, 01-11-21, 02-12-22, 00-11-22, 20-11-02 — всего 8. Проверка сравнением цифр, но это получается нужно каждый раз проверять ВСЁ поле, все 8 комбинаций. Конечно же, это не много, это не поиск чисел Армстронга в интервале от 0 до 1 млрд, здесь вычислений чуть более чем нет совсем, но всё равно хочется что-то более оптимальное, чем проверка всего поля. Вторая идея которая меня посетила, это проверять только ячейку, которую отметили на предыдущем ходе, так еще можно определить победителя, ведь мы будем знать кто сделал этот ход. Таким образом, вместо всех 8 комбинаций мы получаем всего 2, 3 или 4 комбинации, в зависимости от ячейки, см. рисунок: теперь нужно придумать, как определить какую комбинацию нужно запустить? Вот тут я понял, что использовать двухмерный массив не очень удобно. Я решил рассмотреть еще варианты. Сначала я придумал, что поле можно держать в девятизначной цифре, например, то самое поле, которое мы вывели на экран можно записать так 220012111, объясню на пальцах что это … Шифр прежний, 1 – «Х», 2 – «О», 0 – » «, значит 220012111 = «OO__XOXXX», или если после каждой третьей цифрой воткнуть перенос строки и добавить пробелы для наглядности: О О _ _ Х О Х Х Х вот опять, удобно для хранения, приспособу для отображения придумали, но неудобно для сравнения! Решение нашлось когда я пронумеровал ячейки 1-9, потом подумал, ведь в программировании отсчет начинается с 0 и пронумеровал как на картинке Не заметили никаких особенностей? если посмотреть на картинку выше, то станет ясно, что решения имеющие 2 комбинации, имеют нечетный порядковый номер, 4 комбинации — это порядковый номер 4, 3 комбинации – остальные. так я и пришел к тому, что нужно держать игровое поле в обычном массиве чисел: простая итерация между числами, возможность сравнения по алгоритму, который был выбран, простой вывод на экран. Что касается самого алгоритма сравнения. идем по порядку: во всех вариантах есть проверка строки и столбца, проверяем только их. если поиск не дел результатов, проверяем номер ячейки на чёт/нечёт, если нечетная то возвращаемся к игре, нет смысла проверять дальше, если четная, проверяем лежит ли на левой диагонали эта ячейка, номера этой диагонали при делении на 4 в остатке имеют 0. Если она лежит проверяем на совпадения, если совпадений не найдено, то проверяем на цифру 4, если нет – возврат в игру, если да идем дальше по коду и возвращаем результат проверки последней диагонали. Вероятно, для неподготовленного человека, это сложно понять прочитав набор букв выше, а кто-то может сказать, что много букв и в самом коде, что можно проще, буду рад обсудить это. С полем разобрались, теперь нужно разобраться с двумя пользователями, которые ходят по очереди и у каждого из них свой знак, Х или О. На первом этапе у нас нет никакой многопользовательности, значит проще всего будет использовать значки по очереди. Первый ход делает всегда Х, второй всегда О, потом снова Х и так далее. Напрашивается поставить флажок (true/false), и если true – то текущий игрок X, если false – то О и вначале каждого хода флажок=!флажок Осталось как-то принимать сигнал от игроков, о том какую ячейку они выбирают. Тут нам пригодится наш незабвенный BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); Игроки будут вводить номера ячеек, в консоль, и по нажатию Enter будет производиться ход. Ячейка соответствующая введенному номеру, будет менять значение с 0 на 1 или 2, в зависимости от текущего состояния флажка, который обсуждался абзацем выше. Вот тут важно сделать валидацию ввода, чтобы никто не смог поменять Х на О, когда ячейка уже заполнена 🙂 Что может ввести в консоль игрок?

  1. пустая строка
  2. буквы, знаки препинания, скобки.. одним словом неЦифры
  3. некорректные цифры — отрицательные или находящиеся за пределами размеров массива, занятые ячейки.

Стандартный метод получения цифры из строки это статический метод parseInt класса Integer Integer.parseInt(«2»);Он бросает исключение NumberFormatException, если не может получить цифру из заданной строки, защиту от первых двух пунктов мы сможем обеспечить перехватом этого исключения. Для третьего пункта я бы создал еще один метод, который проверяет введенное значение, но правильнее всего будет вынести запрос строки в отдельный метод, в котором будет производиться валидация, а возвращать он будет только число. Резюмируем, мы создали поле, сделали метод, который его отображает, сделали метод, который производит проверку «а не победил ли этот игрок часом?», сделали валидацию вводимых чисел. Осталось совсем немного, сделать проверку на ничью — отдельный метод, который пробегает по массиву и ищет 0, и отображение результатов игры. На этом всё, код готов, игра получилась небольшой, всего один класс, поэтому жесткие копипастеры могут не разбираясь, просто всё скопировать в свой проект и запустить его у себя, я и сам таким был, но сейчас стараюсь уже так не делать и никому не советую 🙂 Всем удачи в изучении JAVA! p.s. остальные пункты — мультиплейер и БД будут позже, я уже начал изучение материала 🙂 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class GameField { static int canvas = {0,0,0, 0,0,0, 0,0,0}; //012, 345, 678, 036, 147, 258, 048, 246 public static void main(String args){ boolean b; boolean isCurrentX = false; do { isCurrentX = !isCurrentX; drawCanvas(); System.out.println(«mark » + (isCurrentX ? «X» : «O»)); int n = getNumber(); canvas = isCurrentX ? 1 : 2; b = !isGameOver(n); if (isDraw()){ System.out.println(«Draw»); return; } } while (b); drawCanvas(); System.out.println(); System.out.println(«The winner is » + (isCurrentX ? «X» : «O») + «!»); } static int getNumber(){ BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); while (true){ try { int n = Integer.parseInt(reader.readLine()); if (n >= 0 && n < canvas.length && canvas==0){ return n; } System.out.println(«Choose free cell and enter its number»); } catch (NumberFormatException e) { System.out.println(«Please enter the number»); } catch (IOException e) { } } } static boolean isGameOver(int n){ // 0 1 2 // 3 4 5 // 6 7 8 //поиск совпадений по горизонтали int row = n-n%3; //номер строки — проверяем только её if (canvas==canvas && canvas==canvas) return true; //поиск совпадений по вертикали int column = n%3; //номер столбца — проверяем только его if (canvas==canvas) if (canvas==canvas) return true; //мы здесь, значит, первый поиск не положительного результата //если значение n находится на одной из граней — возвращаем false if (n%2!=0) return false; //проверяем принадлежит ли к левой диагонали значение if (n%4==0){ //проверяем есть ли совпадения на левой диагонали if (canvas == canvas && canvas == canvas) return true; if (n!=4) return false; } return canvas == canvas && canvas == canvas; } static void drawCanvas(){ System.out.println(» | | «); for (int i = 0; i < canvas.length; i++) { if (i!=0){ if (i%3==0) { System.out.println(); System.out.println(«_____|_____|_____»); System.out.println(» | | «); } else System.out.print(«|»); } if (canvas==0) System.out.print(» » + i + » «); if (canvas==1) System.out.print(» X «); if (canvas==2) System.out.print(» O «); } System.out.println(); System.out.println(» | | «); } public static boolean isDraw() { for (int n : canvas) if (n==0) return false; return true; } }

Введение и подготовка

Привет хаброжители. Данный пост является «рерайтом» моего поста для песочницы. На этот раз я постараюсь охватить больше тем, чем тогда.

Почему Java?

Ничего объективного я тут не скажу, а скажу лишь то, что я люблю этот язык, и мне нравиться писать на нем. Да, на Java нет игр AAA-класса, но Java предоставляет огромные возможности, больше кол-во встроенных средств и быстроту написания кода.

IDE

Начнем с выбора IDE. Я являюсь фанатом Eclipse и посоветую вам его.
Если же почему-то вам он не понравился, вы можете использовать NetBeans, Intellij IDEA или командную строку и ваш любимый редактор.

JDK

И скачаем JDK последней версии: JDK 7u4
Скорее всего проблем с установкой IDE у вас не возникнет, а если у вас 64-битная система, все же посоветую устанавливать 32-битный Eclipse, так как иногда бывают ошибки и Eclipse у вас просто не запустится.
Под катом мы приступим к созданию игры.

Класс Game

Итак, создаем проект, в нем класс Game(попутно создав в нем точку входа). Данный класс должен наследовать класс Canvas и реализовать интерфейс Runnable:
public class Game extends Canvas implements Runnable { private static final long serialVersionUID = 1L; public void run() { //функция run появляется после того, как мы добавили «implements Runnable» } public static void main(String args) { } }
Создадим переменную running типа Boolean, которая, как вы уже догадались будет показывать нам запущена ли игра, или нет.
Создадим функцию start() и в ней мы будем создавать новый поток и переводить running в true:
public void start() { running = true; new Thread(this).start(); }
Создадим три функции — update(long delta), render() и init(). Я надеюсь что их значение вам понятно. В функции run() создадим главный игровой цикл, перед ним будем вызывать init(), а в нем самом render() и update(). Так же мы будем вычислять разницу между кадрами(delta time).
public void run() { long lastTime = System.currentTimeMillis(); long delta; init(); while(running) { delta = System.currentTimeMillis() — lastTime; lastTime = System.currentTimeMillis(); update(delta); render(); } } public void init() { } public void render() { } public void update(long delta) { }
Пока поработаем над функцией render().
public void render() { BufferStrategy bs = getBufferStrategy(); if (bs == null) { createBufferStrategy(2); //создаем BufferStrategy для нашего холста requestFocus(); return; } Graphics g = bs.getDrawGraphics(); //получаем Graphics из созданной нами BufferStrategy g.setColor(Color.black); //выбрать цвет g.fillRect(0, 0, getWidth(), getHeight()); //заполнить прямоугольник g.dispose(); bs.show(); //показать }
Вам наверное уже не терпится запустить и попробовать, но не спешите. Мы должны создать фрейм и добавить наш холст на него. Заодно и объявим три переменных.
public static int WIDTH = 400; //ширина public static int HEIGHT = 300; //высота public static String NAME = «TUTORIAL 1»; //заголовок окна public static void main(String args) { Game game = new Game(); game.setPreferredSize(new Dimension(WIDTH, HEIGHT)); JFrame frame = new JFrame(Game.NAME); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //выход из приложения по нажатию клавиши ESC frame.setLayout(new BorderLayout()); frame.add(game, BorderLayout.CENTER); //добавляем холст на наш фрейм frame.pack(); frame.setResizable(false); frame.setVisible(true); game.start(); }
Примерно вот так выглядит наш класс Game сейчас.

Класс Sprite

Создадим новый класс Sprite. Поскольку этот класс небольшой, я сразу приведу весь его код с комментариями:
import java.awt.Graphics; import java.awt.Image; public class Sprite { private Image image; //изображение public Sprite(Image image) { this.image = image; } public int getWidth() { //получаем ширину картинки return image.getWidth(null); } public int getHeight() { //получаем высоту картинки return image.getHeight(null); } public void draw(Graphics g,int x,int y) { //рисуем картинку g.drawImage(image,x,y,null); } }
Сразу же проверим работоспособность. Возьмем эту картинку и скопируем ее в папку с нашим классом Sprite. Добавим функцию getSprite() в класс Game(временно).
public Sprite getSprite(String path) { BufferedImage sourceImage = null; try { URL url = this.getClass().getClassLoader().getResource(path); sourceImage = ImageIO.read(url); } catch (IOException e) { e.printStackTrace(); } Sprite sprite = new Sprite(Toolkit.getDefaultToolkit().createImage(sourceImage.getSource())); return sprite; }
Добавим нашу картинку в папку assets(папку создать в корне проекта), саму папку надо добавить в build path.
Далее создаем переменную hero типа Sprite. В функции init() инициализируем ее. В Функции render() рисуем:
//в «шапку» public static Sprite hero; //в init() hero = getSprite(«man.png»); //в render() после g.fillRect(0, 0, getWidth(), getHeight()); hero.draw(g, 20, 20);
Результат:
Весь код Game.java

Input

Для обработки инпута мы создадим класс, наследующий KeyAdapter:
private class KeyInputHandler extends KeyAdapter { }
Тут же и объявим две переменных в шапке класса Game:
private boolean leftPressed = false; private boolean rightPressed = false;
Внутри класса KeyInputHandler создадим две функции:
public void keyPressed(KeyEvent e) { //клавиша нажата if (e.getKeyCode() == KeyEvent.VK_LEFT) { leftPressed = true; } if (e.getKeyCode() == KeyEvent.VK_RIGHT) { rightPressed = true; } } public void keyReleased(KeyEvent e) { //клавиша отпущена if (e.getKeyCode() == KeyEvent.VK_LEFT) { leftPressed = false; } if (e.getKeyCode() == KeyEvent.VK_RIGHT) { rightPressed = false; } }
Теперь в функции init() добавим следующее:
addKeyListener(new KeyInputHandler());
Создадим переменные x и y для героя(так как пока что мы еще не написали класс Entity). Сделаем чтобы герой всегда рисовался на этих координатах.

Почему Java?
Ничего объективного я тут не скажу, а скажу лишь то, что я люблю этот язык, и мне нравиться писать на нем. Да, на Java нет игр AAA-класса, но Java предоставляет огромные возможности, больше кол-во встроенных средств и быстроту написания кода.
IDE
Начнем с выбора IDE. Я являюсь фанатом Eclipse и посоветую вам его.
Если же почему-то вам он не понравился, вы можете использовать NetBeans, Intellij IDEA или командную строку и ваш любимый редактор.
JDK
И скачаем JDK последней версии: JDK 7u4

Скорее всего проблем с установкой IDE у вас не возникнет, а если у вас 64-битная система, все же посоветую устанавливать 32-битный Eclipse, так как иногда бывают ошибки и Eclipse у вас просто не запуститься.
Далее мы приступим к созданию игры.

Класс Game

Итак, создаем проект, в нем класс Game(попутно создав в нем точку входа). Данный класс должен наследовать класс Canvas и реализовать интерфейс Runnable:
public class Game extends Canvas implements Runnable { private static final long serialVersionUID = 1L; public void run() { //функция run появляется после того, как мы добавили «implements Runnable» } public static void main(String args) { } }
Создадим переменную running типа Boolean, которая, как вы уже догадались будет показывать нам запущена ли игра, или нет.
Создадим функцию start() и в ней мы будем создавать новый поток и переводить running в true:
public void start() { running = true; new Thread(this).start(); }
Создадим три функции — update(long delta), render() и init(). Я надеюсь что их значение вам понятно. В функции run() создадим главный игровой цикл, перед ним будем вызывать init(), а в нем самом render() и update(). Так же мы будем вычислять разницу между кадрами(delta time).
public void run() { long lastTime = System.currentTimeMillis(); long delta; init(); while(running) { delta = System.currentTimeMillis() — lastTime; lastTime = System.currentTimeMillis(); update(delta); render(); } } public void init() { } public void render() { } public void update(long delta) { }
Пока поработаем над функцией render().
public void render() { BufferStrategy bs = getBufferStrategy(); if (bs == null) { createBufferStrategy(2); //создаем BufferStrategy для нашего холста requestFocus(); return; } Graphics g = bs.getDrawGraphics(); //получаем Graphics из созданной нами BufferStrategy g.setColor(Color.black); //выбрать цвет g.fillRect(0, 0, getWidth(), getHeight()); //заполнить прямоугольник g.dispose(); bs.show(); //показать }
Вам наверное уже не терпится запустить и попробовать, но не спешите. Мы должны создать фрейм и добавить наш холст на него. Заодно и объявим три переменных.
public static int WIDTH = 400; //ширина public static int HEIGHT = 300; //высота public static String NAME = «TUTORIAL 1»; //заголовок окна public static void main(String args) { Game game = new Game(); game.setPreferredSize(new Dimension(WIDTH, HEIGHT)); JFrame frame = new JFrame(Game.NAME); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //выход из приложения по нажатию клавиши ESC frame.setLayout(new BorderLayout()); frame.add(game, BorderLayout.CENTER); //добавляем холст на наш фрейм frame.pack(); frame.setResizable(false); frame.setVisible(true); game.start(); }
Примерно вот так выглядит наш класс Game сейчас.

Класс Sprite

Создадим новый класс Sprite. Поскольку этот класс небольшой, я сразу приведу весь его код с комментариями:
import java.awt.Graphics; import java.awt.Image; public class Sprite { private Image image; //изображение public Sprite(Image image) { this.image = image; } public int getWidth() { //получаем ширину картинки return image.getWidth(null); } public int getHeight() { //получаем высоту картинки return image.getHeight(null); } public void draw(Graphics g,int x,int y) { //рисуем картинку g.drawImage(image,x,y,null); } }
Сразу же проверим работоспособность. Возьмем эту картинку и скопируем ее в папку с нашим классом Sprite. Добавим функцию getSprite() в класс Game(временно).
public Sprite getSprite(String path) { BufferedImage sourceImage = null; try { URL url = this.getClass().getClassLoader().getResource(path); sourceImage = ImageIO.read(url); } catch (IOException e) { e.printStackTrace(); } Sprite sprite = new Sprite(Toolkit.getDefaultToolkit().createImage(sourceImage.getSource())); return sprite; }
Добавим нашу картинку в папку assets(папку создать в корне проекта), саму папку надо добавить в build path.
Далее создаем переменную hero типа Sprite. В функции init() инициализируем ее. В Функции render() рисуем:
//в «шапку» public static Sprite hero; //в init() hero = getSprite(«man.png»); //в render() после g.fillRect(0, 0, getWidth(), getHeight()); hero.draw(g, 20, 20);
Результат:
Весь код Game.java

Input

Для обработки инпута мы создадим класс, наследующий KeyAdapter:
private class KeyInputHandler extends KeyAdapter { }
Тут же и объявим две переменных в шапке класса Game:
private boolean leftPressed = false; private boolean rightPressed = false;
Внутри класса KeyInputHandler создадим две функции:
public void keyPressed(KeyEvent e) { //клавиша нажата if (e.getKeyCode() == KeyEvent.VK_LEFT) { leftPressed = true; } if (e.getKeyCode() == KeyEvent.VK_RIGHT) { rightPressed = true; } } public void keyReleased(KeyEvent e) { //клавиша отпущена if (e.getKeyCode() == KeyEvent.VK_LEFT) { leftPressed = false; } if (e.getKeyCode() == KeyEvent.VK_RIGHT) { rightPressed = false; } }
Теперь в функции init() добавим следующее:
addKeyListener(new KeyInputHandler());
Создадим переменные x и y для героя(так как пока что мы еще не написали класс Entity). Сделаем чтобы герой всегда рисовался на этих координатах.
private static int x = 0; private static int y = 0; hero.draw(g, x, y);
А теперь в функции update() будем проверять нажаты ли клавиши и изменять x-координату.
public void update(long delta) { if (leftPressed == true) { x—; } if (rightPressed == true) { x++; } }
Он двигается!
Спасибо за внимание.
P.S. Ссылка на github
Если вы ведёте свой блог, микроблог, либо участвуете в какой-то популярной социальной сети, то вы можете быстро поделиться данной заметкой со своими друзьями и посетителями. Для этого воспользуйтесь предлагаемыми ниже кнопками:

Оставить комментарий

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