🎉

ブラックジャックゲーム(オブジェクト指向で実装)

に公開

ブラックジャックについて

今回は、Rubyを使ってブラックジャックというゲームを実装しました。
ブラックジャックは、プレイヤーとディーラーがカードを引いて、合計点数が21に近い方が勝つというシンプルなゲームです。この記事では、実装したコードをクラスごとにファイルを分け、それぞれのメソッドごとに解説していきます。

Cardクラス

このクラスは、カードのスートと数字を保持し、カードの文字列表現を提供します。

carb.rb
class Card
  SUITS = ["スペード", "ハート", "ダイヤ", "クローバー"].freeze
  NUMBERS = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"].freeze

  attr_reader :suit, :number

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

  def value
    case @number
    when "A"
      1
    when "K", "Q", "J", "10"
      10
    else
      @number.to_i
    end
  end

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

このCardクラスは、カードのスートと数字を保持するための@suitと@numberインスタンス変数を持っています。initializeメソッドでこれらの変数が設定され、to_sメソッドでカードの文字列表現を返します。

また、カードの値を計算するためにvalueメソッドを追加しています。このメソッドは、カードの値を返すことで、プレイヤーの手札の計算に役立ちます。

Deckクラス

このクラスは、52枚のカードを持つデッキを作成し、シャッフルしてカードを引く機能を提供します。

deck.rb
class Deck
  def initialize
    @cards = []
    Card::SUITS.each do |suit|
      Card::NUMBERS.each do |number|
        @cards << Card.new(suit, number)
      end
    end
    @cards.shuffle!
  end

  def draw(number)
    @cards.pop(number)
  end
end

initializeメソッドでは、Card::SUITSとCard::NUMBERSを使用して52枚のカードを作成し、@cardsインスタンス変数に格納します。デッキが作成されたら、shuffle!メソッドでカードをシャッフルします。

drawメソッドでは、引数で指定された枚数のカードをデッキから引き、そのカードを返します。popメソッドは、配列の最後の要素を取り出すために使用されています。

ブラックジャックゲームを始める

具体的なクラスを作成する前に、ブラックジャックゲームを始めるにあたってgameクラスを動かすとゲームが始まるイメージで設定します。

blackjack.rb
require_relative 'card'
require_relative 'game'
#require_relativeは、同じディレクトリにあるファイルを読み込むための記述

game = Game.new
game.start

Gameクラス

次に、ゲーム全体を管理するGameクラスです。
このクラスでは、ゲームの開始、プレイヤーとディーラーのターン、結果の表示、ゲームの終了といった一連の流れを制御します。

game.rb
class Game
  def initialize
    @deck = Deck.new
    @player = Player.new('あなた')
    @dealer = Dealer.new('ディーラー')
  end

  def start
    puts "ブラックジャックを開始します。"
    
    @player.draw(@deck, 2)
    @dealer.draw(@deck, 2)

    puts "#{@player.name}の引いたカードは#{@player.hand}です。"
    @dealer.show_initial_card

    @player.play_turn(@deck)
    if @player.hand.calculate_value > 21
      puts "#{@dealer.name}の勝ちです!"
    else
      @dealer.play_turn(@deck)
      if @dealer.hand.calculate_value > 21 || @player.hand.calculate_value > @dealer.hand.calculate_value
        puts "#{@player.name}の勝ちです!"
      elsif @player.hand.calculate_value == @dealer.hand.calculate_value
        puts "引き分けです。"
      else
        puts "#{@dealer.name}の勝ちです!"
      end
    end
    puts "ブラックジャックを終了します。"
  end
end

Gameクラスでは、プレイヤーとディーラーがHandクラスを使用するようになりました。これにより、ゲームの流れがよりわかりやすくなっています。また、プレイヤーとディーラーの得点計算はHandクラスのcalculate_valueメソッドを使用しています。これで、Handクラスを含むブラックジャックの実装が完成しました。

PlayerクラスとDealerクラス

プレイヤーを表現するPlayerクラスです。プレイヤーは名前と手札のカードを持ちます。また、カードを引いたり、手札の合計点数を計算したりできます。

player.rb
require_relative 'deck'
require_relative 'hand'
require_relative 'card'

class Player #プレイヤーを管理するクラス
attr_reader :name, :hand

  def initialize(name)
    @name = name
    @hand = Hand.new
  end

  def draw(deck, number) # 山札からカードを引く
    new_cards = deck.draw(number)
    number.times { @hand.add_card(deck.draw(1).first) }
  end

  def show_initial_card # 初期手札を表示する
    puts "#{@name}の引いたカードは#{@hand.cards[0].to_s}です。"
    puts "#{@name}の引いたカードは#{@hand.cards[1].to_s}です。"
  end

  def play_turn(deck) # ターンを実行する
    while true # 無限ループ
      puts "#{name}の現在の得点は#{@hand.calculate_value}です。カードを引きますか?(Y/N)"
      input = gets.chomp.upcase # YかNを入力する
      if input == "Y"
      draw(deck, 1) 
      puts "#{name}の引いたカードは#{@hand.cards.last}です。"
        if @hand.calculate_value > 21 # 21を超えたら終了
          puts "#{name}の得点が21を超えました。"
          break
        end
      elsif input == "N" # Nを入力したら終了
        break
      else
        puts "無効な入力です。YまたはNを入力してください。" # YでもNでもない場合はエラー
      end
    end
  end
end

class Dealer < Player #ディーラーを管理するクラス
  def show_initial_card 
    puts "#{@name}の引いたカードは#{@hand.cards[0].to_s}です。"
    puts "#{@name}の引いた2枚目のカードはわかりません。"
  end

  def play_turn(deck) 
    puts "#{@name}の引いた2枚目のカードは#{@hand.cards[1].to_s}でした。"
    while @hand.calculate_value < 17 # 17以上になるまで引き続ける
      draw(deck, 1)
      puts "#{@name}の引いたカードは#{@hand.cards.last}です。"
    end
    puts "#{@name}の現在の得点は#{@hand.calculate_value}です。"
  end
end

継承を利用してコードが簡潔になります。Player クラスと Dealer クラスで独自のターンの挙動を定義し、Game クラス内でそれらを呼び出すようにしました。

Discussion