【Ruby】ブラックジャックゲームの作成方法について

<h2>１　はじめに</h2>

　Rubyの学習するにあたり、オブジェクト指向を使用し、ブラックジャックゲームを作成しました。
　この記事では、macOSにインストールしたRuby3.2を使っています。

<h2>２　ゲーム内容について</h2>
　ブラックジャックは、1〜13までの数が書かれたカード52枚を使って、ゲームを行います。
<br>　ルールは次の通り設定します。
<br>（1）実行開始時、ディーラーとプレイヤー全員に２枚ずつカードが配られます。
<br>（2）自分のカードの合計値が21に近づくよう、カードを追加するか、追加しないかを決めます。
<br>（3）カードの合計値が21を超えてしまった時点で、その場で負けが確定します。
<br>（4）プレイヤーはカードの合計値が21を超えない限り、好きなだけカードを追加できます。
<br>（5）ディーラーはカードの合計値が17を超えるまでカードを追加します。
<br>（6）各カードの点数は次のように決まっています。
<br>　・2から9までは、書かれている数の通りの点数
<br>　・10,J,Q,Kは10点
<br>　・Aは1点あるいは11点として、手の点数が最大となる方で数える。
<br>　　(カードの合計値が21以内で最大となる方で数えるようにします。)
<br>（7）ゲームが開始されて、プレイヤーはカードを引くか引かないか選択(引く場合:y、引かない場合:n)でき、ディーラーは自動的にカードを引くかどうかを決定します。ゲームが終了すると、勝者が表示されます。

<h2>３　オブジェクト指向について</h2>
オブジェクト指向は、プログラムの構造化や再利用性、拡張性を向上させるための有用な手法となっています。下記の通り、オブジェクト指向を使用しました。

<br>（1）クラス: クラスはオブジェクトの設計図のようなもので、オブジェクトの属性とメソッドを定義します。このコードでは、Card、Deck、Hand、Player、Dealer、Game という6つのクラスが定義されています。
<br>（2）インスタンス化: クラスをもとにオブジェクト（インスタンス）を生成することです。たとえば、Card.new(value, suit) は Card クラスの新しいインスタンスを生成しています。同様に、Deck.new、Hand.new、Player.new、Dealer.new、Game.new は、それぞれのクラスのインスタンスを生成します。
<br>（3）継承: クラスが別のクラスの属性とメソッドを引き継ぐことです。このコードでは、Dealer クラスは Player クラスを継承しています。これにより、Dealer クラスは Player クラスの属性（hand）とメソッド（busted?）を引き継ぎ、追加の機能（show_facedown_card）を実装できます。
<br>（4）カプセル化: オブジェクトの内部状態を外部から直接操作できないようにし、オブジェクトのメソッドを通じてのみアクセス可能にすることです。例えば、Card クラスでは、attr_reader :value, :suit が定義されており、これにより value と suit は読み取り専用の属性となります。また、Hand クラスでは、points メソッドが内部のカードの点数を計算し、外部から直接カードの点数を変更できないようにしています。

<h2>４　各クラス等の説明について</h2>
　今回のクラスは、以下のように分類しました。
<br>（1）Card クラス: トランプのカードを表現します。
<br>（2）Deck クラス: トランプのデッキ（山札）を表現し、カードを引く機能があります。
<br>（3）Hand クラス: プレイヤーの手札を表現し、点数を計算する機能があります。
<br>（4）Player クラス: プレイヤー（参加者）を表現し、手札やバストの判定ができます。
<br>（5）Dealer クラス: ディーラーを表現し、プレイヤークラスを継承し、伏せカードを表示できます。
<br>（6）Game クラス: ブラックジャックのゲームを制御し、ゲームの進行や勝者の決定ができます。
<br>（7）gameインスタンスを作成・実行
<h2>(1)　Cardクラスについて</h2>
　Cardクラスは、値とスートの情報を持つカードオブジェクトを表現します。initializeメソッドでカードの値とスートを設定し、to_sメソッドで文字列 表現を返します。pointsメソッドは、カードのポイントを計算します。
<pre class="prettyprint">
class Card
    attr_reader :value, :suit

    def initialize(value, suit)
        @value = value
        @suit = suit
    end

    def to_s
        "#{@value}#{@suit}"
    end

    def points
        case @value
        when "A"
            11
        when "K", "Q", "J"
            10
        else
            @value.to_i
        end
    end
end
</pre>

