Объектно-ориентированное программирование

Основы языка Kotlin

Теоретические сведения

Цель работы

Освоить основные конструкции языка Kotlin для структурного программирования, среду разработки, методы тестирования и отладки программ.

Задание

При выполнении этой работы нужно реализовать консольную игру «Крестики-нолики» на доске 3*3.

Среда разработки

  1. Установите и настройте на своем компьютере среду разработки.

  2. Напишите функцию для вычисления площади круга по его радиусу. Параметр и результат функции должны быть типа Double, константу PI можно импортировать из библиотеки kotlin.math.

  3. Напишите тесты для этой функции. Для корректного сравнения вещественных чисел задайте с помощью функции plusOrMinus точность сравнения (например, для радиуса 1 результат должен быть PI.plusOrMinus(0.001))

  4. Переименуйте функцию с помощью рефакторинга. Отформатируйте текст с помощью меню среды Code-Reformat Code. Запомните сочетания клавиш для выполненных действий.

  5. Установите точку остановки вначале функции тестирования, запустите программу в режиме отладки. Проверьте как работают в окне отладки кнопки «Step over», «Step into», «Rerun», «Resume», «Stop». Добавьте в код функции произвольную переменную, затем просмотрите значение этой переменной в режиме отладки и добавьте эту переменную для просмотра с помощью поля «Evaluate expression». Запомните сочетания клавиш для выполненных действий.

  6. Реализуйте функцию выводящую в выходной поток out игровое поле (размер игрового поля задается с помощью глобальной переменной var boardSize = 3):

    fun printBoard(board: Array<Array<Char>>, out: PrintStream = outputConsole)
    

    Для тестирования функции можно использовать следующий код:

    "print" {
        val board = arrayOf(
            arrayOf(' ',' ',' '),
            arrayOf(' ','X',' '),
            arrayOf(' ',' ','0'))
        printBoard(board, output)
        outputBuffer.toString() shouldBe "  012\n0    \n1  X \n2   0\n"
    }
    

    Напишите тест функции printBoard для случая другого размера доски.

Реализации игры

