- 投稿日:2019-03-30T19:10:11+09:00
ブラックジャックゲームを作ってみた[Ruby]
はじめに
プログラミング入門者からの卒業試験は『ブラックジャック』を開発すべしという記事に影響されて、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の空行が至る所にありますが、これは出力結果を見やすくするために出力しています。blackjack.rbrequire './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_gamePlayerクラス
プレイヤークラスには手札を持たせており、手札に関係する処理を行うメソッドを定義しました。
ディーラーとプレイヤーで処理を分けています。player.rb# プレイヤークラス 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 endDeckクラス
デッキクラスでは、山札を作成する役割を与えています。
Deck.newと同時にbuildメソッドが動き、52枚のカードを配列内に作成します。
その後、shuffleメソッドを動かすことで配列内の順番をランダムにします。deck.rb# デッキクラス 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 endCardクラス
ここでは、カードを定義しています。カードにスートに関するパラメータと数字に関するパラメータを持たせることでトランプとして再現しています。countメソッドでは、J, Q, Kを点数計算時のみ「10」に置き換えています。
card.rb# カードクラス 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から試行錯誤を繰り返して作り上げることはとても楽しかったです。
実装する機能も細分化してどういった順番で書いていけば、スムーズに開発できるか考えることもいい訓練になりました!もし、僕と同じような初心者の方がいたら、ぜひ挑戦してみてください!
今回作成したコードのサンプルは以下にあります。
コードサンプル最後までお読みいただきありがとうございました。