How to initialize a class

This is my simple application to showcase how an Array works.
It compares the cards (as strings) dealt to two players, like a slowmo gamesmaster demo.

Moving stuff into methods worked easily, but class has more features and a simple inheritance is disallowed. So what would be needed here instead? A getter, a property, an include? Please demonstrate by replacing the first line with a proper Crystal class initialization. The rest of the application is complete and should return different results and scores with each run on the CLI terminal.


class Card < String    

  
  def card_value    
    case self
      when "ace"
        14
      when "king"
        13
      when "queen"
        12
      else
        super.to_i
    end
  end
end

def card_factory
  card_deck = [] of Card
  4.times do card_deck <<     "ace" end
  4.times do card_deck <<    "king" end
  4.times do card_deck <<   "queen" end
  4.times do card_deck <<       "6" end
  return card_deck
end

# card_deck = card_factory.shuffle
# player01  = card_deck.pop
# player02  = card_deck.pop
# puts "We have a winner!" unless player01 == player02
# sleep 2
# puts "#{player01} vs #{player02}"
# puts "It was a tie."     unless player01 != player02

def deal_all_cards(argument)
  shuffled_deck = argument.shuffle
  hand1 = [] of Card
  hand2 = [] of Card
  until shuffled_deck.size < 2
    hand1 << shuffled_deck.pop
    hand2 << shuffled_deck.pop
  end
  return hand1, hand2
end


player1, player2 = deal_all_cards card_factory

score_player1 = score_player2 = tie_count = 0

player1.each_with_index do |card, index|
    if    card.card_value > player2[index].card_value
      score_player1 += 1
    elsif card.card_value < player2[index].card_value
      score_player2 += 1
    else
      tie_count     += 1
    end
    break if score_player1 == 3
    break if score_player2 == 3
#     break if score_player1 == 3 | score_player2 == 3 #abuse of | bugs crystal, keep breaks singular.
end

puts "Player 1 has #{score_player1} points."     if score_player1 != 1
puts "Player 1 has #{score_player1} point."      if score_player1 == 1
puts "Player 2 has #{score_player2} points." unless score_player2 == 1
puts "Player 2 has #{score_player2} point."  unless score_player2 != 1
puts "#{tie_count} rounds ended in a tie"
# puts
# puts "player one's cards: #{player1}"
# puts "        vs"
# puts "player two's cards: #{player2}"

Inheriting from a stdlib type is usually discouraged, and even prevented in this case. You’d want to define your own Card type that wraps a string value, storing its integer value, something like:

class Card
  getter value : Int32
 
  def initialize(value : String)
    @value = case value
      when "ace" then 14
      when "king" then 13
      when "queen" then 12
      else
        value.to_i
    end
  end
end

Probably also wouldn’t hurt to add validation, like what if the user passes "9999", or "foo" etc.

Then add them to the deck via card_deck << Card.new("queen") or what have you.

Full code: Carcin

1 Like

This wasn’t part of the question but a construct that could be useful here, since card values are low-cardinality and known at compile time, is an enum:

enum Card
  ONE   =  1
  TWO   =  2
  THREE =  3
  FOUR  =  4
  FIVE  =  5
  SIX   =  6
  SEVEN =  7
  EIGHT =  8
  NINE  =  9
  TEN   = 10
  JACK  = 11
  QUEEN = 12
  KING  = 13
  ACE   = 14
end

def card_factory
  deck = [] of Card
  4.times { deck << :ace }
  4.times { deck << :king }
  4.times { deck << :queen }
  4.times { deck << :six }
  deck
end

You won’t be able to use an enum for the card itself if you need to add a suit property, but you could split the value and the suit and they could both be enums (you can scroll this code block to see the output):

