3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ブラックジャックゲームで学ぶRubyのオブジェクト指向プログラミング

Posted at

はじめに

この記事では、ブラックジャックゲームを作成しながら、Rubyのオブジェクト指向プログラミングについて学びます。

全体のコード
class Card
  attr_reader :value, :suit  # カードの値とスートの読み取り専用アクセサを定義

  def initialize(value, suit)  # Cardクラスの初期化。値とスートを引数に取る
    @value = value  # カードの値をインスタンス変数に設定
    @suit = suit    # カードのスートをインスタンス変数に設定
  end

  def to_s  # カードの値とスートを文字列で返すメソッド
    "#{suit}#{value}"
  end

  def point  # カードのポイントを計算するメソッド
    case value
    when 'A' then 11
    when 'K', 'Q', 'J' then 10
    else value.to_i
    end
  end
end

class Deck
  attr_reader :cards  # デッキ内のカードの読み取り専用アクセサを定義

  def initialize  # Deckクラスの初期化
    @cards = build_deck  # デッキを構築
  end

  def build_deck  # 52枚のカードを含むデッキを構築してシャッフルするメソッド
    suits = ["ハート", "ダイヤ", "クラブ", "スペード"]  # 4種類のスートを表す配列
    values = %w(A 2 3 4 5 6 7 8 9 10 J Q K)  # 2から10までの数値およびA, K, Q, Jを表す配列
  
    # スートと値のすべての組み合わせを生成し、それぞれの組み合わせに対してCardインスタンスを作成
    # 最後に生成されたCardインスタンスの配列をシャッフルする
    suits.product(values).map { |suit, value| Card.new(value, suit) }.shuffle
  end
  

  def draw  # デッキから一枚のカードを引くメソッド
    cards.pop
  end
end

class Hand
  attr_accessor :cards  # 手札のカードのアクセサを定義

  def initialize  # Handクラスの初期化
    @cards = []
  end

  def add_card(card)  # 手札にカードを追加するメソッド
    cards << card
  end

  def points # 手札の合計ポイントを計算するメソッド
    aces = 0  # エースの数をカウントするための変数を初期化
  
    # cards配列(手札)にあるすべてのカードに対して、合計ポイントを計算
    total = cards.inject(0) do |sum, card|
      aces += 1 if card.value == 'A'  # カードがエースの場合、エースの数を1増やす
      sum + card.point  # 現在の合計ポイントにカードのポイントを加算
    end
  
    # エースの数だけループを回して、合計ポイントが21を超えている場合は10を引く
    aces.times { total -= 10 if total > 21 }
    total  # 最終的な合計ポイントを返す
  end

  def busted?  # 手札がバーストしているかどうかを判定するメソッド
    points > 21
  end
end

class Player
  attr_reader :hand  # プレイヤーの手札の読み取り専用アクセサを定義

  def initialize  # Playerクラスの初期化
    @hand = Hand.new
  end

  def hit(card)  # プレイヤーがカードをヒットするメソッド
    hand.add_card(card)
  end
end

class Dealer < Player  # DealerクラスはPlayerクラスを継承
  def show_one_card  # ディーラーが一枚のカードを見せるメソッド
    hand.cards.first
  end
end

class Game
  attr_reader :player, :dealer, :deck  # プレイヤー、ディーラー、デッキの読み取り専用アクセサを定義

  def initialize  # Gameクラスの初期化
    @player = Player.new
    @dealer = Dealer.new
    @deck = Deck.new
  end

  def deal_initial_cards  # プレイヤーとディーラーに初期カードを配るメソッド
    2.times do
      player.hit(deck.draw)
      dealer.hit(deck.draw)
    end
  end

  def play  # ゲームをプレイするメソッド
    puts "ブラックジャックを開始します。"
    deal_initial_cards

    puts "あなたの引いたカードは#{player.hand.cards[0]}です。"
    puts "あなたの引いたカードは#{player.hand.cards[1]}です。"
    puts "ディーラーの引いたカードは#{dealer.show_one_card}です。"
    puts "ディーラーの引いた2枚目のカードはわかりません。"

    loop do
      puts "あなたの現在の得点は#{player.hand.points}です。カードを引きますか?(Y/N)"
      decision = gets.chomp.downcase
      break if decision == "n"
    
      if decision == "y"
        card = deck.draw
        player.hit(card)
        puts "あなたの引いたカードは#{card}です。"
        break if player.hand.busted?
      end
    end
    
    if player.hand.busted?
      puts "あなたの現在の得点は#{player.hand.points}です。"
      puts "バーストしました、あなたの負けです..."
    else
      puts "ディーラーの引いた2枚目のカードは#{dealer.hand.cards[1]}でした。"
      puts "ディーラーの現在の得点は#{dealer.hand.points}です。"

      while dealer.hand.points < 17
        card = deck.draw
        dealer.hit(card)
        puts "ディーラーの引いたカードは#{card}です。"
        puts "ディーラーの現在の得点は#{dealer.hand.points}です。"
      end

      if dealer.hand.busted?
        puts "ディーラーの得点は#{dealer.hand.points}です。あなたの勝ちです!"
      elsif player.hand.points > dealer.hand.points
        puts "あなたの得点は#{player.hand.points}です。"
        puts "ディーラーの得点は#{dealer.hand.points}です。あなたの勝ちです!"
      elsif player.hand.points < dealer.hand.points
        puts "あなたの得点は#{player.hand.points}です。"
        puts "ディーラーの得点は#{dealer.hand.points}です。あなたの負けです..."
      else
        puts "引き分けです。"
      end
      puts "ブラックジャックを終了します。"
    end
  end
