Страницы

Поиск по вопросам

понедельник, 2 декабря 2019 г.

Как правильно в коде описать отношения между картами в колоде?

#ruby #логика


Предположим, что каждое достоинство игральной карты описывается отдельным классом:

class Jack {
    ...
}

class Queen {
    ...
}


Также есть отдельный класс колоды:

class Deck {
    ...
}


Интересует, как правильно описать взаимоотношения старшинств карт. Например, что
в любой карточной игре (практически, но это неважно) Дама старше Валета, а двойка младше
Короля. Также не стоит забывать, что в некоторых играх есть козырь, соответственно
любая козырная карта будет старше любой не козырной.

Насколько я понимаю, это должно описываться в каждом классе (для каждого вида карты).
Но как? И придется ли перечислять все карты, старше (и младше) которой является данная?
И как это сделать, если я рассуждаю правильно, в языке Ruby? Или все-таки взаимоотношения
должны описываться на уровне всей колоды?
    


Ответы

Ответ 1



Я бы предложил унаследовать все типы карт от базового класса Card, в котором бы при помощи внутреннего массива, например, TYPES задал бы порядок следования "типов" карт. Индекс такого массива будет определять старшинство среди типов. В конструктор можно было бы передавать масть suit и признак, является ли карта козырем trump. Чтобы объекты класса можно было сравнивать друг с другом мы при помощи include модуль Comparable, а потом переопределяем оператор <=>. class Card include Comparable # Массив-константа типов карт в порядке убывания значения TYPES = %w(Jocker Ace King Queen Jack Ten Nine Eight Seven Six).freeze # Конструктор принимает масть suit и признак является ли карта козырем trump def initialize(suit, trump = false) @suit = suit @trump = trump end # Вес мастей относительно друг друга def weight TYPES.find_index(self.class.name) end # Козырь или нет? def trump @trump end # Масть def suit @suit end # Переопределение оператора сравнения <=> def <=>(other) if other.trump == @trump other.weight <=> weight else (other.trump && !@trump) ? -1 : 1 end end end После этого можно либо явно унаследовать классы карты от базового класса Card class Jocker < Card end class Ace < Card end class King < Card end class Queen < Card end class Jack < Card end ... class Six < Card end Либо создать их средствами метапрограммирования (все-равно они однотипные). В цикле обходим наш массив карт Card::TYPES и динамически создаем одноименные классы, унаследованные от класса Card Card::TYPES.each do |klass| Kernel.const_set(klass, Class.new(Card)) end В результате объекты эти классов можно сравнивать друг с другом. С учетом козырной масти, чтобы пометить карту козырной, передаем true в качестве второго аргумента конструктора. jack = Jack.new(:hearts) ace = Ace.new(:clubs) p ace < jack # false p ace > jack # true p ace == ace # true jack = Jack.new(:hearts, true) ace = Ace.new(:clubs) trump = Ace.new(:clubs, true) p ace < jack # true p ace > jack # false p ace == trump # false В результате, какую бы колоду вы не сформировали затем в Deck ее всегда можно будет корректно отсортировать и сравнить каждую карту с другой, учитывая текущую козырную масть.

Ответ 2



Не смотря на кажущуюся очевидность, считаю что наследование здесь ни к чему. Как минимум, получится много классов, каждый из которых надо не только определить, но и инициализировать. Конкретно для определения старшинства достаточно определить атрибут seniority (старшинство) и метод can_hit?. Этого будет достаточно и для определения старшинства, и для переопределения (тот самый пример, когда двойка может бить туза). class Card attr_reader :name, :seniority, :hitable_seniorities def initialize(name, seniority, hitable_seniorities) @name = name @seniority = seniority @hitable_seniorities = hitable_seniorities end def can_hit?(other_card) hitable_seniorities.include?(other_card.seniority) end end Например вот так мы определим что двойка может бить туза: Card.new("two", 2, [14]) Ну и осталось определить фабрику: def build_deck(cards_params) cards_params.map { |card_params| Card.new(*card_params) } end Пример использования: build_deck([ ["three", 3, [2]], ["seven", 7, [2, 3, 4, 5, 6]], ["ace", 14, [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]] ]) Я умышленно не учитывал в примерах масти т.к. про них не было ничего в вопросе. Для использования мастей (особенно с учётом таких карт, как джокер), скорее всего, понадобится использовать композиции (см. Practical Object-Oriented Design in Ruby, главу Combining Objects with Composition). Но общее направление мыслей, надеюсь, понятно. UPD: Сделал небольшой пример. Масти тоже различаются по старшинству, но для того, чтобы пример был более полным, сравниваются "традиционным" методом. class Suit attr_reader :name, :seniority def intialize(name, seniority) @name = name @seniority = seniority @trump = false end def trump! @trump = true end def trump? @trump end def same?(other_suit) seniority == other_suit.seniority end def highter?(other_suit) return false if same?(other_suit) return true if trump? seniority > other_suit.seniority end end class Card attr_reader :name, :seniority, :hitable_seniorities, :suit def initialize(name, seniority, hitable_seniorities, suit) @name = name @seniority = seniority @hitable_seniorities = hitable_seniorities @suit = suit end def can_hit?(other_card) #hitable_seniorities.include?(other_card.seniority) return true if suit_highter?(other_card) return false unless same_suit?(other_card) seniority_highter?(other_card) end private def suit_highter?(other_card) suit.highter?(other_card.suit) end def same_suit?(other_card) suit.same?(other_card.suit) end def seniority_highter?(other_card) hitable_seniorities.include?(other_card.seniority) end end class Game attr_reader :suits_factory, :deck_factory attr_reader :suits, :deck def initialize(suits_factory, deck_factory) @suits_factory = suits_factory @deck_factory = deck_factory end def new_game! build_deck! choose_tramp! end private def build_deck! @suits = suits_factory.build @deck = deck_factory.build(suits) end def choose_tramp! @suits.example.trump! end end class SuitsFactory attr_reader :suits_data def initialize(suits_data) # возможные масти в игре @suits_data = suits_data end def build suits_data.map do |suit_data| Suit.new(*suit_data) end end end class DesckFactory attr_reader :cards_data def initialize(cards_data) # возможные в игре достоинства карт @cards_data = cards_data end def build(suits) suits.map { |suits| build_for_suit(suit) }.flatten end private def build_for_suit(suit) cards_data.map do |card_data| Suit.new(*card_data, suit) end end end suits_factory = SuitsFactory.new([ ["Clubs", 1], ["Diamonds", 2], ["Hearts", 3], ["Diamonds", 4] ]) desk_factory = DesckFactory.new([ ["three", 3, [2]], ["seven", 7, [2, 3, 4, 5, 6]], ["ace", 14, [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]] ]) Пример использования: game = Game.new(suits_factory, desk_factory) game.new_game!

Ответ 3



Подозреваю, что отношения старшенства, можно описать некоторым свойством, value/weight, которое будет содержать число. 2 = 2 ... A = 14 // либо туз может быть младшим Тогда все отношения между картами, можно будет описать в виде математических или логических выражений. Если масть - козырь, то можно прибавлять некоторое значение ко всем картам одной масти.

Комментариев нет:

Отправить комментарий