我本周末挑战赛很晚(对不起),但是由于这很有趣,所以我希望没事。我不是扑克玩家,所以我可能完全忽略了一些事情。

Hand类进行评估,并计算(除其他外)一个score数组,该数组被安排与另一个数组进行比较扑克手。第一项是手的得分(0-8),接下来的1-5条是决胜局数值/踢球者。例如。三种同类的手可能会得分为

[3, 5, 9, 6] # base score, value of tripled card, kicker, kicker


或者出于比较目的,请考虑两只两对手

player1.score #=> [2, 7, 5, 3]
player2.score #=> [2, 7, 5, 8]


两个玩家都有两对7s和5s,但是玩家2由于具有较高的踢脚而获胜。

(已知和有意的)限制是:


每手只能使用5张卡片(即,没有公用卡等)
不支持小丑/通配符
不验证卡

它在确定平直时确实考虑到了高一点和低一点,但否则就不那么灵活了。 (当然,您可以通过使用Array#combination一次一次蛮力地检查5张卡片组合来避开“仅5张卡片”的限制,但这是另一回事了。)

我还没有没看过其他语言如何解决了这一挑战,所以也许我缺少一些技巧。但实际上,重点主要在于看一看使用功能相当的方法和数组/可枚举方法可以达到的程度。我认为代码主要是单行方法,因此一切正常。

还没有为优化而烦恼,但是(如果没有其他的话)一堆值可以用||=来记住。但是,我对整体方法(我就像?方法一样,对吗?)和可能的替代方法(整体或特定部分)的批判更感兴趣。完整代码(包括测试和要点包括更多详细说明);以下是主要类(请参见下面的其他说明)

ACE_LOW  = 1
ACE_HIGH = 14

# Use Struct to model a simple Card class
Card = Struct.new :suit, :value

# This class models and evaluates a hand of cards
class Hand
  attr_reader :cards

  RANKS = {
    straight_flush:  8,
    four_of_a_kind:  7,
    full_house:      6,
    flush:           5,
    straight:        4,
    three_of_a_kind: 3,
    two_pair:        2,
    pair:            1
  }.freeze

  def initialize(cards)
    raise ArgumentError unless cards.count == 5
    @cards = cards.freeze
  end

  # The hand's rank as an array containing the hand's
  # type and that type's base score
  def rank
    RANKS.detect { |method, rank| send :"#{method}?" } || [:high_card, 0]
  end

  # The hand's type (e.g. :flush or :pair)
  def type
    rank.first
  end

  # The hand's base score (based on rank)
  def base_score
    rank.last
  end

  # The hand's score is an array starting with the
  # base score, followed by the kickers.
  def score
    [base_score] + kickers
  end

  # Tie-breaking kickers, ordered high to low.
  def kickers
    repeat_values + (aces_low? ? aces_low_values.reverse : single_values)
  end

  # If the hand's straight and flush, it's a straight flush
  def straight_flush?
    straight? && flush?
  end

  # Is a value repeated 4 times?
  def four_of_a_kind?
    repeat_counts.include? 4
  end

  # Three of a kind and a pair make a full house
  def full_house?
    three_of_a_kind? && pair?
  end

  # If the hand only contains one suit, it's flush
  def flush?
    suits.uniq.count == 1
  end

  # This is the only hand where high vs low aces comes into play.
  def straight?
    aces_high_straight? || aces_low_straight?
  end

  # Is a card value repeated 3 times?
  def three_of_a_kind?
    repeat_counts.include? 3
  end

  # Are there 2 instances of repeated card values?
  def two_pair?
    repeat_counts.count(2) == 2
  end

  # Any repeating card value?
  def pair?
    repeat_counts.include? 2
  end

  # Actually just an alias for aces_low_straight?
  def aces_low?
    aces_low_straight?
  end

  # Does the hand include one or more aces?
  def aces?
    values.include? ACE_HIGH
  end

  # The repeats in the hand
  def repeats
    cards.group_by &:value
  end

  # The number of repeats in the hand, unordered
  def repeat_counts
    repeats.values.map &:count
  end

  # The values that are repeated more than once, sorted by
  # number of occurrences
  def repeat_values
    repeated = repeats.map { |value, repeats| [value.to_i, repeats.count] }
    repeated = repeated.reject { |value, count| count == 1 }
    repeated = repeated.sort_by { |value, count| [count, value] }.reverse
    repeated.map(&:first)
  end

  # Values that are not repeated, sorted high to low
  def single_values
    repeats.select { |value, repeats| repeats.count == 1 }.map(&:first).sort.reverse
  end

  # Ordered (low to high) array of card values (assumes aces high)
  def values
    cards.map(&:value).sort
  end

  # Unordered array of card suits
  def suits
    cards.map(&:suit)
  end

  # A "standard" straight, treating aces as high
  def aces_high_straight?
    straight_values_from(values.first) == values
  end

  # Special case straight, treating aces as low
  def aces_low_straight?
    aces? && straight_values_from(aces_low_values.first) == aces_low_values
  end

  # The card values as an array, treating aces as low
  def aces_low_values
    cards.map(&:value).map { |v| v == ACE_HIGH ? ACE_LOW : v }.sort
  end

  private

  # Generate an array of 5 consecutive values
  # starting with the `from` value
  def straight_values_from(from)
    (from...from + 5).to_a
  end
end


注释和编辑

通常,我认为ace高(值为14),并且在检查ace时仅将它们视为低(值为1)。 -低直。也就是说,一个ace Card实例的值将为14,但是在ace-low直线的情况下,一个Hand实例会将其报告为1。但是,从技术上讲,卡并不是一成不变的,因为我使用的是Hand,但这仅是为了解决这一挑战;否则,我将定义一个“适当的”类)同样,这是我自己的关注点(超出了上述限制):