<h2>(2)　Deckクラスについて</h2>
　Deckクラスは、52枚のカードが含まれたデッキを表現します。initializeメソッドでカードの生成とシャッフルを行い、drawメソッドでカードを1枚引きます。

<pre class="prettyprint">
class Deck
  SUITS = ["♠︎", "♣︎", "♦︎", "♥︎"]
  VALUES = (2..10).to_a + ["J", "Q", "K", "A"]

  def initialize
    @cards = []
    SUITS.each do |suit|
      VALUES.each do |value|
        @cards << Card.new(value, suit)
      end
    end
    @cards.shuffle!
  end

  def draw
    @cards.pop
  end
end
</pre>

<h2>(3)　Handクラスについて</h2>
　Handクラスは、プレイヤーの手札を表現します。add_cardメソッドでカードを追加し、pointsメソッドで手札のポイントを計算します。エースは1または11として計算されます。

<pre class="prettyprint">
class Hand
  attr_accessor :cards

  def initialize
    @cards = []
  end

  def add_card(card)
    @cards << card
  end

  def points
    total_points = 0
    aces_count = 0

    @cards.each do |card|
      if card.value == "A"
        aces_count += 1
        total_points += 11
      else
        total_points += card.points
      end
    end

    while total_points > 21 && aces_count > 0
      total_points -= 10
      aces_count -= 1
    end

    total_points
  end
end
</pre>

<h2>(4)　Playerクラスについて</h2>
　Playerクラスは、ゲームに参加するプレイヤーを表現し、それぞれのプレイヤーが持っている手札やバストしたかどうかを管理できるように設計しています。

<pre class="prettyprint">
class Player
  attr_accessor :hand

  def initialize
    @hand = Hand.new
  end

  def busted?
    @hand.points > 21
  end
end
</pre>

<h2>(5)　Dealerクラスについて</h2>
　Dealerクラスは、ディーラーを表現するクラスで、Playerクラスを継承しています。show_facedown_cardメソッドで伏せカードを表現する文字（ここでは "X"）を返します。
<pre class="prettyprint">
class Dealer < Player
  def show_facedown_card
    "X"
  end
end
</pre>

<h2>(6)　Gameクラスについて</h2>
　Gameクラスは、ブラックジャックゲーム全体を制御します。ゲームの進行、手札の表示、プレイヤーとディーラーの手番、勝敗の判定などが含まれています。

<pre class="prettyprint">
class Game
  def initialize
    @deck = Deck.new
    @player = Player.new
    @dealer = Dealer.new
  end

  def initial_deal
    2.times do
      @player.hand.add_card(@deck.draw)
      @dealer.hand.add_card(@deck.draw)
    end
  end

  def display_initial_hands
    puts "Player: #{@player.hand.cards.join(' ')} Total: #{@player.hand.points}"
    puts "Dealer: #{@dealer.hand.cards.first} #{@dealer.show_facedown_card}"
  end

  def player_turn
    while !@player.busted?
      puts "Do you want to hit? (y/n)"
      answer = gets.chomp.downcase
      break if answer == "n"

      card = @deck.draw
      @player.hand.add_card(card)
      puts "Player drew: #{card}"
      puts "Player: #{@player.hand.cards.join(' ')} Total: #{@player.hand.points}"
    end
  end

  def dealer_turn
    while @dealer.hand.points < 17
      card = @deck.draw
      @dealer.hand.add_card(card)
      puts "Dealer drew: #{card}"
    end
  end

  def display_final_hands
    puts "Player: #{@player.hand.cards.join(' ')} Total: #{@player.hand.points}"
    puts "Dealer: #{@dealer.hand.cards.join(' ')} Total: #{@dealer.hand.points}"
  end

  def determine_winner
    if @player.busted?
      puts "Player busted! Dealer wins!"
    elsif @player.hand.points > @dealer.hand.points
      puts "Player wins!"
    elsif @player.hand.points < @dealer.hand.points
      puts "Dealer wins!"
    else
      puts "It's a tie!"
    end
  end

  def play
    initial_deal
    display_initial_hands
    player_turn
    dealer_turn unless @player.busted?
    display_final_hands
    determine_winner
  end
end
</pre>

<h2>(7)　gameインスタンスを作成・実行　</h2>
　最後に、ゲームを開始するコードを示します。
<pre class="prettyprint">game = Game.new
game.play</pre>

<h2>4　まとめ</h2>
 今回はオブジェクト指向を使用し、ブラックジャックゲームを作成しました。このブログ記事を見た方の少しでも勉強の参考になれば嬉しいです。