-
-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1393cca
commit 623d31e
Showing
8 changed files
with
790 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Instructions | ||
|
||
Pick the best hand(s) from a list of poker hands. | ||
|
||
See [wikipedia][poker-hands] for an overview of poker hands. | ||
|
||
[poker-hands]: https://en.wikipedia.org/wiki/List_of_poker_hands |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"authors": ["ryanplusplus"], | ||
"files": { | ||
"solution": [ | ||
"src/poker.cr" | ||
], | ||
"test": [ | ||
"spec/poker_spec.cr" | ||
], | ||
"example": [ | ||
".meta/src/example.cr" | ||
] | ||
}, | ||
"blurb": "Pick the best hand(s) from a list of poker hands.", | ||
"source": "Inspired by the training course from Udacity.", | ||
"source_url": "https://www.udacity.com/course/design-of-computer-programs--cs212" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
module Poker | ||
def self.best_hands(hands) | ||
hands = hands.map { |hand| Hand.new(hand) } | ||
max_score = hands.map(&.score).max | ||
hands.select { |hand| hand.score == max_score }.map(&.to_s) | ||
end | ||
end | ||
|
||
class Poker::Hand | ||
@cards : Array(Card) | ||
@s : String | ||
|
||
def initialize(@s) | ||
@cards = @s.split.map { |card| Card.new(card) } | ||
end | ||
|
||
def to_s | ||
@s | ||
end | ||
|
||
def score | ||
straight_flush || | ||
straight_flush_ace_low || | ||
four_of_a_kind || | ||
full_house || | ||
flush || | ||
straight || | ||
straight_ace_low || | ||
three_of_a_kind || | ||
two_pair || | ||
pair || | ||
high_card | ||
end | ||
|
||
private def partitioned_by_rank | ||
@cards.group_by { |card| card.rank }.values | ||
end | ||
|
||
private def partitioned_by_suit | ||
@cards.group_by { |card| card.suit }.values | ||
end | ||
|
||
private def sorted_by_rank(cards = @cards) | ||
cards.sort_by { |card| card.rank }.reverse | ||
end | ||
|
||
private def sorted_by_rank_ace_low | ||
@cards.sort_by { |card| card.rank_ace_low }.reverse | ||
end | ||
|
||
private def base_score(type) | ||
{ | ||
:high_card => 1, | ||
:pair => 2, | ||
:two_pair => 3, | ||
:three_of_a_kind => 4, | ||
:straight => 5, | ||
:flush => 6, | ||
:full_house => 7, | ||
:four_of_a_kind => 8, | ||
:straight_flush => 9, | ||
}[type] | ||
end | ||
|
||
private def fractional_score(cards, rank_type = :rank) | ||
ranks = rank_type == :rank_ace_low ? cards.map(&.rank_ace_low) : cards.map(&.rank) | ||
ranks += [0, 0, 0, 0, 0] | ||
[ | ||
ranks[0] * 0.01, | ||
ranks[1] * 0.00_01, | ||
ranks[2] * 0.00_00_01, | ||
ranks[3] * 0.00_00_00_01, | ||
ranks[4] * 0.00_00_00_00_01, | ||
].sum | ||
end | ||
|
||
private def high_card | ||
base_score(:high_card) + fractional_score(sorted_by_rank) | ||
end | ||
|
||
private def pair | ||
pairs = partitioned_by_rank.select { |p| p.size == 2 } | ||
|
||
if pairs.size == 1 | ||
everything_else = partitioned_by_rank.select { |p| p.size == 1 }.flatten | ||
base_score(:pair) + fractional_score([pairs.first.first, *sorted_by_rank(everything_else)]) | ||
end | ||
end | ||
|
||
private def two_pair | ||
pairs = partitioned_by_rank.select { |p| p.size == 2 } | ||
|
||
if pairs.size == 2 | ||
everything_else = partitioned_by_rank.select { |p| p.size == 1 }.flatten | ||
pair_ranks = pairs.flatten.map(&.rank) | ||
base_score(:two_pair) + fractional_score([ | ||
pairs.flatten.max_by { |c| c.rank }, | ||
pairs.flatten.min_by { |c| c.rank }, | ||
*sorted_by_rank(everything_else), | ||
]) | ||
end | ||
end | ||
|
||
private def three_of_a_kind | ||
triples = partitioned_by_rank.select { |p| p.size == 3 } | ||
|
||
if triples.size == 1 | ||
everything_else = partitioned_by_rank.select { |p| p.size == 1 }.flatten | ||
base_score(:three_of_a_kind) + fractional_score([triples.first.first, *sorted_by_rank(everything_else)]) | ||
end | ||
end | ||
|
||
private def straight | ||
cards = sorted_by_rank | ||
|
||
if cards.each_cons(2).all? { |pair| pair[1].rank + 1 == pair[0].rank } | ||
return base_score(:straight) + fractional_score([cards.last]) | ||
end | ||
end | ||
|
||
private def straight_ace_low | ||
cards = sorted_by_rank_ace_low | ||
|
||
if cards.each_cons(2).all? { |pair| pair[1].rank_ace_low + 1 == pair[0].rank_ace_low } | ||
base_score(:straight) + fractional_score([cards.last], :rank_ace_low) | ||
end | ||
end | ||
|
||
private def flush | ||
cards = partitioned_by_suit | ||
|
||
if cards.size == 1 | ||
base_score(:flush) + fractional_score([*sorted_by_rank(cards.first)]) | ||
end | ||
end | ||
|
||
private def full_house | ||
triples = partitioned_by_rank.select { |p| p.size == 3 } | ||
pairs = partitioned_by_rank.select { |p| p.size == 2 } | ||
|
||
if pairs.size == 1 && triples.size == 1 | ||
base_score(:full_house) + fractional_score([triples.first.first, pairs.first.first]) | ||
end | ||
end | ||
|
||
private def four_of_a_kind | ||
quads = partitioned_by_rank.select { |p| p.size == 4 } | ||
|
||
if quads.size == 1 | ||
everything_else = partitioned_by_rank.select { |p| p.size == 1 } | ||
base_score(:four_of_a_kind) + fractional_score([quads.first.first, *everything_else.flatten]) | ||
end | ||
end | ||
|
||
private def straight_flush | ||
if straight && flush | ||
base_score(:straight_flush) + fractional_score([sorted_by_rank.last]) | ||
end | ||
end | ||
|
||
private def straight_flush_ace_low | ||
if straight_ace_low && flush | ||
base_score(:straight_flush) + fractional_score([sorted_by_rank_ace_low.last], :rank_ace_low) | ||
end | ||
end | ||
end | ||
|
||
class Poker::Hand::Card | ||
getter suit : Char, rank : Int32, rank_ace_low : Int32 | ||
|
||
def initialize(s) | ||
@rank = parse_rank(s) | ||
@rank_ace_low = parse_rank_ace_low(s) | ||
@suit = parse_suit(s) | ||
end | ||
|
||
private def parse_rank(s) | ||
{ | ||
'J' => 11, | ||
'Q' => 12, | ||
'K' => 13, | ||
'A' => 14, | ||
}[s[0]]? || s[0...-1].to_i | ||
end | ||
|
||
private def parse_rank_ace_low(s) | ||
{ | ||
'J' => 11, | ||
'Q' => 12, | ||
'K' => 13, | ||
'A' => 1, | ||
}[s[0]]? || s[0...-1].to_i | ||
end | ||
|
||
private def parse_suit(s) | ||
s[-1] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
require "spec" | ||
require "../src/*" | ||
|
||
describe <%= to_capitalized(@json["exercise"].to_s) %> do | ||
<% @json["cases"].as_a.each do |cases| %> | ||
<%= status()%> "<%-= cases["description"] %>" do | ||
hands = <%= cases["input"]["hands"].inspect.gsub("[", "[\n").gsub("\", \"", "\",\n\"") %> | ||
expected = <%= cases["expected"].inspect.gsub("[", "[\n").gsub("\", \"", "\",\n\"") %> | ||
<%= to_capitalized(@json["exercise"].to_s) %>.<%= cases["property"].to_s.underscore %>(hands).should eq(expected) | ||
end | ||
<% end %> | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
# This is an auto-generated file. | ||
# | ||
# Regenerating this file via `configlet sync` will: | ||
# - Recreate every `description` key/value pair | ||
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications | ||
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) | ||
# - Preserve any other key/value pair | ||
# | ||
# As user-added comments (using the # character) will be removed when this file | ||
# is regenerated, comments can be added via a `comment` key. | ||
|
||
[161f485e-39c2-4012-84cf-bec0c755b66c] | ||
description = "single hand always wins" | ||
|
||
[370ac23a-a00f-48a9-9965-6f3fb595cf45] | ||
description = "highest card out of all hands wins" | ||
|
||
[d94ad5a7-17df-484b-9932-c64fc26cff52] | ||
description = "a tie has multiple winners" | ||
|
||
[61ed83a9-cfaa-40a5-942a-51f52f0a8725] | ||
description = "multiple hands with the same high cards, tie compares next highest ranked, down to last card" | ||
|
||
[da01becd-f5b0-4342-b7f3-1318191d0580] | ||
description = "winning high card hand also has the lowest card" | ||
|
||
[f7175a89-34ff-44de-b3d7-f6fd97d1fca4] | ||
description = "one pair beats high card" | ||
|
||
[e114fd41-a301-4111-a9e7-5a7f72a76561] | ||
description = "highest pair wins" | ||
|
||
[b3acd3a7-f9fa-4647-85ab-e0a9e07d1365] | ||
description = "both hands have the same pair, high card wins" | ||
|
||
[935bb4dc-a622-4400-97fa-86e7d06b1f76] | ||
description = "two pairs beats one pair" | ||
|
||
[c8aeafe1-6e3d-4711-a6de-5161deca91fd] | ||
description = "both hands have two pairs, highest ranked pair wins" | ||
|
||
[88abe1ba-7ad7-40f3-847e-0a26f8e46a60] | ||
description = "both hands have two pairs, with the same highest ranked pair, tie goes to low pair" | ||
|
||
[15a7a315-0577-47a3-9981-d6cf8e6f387b] | ||
description = "both hands have two identically ranked pairs, tie goes to remaining card (kicker)" | ||
|
||
[f761e21b-2560-4774-a02a-b3e9366a51ce] | ||
description = "both hands have two pairs that add to the same value, win goes to highest pair" | ||
|
||
[fc6277ac-94ac-4078-8d39-9d441bc7a79e] | ||
description = "two pairs first ranked by largest pair" | ||
|
||
[21e9f1e6-2d72-49a1-a930-228e5e0195dc] | ||
description = "three of a kind beats two pair" | ||
|
||
[c2fffd1f-c287-480f-bf2d-9628e63bbcc3] | ||
description = "both hands have three of a kind, tie goes to highest ranked triplet" | ||
|
||
[eb856cc2-481c-4b0d-9835-4d75d07a5d9d] | ||
description = "with multiple decks, two players can have same three of a kind, ties go to highest remaining cards" | ||
include = false | ||
|
||
[26a4a7d4-34a2-4f18-90b4-4a8dd35d2bb1] | ||
description = "with multiple decks, two players can have same three of a kind, ties go to highest remaining cards" | ||
reimplements = "eb856cc2-481c-4b0d-9835-4d75d07a5d9d" | ||
|
||
[a858c5d9-2f28-48e7-9980-b7fa04060a60] | ||
description = "a straight beats three of a kind" | ||
|
||
[73c9c756-e63e-4b01-a88d-0d4491a7a0e3] | ||
description = "aces can end a straight (10 J Q K A)" | ||
|
||
[76856b0d-35cd-49ce-a492-fe5db53abc02] | ||
description = "aces can start a straight (A 2 3 4 5)" | ||
|
||
[e214b7df-dcba-45d3-a2e5-342d8c46c286] | ||
description = "aces cannot be in the middle of a straight (Q K A 2 3)" | ||
|
||
[6980c612-bbff-4914-b17a-b044e4e69ea1] | ||
description = "both hands with a straight, tie goes to highest ranked card" | ||
|
||
[5135675c-c2fc-4e21-9ba3-af77a32e9ba4] | ||
description = "even though an ace is usually high, a 5-high straight is the lowest-scoring straight" | ||
|
||
[c601b5e6-e1df-4ade-b444-b60ce13b2571] | ||
description = "flush beats a straight" | ||
|
||
[4d90261d-251c-49bd-a468-896bf10133de] | ||
description = "both hands have a flush, tie goes to high card, down to the last one if necessary" | ||
include = false | ||
|
||
[e04137c5-c19a-4dfc-97a1-9dfe9baaa2ff] | ||
description = "both hands have a flush, tie goes to high card, down to the last one if necessary" | ||
reimplements = "4d90261d-251c-49bd-a468-896bf10133de" | ||
|
||
[3a19361d-8974-455c-82e5-f7152f5dba7c] | ||
description = "full house beats a flush" | ||
|
||
[eb73d0e6-b66c-4f0f-b8ba-bf96bc0a67f0] | ||
description = "both hands have a full house, tie goes to highest-ranked triplet" | ||
|
||
[34b51168-1e43-4c0d-9b32-e356159b4d5d] | ||
description = "with multiple decks, both hands have a full house with the same triplet, tie goes to the pair" | ||
|
||
[d61e9e99-883b-4f99-b021-18f0ae50c5f4] | ||
description = "four of a kind beats a full house" | ||
|
||
[2e1c8c63-e0cb-4214-a01b-91954490d2fe] | ||
description = "both hands have four of a kind, tie goes to high quad" | ||
|
||
[892ca75d-5474-495d-9f64-a6ce2dcdb7e1] | ||
description = "with multiple decks, both hands with identical four of a kind, tie determined by kicker" | ||
|
||
[923bd910-dc7b-4f7d-a330-8b42ec10a3ac] | ||
description = "straight flush beats four of a kind" | ||
|
||
[d9629e22-c943-460b-a951-2134d1b43346] | ||
description = "aces can end a straight flush (10 J Q K A)" | ||
|
||
[05d5ede9-64a5-4678-b8ae-cf4c595dc824] | ||
description = "aces can start a straight flush (A 2 3 4 5)" | ||
|
||
[ad655466-6d04-49e8-a50c-0043c3ac18ff] | ||
description = "aces cannot be in the middle of a straight flush (Q K A 2 3)" | ||
|
||
[d0927f70-5aec-43db-aed8-1cbd1b6ee9ad] | ||
description = "both hands have a straight flush, tie goes to highest-ranked card" | ||
|
||
[be620e09-0397-497b-ac37-d1d7a4464cfc] | ||
description = "even though an ace is usually high, a 5-high straight flush is the lowest-scoring straight flush" |
Oops, something went wrong.