Suppose that each value of a playing card is described by a separate class:

class Jack { ... } class Queen { ... } 

There is also a separate deck class:

 class Deck { ... } 

Interested in how to properly describe the relationship of seniority cards. For example, that in any card game (practically, but it does not matter) Dame is older than Jack, and two is younger than the King. Also, do not forget that in some games there is a trump card, respectively, any trump card will be older than any non-trump card.

As I understand it, this should be described in each class (for each type of card). But how? And will it be necessary to list all the cards, older (and younger) of which this one is? And how to do it, if I reason correctly, in the Ruby language? Or should the relationship be described at the level of the whole deck?

  • Add the force (number) and is_trump (boolean) fields to the class of each map. And check them out as needed. - eanmos
  • four
    The card characterizes the suit and dignity . - D-side
  • @ D-side here, for sure :) - smellyshovel

3 answers 3

I would suggest inheriting all types of cards from the base class Card , in which, using an internal array, for example, TYPES would set the order of the "types" of cards. The index of such an array will determine the precedence among the types. In the designer it would be possible to transfer the suit suit and a sign whether the card is a trump card.

So that the objects of the class can be compared with each other, we use the include module Comparable , and then override the <=> operator.

 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 

After that, you can either explicitly inherit the card classes from the base class 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 

Or to create them by means of metaprogramming (anyway they are of the same type). In the loop we go around our array of cards Card::TYPES and dynamically create classes of the same name inherited from the class Card

 Card::TYPES.each do |klass| Kernel.const_set(klass, Class.new(Card)) end 

As a result, the objects of these classes can be compared with each other. Given a trump suit, to mark a trump card, pass true as the second argument to the constructor.

 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 

As a result, no matter what deck you create then in Deck it will always be possible to correctly sort and compare each card with another one, taking into account the current trump suit.

  • one
    I learned so much new from the code ... If you added comments to the code, then there would be no price for this answer. - smellyshovel
  • @smellyshovel and which areas cause difficulties? What is required to comment? If you clarify difficult moments, I will comment with pleasure. - cheops
  • @smellyshovel commented, if something raises questions write - I will expand the answer. - cheops
  • @Kromster, as I understand it, the rule does not work in all games, so to be honest, I myself would have made a block in one of the methods in which it would be possible to redefine this rule ... If a ring is always needed, then instead of an array like cards would use a doubly linked list. Perhaps we will still see an alternative answer, the problem is really very interesting and not only within Ruby. - cheops
  • @cheops I saw the question, I immediately thought that it was about the rings, otherwise everything is trivial .. And as I read it carefully, the question turned out to be about the triviality ... - Kromster

Despite the apparent evidence, I believe that inheritance is useless here. At a minimum, there will be many classes, each of which must not only be defined, but also initialized. Specifically, to define seniority, it is enough to define the seniority attribute and the can_hit? method can_hit? . This will be sufficient both for defining precedence and for redefining (that same example, when a deuce can beat an ace).

 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 

For example, this is how we determine that a deuce can beat an ace:

 Card.new("two", 2, [14]) 

Well, it remains to determine the factory:

 def build_deck(cards_params) cards_params.map { |card_params| Card.new(*card_params) } end 

Example of use:

 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]] ]) 

I deliberately did not take into account the suit in the examples. about them there was nothing in the question. To use the suits (especially with regard to cards such as the joker), you will most likely need to use compositions (see Practical Object-Oriented Design in Ruby , chapter Combining Objects with Composition ). But the general direction of thought, I hope, is understandable.

UPD:

Made a small example. Suits also differ in seniority, but in order to make the example more complete, they are compared by the “traditional” method.

 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]] ]) 

Example of use:

 game = Game.new(suits_factory, desk_factory) game.new_game! 
  • It also seemed to me that a separate class for every virtue is a bit overkill. One class of the map, specific maps - copies, and their seniority is described by internal properties. So somehow more logical. Plus this option. - pitersky
  • And it seems to me that creating a class for each individual card is correct. It would not be correct if there were no suits, and there were only one copy of all the cards. And since the Jack can be either a diamond or a club, it is necessary to describe seniority just at the class level, and let the suit be just its property. - smellyshovel
  • And yes, by the way, it was about the last mentioned part of the code that I said, “And will it be necessary to list all the cards, older (and younger) of which this one is?”. You have described only 3 cards (values). And I will have to describe everything using this template. I do not think that this is the best thing that can be done (something like this I would have done myself, and I am looking for the best solution. Actually, this is why the question was asked). - smellyshovel
  • Although on the other hand can be described using Range. Do not have to write so much. I advise you to make the appropriate edit. - smellyshovel
  • The factory, agrees, it turns out cumbersome. But at the same time, it is more flexible than initializing 52 objects, each of its class. In some cases, even less cumbersome. Well, the data itself can be easily transferred, for example, to the config. An example of working with suits I can add, if necessary. - anoam

I suspect that the seniority relationship can be described by some property, value / weight, which will contain a number.

 2 = 2 ... A = 14 // либо туз может быть младшим 

Then all the relationships between the cards can be described in the form of mathematical or logical expressions.

If the suit is a trump card, then you can add some value to all cards of the same suit.