Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

Rubyでブラックジャックを実装してみた

はじめに

プロを目指す人のためのRuby入門 を写経しながら1周したので、
学習したことを試してみたい!と思い、難しすぎないプログラミング問題を探していた所、
プログラミング入門者からの卒業試験は『ブラックジャック』を開発すべし という記事を見つけ、挑戦してみることに。

ちなみに、実装に費やした時間は丸一日です。。。
記事に書いてある実装のヒントは見ずに書きました。

Github

書いたコードはコチラです

BlackJackクラス

black_jack.rb
class BlackJack
  def judge(player, dealer)
    if player.score > dealer.score
      puts "あなたの勝ちです!"
    elsif player.score < dealer.score
      puts "ディーラーの勝ちです。"
    else
      puts "引き分けです。"
    end
  end

  def play
    player = Player.new
    dealer = Dealer.new
    deck = Deck.new

    puts "☆★☆★☆★☆★☆★ブラックジャックへようこそ!☆★☆★☆★☆★☆★"
    puts "ゲームを開始します。"

    deck.shuffle

    player.first_turn(deck)
    dealer.first_turn(deck)

    player.calc_score
    player.present_score

    # プレイヤーのターン 
    while player.score < 22
      begin
        puts "カードを引きますか?引く場合はYを、引かない場合はNを入力してください。"
        answer = gets.chomp
      rescue
        raise
        retry
      end

      case answer
      when 'Y'
        player.turn(deck)
      when 'N'
        puts "あなたのターンは終了です。\n次はディーラーのターンです。"
        break
      end
    end

    if player.score > 21
      puts "ディーラーの勝ちです。"
    end

    # ディーラーのターン
    puts "ディーラーの2枚目のカードは#{dealer.card.info}でした。"
    dealer.calc_score
    dealer.present_score

    while dealer.score <= 17
      dealer.turn(deck)
    end

    # 結果発表
    player.result
    dealer.result

    if dealer.score > 21
      puts "あなたの勝ちです!"
    else
      judge(player, dealer)
    end

    puts "ブラックジャック終了!また遊んでね!"
  end
end

game = BlackJack.new
game.play

ゲームの実行を担当するクラスです。
まだ全部読んではいませんが、オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 では

単一の責任をもつクラスを作る

とのことなので、そこを意識してクラスの設計を行なっていきました。

実行するとこんな感じ。
実装イメージ

Deckクラス

deck.rb
class Deck
  attr_reader :cards

  def initialize
    @cards
    create
  end

  def create
    @cards = [
      Card.new('ハート', 'A'),
      Card.new('ハート', 2),
      Card.new('ハート', 3),
      Card.new('ハート', 4),
      Card.new('ハート', 5),
      Card.new('ハート', 6),
      Card.new('ハート', 7),
      Card.new('ハート', 8),
      Card.new('ハート', 9),
      Card.new('ハート', 10),
      Card.new('ハート', 'J'),
      Card.new('ハート', 'Q'),
      Card.new('ハート', 'K'),
      Card.new('クローバー', 'A'),
      Card.new('クローバー', 2),
      Card.new('クローバー', 3),
      Card.new('クローバー', 4),
      Card.new('クローバー', 5),
      Card.new('クローバー', 6),
      Card.new('クローバー', 7),
      Card.new('クローバー', 8),
      Card.new('クローバー', 9),
      Card.new('クローバー', 10),
      Card.new('クローバー', 'J'),
      Card.new('クローバー', 'Q'),
      Card.new('クローバー', 'K'),
      Card.new('スペード', 'A'),
      Card.new('スペード', 2),
      Card.new('スペード', 3),
      Card.new('スペード', 4),
      Card.new('スペード', 5),
      Card.new('スペード', 6),
      Card.new('スペード', 7),
      Card.new('スペード', 8),
      Card.new('スペード', 9),
      Card.new('スペード', 10),
      Card.new('スペード', 'J'),
      Card.new('スペード', 'Q'),
      Card.new('スペード', 'K'),
      Card.new('ダイヤ', 'A'),
      Card.new('ダイヤ', 2),
      Card.new('ダイヤ', 3),
      Card.new('ダイヤ', 4),
      Card.new('ダイヤ', 5),
      Card.new('ダイヤ', 6),
      Card.new('ダイヤ', 7),
      Card.new('ダイヤ', 8),
      Card.new('ダイヤ', 9),
      Card.new('ダイヤ', 10),
      Card.new('ダイヤ', 'J'),
      Card.new('ダイヤ', 'Q'),
      Card.new('ダイヤ', 'K')
    ]
  end

  def take_out_card
    @cards.last
  end

  def remove(card)
    @cards.delete(card)
  end

  def shuffle
    i = @cards.length - 1
    while (i > 0)
      j = rand(@cards.length - 1)
      @cards[j], @cards[i] = @cards[i], @cards[j]
      i -= 1
    end
    return @cards
  end