end

Game.new.play

学ぶポイント

クラスとオブジェクト指向の概念
このゲームでは、Card, Deck, Hand, Player, Dealer, Gameといったクラスを定義しています。これらのクラスは、オブジェクト指向プログラミングにおいて、それぞれの役割や機能を持つオブジェクトを表現しています。例えば、Cardクラスはカードの情報を表現し、Deckクラスはカードのデッキを管理します。

class Card
  ...
end

class Deck
  ...
end

クラスの継承
Dealerクラスは、Playerクラスを継承しています。これにより、DealerクラスはPlayerクラスが持つすべてのメソッドやプロパティを引き継ぎ、共通の機能を再利用できます。継承を使うことで、コードの重複を避けることができ、保守性や拡張性を向上させることができます。

class Dealer < Player
  ...
end

カプセル化
オブジェクト指向プログラミングでは、データやメソッドをクラス内にカプセル化することで、外部から直接アクセスできないようにします。これにより、コードの安全性や保守性が向上します。attr_accessorattr_readerattr_writerなどのアクセサメソッドを使って、インスタンス変数にアクセスするためのメソッドを定義できます。例えば、Handクラスでは、cardsのアクセサを定義しています。

class Hand
  attr_accessor :cards
  ...
end

ブロックを使った繰り返し処理
Rubyでは、eachやtimesなどの繰り返し処理を行うメソッドとブロックを組み合わせて使うことが一般的です。例えば、Handクラスのpointsメソッドでは、injectメソッドとブロックを使って、カードの合計ポイントを計算しています。

def points
  ...
  total = cards.inject(0) do |sum, card|
    ...
  end
  ...
end

配列の活用
Rubyの配列を活用して、コードをシンプルに保つことができます。例えば、Deckクラスのbuild_deckメソッドでは、suitsとvaluesという配列を用意し、productメソッドとmapメソッドを使って、52枚のカードを生成しています。

def build_deck
 suits = ["ハート", "ダイヤ", "クラブ", "スペード"]
 values = %w(A 2 3 4 5 6 7 8 9 10 J Q K)

 suits.product(values).map { |suit, value| Card.new(value, suit) }.shuffle
end

条件分岐とループ処理
Rubyの条件分岐やループ処理を使って、ゲームのロジックを制御できます。例えば、Gameクラスのplayメソッドでは、loopを使ってユーザーの入力に応じてカードを引くか決め、条件分岐を使ってバーストや勝敗を判定しています。

def play
  ...
  loop do
    ...
    break if decision == "n"
    ...
  end

  if player.hand.busted?
    ...
  else
    ...
    if dealer.hand.busted?
      ...
    elsif player.hand.points > dealer.hand.points
      ...
    elsif player.hand.points < dealer.hand.points
      ...
    else
      ...
    end
  end
end

メソッドチェーン
Rubyでは、メソッドチェーンを使って複数のメソッドを連結して呼び出すことができます。これにより、コードが簡潔で読みやすくなります。例えば、Deckクラスのbuild_deckメソッドでは、productメソッドとmapメソッドをチェーンしています。

def build_deck
  ...
  suits.product(values).map { |suit, value| Card.new(value, suit) }.shuffle
end

