На информационном ресурсе применяются рекомендательные технологии (информационные технологии предоставления информации на основе сбора, систематизации и анализа сведений, относящихся к предпочтениям пользователей сети "Интернет", находящихся на территории Российской Федерации)

GeekBrains

4 подписчика

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

Перед прочтением данной статьи рекомендую ознакомиться с предыдущей, «Быстрый старт с Java: начало», поскольку ожидается, что читатель владеет материалом, изложенным в ней — знает о переменных, условиях, циклах и импорте классов. Сегодня мы углублим знания о Java, создавая игру «Крестики-нолики», которая работает в командной строке (консоли). В процессе будет рассмотрена работа с массивами, а также некоторые аспекты объектно-ориентированного программирования (нестатические методы, нестатические поля, конструктор).

Массивы

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

В Java массивы являются объектами, их создают с помощью директивы new. При создании указываем количество элементов массива или инициализируем его набором значений. Приведенный код иллюстрирует оба варианта:

 class Arrays {     public static void main(String[] args) {         int[] arr = new int[5];         int[] arrInit = {1, 2, 3, 4, 5};         for (int i = 0; i < arr.length; i++) {             arr[i] = i * 2 + arrInit[i];         }         for (int a : arr) {             System.out.println(a);         }     } }

С элементами массива можно работать как с обычными переменными, присваивая им результат выражения и читая хранимые значения. При этом в квадратных скобках указывается индекс элемента массива. Индексация в Java идёт с 0 (с нуля). Первый цикл инициализирует элементы массива arr при помощи значений из массива arrInit. Каждый массив имеет поле length, содержащее количество его элементов. Второй цикл выводит элементы массива в консоль, используя второй вариант for  без счётчика цикла.

Методы

Кроме main() класс может содержать и другие методы. Рассмотрим в качестве примера класс с методом add(), который вычисляет и возвращает сумму двух значений, переданных как параметры. Обратите внимание на тип int, который стоит перед именем метода — это тип возвращаемого значения. Две переменные в скобках — параметры. Совокупность имени и параметров называют сигнатурой метода. Вызов метода происходит по имени, в скобках указывают передаваемые значения. В методе они попадают в параметры-переменные. Команда return возвращает результат сложения этих двух переменных и обеспечивает выход из метода.

 class MethodStatic {     public static void main(String[] args) {         int c = add(5, 6);         System.out.println("5 + 6 = " + c);     }       static int add(int a, int b) {         return a + b;     } }

Слово static означает, что метод статический. Если мы обращается к какому-либо методу из статического метода, то вызываемый тоже должен быть статическим. Вот почему add() статический — он вызывается из статического main(). Использование статических методов — скорее исключение, чем правило, поэтому давайте посмотрим как сделать add() нестатическим.

Решение одно — создать объект на основании класса. И затем вызывать метод через точку после имени объекта. В этом случае метод может быть нестатическим. Представленный ниже код это иллюстрирует.

 class MethodNotStatic {     public static void main(String[] args) {         MethodNotStatic method = new MethodNotStatic();         int c = method.add(5, 6);         System.out.println("5 + 6 = " + c);     }       int add(int a, int b) {         return a + b;     } }

Поля класса

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

 class FieldExample {     int a;       public static void main(String[] args) {         FieldExample field = new FieldExample();         field.a = 12;         System.out.println("a = " + field.a);         System.out.println(field.getA());         field.printA();     }       int getA() {         return a;     }       void printA() {         System.out.println(a);     } }

Приведённый выше код иллюстрирует работу с нестатическим полем int a. Описание полей принято размещать первыми в коде класса, затем идут описания методов. Возможность обращаться к полю (запись, чтение) мы получаем только после создания объекта. Также видно, что это поле доступно во всех нестатических методах объекта, а в статическом main() — через точку после имени объекта.

Крестики-нолики. Шаблон класса

