はじめに
プログラミング入門者からの卒業試験は『ブラックジャック』を開発すべしという記事に影響されて、Rubyで作ってみました!
参考にさせていただいた記事の「開発開始!」まで読み進めて作成したものとなります。
作成者のスペック
- 実務経験なし
- 学習歴 約3ヶ月
- Ruby, Railsを学習中(Javaもいつかやりたいな・・・)。
未熟な部分しかございませんが、よろしくお願いします!
ルールの整理(実際には様々なルールが存在しますが、今回は以下のようにしました。)
- カードは52枚, 同じカードは存在しない。
- Aは1点として扱う。(得点に応じて、1か11にする例外処理が実装できませんでした。)
- プレイヤーがバーストした時点で即敗北。(ディーラーのターンは来ない。)
- スコアが同じであれば、引き分け(両者ブラックジャックの場合も)
- プレイヤーのドロー終了後、ディーラーのターン開始
- プレイヤーのみ、特定条件および一定確率でデスティニードロー(イカサマ)発生。 ・・・ オリジナル
工夫したところ
一定確率で確実にブラックジャックになるカードを引くことのできる特殊演出を実装しました。
ゲームとしては、崩壊していますが遊び心として大目にみてください。
発生条件
- プレイヤーの得点が21未満11以上
- 手札が3枚以上
- ランダムで生成される数字がif文の条件と一致。(10%の確率)
コードとしては、「Playerクラスのdestiny_drawメソッド」と「Deckクラスのdestiny_draw_cardメソッド」になります。特定条件の判定はblackjack.rbにて行なっています。
blackjack.rb
blackjack.rbは主にゲームの進行を取り扱うところです。
run_gameが実行されるとゲームが始まり、プレイヤーとディーラーお互いの手札が配分され、プレイヤーのターン開始となります。
putsの空行が至る所にありますが、これは出力結果を見やすくするために出力しています。
require './card'
require './deck'
require './player'
# ゲームの進行
def run_game
# 初期手札2枚の準備,手札公開,得点表示
deck = Deck.new
deck.shuffle
player = Player.new
player.first_draw(deck)
player.score_count
dealer = Dealer.new
dealer.first_draw(deck)
dealer.score_count
#ユーザーのターン
while true do
number = rand(11)
if number == 3 && (player.instance_variable_get :@hands).length >= 3 && player.score_count >= 11 && player.score_count != 21
puts "山札が輝きだした!\n山札を信じて引きますか?[Y/N]"
puts "あなたの得点: #{player.score_count}"
answer = gets.chomp.to_s
if answer == "Y" || answer == "y"
player.destiny_draw(deck, player)
unless blackjack?(player)
break
end
elsif answer == "N" || answer == "n"
puts
puts "さらにカードを引きますか?[Y/N]"
puts "あなたの得点: #{player.score_count}"
answer = gets.chomp.to_s
puts "Answer: #{answer}"
if answer == "Y" || answer == "y"
player.draw(deck, player)
unless burst?(player)
exit
end
unless blackjack?(player)
break
end
elsif answer == "N" || answer == "n"
break
else
puts "無効な値です。もう一度入力してください。"
end
else
puts "無効な値です。もう一度入力してください。"
end
else # 通常時の処理
puts
puts "さらにカードを引きますか?[Y/N]"
puts "あなたの得点: #{player.score_count}"
answer = gets.chomp.to_s
puts "Answer: #{answer}"
if answer == "Y" || answer == "y"
player.draw(deck, player)
unless burst?(player)
exit
end
unless blackjack?(player)
break
end
elsif answer == "N" || answer == "n"
break
else
puts "無効な値です。もう一度入力してください。"
end
end
end
#ディーラーのターン
while true do
if dealer.score_count < 17
dealer.draw(deck, dealer)
else
break
end
end
judge(player, dealer)
end
# 勝敗判定
def judge(player, dealer)
player_score = player.score_count
dealer_score = dealer.score_count
puts
puts "----- あなたの得点 -----"
puts "#{player_score}"
puts "--- ディーラーの得点 ---"
puts "#{dealer_score}"
puts
if dealer.score_count == player_score
puts "引き分け"
elsif player_score == 21
puts "ブラックジャック!\nあなたの勝ちです!"
elsif dealer_score == 21
puts "ディーラーのブラックジャック!\nあなたの負けです..."
elsif dealer_score > 21
puts "ディーラーはバーストしました。"
puts "あなたの勝ちです!"
elsif dealer_score > player_score
puts "あなたの負けです..."
elsif dealer_score < player_score
puts "あなたの勝ちです!"
end
end
# バーストしたか?
def burst?(player)
if player.score_count <= 21
return true
else
puts
puts "あなたの得点: #{player.score_count}"
puts "バーストしました。あなたの負けです。"
return false
end
end
# ブラックジャックかどうか
def blackjack?(player)
if player.score_count == 21
return false
else
return true
end
end
puts "---------------------"
puts "ブラックジャックへようこそ!"
puts "---------------------"
puts
run_game
Playerクラス
プレイヤークラスには手札を持たせており、手札に関係する処理を行うメソッドを定義しました。
ディーラーとプレイヤーで処理を分けています。
# プレイヤークラス
class Player
def initialize
@hands = []
end
# 一枚引く
def draw(deck, player)
card = deck.draw_card
@hands.push(card)
puts
puts "あなたが引いたカードは#{card.show}です"
puts "----- あなたの手札 -----"
@hands.each_with_index do |hand, i|
puts "[ #{hand.show} ]"
end
puts "---------------------"
end
# 得点計算
def score_count
score = 0
@hands.each do |hand|
score += hand.count
end
return score
end
# 最初のドローおよび手札公開
def first_draw(deck)
2.times do
card = deck.draw_card
@hands.push(card)
end
puts "----- あなたの手札 -----"
@hands.each_with_index do |hand, i|
puts "#{i+1}枚目: #{hand.show}"
end
puts "---------------------"
end
# デスティニードロー
def destiny_draw(deck, player)
card = deck.destiny_draw_card(player)
@hands.push(card)
puts
puts "あなたが引いたカードは#{card.show}です"
puts "----- あなたの手札 -----"
@hands.each_with_index do |hand, i|
puts "[ #{hand.show} ]"
end
puts "---------------------"
end
end
# ディーラークラス
class Dealer
def initialize
@dealer_hands = []
end
# 一枚引く
def draw(deck, player)
card = deck.draw_card
@dealer_hands.push(card)
puts "ディーラーはカードを引きました"
end
# 得点計算
def score_count
score = 0
@dealer_hands.each do |hand|
score += hand.count
end
return score
end
# 最初のドローおよび手札公開
def first_draw(deck)
2.times do
card = deck.draw_card
@dealer_hands.push(card)
end
puts "--- ディーラーの手札 ---"
puts "1枚目: #{@dealer_hands.first.show}"
puts "2枚目: 伏せられている"
puts "---------------------"
end
end
Deckクラス
デッキクラスでは、山札を作成する役割を与えています。
Deck.newと同時にbuildメソッドが動き、52枚のカードを配列内に作成します。
その後、shuffleメソッドを動かすことで配列内の順番をランダムにします。
# デッキクラス
class Deck
def initialize
@cards = []
build
end
# 山札作成
def build
for suit in ["♡", "♠", "♦︎", "♣︎"] do
for number in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"] do
card = Card.new(suit, number)
@cards << card
end
end
end
# 山札シャッフル
def shuffle
cards_length =(@cards.count) - 1
cards_length.step(1,-1) do |i|
r = rand(i)
@cards[i] , @cards[r] = @cards[r], @cards[i]
end
end
# 山札の上から一枚引く
def draw_card
@cards.pop
end
# 得点が21になるようなカードを探して引く
def destiny_draw_card(player)
target = (21 - player.score_count)
@cards.find { |card| (card.instance_variable_get :@number) == target.to_s }
end
end
Cardクラス
ここでは、カードを定義しています。カードにスートに関するパラメータと数字に関するパラメータを持たせることでトランプとして再現しています。countメソッドでは、J, Q, Kを点数計算時のみ「10」に置き換えています。
# カードクラス
class Card
def initialize(suit, number)
@suit = suit
@number = number
end
def show
return "#{@suit} of #{@number}"
end
# J,Q,Kの処理
def count
if @number == "J" || @number == "Q" || @number == "K"
return @number = 10
else
return @number.to_i
end
end
end
実行結果
こちらが実行結果になります。
通常時
---------------------
ブラックジャックへようこそ!
---------------------
----- あなたの手札 -----
1枚目: ♦︎ of 6
2枚目: ♣︎ of 4
---------------------
--- ディーラーの手札 ---
1枚目: ♣︎ of K
2枚目: 伏せられている
---------------------
さらにカードを引きますか?[Y/N]
あなたの得点: 10
y
Answer: y
あなたが引いたカードは♦︎ of 8です
----- あなたの手札 -----
[ ♦︎ of 6 ]
[ ♣︎ of 4 ]
[ ♦︎ of 8 ]
---------------------
さらにカードを引きますか?[Y/N]
あなたの得点: 18
n
Answer: n
ディーラーはカードを引きました
----- あなたの得点 -----
18
--- ディーラーの得点 ---
24
ディーラーはバーストしました。
あなたの勝ちです!
特殊演出
「山札を信じて引きますか?」で「N」を選択すると、通常処理に戻り、カードを引くか再び聞くようにしています。
以下の例では、「Y」を選択しています。
---------------------
ブラックジャックへようこそ!
---------------------
----- あなたの手札 -----
1枚目: ♦︎ of 5
2枚目: ♣︎ of 3
---------------------
--- ディーラーの手札 ---
1枚目: ♡ of 4
2枚目: 伏せられている
---------------------
さらにカードを引きますか?[Y/N]
あなたの得点: 8
y
Answer: y
あなたが引いたカードは♠ of 3です
----- あなたの手札 -----
[ ♦︎ of 5 ]
[ ♣︎ of 3 ]
[ ♠ of 3 ]
---------------------
山札が輝きだした!
山札を信じて引きますか?[Y/N]
あなたの得点: 11
y
あなたが引いたカードは♡ of 10です
----- あなたの手札 -----
[ ♦︎ of 5 ]
[ ♣︎ of 3 ]
[ ♠ of 3 ]
[ ♡ of 10 ]
---------------------
ディーラーはカードを引きました
----- あなたの得点 -----
21
--- ディーラーの得点 ---
22
ブラックジャック!
あなたの勝ちです!
終わりに
思った以上にコード量が増えすぎてしまい、まだまだ可読性を上げる訓練が必要だなと感じました。
変数名やクラス名の命名や処理のメソッド化など改善の余地はたくさんありますね...。
プログラミング入門者の卒業試験!とのことでしたが、個人的には留年判定かと思います。
しかし、形はどうであれ、0から試行錯誤を繰り返して作り上げることはとても楽しかったです。
実装する機能も細分化してどういった順番で書いていけば、スムーズに開発できるか考えることもいい訓練になりました!
もし、僕と同じような初心者の方がいたら、ぜひ挑戦してみてください!
今回作成したコードのサンプルは以下にあります。
コードサンプル
最後までお読みいただきありがとうございました。