end

Deckクラスが一番悩んだ部分で、特にデッキを作成する所は自分でもひどいと思っています。
絶対もっといい方法があるとは思うですが、思い付かなかったのでゴリ押ししました。

shuffle メソッドはドットインストールのJavaScriptの講座でフィッシャー・イェーツのアルゴリズムなるものを
知ったので、それをrubyで実装してみました。これで合ってるのかな。。。

Cardクラス

card.rb
class Card
  attr_reader :suit, :number

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

  def info
    "#{suit}#{number.to_s}"
  end

  def convert_number
    if number == 'A'
      1
    elsif number == 'J' || number == 'Q' || number == 'K'
      10
    else
      number
    end
  end
end

書いてて思ったのが、メソッドの名前を考えるのが難しいということです。
リーダブルコード読まないと。

Userクラス

user.rb
class User
  attr_reader :hands, :score, :role

  def initialize
    @hands = []
    @score = 0
  end

  def draw_card(deck)
    card = deck.take_out_card
    hands << card
    deck.remove(card)
  end

  def present_a_card
    puts "#{role}の引いたカードは#{card.info}です。"
  end

  def calc_score
    score = 0
    hands.each do |card|
      score += card.convert_number
    end
    @score = score
  end

  def present_score
    puts "#{role}の現在の得点は#{score}点です。"
  end

  def result
    puts "#{role}の得点は#{score}点です。"
  end

  def card
    hands.last
  end

  def turn(deck)
    draw_card(deck)
    present_a_card
    calc_score
    present_score
  end
end

calc_score メソッドはどのクラスが持つべきなのか、Cardクラスに移譲した方が
いいんじゃないかとも思いましたが、結局どちらがいいのか分からないままUserクラスで実装。
あと、cardメソッドは、hands.last.suitといった処理がたくさん出てきたので追加しました。

何故かチェーンが2つ以上続くと不安になります。。。

player.rb
class Player < User
  def initialize
    super
    @role = 'あなた'
  end

  def first_turn(deck)
    2.times do
      draw_card(deck)
      present_a_card
    end
  end
end
dealer.rb
class Dealer < User
  def initialize
    super
    @role = 'ディーラー'
  end

  def first_turn(deck)
    draw_card(deck)
    present_a_card
    draw_card(deck)
    puts "ディーラーの2枚目のカードは分かりません。"
  end
end

PlayerとDealerには共通の処理が多いので、継承を利用しました。
ただし、最初のターンだけはDealerは2枚目に引いたカードを見せないという条件があるので、
first_turn メソッドをそれぞれ追加しました。

最後に

チェリー本のおかげで初心者向けの問題であれば、とりあえず実装出来るくらいにはなったのかな?と思いました。
だけどrubyらしい書き方、オブジェクト指向らしい書き方、読みやすいコードはまだ全然わかってないので、
これからはそこら辺も踏まえて学習を継続して行きたいです。

アウトプットのネタに困ったらこれ!?Ruby初心者向けのプログラミング問題を集めてみた(全10問)
次はコチラの問題にでも挑戦しようかな。

ご意見待ってます!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
3
Help us understand the problem. What are the problem?