Приступим к написанию кода игры. Начнём с шаблона класса и определения нужных полей. Именно это содержит приведённый ниже код. Первые две строки — импорт классов. Первыми в теле класса идут описания полей, затем методов. Метод main() используется для создания объекта (так как поля и методы нестатические) и вызова метода game() с игровой логикой.

 import java.util.Random; import java.util.Scanner;   class TicTacToe {     final char SIGN_X = 'x';     final char SIGN_O = 'o';     final char SIGN_EMPTY = '.';     char[][] table;     Random random;     Scanner scanner;       public static void main(String[] args) {         new TicTacToe().game();     }       TicTacToe() {         // конструктор: инициализация полей     }       void game() {         // игровая логика     }       // дополнительные методы }

В качестве полей используем три символьные константы: SIGN_X, SIGN_O и SIGN_EMPTY. Их значения нельзя изменять, об этом говорит модификатор final. Двумерный символьный массив table будет нашим игровым полем. Потребуется также объект random для генерации ходов компьютера и scanner для ввода данных от пользователя.

Имена методов принято писать с маленькой буквы. Однако в коде мы видим метод TicTacToe() — есть ли тут нарушение? Нет, поскольку этот метод особенный и в объектно-ориентированном программировании называется конструктор. Конструктор вызывается сразу после того, как объект создан. Его имя, как видим, должно совпадать с именем класса. Мы используем конструктор для инициализации полей.

 TicTacToe() {     random = new Random();     scanner = new Scanner(System.in);     table = new char[3][3]; }

Игровая логика

Игровая логика располагается в методе game() и базируется на бесконечном цикле while. Ниже в фрагменте кода последовательность действий описана через комментарии:

 // инициализация таблицы while (true) {     // ход человека     // проверка: если победа человека или ничья:     //    сообщить и выйти из цикла     // ход компьютера     // проверка: если победа компьютера или ничья:     //    сообщить и выйти из цикла }

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

 void game() {     initTable();     while (true) {         turnHuman();         if (checkWin(SIGN_X)) {             System.out.println("YOU WIN!");             break;         }         if (isTableFull()) {             System.out.println("Sorry, DRAW!");             break;         }         turnAI();         printTable();         if (checkWin(SIGN_O)) {             System.out.println("AI WIN!");             break;         }         if (isTableFull()) {             System.out.println("Sorry, DRAW!");             break;         }     }     System.out.println("GAME OVER.");     printTable(); } 

Реализация вспомогательных методов

Пришло время написать код методов, вызываемых в game(). Самый первый, initTable(), обеспечивает начальную инициализацию игровой таблицы, заполняя её ячейки «пустыми» символами. Внешний цикл, со счетчиком int row, выбирает строки, а внутренний, со счётчиком int col, перебирает ячейки в каждой строке.

   void initTable() {     for (int row = 0; row < 3; row++)         for (int col = 0; col < 3; col++)             table[row][col] = SIGN_EMPTY; }

Также потребуется метод, отображающий текущее состояние игровой таблицы printTable().

 void printTable() {     for (int row = 0; row < 3; row++) {         for (int col = 0; col < 3; col++)             System.out.print(table[row][col] + " ");         System.out.println();     } }

В методе turnHuman(), который позволяет пользователю сделать ход, мы используем метод nextInt() объекта scanner, чтобы прочитать два целых числа (координаты ячейки) с консоли. Обратите внимание как используется цикл do-while: запрос координат повторяется в случае, если пользователь укажет координаты невалидной ячейки (ячейка таблицы занята или не существует). Если с ячейкой всё в порядке, туда заносится символ SIGN_X — «крестик».

 void turnHuman() {     int x, y;     do {         System.out.println("Enter X and Y (1..3):");         x = scanner.nextInt() - 1;         y = scanner.nextInt() - 1;     } while (!isCellValid(x, y));     table[y][x] = SIGN_X; }

Валидность ячейки определяет метод isCellValid(). Он возвращает логическое значение: true — если ячейка свободна и существует, false — если ячейка занята или указаны ошибочные координаты.

 boolean isCellValid(int x, int y) {     if (x < 0 || y < 0 || x >= 3|| y >= 3)         return false;     return table[y][x] == SIGN_EMPTY; }

Метод turnAI() похож на метод turnHuman() использованием цикла do-while. Только координат ячейки не считываются с консоли, а генерируются случайно, при помощи метода nextInt(3) объекта random. Число 3, передающееся как параметр, является ограничителем. Таким образом, генерируются случайные целые числа от 0 до 2 (в рамках индексов массива игровой таблицы). И метод isCellValid() снова позволяет нам выбрать только свободные ячейки для занесения в них знака SIGN_O — «нолика».

 void turnAI() {     int x, y;     do {         x = random.nextInt(3);         y = random.nextInt(3);     } while (!isCellValid(x, y));     table[y][x] = SIGN_O; }

Осталось дописать два последних метода — проверка победы и проверка на ничью. Метод checkWin() проверяет игровую таблицу на «победную тройку» — три одинаковых знака подряд, по вертикали или горизонтали (в цикле), а также по двум диагоналям. Проверяемый знак указан как параметр char dot, за счёт чего метод универсален - можно проверять победу и по «крестикам» и по «ноликам». В случае победы возвращается булевское значение true, в противном случае — false.

 boolean checkWin(char dot) {     for (int i = 0; i < 3; i++)         if ((table[i][0] == dot && table[i][1] == dot &&                          table[i][2] == dot) ||                 (table[0][i] == dot && table[1][i] == dot &&                                   table[2][i] == dot))             return true;         if ((table[0][0] == dot && table[1][1] == dot &&                   table[2][2] == dot) ||                     (table[2][0] == dot && table[1][1] == dot &&                       table[0][2] == dot))             return true;     return false; }

Метод isTableFull() во вложенном двойном цикле проходит по всем ячейкам игровой таблицы и, если они все заняты, возвращает true. Если хотя бы одна ячейка ещё свободна, возвращается false.

 boolean isTableFull() {     for (int row = 0; row < 3; row++)         for (int col = 0; col < 3; col++)             if (table[row][col] == SIGN_EMPTY)                 return false;     return true; }

Теперь осталось собрать все эти методы внутри TicTacToe. Последовательность их расположения в теле класса не важна. А после этого можно попробовать сыграть с компьютером в крестики-нолики.

Заключение

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

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

Пройти обучение
Ссылка на первоисточник

Картина дня

наверх