Отношения между типами
Теоретические сведения
Цель работы
Разобраться с работой объектов из иерархии типов и применением полиморфных функции для повторного использования кода.
Задание
В этой работе добавиться вторая игра – «Балда», в который игроки выставляют по очереди буквы на игровое поле. Реализовывать проверку правил игры не требуется. Необходимо реализовать возможность задать начальное слово и делать ходы, подсчитывая при этом набранные очки. Ход игрока будет заключатся в указании координат выставляемой буквы, выставляемой буквы и образуемого слова.
При написании кода второй игры нужно добиться максимального переиспользования кода из первой игры. Классы 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())
.