Permalink
Browse files

Initial commit: state of code at end of filming

  • Loading branch information...
camillebaldock committed Sep 2, 2014
0 parents commit c74f073586180cd2a4cd066ff24ee10baf8671ed
Showing with 335 additions and 0 deletions.
  1. +1 −0 lib/card.rb
  2. +104 −0 lib/hand.rb
  3. +28 −0 lib/hand_parser.rb
  4. +33 −0 spec/hand_parser_spec.rb
  5. +91 −0 spec/hand_spec.rb
  6. +78 −0 spec/spec_helper.rb
@@ -0,0 +1 @@
+Card = Struct.new(:pips, :suit)
@@ -0,0 +1,104 @@
+class Hand
+
+ attr_reader :cards
+
+ def initialize(cards)
+ @cards = cards
+ end
+
+ def better_than?(other_hand)
+ POKER_RANKS.index(rank.fetch(:type)) > POKER_RANKS.index(other_hand.rank.fetch(:type))
+ end
+
+ POKER_RANKS = [
+ :full_house,
+ :four_of_a_kind,
+ ]
+
+ def rank
+ if has_four
+ { :type => :four_of_a_kind }
+ elsif full_house
+ { :type => :full_house }
+ elsif straight
+ { :type => :straight }
+ elsif flush
+ { :type => :flush }
+ elsif has_three
+ { :type => :three_of_a_kind }
+ elsif has_two
+ { :type => :pair }
+ else
+ { :type => :highest }
+ end
+ end
+
+ ##bob
+ def pip_count
+ grouped_cards = cards.map(&:pips).group_by { |i| i }
+ grouped_cards.each do |key, value|
+ grouped_cards[key] = value.count
+ end
+ end
+
+ ##DUPE! pip_count
+ def suit_count
+ grouped_cards = cards.map(&:suit).group_by { |i| i }
+ grouped_cards.each do |key, value|
+ grouped_cards[key] = value.count
+ end
+ end
+
+ private
+
+ def has_four
+ pip_count.values.include?(4)
+ end
+
+ def has_three
+ pip_count.values.include?(3)
+ end
+
+ def has_two
+ pip_count.values.include?(2)
+ end
+
+ def full_house
+ has_three && has_two
+ end
+
+ def flush
+ suit_count.values.include?(5)
+ end
+
+ def straight
+ card_values = cards.map(&:pips)
+ number_aces = card_values.select{|i| i == 14}.count
+ if number_aces >= 1
+ aces_as_ones = card_values.clone
+ aces_as_ones.delete(14)
+ number_aces.times do
+ aces_as_ones << 1
+ end
+ consecutive_cards?(card_values) ||
+ consecutive_cards?(aces_as_ones)
+ else
+ consecutive_cards?(card_values)
+ end
+ end
+
+ #TODO: live in a helper, utility
+ def consecutive_cards?(card_values)
+ card_values.sort!
+ #Magic number 4 => always 5 cards per hand
+ difference_always_1 = true
+ i = 0
+ while (difference_always_1 && i < 4) do
+ difference_between_pips = card_values[i+1] - card_values[i]
+ difference_always_1 = difference_between_pips == 1
+ i += 1
+ end
+ difference_always_1
+ end
+
+end
@@ -0,0 +1,28 @@
+require 'hand'
+require 'card'
+
+class HandParser
+ #TODO: fail nicely when not 5 cards
+ def parse(array_of_string_cards)
+ cards = array_of_string_cards.map do |card_string|
+ make_card(card_string)
+ end
+ Hand.new(cards)
+ end
+
+ private
+
+ SUITS = { "h" => :heart, "d" => :diamonds,
+ "s" => :spades, "c" => :clubs}
+ PIPS_FOR_HEADS = { "j" => 11, "q" => 12, "k" => 13, "a" => 14}
+
+ #TODO: fail loudly and with better errors with unexpected inputs
+ #assumes correct capitalisation, correct letters...
+ def make_card(card_string)
+ end_of_card_number_index = (card_string.length)-2
+ pips_string = card_string[0..end_of_card_number_index]
+ number_of_pips = PIPS_FOR_HEADS.fetch(pips_string, pips_string.to_i)
+ Card.new(number_of_pips, SUITS.fetch(card_string[end_of_card_number_index+1]))
+ end
+
+end
@@ -0,0 +1,33 @@
+require "spec_helper"
+require "hand_parser"
+
+describe HandParser do
+ describe "#parse"
+ it "returns a hand of cards" do
+ array_of_cards = ["5h", "6d", "ks", "qc", "1h"]
+ hand = described_class.new.parse(array_of_cards)
+ expect(hand.cards.count).to eq 5
+ end
+
+ it "returns the right cards" do
+ array_of_cards = ["6d"]
+ hand = described_class.new.parse(array_of_cards)
+ expect(hand.cards.first.suit).to eq :diamonds
+ expect(hand.cards.first.pips).to eq 6
+ end
+
+ it "returns the right cards" do
+ array_of_cards = ["10d"]
+ hand = described_class.new.parse(array_of_cards)
+ expect(hand.cards.first.suit).to eq :diamonds
+ expect(hand.cards.first.pips).to eq 10
+ end
+
+ it "returns the right cards" do
+ array_of_cards = ["qd"]
+ hand = described_class.new.parse(array_of_cards)
+ expect(hand.cards.first.suit).to eq :diamonds
+ expect(hand.cards.first.pips).to eq 12
+ end
+
+end
@@ -0,0 +1,91 @@
+require 'spec_helper'
+require 'hand'
+require 'hand_parser'
+
+describe Hand do
+
+ #TODO: this is not a nice interface
+ let(:pair_hand) {
+ HandParser.new.parse(["5h", "5d", "6d", "7d", "8d"])
+ }
+ let(:highest_hand) {
+ HandParser.new.parse(["4h", "5d", "6d", "7d", "9d"])
+ }
+ let(:three_of_a_kind_hand) {
+ HandParser.new.parse(["5h", "5d", "5s", "7d", "8d"])
+ }
+ let(:four_of_a_kind_hand) {
+ HandParser.new.parse(["5h", "5d", "5s", "5c", "8d"])
+ }
+ let(:full_house_hand) {
+ HandParser.new.parse(["5h", "5d", "5s", "6c", "6h"])
+ }
+ let(:flush_hand) {
+ HandParser.new.parse(["5h", "6h", "7h", "8h", "10h"])
+ }
+ let(:straight_hand) {
+ HandParser.new.parse(["5h", "6h", "7h", "8h", "9d"])
+ }
+ let(:straight_hand_low_ace) {
+ HandParser.new.parse(["2h", "3h", "4h", "5h", "ad"])
+ }
+ let(:straight_hand_high_ace) {
+ HandParser.new.parse(["10h", "jh", "qh", "kh", "ad"])
+ }
+
+ describe "#better_than" do
+ it "tells me if the hand is better" do
+ expect(four_of_a_kind_hand.better_than?(full_house_hand)).to be true
+ end
+ end
+
+ describe "#pip_count" do
+ it "returns information about the winning potential of the hand" do
+ expect(pair_hand.pip_count).to eq ({ 5 => 2, 6 => 1, 7 => 1, 8 => 1 })
+ end
+ end
+
+ describe "#suit_count" do
+ it "returns information about the winning potential of the hand" do
+ expect(pair_hand.suit_count).to eq ({ :heart => 1, :diamonds => 4 })
+ end
+ end
+
+ describe "#rank" do
+ it "returns information about the winning potential of the hand" do
+ expect(pair_hand.rank).to eq ({:type => :pair})
+ end
+
+ it "returns information about the winning potential of the hand" do
+ expect(highest_hand.rank).to eq ({:type => :highest})
+ end
+
+ it "returns information about the winning potential of the hand" do
+ expect(three_of_a_kind_hand.rank).to eq ({:type => :three_of_a_kind})
+ end
+
+ it "returns information about the winning potential of the hand" do
+ expect(four_of_a_kind_hand.rank).to eq ({:type => :four_of_a_kind})
+ end
+
+ it "returns information about the winning potential of the hand" do
+ expect(full_house_hand.rank).to eq ({:type => :full_house})
+ end
+
+ it "returns information about the winning potential of the hand" do
+ expect(flush_hand.rank).to eq ({:type => :flush})
+ end
+
+ it "returns information about the winning potential of the hand" do
+ expect(straight_hand.rank).to eq ({:type => :straight})
+ end
+
+ it "works with a straight with low ace" do
+ expect(straight_hand_low_ace.rank).to eq ({:type => :straight})
+ end
+
+ it "works with a straight with high ace" do
+ expect(straight_hand_high_ace.rank).to eq ({:type => :straight})
+ end
+ end
+end
@@ -0,0 +1,78 @@
+# This file was generated by the `rspec --init` command. Conventionally, all
+# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
+# The generated `.rspec` file contains `--require spec_helper` which will cause this
+# file to always be loaded, without a need to explicitly require it in any files.
+#
+# Given that it is always loaded, you are encouraged to keep this file as
+# light-weight as possible. Requiring heavyweight dependencies from this file
+# will add to the boot time of your test suite on EVERY test run, even for an
+# individual file that may not need all of that loaded. Instead, make a
+# separate helper file that requires this one and then use it only in the specs
+# that actually need it.
+#
+# The `.rspec` file also contains a few flags that are not defaults but that
+# users commonly want.
+#
+# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
+RSpec.configure do |config|
+# The settings below are suggested to provide a good initial experience
+# with RSpec, but feel free to customize to your heart's content.
+=begin
+ # These two settings work together to allow you to limit a spec run
+ # to individual examples or groups you care about by tagging them with
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
+ # get run.
+ config.filter_run :focus
+ config.run_all_when_everything_filtered = true
+
+ # Many RSpec users commonly either run the entire suite or an individual
+ # file, and it's useful to allow more verbose output when running an
+ # individual spec file.
+ if config.files_to_run.one?
+ # Use the documentation formatter for detailed output,
+ # unless a formatter has already been configured
+ # (e.g. via a command-line flag).
+ config.default_formatter = 'doc'
+ end
+
+ # Print the 10 slowest examples and example groups at the
+ # end of the spec run, to help surface which specs are running
+ # particularly slow.
+ config.profile_examples = 10
+
+ # Run specs in random order to surface order dependencies. If you find an
+ # order dependency and want to debug it, you can fix the order by providing
+ # the seed, which is printed after each run.
+ # --seed 1234
+ config.order = :random
+
+ # Seed global randomization in this process using the `--seed` CLI option.
+ # Setting this allows you to use `--seed` to deterministically reproduce
+ # test failures related to randomization by passing the same `--seed` value
+ # as the one that triggered the failure.
+ Kernel.srand config.seed
+
+ # rspec-expectations config goes here. You can use an alternate
+ # assertion/expectation library such as wrong or the stdlib/minitest
+ # assertions if you prefer.
+ config.expect_with :rspec do |expectations|
+ # Enable only the newer, non-monkey-patching expect syntax.
+ # For more details, see:
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
+ expectations.syntax = :expect
+ end
+
+ # rspec-mocks config goes here. You can use an alternate test double
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
+ config.mock_with :rspec do |mocks|
+ # Enable only the newer, non-monkey-patching expect syntax.
+ # For more details, see:
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
+ mocks.syntax = :expect
+
+ # Prevents you from mocking or stubbing a method that does not exist on
+ # a real object. This is generally recommended.
+ mocks.verify_partial_doubles = true
+ end
+=end
+end

0 comments on commit c74f073

Please sign in to comment.