1

I'm creating a module for a deck of cards. I want the Deck object to be able to accept another implementation of the shuffle method I have written below.

I was originally thinking that my shuffle method should accept a parameter that is any object that responds to shuffle. Is that a good approach here?

I'm not sure exactly how to approach this in Ruby.

module PlayingCards
  class Deck
    RANKS = [*2..10, 'Jack', 'Queen', 'King', 'Ace']
    SUITS = %w{ Clubs Diamonds Hearts Spades }

    attr_accessor :cards, :discarded
    def initialize(options={})
      @cards = []
      @discarded = []
      options[:number_decks] ||= 1

      options[:number_decks].times do
        (RANKS).product(SUITS).each do |rank, suit|
          @cards << Card.new(rank, suit)
        end
      end
    end

    def shuffle()
      cards.push(*discarded).shuffle!
      discarded = []
      self
    end
  end
end

This is the hand class that is responsible for drawing cards from the deck and also folding a hand.

module PlayingCards
  class Hand
    attr_accessor :deck, :draw_count, :cards, :discarded

    def initialize(args = {})
      @deck = args[:deck]
      @discarded = args[:deck].discarded
      @draw_count = args[:draw_count]
      @cards = []
    end

    def draw
      draw_count.times do
        cards.push(deck.cards[0])
        deck.cards.shift
      end

      cards
    end

    def fold
      discarded.push(cards).flatten!
    end
  end
end
6
  • I guess you need to place cards as last line in shuffle, to return the value. Commented Jul 26, 2018 at 14:41
  • Don't mind, I missed the ! Commented Jul 26, 2018 at 14:48
  • I'm a bit confused what you're aiming to achieve with the discarded variable. My best guess is that each time you shuffle, it's possible that some cards will be discarded; but the deck should always be fully restored to its original state before re-shuffling? Commented Jul 26, 2018 at 14:56
  • And also, I find it a bit off that the shuffle method should return the discarded list. I would consider it more normal for such a method to return self. Commented Jul 26, 2018 at 14:57
  • I updated my question to include another class I have, Hand. A Hand can be folded, which pushes the cards into the discarded array, that exists on the Deck object... When a deck shuffles itself it needs to know about both the cards left in the deck and the cards that have been folded. Commented Jul 26, 2018 at 15:04

1 Answer 1

2

I want the Deck object to be able to accept another implementation of the shuffle method I have written below.

There are a few ways to implement this. There's no single right answer. Here's a few techniques:

  1. Just define multiple methods:

    def shuffle
      cards.push(*discarded).shuffle!
      discarded = []
      self
    end
    
    # For example
    def shuffle_before_replacing
      cards.shuffle!
      cards.push(*discarded)
      discarded = []
      self
    end
    
  2. Define a higher order method, for example:

    SHUFFLE_MODES = {
      standard: ->(cards, discarded) { cards.push(*discarded).shuffle },
      # ...
    }
    
    def shuffle(mode: :standard)
      cards = SHUFFLE_MODES.fetch(mode).call(cards, discarded)
      discarded = []
      self
    end
    
  3. Inject a dependency to perform the shuffling, for example:

    def shuffle(shuffler: Shuffler::Standard)
      shuffler = shuffler.new(cards, discarded)
      shuffler.shuffle
      cards = shuffler.cards
      shuffler = shuffler.discarded
      self
    end
    
    # ...
    
    module Shuffler
      class Base
        attr_reader :cards, :discarded
        def initialize(cards, discarded)
          @cards = cards
          @discarded = discarded
        end
      end
    end
    
    module Shuffler
      class Standard < Base
        def shuffle
          @cards.push(*discarded).shuffle
          @discarded = []
          self
        end
      end
    end
    
    module Shuffler
      class BeforeReplacing < Base
        def shuffle
          @cards.shuffle!
          @cards.push(*discarded)
          @discarded = []
          self
        end
      end
    end
    

    end

One main benefits of this third approach is that you can define Shufflers independently to the Deck class - so for example, perhaps you'll eventually have multiple types of Deck that can each use the Shuffler interface in different ways. (For example, maybe you'll want to shuffle different things, or shuffle multiple times.)

Likewise, you can also test the Shufflers independently to the Deck. This works both ways; you can also now test the Deck independently to the Shuffler (by passing a mock object into the Deck#shuffle method).

This technique is the most advanced of the three, and is something I'd only usually expect an experienced developer to fully utilise, in more complex scenarios. It's hard to say whether such an abstraction is even with the effort yet in your case, given the limited information I have!


All code above is untested, so may not be spot on, but hopefully this gives you some inspiration.

Sign up to request clarification or add additional context in comments.

2 Comments

This was really helpful Tom, I got something working from it. Thank you.
Oh.. yeah, fixed thanks. If you spot a silly mistake like that, feel free to just edit my post!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.