При реализации игры нужно реализовать несколько функций. Каждая функция должны быть протестирована.

  1. Реализуйте функцию проверки состояния игровой доски на наличие на ней выигрышной комбинации (трех одинаковых символов в линию):

    fun checkWin(board: Array<Array<Char>>): Char
    

    Для упрощение реализации этой функции все выигрышные комбинации можно задать с помощью массива:

    val winLines = arrayOf(
        arrayOf(arrayOf(0, 0), arrayOf(0, 1), arrayOf(0, 2)),
        arrayOf(arrayOf(1, 0), arrayOf(1, 1), arrayOf(1, 2)),
        arrayOf(arrayOf(2, 0), arrayOf(2, 1), arrayOf(2, 2)),
        arrayOf(arrayOf(0, 0), arrayOf(1, 0), arrayOf(2, 0)),
        arrayOf(arrayOf(0, 1), arrayOf(1, 1), arrayOf(2, 1)),
        arrayOf(arrayOf(0, 2), arrayOf(1, 2), arrayOf(2, 2)),
        arrayOf(arrayOf(0, 0), arrayOf(1, 1), arrayOf(2, 2)),
        arrayOf(arrayOf(0, 2), arrayOf(1, 1), arrayOf(2, 0))
    )
    

    Внутри функции должен быть цикл, перебирающий все элементы массива winLines. Для каждого элемента массива (назовем его линия) выполняется проверка: если символ доски с координатами, указанными в первом элементе линии, не равен пробелу и равен символам с координатами во втором и в третьем элементах линии, то это выигрышная комбинации и функция возвращает этот символ. Если после перебора всех линий выигрышная комбинация не найдена, то функция должна вернуть символ пробела.

  2. Реализуйте функцию, которая проверяет, имеются ли на доске пустые поля:

    fun isFill(board: Array<Array<Char>>): Boolean
    
  3. Реализуйте функцию для преобразования введенной пользователем с клавиатуры строки в пару чисел, соответствующую координатам на доске:

    fun pointFromString(string: String): Array<Int>
    

    Для разделения строки на отдельные элементы и преобразования ее в массив слов используйте функцию split с разделителем равным пробелу.

    Если пользователь ввел не правильную строку, то при преобразовании строки в число может возникнуть ошибка. Обработка ошибок будет рассмотрена в курсе позже, а пока можно воспользоваться следующим примером:

        val x = arr[0].toIntOrNull() ?: return arrayOf(0)
    

    Здесь arr[0] элемент массива строк, toIntOrNull – функция, преобразующая строку в число и возвращающая в случае ошибки специальное значение (null), ?: – оператор, второй операнд которого срабатывает, если первый операнд равен null, arrayOf(0) – значение, которая наша функция возвращает, если преобразование строки в координаты прошло неуспешно.

  4. Реализуйте функцию для проверки возможности хода в заданное поле доски:

    fun isRightMove(board: Array<Array<Char>>, point: Array<Int>): Boolean
    

    Функция должна проверить, что координаты point не выходят за пределы доски (используйте функцию `in`) и что соответствующее поле доски пустое.

  5. Реализуйте основную функцию игры:

    fun game(inputStream: InputStream= System.`in`, output: PrintStream = outputConsole)
    

    Функция должна содержать внешний цикл while, который выполняется пока на доске нет выигрышной комбинации, есть свободные поля и пользователь вводит корректные координаты. В цикле пользователь вводит координаты, на поле доски выставляется текущий символ (X или 0), текущий символ меняется на противоположный, доска печатается.

    Координаты пользователь вводит во внутреннем цикле типа do {...} while(). Цикл продолжается пока пользователь вводит два числа – координаты, но по этим координатам нельзя сделать правильный ход. Если пользователь вводит что-то отличное от двух координат, то внутренний цикл, а затем и внешний, заканчиваются.

    Функция game должна протестирована на правильное завершение игры в случае победы одного из игроков; на правильное завершение игры в ничью; на правильную отработку хода, сделанного за пределы доски и на правильную отработку хода, сделанного в уже занятую клетку.

    Для экономии времени ниже приведены первые две проверки. Они будут использоваться всех лабораторных работ, отдельные строки в этой проверки нужно будет подключить (установив переменную lab3=true) только для третьей и последующих работ.

    class TestXO : StringSpec({
        val stepsWinX = "1 1\n1 2\n0 1\n0 2\n2 1\n"
        fun resultWinX(lab3: Boolean = false) = (if (lab3) "Ход 0\n" else "") +
            "  012\n" +
            "0    \n" +
            "1    \n" +
            "2    \n" +
            (if (lab3) " Очередь хода X\n" else "") +
            "Ваш ход или команда\n" +
            (if (lab3) "Ход 1\n" else "") +
            "  012\n" +
            "0    \n" +
            "1  X \n" +
            "2    \n" +
            (if (lab3) " Очередь хода 0\n" else "") +
            "Ваш ход или команда\n" +
            (if (lab3) "Ход 2\n" else "") +
            "  012\n" +
            "0    \n" +
            "1  X0\n" +
            "2    \n" +
            (if (lab3) " Очередь хода X\n" else "") +
            "Ваш ход или команда\n" +
            (if (lab3) "Ход 3\n" else "") +
            "  012\n" +
            "0  X \n" +
            "1  X0\n" +
            "2    \n" +
            (if (lab3) " Очередь хода 0\n" else "") +
            "Ваш ход или команда\n" +
            (if (lab3) "Ход 4\n" else "") +
            "  012\n" +
            "0  X0\n" +
            "1  X0\n" +
            "2    \n" +
            (if (lab3) " Очередь хода X\n" else "") +
            "Ваш ход или команда\n" +
            if (lab3) "Победил X"
            else "  012\n" +
                    "0  X0\n" +
                    "1  X0\n" +
                    "2  X \n"
    
        val stepsDraw = "0 0\n0 1\n0 2\n1 1\n1 0\n1 2\n2 2\n2 0\n2 1\n"
        fun resultDraw(lab3: Boolean = false) = (if (lab3) "Ход 0\n" else "") +
            "  012\n" +
            "0    \n" +
            "1    \n" +
            "2    \n" +
            (if (lab3) " Очередь хода X\n" else "") +
            "Ваш ход или команда\n" +
            (if (lab3) "Ход 1\n" else "") +
            "  012\n" +
            "0 X  \n" +
            "1    \n" +
            "2    \n" +
            (if (lab3) " Очередь хода 0\n" else "") +
            "Ваш ход или команда\n" +
            (if (lab3) "Ход 2\n" else "") +
            "  012\n" +
            "0 X0 \n" +
            "1    \n" +
            "2    \n" +
            (if (lab3) " Очередь хода X\n" else "") +
            "Ваш ход или команда\n" +
            (if (lab3) "Ход 3\n" else "") +
            "  012\n" +
            "0 X0X\n" +
            "1    \n" +
            "2    \n" +
            (if (lab3) " Очередь хода 0\n" else "") +
            "Ваш ход или команда\n" +
            (if (lab3) "Ход 4\n" else "") +
            "  012\n" +
            "0 X0X\n" +
            "1  0 \n" +
            "2    \n" +
            (if (lab3) " Очередь хода X\n" else "") +
            "Ваш ход или команда\n" +
            (if (lab3) "Ход 5\n" else "") +
            "  012\n" +
            "0 X0X\n" +
            "1 X0 \n" +
            "2    \n" +
            (if (lab3) " Очередь хода 0\n" else "") +
            "Ваш ход или команда\n" +
            (if (lab3) "Ход 6\n" else "") +
            "  012\n" +
            "0 X0X\n" +
            "1 X00\n" +
            "2    \n" +
            (if (lab3) " Очередь хода X\n" else "") +
            "Ваш ход или команда\n" +
            (if (lab3) "Ход 7\n" else "") +
            "  012\n" +
            "0 X0X\n" +
            "1 X00\n" +
            "2   X\n" +
            (if (lab3) " Очередь хода 0\n" else "") +
            "Ваш ход или команда\n" +
            (if (lab3) "Ход 8\n" else "") +
            "  012\n" +
            "0 X0X\n" +
            "1 X00\n" +
            "2 0 X\n" +
            (if (lab3) " Очередь хода X\n" else "") +
            "Ваш ход или команда\n" +
            if (lab3) "Ничья"
            else "  012\n" +
                    "0 X0X\n" +
                    "1 X00\n" +
                    "2 0XX\n"
    
        // Другие тесты
    
        "Тест крестиков-ноликов" {
    
            listOf(
                stepsWinX to resultWinX(),
                stepsDraw to resultDraw(),
                stepsOutBoard to resultOutBoard(), 
                stepsNoEmpty to resultNoEmpty()
            ).forEach {
                outputBuffer.reset()
                lab1.game(ByteArrayInputStream(it.first.toByteArray()), output)
                outputBuffer.toString() shouldBe it.second
            }
        }
    })