Как написать своего сапёра на Java за 15 минут

Нам понадобятся:

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

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

Для работы с графикой создадим отдельный класс — GUI. От него нам потребуется хранение всех графических элементов управления (т.е. полей клеток), определение элемента, по которому пришёлся клик и передача ему управления, вывод графических элементов на экран и управление основными функциями OpenGL.

Благо класс GUI будет взаимодействовать с графическими элементами, нам нужно создать интерфейс (писать классы сетки, клеток и прочую механику пока рано), который определит, что это такое. Логика подсказывает, что у графического элемента должны быть:

  • Внешний вид (текстура);
  • Координаты;
  • Размеры (ширина и высота);
  • Метод, который по переданным координатам клика определит, попал ли клик по элементу;
  • Метод, который обработает нажатие на элемент.

Таким образом, пишем:

В GUI должны храниться ячейки поля. Создадим для этих целей двумерный массив:

GUI должен передавать клики элементам, которые он содержит. Вычислить адрес клетки, по которой кликнули, нетрудно:

Теперь разберёмся с основными функциями OpenGL. Во-первых, нам нужна инициализация.

На этом мы подробно останавливаться не будем, т.к. изучение LWJGL не входит в наши сегодняшние планы. Во-вторых, нам нужно обновлять изображение на экране:

И, наконец, нам нужно это изображение вообще рисовать. Для этого пора закончить enum Sprite. Его элементы будут представлять из себя обёртку для текстуры с удобочитаемыми именами.

Теперь мы можем написать метод для GUI, который будет рисовать элементы:

Нам осталось только сделать единый метод для инициализации графики, и остальное мы будем писать в основном управляющем классе, возвращаясь сюда, только чтобы внести незначительные изменения.

Ячейки

Создадим класс Cell, реализующий интерфейс GUIElement. В методах getWidth() и getHeight() вернём константу, для координат придётся создать поля, которые будут инициализироваться конструктором. Так же конструктором будем передавать состояние клетки: «-1», если это мина, «-2», если это взорванная мина, число мин поблизости в остальных случаях. Для этой цели можно было бы использовать enum, но число мин удобнее передавать как integer, имхо. Итак, конструктор:

Ещё два поля — boolean isMarked и boolean isHidden будут отвечать за то, отметили ли клетку флажком, и открыли ли её. По умолчанию оба флага выставлены на false.

Разберёмся с методом getSprite().

В случае, если на кнопку нажали, нам снова необходимо рассмотреть несколько простейших случаев:

Чтобы при поражении клетки можно было вскрыть, добавим метод:

Для более удобной реализации генератора добавьте ещё и этот метод:

Обработка ответов от клеток

Вернёмся к методу GUI.receiveClick(). Теперь мы не можем просто вернуть результат назад, т.к. если результат выполнения — единица, то нам нужно открыть соседние ячейки, а в главный управляющий класс вернуть уже ноль, в знак того, что всё прошло корректно.

Пишем генератор

Задачка эта не сложнее, чем создать массив случайных boolean-величин. Идея следующая — для каждой ячейки матрицы мы генерируем случайное число от 0 до 100. Если это число  меньше 15, то в этом месте записываем в матрицу мину (таким образом, шанс встретить мину — 15%). Записав мину, мы вызываем у всех клеток вокруг метод incNearMines(), а для тех ячеек, где клетка ещё не создана храним значение в специальном массиве.

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

Создадим класс Main, в нём входной метод — public static void main(String[] args). Этот метод должен будет делать всего две вещи: вызывать инициализацию GUI и циклически вызывать рабочие методы (input(), GUI.draw() и GUI.update()), пока не получит сигнал закрытия.

Здесь нам не хватает метода input(), займёмся им.

Метод GUI.gameover() будет просто вызывать метод show() у каждой клетки, показывая таким образом всё поле:

Запускаем: gameplay

Готово!

UPD: исходники выложены на GitHub

Пётр Соковых, транслятор двоичного кода в русский язык