普通の文系大学生がRubyでブラックジャックを作る
はじめに
こんにちは。neat02と申します。現在大学4年生でエンジニア志望です。最近は、自身のプログラミングの技術力を無さを実感し、ポートフォリオの作成を目標に日々学習しています。前置きが長くなりましたが、今回はブラックジャックを作成したので、解説していきたいと思います。
設計
プログラムを組む上で最も重要なことは、設計です。
今回作成したブラックジャックは以下の項目に沿って設計しました。
-
プレイヤーは実行者、ディーラーはCPUが自動実行する
-
実行開始時、プレイヤーとディーラーはそれぞれ、カードを2枚引く。引いたカードは画面に表示する。ただし、ディーラーの2枚目のカードは分からないようにする
-
その後、先にプレイヤーがカードを引く。プレイヤーのカードの合計値が21を超えたらプレイヤーの負け
-
プレイヤーはカードを引くたびに次のカードを引くか選択できる
-
プレイヤーがカードを引き終えたら、ディーラーは自分のカードの合計値が17以上になるまで引き続ける
-
プレイヤーとディーラーが引き終えたら勝負。カードの合計値が21により近い方が勝ち
-
Aは1 or11点として取り扱う。(合計値が21を越えないようにする)
実装
ここで考えなければならないのは、クラス分けです。
クラス分けとは、そのままの意味で役割を分担することです。
ブラックジャックは、プレイヤー,ディーラーはカードを2枚配られ、プレイヤ―は合計値が21に近づくまでカードを引き続けます。
現実世界と同様に考えるのはナンセンスではありますが、プログラム上でもカードを用意する必要があるため、カードクラスを作ります。
class Card
RANKS = %w[2 3 4 5 6 7 8 9 10 J Q K A].freeze
SUITS = %w[クローバー ハート ダイヤ スペード].freeze
attr_reader :rank, :suit
def initialize(rank, suit)
@rank = rank
@suit = suit
end
def value
case @rank
when 'A' then 11
when 'K', 'Q', 'J' then 10
else @rank.to_i
end
end
end
RANKS = %w[2 3 4 5 6 7 8 9 10 J Q K A].freeze
SUITS = %w[クローバー ハート ダイヤ スペード].freeze
attr_reader :rank, :suit
def initialize(rank, suit)
@rank = rank
@suit = suit
end
def value
case @rank
when 'A' then 11
when 'K', 'Q', 'J' then 10
else @rank.to_i
end
end
end
こんな感じです。トランプのカードは数字とスーツ(柄)で構成されているため、要素を書き出します。文字のままだと、得点の計算が出来ないので、J, Q, Kは10、Aは11に変換します。
次にデッキクラスを作ります。
さっきのカードクラスはカードを説明しただけなので、カードを配る(引く)機能を付けます。
require_relative 'card'
class Deck
def initialize
@cards = []
Card::SUITS.each do |suit|
Card::RANKS.each do |rank|
@cards << Card.new(rank, suit)
end
end
@cards.shuffle!
end
def draw
@cards.pop
end
end
def initialize
@cards = []
Card::SUITS.each do |suit|
Card::RANKS.each do |rank|
@cards << Card.new(rank, suit)
end
end
@cards.shuffle!
end
def draw
@cards.pop
end
end
ここでは、数字とスーツの要素を@cardという変数に入れています。そしてその要素がランダムになるようshuffle!して、カードを引けるようにします。
最後にゲームの流れを説明したゲームクラスを作ります。
require_relative 'card'
require_relative 'deck'
class Blackjack
def hand_value(hand)
base_value = hand.map(&:value).reduce(:+)
num_aces = hand.count { |card| card.rank == 'A' }
num_aces.times do
base_value -= 10 if base_value > 21
end
base_value
end
def display_card(card, owner)
puts "#{owner}の引いたカードは#{card.suit}の#{card.rank}です。"
end
def play
puts 'ブラックジャックを開始します。'
deck = Deck.new
player_hand = [deck.draw, deck.draw]
dealer_hand = [deck.draw, deck.draw]
display_card(player_hand[0], 'あなた')
display_card(player_hand[1], 'あなた')
display_card(dealer_hand[0], 'ディーラー')
puts 'ディーラーの引いた2枚目のカードはわかりません。'
puts "あなたの現在の得点は#{hand_value(player_hand)}です。カードを引きますか?(Y/N)"
while hand_value(player_hand) < 21
input = gets.chomp.upcase
break if input == 'N'
unless %w[Y N].include?(input)
puts 'エラー: 正しく入力してください。'
next
end
new_card = deck.draw
player_hand << new_card
display_card(new_card, 'あなた')
puts "あなたの現在の得点は#{hand_value(player_hand)}です。カードを引きますか?(Y/N)
require_relative 'deck'
class Blackjack
def hand_value(hand)
base_value = hand.map(&:value).reduce(:+)
num_aces = hand.count { |card| card.rank == 'A' }
num_aces.times do
base_value -= 10 if base_value > 21
end
base_value
end
def display_card(card, owner)
puts "#{owner}の引いたカードは#{card.suit}の#{card.rank}です。"
end
def play
puts 'ブラックジャックを開始します。'
deck = Deck.new
player_hand = [deck.draw, deck.draw]
dealer_hand = [deck.draw, deck.draw]
display_card(player_hand[0], 'あなた')
display_card(player_hand[1], 'あなた')
display_card(dealer_hand[0], 'ディーラー')
puts 'ディーラーの引いた2枚目のカードはわかりません。'
puts "あなたの現在の得点は#{hand_value(player_hand)}です。カードを引きますか?(Y/N)"
while hand_value(player_hand) < 21
input = gets.chomp.upcase
break if input == 'N'
unless %w[Y N].include?(input)
puts 'エラー: 正しく入力してください。'
next
end
new_card = deck.draw
player_hand << new_card
display_card(new_card, 'あなた')
puts "あなたの現在の得点は#{hand_value(player_hand)}です。カードを引きますか?(Y/N)
" unless hand_value(player_hand) >= 21
end
puts "ディーラーの引いた2枚目のカードは#{dealer_hand[1].suit}の#{dealer_hand[1].rank}
end
puts "ディーラーの引いた2枚目のカードは#{dealer_hand[1].suit}の#{dealer_hand[1].rank}
でした。"
while hand_value(dealer_hand) < 17
new_card = deck.draw
dealer_hand << new_card
display_card(new_card, 'ディーラー')
break if hand_value(dealer_hand) > 17
puts "ディーラーの現在の得点は#{hand_value(dealer_hand)}です。"
end
puts "あなたの得点は#{hand_value(player_hand)}です。"
puts "ディーラーの得点は#{hand_value(dealer_hand)}です。"
if hand_value(player_hand) > 21
puts 'あなたの負けです!'
elsif hand_value(dealer_hand) > 21 || hand_value(player_hand) > hand_value(dealer_hand)
puts 'あなたの勝ちです!'
elsif hand_value(player_hand) == hand_value(dealer_hand)
puts '引き分けです!'
else
puts 'あなたの負けです!'
end
puts 'ブラックジャックを終了します。'
end
end
Blackjack.new.play
while hand_value(dealer_hand) < 17
new_card = deck.draw
dealer_hand << new_card
display_card(new_card, 'ディーラー')
break if hand_value(dealer_hand) > 17
puts "ディーラーの現在の得点は#{hand_value(dealer_hand)}です。"
end
puts "あなたの得点は#{hand_value(player_hand)}です。"
puts "ディーラーの得点は#{hand_value(dealer_hand)}です。"
if hand_value(player_hand) > 21
puts 'あなたの負けです!'
elsif hand_value(dealer_hand) > 21 || hand_value(player_hand) > hand_value(dealer_hand)
puts 'あなたの勝ちです!'
elsif hand_value(player_hand) == hand_value(dealer_hand)
puts '引き分けです!'
else
puts 'あなたの負けです!'
end
puts 'ブラックジャックを終了します。'
end
end
Blackjack.new.play
コンソールで動かすとこんな感じになります。
あなたの引いたカードはハートの2です。
ディーラーの引いたカードはダイヤのAです。
ディーラーの引いた2枚目のカードはわかりません。
あなたの現在の得点は11です。カードを引きますか?(Y/N)
Y
あなたの引いたカードはハートのQです。
ディーラーの引いた2枚目のカードはダイヤの7でした。
あなたの得点は21です。
ディーラーの得点は18です。
あなたの勝ちです!
ブラックジャックを終了します。
ディーラーの引いたカードはダイヤのAです。
ディーラーの引いた2枚目のカードはわかりません。
あなたの現在の得点は11です。カードを引きますか?(Y/N)
Y
あなたの引いたカードはハートのQです。
ディーラーの引いた2枚目のカードはダイヤの7でした。
あなたの得点は21です。
ディーラーの得点は18です。
あなたの勝ちです!
ブラックジャックを終了します。
締め
以上がブラックジャックの説明になります。カードとデッキのコードは非常に簡略化(人に教えてもらった)出来たのは良かったと思います。ゲームの流れについては、冗長的なコードが多いと思うのでそこを改善していければと思います。
今後もエンジニアの就職目指していくので、応援して頂けると幸いです。