record Card, value : Value, suit : Suit do
  enum Value
    ONE   =  1
    TWO   =  2
    THREE =  3
    FOUR  =  4
    FIVE  =  5
    SIX   =  6
    SEVEN =  7
    EIGHT =  8
    NINE  =  9
    TEN   = 10
    JACK  = 11
    QUEEN = 12
    KING  = 13
    ACE   = 14
  end

  enum Suit
    Spades
    Clubs
    Hearts
    Diamonds
  end

  def self.deck
    deck = [] of Card
    Suit.each do |suit|
      Value.each do |value|
        deck << new(value, suit)
      end
    end
    deck
  end
end

pp Card.deck.shuffle
# [Card(@suit=Clubs, @value=NINE),
#  Card(@suit=Spades, @value=SEVEN),
#  Card(@suit=Clubs, @value=KING),
#  Card(@suit=Spades, @value=SIX),
#  Card(@suit=Clubs, @value=SEVEN),
#  Card(@suit=Spades, @value=ACE),
#  Card(@suit=Hearts, @value=ACE),
#  Card(@suit=Clubs, @value=QUEEN),
#  Card(@suit=Clubs, @value=ACE),
#  Card(@suit=Diamonds, @value=FOUR),
#  Card(@suit=Spades, @value=TWO),
#  Card(@suit=Spades, @value=ONE),
#  Card(@suit=Hearts, @value=THREE),
#  Card(@suit=Hearts, @value=TEN),
#  Card(@suit=Clubs, @value=EIGHT),
#  Card(@suit=Diamonds, @value=SIX),
#  Card(@suit=Clubs, @value=FIVE),
#  Card(@suit=Clubs, @value=TEN),
#  Card(@suit=Diamonds, @value=ONE),
#  Card(@suit=Diamonds, @value=TWO),
#  Card(@suit=Hearts, @value=FOUR),
#  Card(@suit=Hearts, @value=NINE),
#  Card(@suit=Spades, @value=TEN),
#  Card(@suit=Hearts, @value=TWO),
#  Card(@suit=Hearts, @value=SIX),
#  Card(@suit=Hearts, @value=EIGHT),
#  Card(@suit=Diamonds, @value=SEVEN),
#  Card(@suit=Diamonds, @value=FIVE),
#  Card(@suit=Clubs, @value=SIX),
#  Card(@suit=Spades, @value=NINE),
#  Card(@suit=Diamonds, @value=NINE),
#  Card(@suit=Hearts, @value=QUEEN),
#  Card(@suit=Diamonds, @value=EIGHT),
#  Card(@suit=Clubs, @value=FOUR),
#  Card(@suit=Spades, @value=JACK),
#  Card(@suit=Hearts, @value=SEVEN),
#  Card(@suit=Spades, @value=KING),
#  Card(@suit=Spades, @value=THREE),
#  Card(@suit=Clubs, @value=THREE),
#  Card(@suit=Spades, @value=FIVE),
#  Card(@suit=Diamonds, @value=KING),
#  Card(@suit=Clubs, @value=ONE),
#  Card(@suit=Clubs, @value=JACK),
#  Card(@suit=Hearts, @value=JACK),
#  Card(@suit=Spades, @value=EIGHT),
#  Card(@suit=Spades, @value=QUEEN),
#  Card(@suit=Diamonds, @value=THREE),
#  Card(@suit=Diamonds, @value=JACK),
#  Card(@suit=Diamonds, @value=QUEEN),
#  Card(@suit=Clubs, @value=TWO),
#  Card(@suit=Hearts, @value=KING),
#  Card(@suit=Diamonds, @value=TEN),
#  Card(@suit=Diamonds, @value=ACE),
#  Card(@suit=Hearts, @value=FIVE),
#  Card(@suit=Spades, @value=FOUR),
#  Card(@suit=Hearts, @value=ONE)]
1 Like

Thanks for the elegant solution, Blacksmoke16. Even the full code in the play link runs cleanly on the CLI, thanks for taking the time complete and confirm that. What is interesting is that now all strings are used to create object IDs with attached values; and strings are no longer needed to run the game. I assume that the compiler will now create an executable without any strings or string related libs or routines. Creating a tiny executable from large, easy to read crystal code is of course an advantage of using Crystal.