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

# はじめに
[プロを目指す人のためのRuby入門](プロを目指す人のためのRuby入門) を写経しながら１周したので、
学習したことを試してみたい！と思い、難しすぎないプログラミング問題を探していた所、
[プログラミング入門者からの卒業試験は『ブラックジャック』を開発すべし](https://qiita.com/hirossyi73/items/cf8648c31898216312e5#%E9%9B%A3%E3%82%AB%E3%83%BC%E3%83%89%E3%81%AEno%E3%81%AF3%E3%81%A4%E3%81%AE%E6%84%8F%E5%91%B3%E3%81%8C%E3%81%82%E3%82%8B) という記事を見つけ、挑戦してみることに。

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

[Github](https://github.com/machamp0714/BlackJack)

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

## BlackJackクラス
```ruby: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でわかる 進化しつづける柔軟なアプリケーションの育て方](https://www.amazon.co.jp/gp/product/B01L8SEVYI/ref=ppx_yo_dt_b_d_asin_title_o04?ie=UTF8&psc=1) では

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

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

実行するとこんな感じ。
![実装イメージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254048/3695d75b-bf79-822e-f596-ed67b4598ff0.png)

## Deckクラス

```ruby: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クラス

```ruby: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クラス

```ruby: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`といった処理がたくさん出てきたので追加しました。

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

```ruby: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
```

```ruby: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問）](https://blog.jnito.com/entry/2019/05/03/121235)
次はコチラの問題にでも挑戦しようかな。

ご意見待ってます！