有些方法返回无序数组,有些从高到低排序,而另一些从低到高排序。最好保持一致。
直接检查非常幼稚:生成5个连续的数字,并查看它们是否与卡值匹配。我考虑以各种方式枚举值,但是与生成的数组进行简单的Card比较似乎比我想像的要简单得多。发现它比我玩过的替代产品更干净。


评论

看来我们需要更多的红宝石编码器...

@retailcoder是的:(实际上,Kinda很奇怪,考虑到Ruby世界对代码质量,约定,习惯用法以及所有这些方面的重视程度。我一直在回答很多问题,这就是为什么我想要有人批评我的原因一次-也许我做错了!但是,嘿,如果没有人提高,您可能会保留50个代表,所以这还不是很糟糕:)

不,那50个代表已经消失了,但是还好-但是我想手动授予这个赏金(我想要那顶帽子!),所以您可能要对它进行自我检查,以防万一:)

@retailcoder嗯,是的,rep立即撤回。好吧,我会很乐意拿你的钱-我的意思是代表,但不仅是万不得已:如果没有人及时出现赏金,我会进行自我审查,因为你应该戴上新帽子! :)

应该有一个杀死自己的僵尸的帽子-哦,有一个:袜子木偶:)

#1 楼

我认为您的示例结构合理,我希望我的评论能为您带来一些公正。有您的担忧和其他要讨论的地方:

您将在此处找到以下代码的有效实现




常规编码风格

很好并且一致。我特别喜欢您使用问号来表示返回布尔值的方法。


也许您也可以从方法定义中省略花括号,因为您在可能的情况下在方法调用中忽略了它们。 />您留下了RANKS,您的属性和初始化程序,而没有文档。我认为特别是RANKS和初始化程序将从中受益。



RANKS和实例方法中的重复

我认为这没问题,我不会提出更好或更具可读性的方法。


flush方法

使用Enumerable#one?可以更优雅地编写它们

/>使卡片成为Struct的决定
我喜欢在代码中使用常量来提高可读性和可维护性。引起我注意的一件事是ACE_LOWACE_HIGH是全局常数,而RANKS不是全局常数(很好)。我认为这种设计缺陷来自于您将Card设为Struct对象的决定,以及这种对象通常缺乏逻辑的原因。让我们进行更改,使Card成为一流的公民:您的代码将受益。

 # If the hand only contains one suit, it's flush
 def flush?
   suits.uniq.one?
 end


这将对Hand中的代码进行以下改进:

class Card
  include Comparable

  attr_reader :suit, :value

  # Value to use as ace low
  ACE_LOW = 1

  # Value to use as ace high
  ACE_HIGH = 14

  # initialize the card with a suit and a value
  def initialize suit, value
    super()
    @suit = suit
    @value = value
  end

  # Return the low card
  def low_card
    ace? ? Card.new(suit, ACE_LOW) : self
  end

  # Return if the card is an ace high
  def ace?
    value == ACE_HIGH
  end

  def ace_low?
    value == ACE_LOW
  end

  # Return if the card has suit spades
  def spades?
    suit == :spades
  end

  # Return if the card has suit diamonds
  def diamonds?
    suit == :diamonds
  end

  # Return if the card is suit hearts
  def hearts?
    suit == :hearts
  end

  # Return if the card has suit clubs
  def clubs?
    suit == :clubs
  end

  # Compare cards based on values and suits
  # Ordered by suits and values - the suits_index will be introduced below
  def <=> other
    if other.is_a? Card
      (suit_index(suit) <=> suit_index(other.suit)).nonzero? || value <=> other.value
    else
      value <=> other
    end
  end

  # Allow for construction of card ranges across suits
  # the suits_index will be introduced below
  def succ
    if ace?
      i = suit_index suit
      Card.new(Deck::SUITS[i + 1] || Deck::SUITS.first, ACE_LOW)
    else
      Card.new(suit, value + 1)
    end
  end

  def successor? other
    succ == other
  end

  def straight_successor? other
    succ === other
  end

  # Compare cards for equality in value
  def == other
    if other.is_a? Card
      value == other.value
    else
      value == other
    end
  end
  alias :eql? :==

  # overwrite hash with value since cards with same values are considered equal
  alias :hash :value

  # Compare cards for strict equality (value and suit)
  def === other
    if other.is_a? Card
      value == other.value && suit == other.suit
    else
      false
    end
  end

  private

  # If no deck, this has to be done with an array of suits
  # gets the suit index
  def suit_index suit
    Deck::SUITS.index suit
  end