以上のポイントを踏まえて、ブラックジャックゲームの実装を見ていきましょう。

ゲームの流れ

  1. Gameクラスのインスタンスを作成し、playメソッドを呼び出すことでゲームが開始されます。
  2. 初期カードをプレイヤーとディーラーに配ります。このとき、プレイヤーには2枚のカードが公開され、ディーラーには1枚のカードが公開されます。
  3. プレイヤーは、カードをヒットするかどうかを選択できます。カードを引くと、手札の合計ポイントが計算され、バーストしているかどうかが判定されます。
  4. プレイヤーがバーストした場合、ゲームは終了し、プレイヤーの負けとなります。
  5. プレイヤーがバーストしなかった場合、ディーラーが手番を実行します。ディーラーは、手札の合計ポイントが17以上になるまでカードを引き続けます。
  6. ディーラーがバーストした場合、ゲームは終了し、プレイヤーの勝ちとなります。
  7. 両者がバーストしていない場合、手札の合計ポイントが高い方が勝ちとなります。同点の場合は引き分けとなります。

クラス構成

ブラックジャックゲームでは、以下のクラスを使用します。

  1. Card: カードの値とスートを管理するクラス
  2. Deck: デッキを管理するクラス
  3. Hand: プレイヤーの手札を管理するクラス
  4. Player: プレイヤーの振る舞いを管理するクラス
  5. Dealer: ディーラーの振る舞いを管理するクラス(Playerを継承)
  6. Game: ゲーム全体の流れを管理するクラス

Cardクラス

Cardクラスは、カードの値とスートを管理します。to_sメソッドは、カードの情報を文字列で返すために使用されます。pointメソッドは、カードのポイントを計算します。

class Card
  attr_reader :value, :suit  # カードの値とスートの読み取り専用アクセサを定義

  def initialize(value, suit)  # Cardクラスの初期化。値とスートを引数に取る
    @value = value  # カードの値をインスタンス変数に設定
    @suit = suit    # カードのスートをインスタンス変数に設定
  end

  def to_s  # カードの値とスートを文字列で返すメソッド
    "#{suit}#{value}"
  end

  def point  # カードのポイントを計算するメソッド
    case value
    when 'A' then 11
    when 'K', 'Q', 'J' then 10
    else value.to_i
    end
  end
end

Deckクラス

Deckクラスは、デッキを構築し、カードをシャッフルし、カードを引く処理を管理します。デッキは、build_deckメソッドで生成され、drawメソッドでカードを引きます。

class Deck
  attr_reader :cards  # デッキ内のカードの読み取り専用アクセサを定義

  def initialize  # Deckクラスの初期化
    @cards = build_deck  # デッキを構築
  end

  def build_deck  # 52枚のカードを含むデッキを構築してシャッフルするメソッド
    suits = ["ハート", "ダイヤ", "クラブ", "スペード"]  # 4種類のスートを表す配列
    values = %w(A 2 3 4 5 6 7 8 9 10 J Q K)  # 2から10までの数値およびA, K, Q, Jを表す配列
  
    # スートと値のすべての組み合わせを生成し、それぞれの組み合わせに対してCardインスタンスを作成
    # 最後に生成されたCardインスタンスの配列をシャッフルする
    suits.product(values).map { |suit, value| Card.new(value, suit) }.shuffle
  end
  

  def draw  # デッキから一枚のカードを引くメソッド
    cards.pop
  end
end

Handクラス

Handクラスは、プレイヤーの手札を管理します。add_cardメソッドは、手札にカードを追加するために使用されます。pointsメソッドは、手札の合計ポイントを計算します。また、busted?メソッドは、手札がバーストしているかどうかを判定します。

class Hand
  attr_accessor :cards  # 手札のカードのアクセサを定義

  def initialize  # Handクラスの初期化
    @cards = []
  end

  def add_card(card)  # 手札にカードを追加するメソッド
    cards << card
  end

  def points # 手札の合計ポイントを計算するメソッド
    aces = 0  # エースの数をカウントするための変数を初期化
  
    # cards配列(手札)にあるすべてのカードに対して、合計ポイントを計算
    total = cards.inject(0) do |sum, card|
      aces += 1 if card.value == 'A'  # カードがエースの場合、エースの数を1増やす
      sum + card.point  # 現在の合計ポイントにカードのポイントを加算
    end
  
    # エースの数だけループを回して、合計ポイントが21を超えている場合は10を引く
    aces.times { total -= 10 if total > 21 }
    total  # 最終的な合計ポイントを返す
  end

  def busted?  # 手札がバーストしているかどうかを判定するメソッド
    points > 21
  end
