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
比较似乎比我想像的要简单得多。发现它比我玩过的替代产品更干净。#1 楼
我认为您的示例结构合理,我希望我的评论能为您带来一些公正。有您的担忧和其他要讨论的地方:您将在此处找到以下代码的有效实现
常规编码风格
很好并且一致。我特别喜欢您使用问号来表示返回布尔值的方法。
也许您也可以从方法定义中省略花括号,因为您在可能的情况下在方法调用中忽略了它们。 />您留下了
RANKS
,您的属性和初始化程序,而没有文档。我认为特别是RANKS
和初始化程序将从中受益。RANKS
和实例方法中的重复我认为这没问题,我不会提出更好或更具可读性的方法。
flush
方法使用
Enumerable#one?
可以更优雅地编写它们/>使卡片成为
Struct
的决定我喜欢在代码中使用常量来提高可读性和可维护性。引起我注意的一件事是
ACE_LOW
和ACE_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_LOW
和Card
更换卡是不可能。由于结构响应
Struct
和cards
,因此仍可以修改冻结的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
上使用Array
和Hand
DSL,如果您进一步学习此代码,这可能会对您有所帮助(考虑一下
Enumerable
和Array
类)另外,我将为您提供默认排序,这将消除您的未排序退货问题,而且您可以为基于价值的排序进行排序。
其他的健全性检查可能是以便在您决定不初始化时立即使用
Hand
:检查
Deck
,Game
,freeze
和Hand
是否获得push
的实例检查是否
unshift
,insert
,<<
和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
评论
看来我们需要更多的红宝石编码器...@retailcoder是的:(实际上,Kinda很奇怪,考虑到Ruby世界对代码质量,约定,习惯用法以及所有这些方面的重视程度。我一直在回答很多问题,这就是为什么我想要有人批评我的原因一次-也许我做错了!但是,嘿,如果没有人提高,您可能会保留50个代表,所以这还不是很糟糕:)
不,那50个代表已经消失了,但是还好-但是我想手动授予这个赏金(我想要那顶帽子!),所以您可能要对它进行自我检查,以防万一:)
@retailcoder嗯,是的,rep立即撤回。好吧,我会很乐意拿你的钱-我的意思是代表,但不仅是万不得已:如果没有人及时出现赏金,我会进行自我审查,因为你应该戴上新帽子! :)
应该有一个杀死自己的僵尸的帽子-哦,有一个:袜子木偶:)