end





它将包含ACE_HIGH中的常数ACE_LOWCard

更换卡是不可能。由于结构响应Structcards,因此仍可以修改冻结的value=阵列中的suit=卡的值。
/>我认为将Hand作为splat参数会更好。用数组初始化会不必要地降低可读性。另外,您提出的cards并不是很好的描述,可能会引起一些混乱。总而言之,这就是我的改进效果:

class Hand

  # ... 

  # Tie-breaking kickers, ordered high to low.
  def kickers
    same_of_kind + (aces_low? ? aces_low.reverse : single_cards)
  end

  # If the hand's straight and flush, it's a straight flush
  def straight_flush?
    straight? && flush?
  end

  # Is a value repeated 4 times?
  def four_of_a_kind?
    same_of_kind? 4
  end

  # Three of a kind and a pair make a full house
  def full_house?
    same_of_kind?(3) && same_of_kind?(2)
  end

  # If the hand only contains one suit, it's flush
  def flush?
    suits.uniq.one?
  end

  # This is the only hand where high vs low aces comes into play.
  def straight?
    aces_high_straight? || aces_low_straight?
  end

  # Is a card value repeated 3 times?
  def three_of_a_kind?
    collapsed_size == 2 && same_of_kind?(3)
  end

  # Are there 2 instances of repeated card values?
  def two_pair?
    collapsed_size == 2 && same_of_kind?(2)
  end

  # Any pair?
  def pair?
    same_of_kind? 2
  end

  def single_cards
    cards.select{|c| cards.count(c) == 1 }
  end

  # Does the hand include one or more aces?
  def aces?
    cards.any? &:ace?
  end

  # Ordered (low to high) array of card values (assumes aces high)
  def values
    cards.map(&:value).sort
  end

  # Unordered array of card suits
  def suits
    cards.map &:suit
  end

  # A "standard" straight, treating aces as high
  def aces_high_straight?
    all_successors? cards.sort_by(&:value)
  end

  # Special case straight, treating aces as low
  def aces_low_straight?
    aces? && all_successors?(aces_low)
  end
  alias :aces_low? :aces_low_straight?

  # The card values as an array, treating aces as low
  def aces_low
    cards.map(&:low_card).sort
  end

  private

  # Are there n cards same of kind?
  def same_of_kind?(n)
    !!cards.detect{|c| cards.count(c) == n }
  end

  # How many cards vanish if we collapse the cards to single values
  def collapsed_size
    cards.size - cards.uniq.size
  end

  # map the cards that are same of kind
  def same_of_kind
    2.upto(4).map{|n| cards.select{|c| cards.count(c) == n }.reverse }.sort_by(&:size).reverse.flatten.uniq
  end

  # Are all cards succeeding each other in value?
  def all_successors?(cards)
    cards.all?{|a| a === cards.last || a.successor?(cards[cards.index(a) + 1]) }
  end

end 

根据使用情况,可能还需要进行其他完整性检查:检查您是否真的收到了ArgumentError的5个实例。

纸牌也是纸牌阵列-相似之处使您可以将Card替换为Hand。这将使您可以直接在Array上使用ArrayHand DSL,
如果您进一步学习此代码,这可能会对您有所帮助(考虑一下EnumerableArray类)

另外,我将为您提供默认排序,这将消除您的未排序退货问题,而且您可以为基于价值的排序进行排序。

其他的健全性检查可能是以便在您决定不初始化时立即使用Hand


检查DeckGamefreezeHand是否获得push的实例

检查是否unshiftinsert<<Card as argument不会在手牌上添加太多卡

那么,这有什么可能呢?让我们重构一下:

def initialize(*cards)
  raise ArgumentError.new "wrong number of cards (#{cards.count} for 5)" unless cards.count == 5
  @cards = cards.freeze
end


