問題描述
我遲到了這個週末的挑戰 (對不起),但由於這一切都很好玩,我希望沒關係。我不是撲克玩家,所以我可能完全忽略了一些東西。
Hand 類進行評估,並計算 (其中包括)score 陣列,可以與另一個撲克牌進行比較。第一項是手分數 (0-8),以下 1-5 項是 tie-breaking 值/踢球者。例如。一個 three-of-a 手牌可能會被打分
[3, 5, 9, 6] # base score, value of tripled card, kicker, kicker
或者,為了比較,考慮兩個 two-pair 手
player1.score #=> [2, 7, 5, 3]
player2.score #=> [2, 7, 5, 8]
兩名選手都有兩雙 7 和 5,但是球員 2 的踢球比較高。
(已知和有意) 的限制是:
-
每隻手只有 5 張卡 (即沒有公用卡等)
-
不支援小丑/萬用字元
-
沒有驗證卡
確定直線時,它確實考慮到高和低的 ace,但否則它不是非常靈活。 (當然,只要 brute-force 使用 Array#combination 一次檢查 5 張卡組合,您可以迴避 「5 張卡」 的限制,但這是另一個故事。)
我還沒有看過這個挑戰如何在其他語言中得到解決,所以也許有一些我錯過的技巧。但實際上,主要是要看到我能用多少功能的方法和陣列/列舉的方法來獲得多少。程式碼主要是 one-line 的方法,所以我覺得沒問題。
還沒有打擾最佳化,但是 (如果沒有其他的話) 一堆值可以用一小段||=來記錄。然而,我對整體方法的批評更感興趣 (我只是喜歡? 方法,好嗎?) 和可能的替代方案 (整體或特定部分)
完整的程式碼 (包括測試和更詳細的註釋) 是 in this gist; 以下是主要班級 (見下文進一步說明)
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
註釋和編輯
作為一個規則,我認為 aces 很高 (值為 14),只有當它直接檢查 aces-low 時才將它們計數為低 (值 1) 。也就是說,一個 ace Card 例項的值為 14,但是在 aces-low 的上下文中,Hand 例項將報告為 1 。
Hand 和 Card 例項被認為是不可變的 (雖然在技術上,卡不是不可變的,因為我使用 Struct,但這只是為了這個挑戰的目的,否則我將定義一個”proper” 類)
再次檢視程式碼,這裡是我自己的關注點 (超出上述限制):
-
一些方法返回無序陣列,一些從高到低,而其他陣列從低到高。可能會讓這更加一致。
-
straight-checking 是非常幼稚的:生成連續 5 個數字,看看它們是否匹配卡值。我考慮以各種方式列舉價值觀,但是與生成的陣列相比,簡單的
==比起我看起來更加直觀。 -
在
RANKS雜湊鍵和方法名稱中需要重複一些,但是我發現它比我玩的替代物更乾淨。
最佳解決方案
我認為你的榜樣結構良好,我希望我的審查有一些正義。有你的疑慮和一些額外的要點我想討論:
You’ll find a working implementation of the following code here
-
一般編碼風格是好的和一致的。我特別喜歡你使用問號來表示返回一個布林值的方法。
-
也許你也可以從方法定義中省略大括號,因為你可能在方法呼叫中省略它們。
-
您沒有檔案離開
RANKS,您的屬性和初始化程式。我認為特別是RANKS和初始化器將從中受益。
-
-
在
RANKS和例項方法中重複我認為這是可以的,我沒有提出更好或更可讀的方式。 -
flush方法他們可以使用
Enumerable#one?更優雅地寫出來# If the hand only contains one suit, it's flush def flush? suits.uniq.one? end -
決定使卡成為
Struct我喜歡使用程式碼中的常量來提高可讀性和可維護性。引起我興趣的一件事是ACE_LOW和ACE_HIGH是全域性常數,而RANKS不是 (這是好的) 。我認為這個設計缺陷來自於你決定使Card成為一個Struct物件,並且這些物件常常導致缺乏邏輯。讓我們改變一下,使Card成為一流的公民:您的程式碼將受益匪淺。class Card
include Comparableattr_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
enddef 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
enddef successor? other
succ == other
enddef 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
endprivate
# 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
這將對Hand中的程式碼進行以下改進: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
enddef 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
endprivate
# 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]) }
endend
還-
它將在
Card中包含常數ACE_HIGH和ACE_LOW -
換卡是不可能的。由於
value=和suit=的結構響應,因此Struct卡在cards陣列中的值仍然可以修改
-
-
Hand初始化程式我認為將
cards作為一個混沌論據會更好一些。要用陣列進行初始化,不必要地降低可讀性。此外,您提出的ArgumentError不是很描述性,這可能會導致一些混亂。總而言之,這是我的改進如何:def initialize(*cards) raise ArgumentError.new "wrong number of cards (#{cards.count} for 5)" unless cards.count == 5 @cards = cards.freeze end根據使用情況,還可能需要額外的理智檢查:檢查您是否真的收到 5 個
Card例項。
以下幾點是您可以從這裡開始的建議
-
使
Hand成為Array的子類卡牌也是一系列的卡 – 相似之處允許您使用
Hand子類Array。這將允許您直接在Hand上使用Enumerable和ArrayDSL,如果您要進一步接受此程式碼,可能會受益您 (考慮Deck和Game類)另外,我會做的這樣做會給你預設的排序,應該排除你的問題與未分類的回報,加上你可以呼叫排序基於 suit-and-value 的排序。
一旦您決定在初始化時決定不進行
freezeHand,則可能需要額外的健康檢查:-
檢查
push,unshift,insert和<<是否得到Card as argument的例項 -
檢查
push,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類一起,這可以幫助您建立一個不錯的 DSL:hand = Hand.new Card.new(:spades, 14), Card.new(:diamonds, 14), Card.new(:hearts, 14), Card.new(:clubs, 14), Card.new(:clubs, 14) hand.all? &:ace? #=> true, this guy is obviously cheating hand.any? &:spades? #=> true, he has spades hand.count &:ace? #=> 5只是為了好玩,如果你有一個
Deck類,它也將Array子類class Deck < Array # the hands this deck creates attr_reader :hands # You can install any order here, Bridge, Preferans, Five Hundred SUITS = %i(clubs diamonds hearts spades).freeze # Initialize a deck of cards def initialize super (Card.new(SUITS.first, 1)..Card.new(SUITS.last, 14)).to_a shuffle! end # Deal n hands def deal! hands=5 @hands = hands.times.map {|i| Hand.new *pop(5) } end # ... and so on end deck = Deck.new deck.deal! deck.hands.sort_by &:rank #see who's winning hand = deck.hands.first # Select cards left in the deck that could be helpful to this hand deck.select do |card| hand.any?{|card_in_hand| (card_in_hand..card_in_hand.succ).include? card } end -
-
去哪裡呢
-
在
Hand上實現Comparable -
擺脫
freeze在手,所以我們可以交換一些型別的遊戲卡 -
田田田田達
-
正如我所說,你的例子已經很棒了,我希望你感謝我和你分享我的想法!
參考文獻
注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。