views:

550

answers:

6

Hi,

as I am learning the Ruby language, I am getting closer to actual programming. I was thinking of creating a simple card game. My question isn't Ruby oriented, but I do know want to learn how to solve this problem with a genuine OOP approach. In my card game, I want to have four players, using a standard deck with 52 cards, no jokers/wildcards. In the game, I won't use the ace as a dual card, it is always the highest card.

So, the programming problems I wonder about are the following:

  1. How can I sort/randomize the deck of cards? There are four types, each having 13 values. Eventually there can be only unique values, so picking random values could generate duplicates.

  2. How can I implement a simple AI? As there are tons of card games, someone would have figured this part out already, so references would be great.

I am a true Ruby nuby, and my goal here is to learn to solve problems, so pseudo code would be great, just to understand how to solve the problem programmatically. I apologize for my grammar and writing style if it's unclear, for it is not my native language.

Also, pointers to sites where such challenges are explained would be a great resource!

Thank you for your comments, answers and feedback!

+7  A: 

Something to get you started

You can ensure unique cards very easily by using numbers from 0 to 51.

The Array#shuffle method is based off the Knuth-Fisher-Yates shuffle algorithm. http://en.wikipedia.org/wiki/Fisher–Yates_shuffle

class Card
  RANKS = %w(2 3 4 5 6 7 8 9 10 J Q K A)
  SUITS = %w(Spade Heart Club Diamond)

  attr_accessor :rank, :suit

  def initialize(id)
    self.rank = RANKS[id % 13]
    self.suit = SUITS[id % 4]
  end
end

class Deck
  attr_accessor :cards
  def initialize
    # shuffle array and init each Card
    self.cards = (0..51).to_a.shuffle.collect { |id| Card.new(id) }
  end
end

# people with Ruby 1.9 (or 1.8.7 with backports) can safely ignore this duck punch
class Array
  # knuth-fisher-yates shuffle algorithm
  def shuffle!
    n = length
    for i in 0...n
      r = rand(n-i)+i
      self[r], self[i] = self[i], self[r]
    end
    self
  end
  def shuffle
    dup.shuffle!
  end
end

test

d = Deck.new
d.cards.each do |card|
  puts "#{card.rank} #{card.suit}"
end

output

6 Spade
5 Heart
2 Heart
8 Heart
8 Diamond
7 Club
J Diamond
4 Club
K Spade
5 Diamond
J Heart
8 Spade
10 Club
4 Diamond
9 Heart
7 Diamond
3 Diamond
K Diamond
7 Spade
Q Diamond
9 Diamond
6 Heart
A Heart
9 Club
A Spade
5 Club
J Club
Q Spade
2 Club
2 Spade
Q Heart
A Diamond
10 Spade
10 Diamond
Q Club
3 Club
A Club
K Club
6 Club
10 Heart
2 Diamond
3 Spade
K Heart
5 Spade
9 Spade
7 Heart
4 Spade
J Spade
3 Heart
4 Heart
8 Club
6 Diamond
macek
@Bill K, as the OP is relatively new to Ruby, I suppose it was a mistake to stay away from OOP and best practices right off the bat. I remodeled the above code to help him get started in the *right* way :)
macek
This doesn't ensure uniqueness of cards picked, because the same card can be picked 52 times, though it is improbable. A single duplicate however, would be very likely. Consider this: `52.times{puts get_card(rand(52))}`
ehsanul
awful code, but it is correct, @ehsanul. still should use fisher yates shuffle
Timmy
Hmm, you changed the code. My comment no longer applies.
ehsanul
@ehsanul, I suppose the wording is a bit ambiguous. I wasn't intending to mean that generating random numbers from 0 to 51 several times would be the way to build a deck or a player's hand.
macek
MRI 1.9, has Array#shuffle, which has been backported to 1.8.7.
Wayne Conrad
Wow, that's what I call an answer. It's that I'm out of votes right now, but you definitely deserve a +100. It surely gives me a head start, especially to build up two arrays, two instance variables and dividing the cards and deck in two classes. Thank you so much!
Shyam
@Wayne Conrad, do you know which algorithm it's using?
macek
Much better! Trying to change my vote to a +1 :)
Bill K
@Shyam In that case make sure you click on the "accepted" answer thick ( the icon below the answer number )
OscarRyz
@macek, It looks like the Fisher-Yates (aka Knuth) shuffle.
Wayne Conrad
In case anyone's wondering, "duck punch" refers to a case of "duck punching", ruby's version of "monkey patching". http://stackoverflow.com/questions/tagged/monkeypatching
Andrew Grimm
+1  A: 

Macek's answer is good as far as for setting up a deck.

You also asked about other entities.

You probably want four "Players". Each player might be either human or machine controlled.