push类一起,这为您提供了可以构建的漂亮DSL:
只是为了好玩,如果您有一个unshift类,它也可以作为insert的子类

class Hand < Array

  # .. RANKS

  def initialize(*cards)
    raise ArgumentError.new "There must be 5 cards" unless cards.count == 5
    super(cards)
    sort_by! &:value # This will give you a nicely sorted hand by default
    freeze
  end

  # The hand's rank as an array containing the hand's
  # type and that type's base score
  def rank
    RANKS.detect { |method, rank| send :"#{method}?" } || [:high_card, 0]
  end

  # The hand's type (e.g. :flush or :pair)
  def type
    rank.first
  end

  # The hand's base score (based on rank)
  def base_score
    rank.last
  end

  # The hand's score is an array starting with the
  # base score, followed by the kickers.
  def score
    ([base_score] + kickers.map(&:value))
  end

  # Tie-breaking kickers, ordered high to low.
  def kickers
    same_of_kind + (aces_low? ? aces_low.reverse : single_cards.reverse)
  end

  # If the hand's straight and flush, it's a straight flush
  def straight_flush?
    straight? && flush?
  end

  # Is a value repeated 4 times?
  def four_of_a_kind?
    same_of_kind? 4
  end

  # Three of a kind and a pair make a full house
  def full_house?
    same_of_kind?(3) && same_of_kind?(2)
  end

  # If the hand only contains one suit, it's flush
  def flush?
    suits.uniq.one?
  end

  # single cards in the hand
  def single_cards
    select{ |c| count(c) == 1 }.sort_by(&:value)
  end

  # This is the only hand where high vs low aces comes into play.
  def straight?
    aces_high_straight? || aces_low_straight?
  end

  # Is a card value repeated 3 times?
  def three_of_a_kind?
    collapsed_size == 2 && same_of_kind?(3)
  end

  # Are there 2 instances of repeated card values?
  def two_pair?
    collapsed_size == 2 && same_of_kind?(2)
  end

  # Any repeating card value?
  def pair?
    same_of_kind?(2)
  end

  # Does the hand include one or more aces?
  def aces?
    any? &:ace?
  end

  # Ordered (low to high) array of card values (assumes aces high)
  def values
    map(&:value).sort
  end

  # Ordered Array of card suits
  def suits
    sort.map &:suit
  end

  # A "standard" straight, treating aces as high
  def aces_high_straight?
    all?{|card| card === last || card.successor?(self[index(card) + 1]) }
  end
  alias :all_successors? :aces_high_straight?

  # Special case straight, treating aces as low
  def aces_low_straight?
    aces? && aces_low.all_successors?
  end
  alias :aces_low? :aces_low_straight?

  # The card values as an array, treating aces as low
  def aces_low
    Hand.new *map(&:low_card)
  end

  private

  # Are there n cards of the same kind?
  def same_of_kind?(n)
    !!detect{|card| count(card) == n }
  end

  def same_of_kind
    2.upto(4).map{|n| select{|card| count(card) == n }.reverse }.sort_by(&:size).reverse.flatten.uniq
  end

  # How many cards vanish if we collapse the cards to single values
  def collapsed_size
    size - uniq.size
  end

end



哪里可以进一步学习


<<上实现Card

摆脱手上的Deck以便我们可以交换卡用于某些类型的游戏
yada yada yada



正如我所说,您的榜样已经很好,希望我感谢与您分享我的想法!

评论


\ $ \ begingroup \ $
现在,这是评论!! (当我在世界标准时间12AM重新加载时肯定会投票)
\ $ \ endgroup \ $
– Mathieu Guindon♦
2013年12月17日19:29



\ $ \ begingroup \ $
@retailcoder您的代码很有启发性:)
\ $ \ endgroup \ $
–击败Richartz
2013年12月17日19:32在

\ $ \ begingroup \ $
你在找帽子吗?
\ $ \ endgroup \ $
– Mathieu Guindon♦
2013年12月17日在21:16



\ $ \ begingroup \ $
@retailcoder非常感谢-我会很自豪地穿上它!
\ $ \ endgroup \ $
–击败Richartz
2013年12月17日在21:25

\ $ \ begingroup \ $
@BeatRichartz很棒的评论,谢谢!基本上,我同意您的所有观点。当然,这只是一个周末挑战,所以我不会自己进一步介绍它,但是您在那里做得很好。我不太知道是否要继承Array。我考虑了一下,尽管它在许多层面上都有意义,但我也发现它有些“钝”。我可能更喜欢具有非常集中的API的完全自定义类而不是泛型类的专业化。但是任何一种方法都可以正常工作,因此实际上只是一种哑光。再次感谢您的审查;好东西!
\ $ \ endgroup \ $
– Flaambino
13年12月18日在12:00