⚠️ Warning: This is a draft ⚠️
This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.
If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.
{{collection|Go Fish}}
class Card
RANKS = %w(2 3 4 5 6 7 8 9 10 J Q K A)
SUITS = %w(C D H S)
def initialize(rank, suit)
@rank = rank
@suit = suit
end
attr_reader :rank, :suit
def <=>(other)
# this ordering sorts first by rank, then by suit
(RANKS.find_index(self.rank) <=> RANKS.find_index(other.rank)).nonzero? ||
(SUITS.find_index(self.suit) <=> SUITS.find_index(other.suit))
end
def to_s
@rank + @suit
end
end
#######################################################################
class Deck
def initialize
@deck = []
Card::SUITS.each do |suit|
Card::RANKS.each do |rank|
@deck << Card.new(rank, suit)
end
end
@deck.shuffle!
end
attr_reader :deck
# returns an array of cards, even for dealing just 1 card
def deal(n=1)
@deck.pop(n)
end
def empty?
@deck.empty?
end
def cards_remaining
@deck.length
end
end
#######################################################################
class Player
def initialize(game)
@hand = {}
@books = []
@game = game
@opponents_hand = {
:known_to_have => [],
:known_not_to_have => [],
}
end
attr_reader :name
def take_cards(cards)
my_cards = @hand.values.flatten.concat(cards)
@hand = my_cards.group_by {|card| card.rank}
# look for, and remove, any books
@hand.each do |rank, cards|
if cards.length == 4
puts "#@name made a book of #{rank}"
@books << rank
@hand.delete(rank)
end
end
if @hand.empty? and not @game.deck.empty?
@game.deal(self, 1)
end
end
def num_books
@books.length
end
# return true if the next turn is still mine
# return false if the next turn is my opponent's
def query(opponent)
wanted = wanted_card
puts "#@name: Do you have a #{wanted}?"
received = opponent.answer(wanted)
@opponents_hand[:known_to_have].delete(wanted)
if received.empty?
@game.deal(self, 1)
# by my next turn, opponent will have been dealt a card
# so I cannot know what he does not have.
@opponents_hand[:known_not_to_have] = []
false
else
take_cards(received)
@opponents_hand[:known_not_to_have].push(wanted).uniq!
true
end
end
def answer(rank)
cards = []
@opponents_hand[:known_to_have].push(rank).uniq!
if not @hand[rank]
puts "#@name: Go Fish!"
else
cards = @hand[rank]
@hand.delete(rank)
puts "#@name: Here you go -- #{cards.join(', ')}"
@game.deal(self, 1) if @hand.empty?
end
cards
end
def print_hand
puts "hand for #@name:"
puts " hand: "+ @hand.values.flatten.sort.join(', ')
puts " books: "+ @books.join(', ')
puts "opponent is known to have: " + @opponents_hand[:known_to_have].sort.join(', ')
end
end
#######################################################################
class ComputerPlayer < Player
def initialize(game)
super
@name = 'Computer'
end
def wanted_card
known = @hand.keys & @opponents_hand[:known_to_have]
if not known.empty?
sort_cards_by_most(known).first
else
possibilities = @hand.keys - @opponents_hand[:known_not_to_have]
if not possibilities.empty?
possibilities.shuffle.first
else
#sort_cards_by_most(@hand.keys).first
@hand.keys.shuffle.first
end
end
end
# sort ranks by ones with most cards in my hand. better chance to make a book
def sort_cards_by_most(array_of_ranks)
array_of_ranks.sort_by {|rank| -@hand[rank].length}
end
end
#######################################################################
class HumanPlayer < Player
def initialize(game)
super
@name = 'Human'
end
def take_cards(cards)
puts "#@name received: #{cards.join(', ')}"
super
end
def wanted_card
print_hand
wanted = nil
loop do
print "\nWhat rank to ask for? "
wanted = $stdin.gets
wanted.strip!.upcase!
if not Card::RANKS.include?(wanted)
puts "not a valid rank: #{wanted} -- try again."
elsif not @hand.has_key?(wanted)
puts "you don't have a #{wanted} -- try again"
else
break
end
end
wanted
end
end
#######################################################################
class GoFishGame
def initialize
@deck = Deck.new
@players = [HumanPlayer.new(self), ComputerPlayer.new(self)]
rotate_players if rand(2) == 1
@players.each {|p| deal(p, 9)}
end
attr_reader :deck
def start
loop do
p1, p2 = @players
# p1.query(p2) method returns true if p1 keeps his turn
# and returns false otherwise
p1.query(p2) or rotate_players
break if p1.num_books + p2.num_books == 13
end
puts "
### ========================
" # add a separator between turns
puts "Game over"
@players.each {|p| puts "#{p.name} has #{p.num_books} books"}
nil
end
def rotate_players
@players.push(@players.shift)
puts "------------------------------" # add a separator between turns
end
def deal(player, n=1)
n = [n, @deck.cards_remaining].min
puts "Dealer: #{n} card(s) to #{player.name}"
player.take_cards(@deck.deal(n))
end
end
#######################################################################
# main
srand
GoFishGame.new.start