end

Playerクラス

Playerクラスは、プレイヤーの振る舞いを管理します。プレイヤーがカードをヒットする処理は、hitメソッドで実装されています。

class Player
  attr_reader :hand  # プレイヤーの手札の読み取り専用アクセサを定義

  def initialize  # Playerクラスの初期化
    @hand = Hand.new
  end

  def hit(card)  # プレイヤーがカードをヒットするメソッド
    hand.add_card(card)
  end
end

Dealerクラス

Dealerクラスは、ディーラーの振る舞いを管理します。ディーラーが一枚のカードを見せる処理は、show_one_cardメソッドで実装されています。このクラスは、Playerクラスを継承しています。

class Dealer < Player  # DealerクラスはPlayerクラスを継承
  def show_one_card  # ディーラーが一枚のカードを見せるメソッド
    hand.cards.first
  end
end

Gameクラス

Gameクラスは、ゲーム全体の流れを管理します。プレイヤー、ディーラー、デッキを初期化し、初期カードを配る処理します。

class Game
  attr_reader :player, :dealer, :deck  # プレイヤー、ディーラー、デッキの読み取り専用アクセサを定義

  def initialize  # Gameクラスの初期化
    @player = Player.new
    @dealer = Dealer.new
    @deck = Deck.new
  end

  def deal_initial_cards  # プレイヤーとディーラーに初期カードを配るメソッド
    2.times do
      player.hit(deck.draw)
      dealer.hit(deck.draw)
    end
  end

  def play  # ゲームをプレイするメソッド
    puts "ブラックジャックを開始します。"
    deal_initial_cards

    puts "あなたの引いたカードは#{player.hand.cards[0]}です。"
    puts "あなたの引いたカードは#{player.hand.cards[1]}です。"
    puts "ディーラーの引いたカードは#{dealer.show_one_card}です。"
    puts "ディーラーの引いた2枚目のカードはわかりません。"

    loop do
      puts "あなたの現在の得点は#{player.hand.points}です。カードを引きますか?(Y/N)"
      decision = gets.chomp.downcase
      break if decision == "n"
    
      if decision == "y"
        card = deck.draw
        player.hit(card)
        puts "あなたの引いたカードは#{card}です。"
        break if player.hand.busted?
      end
    end
    
    if player.hand.busted?
      puts "あなたの現在の得点は#{player.hand.points}です。"
      puts "バーストしました、あなたの負けです..."
    else
      puts "ディーラーの引いた2枚目のカードは#{dealer.hand.cards[1]}でした。"
      puts "ディーラーの現在の得点は#{dealer.hand.points}です。"

      while dealer.hand.points < 17
        card = deck.draw
        dealer.hit(card)
        puts "ディーラーの引いたカードは#{card}です。"
        puts "ディーラーの現在の得点は#{dealer.hand.points}です。"
      end

      if dealer.hand.busted?
        puts "ディーラーの得点は#{dealer.hand.points}です。あなたの勝ちです!"
      elsif player.hand.points > dealer.hand.points
        puts "あなたの得点は#{player.hand.points}です。"
        puts "ディーラーの得点は#{dealer.hand.points}です。あなたの勝ちです!"
      elsif player.hand.points < dealer.hand.points
        puts "あなたの得点は#{player.hand.points}です。"
        puts "ディーラーの得点は#{dealer.hand.points}です。あなたの負けです..."
      else
        puts "引き分けです。"
      end
      puts "ブラックジャックを終了します。"
    end
  end
end

Game.new.play

ruby blackjack.rbを実行することで、ブラックジャックゲームが開始されます。

$ ruby blackjack.rb
ブラックジャックを開始します。
あなたの引いたカードはクラブの9です。
あなたの引いたカードはスペードの5です。
ディーラーの引いたカードはスペードの4です。
ディーラーの引いた2枚目のカードはわかりません。
あなたの現在の得点は14です。カードを引きますか?(Y/N)
Y
あなたの引いたカードはダイヤの8です。
あなたの現在の得点は22です。
バーストしました、あなたの負けです...

まとめ

この記事では、ブラックジャックゲームを作成することで、Rubyのオブジェクト指向プログラミングを学ぶ方法を紹介しました。この知識を活かして、さらに複雑なアプリケーションを開発する際に役立ててみてください。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?