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

Отношения между типами

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

Цель работы

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

Задание

В этой работе добавиться вторая игра – «Балда», в который игроки выставляют по очереди буквы на игровое поле. Реализовывать проверку правил игры не требуется. Необходимо реализовать возможность задать начальное слово и делать ходы, подсчитывая при этом набранные очки. Ход игрока будет заключатся в указании координат выставляемой буквы, выставляемой буквы и образуемого слова.

При написании кода второй игры нужно добиться максимального переиспользования кода из первой игры. Классы Point и Board будут без переделки взяты и предыдущей работы. Класс Game изменится, но будет одинаковым для обоих игр. Наибольшее изменения будут касаться состояния игры.

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

Классы для пользовательского ввода.

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

На вершине этой иерархии будет интерфейс Input. Он не содержит свойств или функций, а служит лишь для обозначения общего типа для всех возможных результатов ввода пользователя.

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

Если эта функция вернет объект класса TakeBack, то пользователь дал команду перейти на несколько ходов назад или вперед. Этот класс содержит одно свойство val shift: Int, которое указывает, на сколько позиций от текущего состояния нужно перейти.

Класс Step используется в случае, если пользователь сделал ход. Он содержит свойства val x: Int, val y: Int для обозначения координат хода и свойство val param: List<String> для параметров хода. В игре «крестики-нолики» нет параметров хода, а в игре «балда» будет два параметра – выставляемый символ и образуемое слово. Для возможности использования кода из предыдущих работ в класс стоит также добавить свойство point: Point, вычисляемое из свойств x и y.

В компаньон-объекте интерфейса Input удобно будет разместить статическую функцию fun parse(string: String): Input, которая анализирует введенную пользователем строку и возвращает один из объектов с типом одного из перечисленных выше классов.

Для удобства анализа результата ввода пользователя с помощью конструкции when интерфейс Input лучше сделать запечатанным.

Классы состояний

Вторая иерархия классов в этой работе включает в себя классы, описывающие состояния игр.

Класс AbstractState

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

abstract class AbstractState(
    val board: Board
) 

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

abstract val gameResult: String?
abstract fun copy(): AbstractState 

Функцию fun step(step: Step): AbstractState? разобьем на две части: проверку правильности хода и собственно ход. Это позволит нам реализовать общую часть кода для обоих игр.

Функция open fun checkStep(step: Step): Boolean будет выполнять проверку правильности хода. Общие проверки (выход за пределы доски, ход в занятое поле, окончание игры) реализуем в класса AbstractState, а проверки характерные для каждой игры – в классе для игр.

Функция abstract fun nextState(step: Step): AbstractState абстрактная и должна быть реализована для каждой игры отдельно.

В результате, функция fun step(step: Step): AbstractState? должна выполнить проверку правильности хода с помощью checkStep, если ход правильный, то вызвать nextState и вернуть новое состояние, а если нет – вернуть null. Обратите внимания, что функция не открытая и ее не нужно переписывать в классах для состояний игр.

Класс StateXO

В классе StateXO описывает стояние игры в «крестики-нолики». В его конструкторе будет параметр board и параметр-свойство класса turn.

В этом классе нужно переписать (override) свойство gameResult и функции nextState, copy и toString. Функцию open fun checkStep(step: Step): Boolean переопределять не нужно, все необходимы проверки для этой игры уже сделаны в классе AbstractState.

Служебную функцию checkWin, которая используется при вычислении свойства gameResult, можно взять из предыдущей работы. Также из предыдущей работы можно взять функцию fun toString(): String.

Класс StateBalda

В классе StateBalda описывает стояние игры «балда». Конструктор класса будет выглядеть следующим образом:

class StateBalda(
    board: Board,
    val turn: Int = 1,
    val words1: List<String> = ArrayList(),
    val words2: List<String> = ArrayList()
) : AbstractState(board) 

Здесь turn определяет очередь хода (возможные варианты 1 или 2, первый и второй игроки), words1 и words2 массивы слов, введенных первым и вторым игроком соответственно.

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

Функцию fun checkStep(step: Step): Boolean следует переопределить. В текущем классе следует выполнить проверку количества параметров хода в переменной step.param и корректно вызвать функцию checkStep суперкласса.

Реализация функций nextState, copy и toString не должна вызвать каких-либо сложностей.

Классы и функции игры

Класс MultiGame будет аналогичен классу MultiGame, за исключением того, что везде вместо класса State будет использоваться класс AbstractState.

Функция game будет отличаться от предыдущей работы способом инициализации состояния игры и способом обработки пользовательского ввода.

Поскольку теперь наша программа поддерживает две игры, у функции game появиться еще один параметр типа AbstractState, в котором будет передаваться начальное состояние текущей игры. Этот параметр будет передавать в виде аргумента конструктору класса MultiGame.

При обработке пользовательского ввода, вместо использовавшихся в прошлой игре ветвлений можно использовать один оператор when, котором передается результат функции Input.parse(reader.readLine()).