На этом шаге мы рассмотрим создание и использование таких классов.
Не все классы, определенные внутри других классов, объявляются без имени. Вы также можете использовать ключевое слово class для объявления именованного класса, вложенного в другой класс. На этом шаге вы объявите новый класс GameInput, вложенный в объект Game.
Теперь, определив игровой цикл, можно заняться анализом команд, которые водит пользователь. NyetHack - это текстовая игра, управляемая командами пользователя, которые читает функция readLine(). Анализируя команду, введенную пользователем, важно, во-первых, проверить допустимость команды и, во-вторых, правильно обработать команду, состоящую из нескольких частей, например . Слово "иди" (move) в данном случае должно преобразовываться в вызов функции move(), а слова "на восток" - передаваться в вызов move() в виде аргумента, определяющего направление движения.
Рассмотрим эти два требования, начав с анализа команд, состоящих из нескольких частей. Логику отделения команды от ее аргументов мы поместим в класс GameInput.
Создайте внутри Game private-класс для реализации этой абстракции.
object Game { . . . . private class GameInput(arg: String?) { private val input = arg ?: "" val command = input.split(" ")[0] val argument = input.split(" ").getOrElse(1, { "" }) } . . . . }
Рис.1. Объявление вложенного класса (Game.kt)
Почему GameInput объявлен приватным и вложен в Game? Дело в том, что класс GameInput непосредственно связан только с объектом Game и не должен быть доступным откуда-то еще. Объявив вложенный класс GameInput приватным, вы как бы говорите, что GameInput может использоваться только внутри Game и не должен загромождать его общедоступный API.
Вы объявили два свойства в классе GameInput: первое для команды, а второе для аргумента. Для этого вы вызываете split(), которая разбивает входную строку по символу пробела, а затем getOrElse(), чтобы получить второй элемент из списка, созданного split(). Если элемент с указанным индексом не существует, getOrElse() вернет пустую строку.
Теперь появилась возможность разбивать команды на части. Осталось проверить допустимость введенной команды.
Для этого воспользуемся выражением when и определим белый список команд, допустимых для Game. Любой качественный белый список начинается с реализации обработки недопустимого ввода. Добавьте функцию commandNotFound() в Gamelnput, которая будет возвращать строку для вывода в консоль в том случае, если введенная команда недопустима.
object Game { . . . . private class GameInput(arg: String?) { private val input = arg ?: "" val command = input.split(" ")[0] val argument = input.split(" ").getOrElse(1, { "" }) private fun commandNotFound() = "I'm not quite sure what you're trying to do!" } . . . . }
Рис.2. Объявление функции во вложенном классе (Game.kt)
Далее добавьте еще одну функцию в GameInput с именем processCommand(). Она должна возвращать результат выражения when, который зависит от введенной пользователем команды. Во избежание разночтений не забудьте преобразовать все буквы в команде в нижний регистр вызовом toLowerCase().
object Game { . . . . private class GameInput(arg: String?) { private val input = arg ?: "" val command = input.commandsplit(" ")[0] val argument = input.split(" ").getOrElse(1, { "" }) fun processCommand() = when (command.toLowerCase()) { else -> commandNotFound() } private fun commandNotFound() = "I'm not quite sure what you're trying to do!" } . . . . }
Рис.3. Объявление функции processCommand() (Game.kt)
Настало время задействовать GameInput. Замените вызов readLine() в Game.play на обращение к классу GameInput.
. . . . object Game { . . . . fun play() { while (true) { println(currentRoom.description()) println(currentRoom.load()) // Состояние игрока printPlayerStatus(player) print("> Enter your command: ") println(GameInput(readLine()).processCommand()) } } . . . . }
Рис.4. Использование GameInput (Game.kt)
Запустите Game.kt. Теперь в ответ на любой ввод будет вызываться commandNotFound():
Welcome, adventurer. Room: Town Square Danger level: 2 The villagers rally and cheer as you enter! The bell tower announces your arrival. GWONG (Aura: GREEN) (Blessed: YES) Madrigal of Hermi Hermi is in excellent condition! > Enter your command: fight I'm not quite sure what you're trying to do! Room: Town Square Danger level: 2 The villagers rally and cheer as you enter! The bell tower announces your arrival. GWONG (Aura: GREEN) (Blessed: YES) Madrigal of Hermi Hermi is in excellent condition! > Enter your command:
Рис.5. Результат работы приложения
Это прогресс: мы ограничили круг допустимых команд небольшим (пока пустым) белым списком. Далее в следующих шагах вы добавите команду move(), и тогда GameInput станет немного полезнее.
Но прежде чем вы сможете перемещаться по миру NyetHack, вашему герою нужен мир, в котором есть еще что-то, кроме городской площади.
На следующем шаге мы рассмотрим классы данных.