Шаг 181.
Основы Kotlin.
Объекты. Вложенные классы

    На этом шаге мы рассмотрим создание и использование таких классов.

    Не все классы, определенные внутри других классов, объявляются без имени. Вы также можете использовать ключевое слово 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, вашему герою нужен мир, в котором есть еще что-то, кроме городской площади.

    На следующем шаге мы рассмотрим классы данных.




Предыдущий шаг Содержание Следующий шаг