To implement a human player, you interface with the screen/mouse/keyboard; to implement the machine controlled players, you have a hand and you can see some central cards on a table (all the players need to know of a central table that holds any cards that would be on the table).

From there the logic is based on what game you are playing.

Once your "Player" (AI) gets the turn (for instance, a takeTurn method is called on your player object), it should examine its cards and make the proper decisions--taking cards from stacks on the table or placing cards from its hand onto the table. (The table almost certainly has at least two stacks a player can access--"Draw" and "Discard".)

When a Human player has his takeTurn method called, it should interface with the screen--updating the player's hand, allowing him to draw and discard.

When each player is done with his turn, it should return. It can't directly call the next player (otherwise you'd start to build up a stack), so you need some form of turn control that can call the players in order. This centralized control also prevents players from knowing about each other, they shouldn't really need to (one of the best OO design tactics is that each object should know as little about other objects as possible).

...Still thinking... I may add more...

Bill K
+1  A: 

I'm not sure what sort of card game you want to build but the most common way of building this sort of AI is generating a tree of possible options. I don't think there's a library to do it as such but ruby can do trees easily.

The aim is to have a root node which is the present time and then each child node is a possible action. Then the child of each possible action is the next possible action. From there you can build a tree of every possible outcome. All that remains is to select the outcome you like.

Where you don't have all the information (ie can't see your opponents cards) you simulate it. By simulate I mean guess. The average of all the simulations/guesses will give you a good idea of which tree branches are 'likely to be the best'.

If you can do all that you're well on the way (and it's a really good exercise), there's hundreds of AI articles about, google will be your friend. The only problem with the approach I described is it can be desperately slow but there are many clever techniques to speed it up like transposition tables, alpha-beta pruning etc... which I don't suggest you look up quite yet.

Daniel
+3  A: 

Don't bother looking for an AI package

You will learn more and get greater satisfaction by coding the "AI" yourself.

Start simple, just consider:

  • game state - what cards have been played or seen, what cards are visible to all players
  • strategy - how does a computer player respond based on its current hand and its knowledge of the game state

once you have that working, you can get develop more sophisticated strategies:

  • inference - what cards does the human player likely hold based on her prior actions
  • game tree search - how to maximize the chance of winning given what could possibly happen

then if you want to get really sophisticated, you can start looking into opponent modeling

klochner
Thank you, these are enough pointers to Google on!
Shyam
+3  A: 

Rather than cramming this all in a comment, I'm adding this as a note for people that might find it useful. Ruby 1.9's native Array#shuffle! and Array#shuffle does in fact use the Knuth-Fisher-Yates shuffle algorithm.

ruby-1.9.1-p376/array.c

/*
 *  call-seq:
 *     array.shuffle!        -> array
 *  
 *  Shuffles elements in _self_ in place.
 */

static VALUE
rb_ary_shuffle_bang(VALUE ary)
{
    long i = RARRAY_LEN(ary);

    rb_ary_modify(ary);
    while (i) {
    long j = rb_genrand_real()*i;
    VALUE tmp = RARRAY_PTR(ary)[--i];
    RARRAY_PTR(ary)[i] = RARRAY_PTR(ary)[j];
    RARRAY_PTR(ary)[j] = tmp;
    }
    return ary;
}


/*
 *  call-seq:
 *     array.shuffle -> an_array
 *  
 *  Returns a new array with elements of this array shuffled.
 *     
 *     a = [ 1, 2, 3 ]           #=> [1, 2, 3]
 *     a.shuffle                 #=> [2, 3, 1]
 */

static VALUE
rb_ary_shuffle(VALUE ary)
{
    ary = rb_ary_dup(ary);
    rb_ary_shuffle_bang(ary);
    return ary;
}
macek
A: 

Something very simple to get you started:

class CardGame
  DECK = %w[A 2 3 4 5 6 7 8 9 T J Q K].product(%w[c d h s]).map(&:join)

  def initialize(decks=1)
    @decks = decks
  end

  def shuffle
    @playing_deck = (DECK*@decks).shuffle
  end

  def deal(players=1, cards=5)
    shuffle
    @dealt = Array.new(players) { Array.new }

    @dealt.map { |hand| cards.times { hand << @playing_deck.pop } }
  end

  def display
    @dealt.each_with_index { |cards, i| puts "Player #{i+1}: #{cards.join(' | ')}" }
    puts "Cards used: #{@dealt.flatten.size}"
    puts "Cards remaining: #{@playing_deck.size}"
  end

  private :shuffle   
end

game1 = CardGame.new   
game1.deal   
game1.display    
puts 
game1.deal(4)  
game1.display   
puts   
game2 = CardGame.new(2)   
game2.deal(6,10)   
game2.display
Michael Kohl