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

Объекты

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

Цель работы

Перейти к объектно-ориентированному подходу к разработке программы. Научиться определять и использовать свойства и функции объектов.

Задание

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

Переписываем предыдущий вариант игры.

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

fun Array<Array<Char>>.get(point: Pair<Int, Int>): Char
fun Array<Array<Char>>.get(point: Array<Int>): Char
fun Array<Array<Char>>.set(point: Pair<Int, Int>, char: Char) 

Далее, перепишите функции из предыдущего задания так, чтобы они стали функциями массивов массивов символов:

fun Array<Array<Char>>.print(out: PrintStream = outputConsole)
fun Array<Array<Char>>.checkWin(): Char
fun Array<Array<Char>>.isFill(): Boolean
fun Array<Array<Char>>.isRightMove(point: Pair<Int, Int>): Boolean

Также добавим свойства к стандартному типу String:

fun String.pointFromString(): Pair<Int, Int>? 

Теперь, для обозначения некорректного ввода пользователя используется null-тип. Код для перевода строки в число изменится на:

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

Код основной функции для игры следует переписать, используя определенные выше операции над объектами. Для тестирования переписанных функций можно использовать тесты из предыдущей работы.

Дополнительный функционал

Дополнительно к программе из предыдущей работы нужно добавить возможность пользователю вернутся на ход или несколько ходов назад, а после этого была бы также возможность перейти вперед к уже сделанным ходам.

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

Для хранения состояния игры введем две переменные:

var currentIndex = 0
val game = ArrayList<Array<Array<Char>>>()

В переменной currentIndex хранится индекс текущей доски в массиве досок game. Кроме массива game текущая доска также хранится в объекте board: Array<Array<Char>>. Ход пользователя отображается в переменной board, после чего эта переменная добавляется (копируется) в массив game с помощью функции ArrayList.add() и увеличивается переменная currentIndex. Если пользователь желает перейти в какое-то состояние игры, то нужно проверить, имеется ли состояние с таким номером в game и, если имеется, то изменить переменную currentIndex и выгрузить нужное состояние в переменную board.

Для реализации в интерфейсе возможности пользователю вводить не только ходы, но и команды перехода, изменим интерпретацию введенной пользователем строки и результата преобразования функции String.pointFromString(). Будем считать, что если пользователь ввел одно число, то это команда сдвинуть текущее состояние на соответствующее число позиций. Таким образом, если пользователь ввел одно число то это команда перехода, если два числа то это координаты хода, в остальных случаях это команда завершить игру. В первом случае функция String.pointFromString() должна вернуть пару чисел, первое из которых равно -1 (признак команды), в второе – на сколько нужно изменить индекс текущего состояния. Во втором случае возвращается пара чисел, соответствующая координатам хода, а в третьем – null.

Для удобства обработки пользовательского ввода рекомендуется реализовать функцию fun Pair<Int, Int>.isCommand(). Тогда ввод информации от пользователя во внутреннем цикле можно записать так:

do {
    output.print("Ваш ход или команда\n")
    point = reader.readLine().pointFromString()
} while (
    point != null && !(
        point.isCommand() ||
        board.isRightMove(point)
    )
)

Отдельное внимание следует уделить правильному копированию объектов. Рассмотрите варианты:

game.add(board)
game.add(board.clone())

Разберитесь, почему они не работают. Реализуйте функцию fun Array<Array<Char>>.copy() для корректного (глубокого) копирования объектов.

Для правильной работы переходов между состояниям нужно также учесть еще два обстоятельства.

Переменная currentChar, которую мы изменяли на каждом ходу, может показывать не верный символ при переходе между состояниями. Ее нужно убрать, а текущий символ определять исходя из четности переменной currentIndex.

После перемещении назад следующий ход будет делаться по уже существующему индексу в массиве game. В этом случае вместо ArrayList.add() следует использовать обычную операцию индексирования.