1 はじめに
Rubyの学習するにあたり、オブジェクト指向を使用し、ブラックジャックゲームを作成しました。
この記事では、macOSにインストールしたRuby3.2を使っています。
2 ゲーム内容について
ブラックジャックは、1〜13までの数が書かれたカード52枚を使って、ゲームを行います。ルールは次の通り設定します。
(1)実行開始時、ディーラーとプレイヤー全員に2枚ずつカードが配られます。
(2)自分のカードの合計値が21に近づくよう、カードを追加するか、追加しないかを決めます。
(3)カードの合計値が21を超えてしまった時点で、その場で負けが確定します。
(4)プレイヤーはカードの合計値が21を超えない限り、好きなだけカードを追加できます。
(5)ディーラーはカードの合計値が17を超えるまでカードを追加します。
(6)各カードの点数は次のように決まっています。
・2から9までは、書かれている数の通りの点数
・10,J,Q,Kは10点
・Aは1点あるいは11点として、手の点数が最大となる方で数える。
(カードの合計値が21以内で最大となる方で数えるようにします。)
(7)ゲームが開始されて、プレイヤーはカードを引くか引かないか選択(引く場合:y、引かない場合:n)でき、ディーラーは自動的にカードを引くかどうかを決定します。ゲームが終了すると、勝者が表示されます。
3 オブジェクト指向について
オブジェクト指向は、プログラムの構造化や再利用性、拡張性を向上させるための有用な手法となっています。下記の通り、オブジェクト指向を使用しました。
(1)クラス: クラスはオブジェクトの設計図のようなもので、オブジェクトの属性とメソッドを定義します。このコードでは、Card、Deck、Hand、Player、Dealer、Game という6つのクラスが定義されています。
(2)インスタンス化: クラスをもとにオブジェクト(インスタンス)を生成することです。たとえば、Card.new(value, suit) は Card クラスの新しいインスタンスを生成しています。同様に、Deck.new、Hand.new、Player.new、Dealer.new、Game.new は、それぞれのクラスのインスタンスを生成します。
(3)継承: クラスが別のクラスの属性とメソッドを引き継ぐことです。このコードでは、Dealer クラスは Player クラスを継承しています。これにより、Dealer クラスは Player クラスの属性(hand)とメソッド(busted?)を引き継ぎ、追加の機能(show_facedown_card)を実装できます。
(4)カプセル化: オブジェクトの内部状態を外部から直接操作できないようにし、オブジェクトのメソッドを通じてのみアクセス可能にすることです。例えば、Card クラスでは、attr_reader :value, :suit が定義されており、これにより value と suit は読み取り専用の属性となります。また、Hand クラスでは、points メソッドが内部のカードの点数を計算し、外部から直接カードの点数を変更できないようにしています。
4 各クラス等の説明について
今回のクラスは、以下のように分類しました。(1)Card クラス: トランプのカードを表現します。
(2)Deck クラス: トランプのデッキ(山札)を表現し、カードを引く機能があります。
(3)Hand クラス: プレイヤーの手札を表現し、点数を計算する機能があります。
(4)Player クラス: プレイヤー(参加者)を表現し、手札やバストの判定ができます。
(5)Dealer クラス: ディーラーを表現し、プレイヤークラスを継承し、伏せカードを表示できます。
(6)Game クラス: ブラックジャックのゲームを制御し、ゲームの進行や勝者の決定ができます。
(7)gameインスタンスを作成・実行
(1) Cardクラスについて
Cardクラスは、値とスートの情報を持つカードオブジェクトを表現します。initializeメソッドでカードの値とスートを設定し、to_sメソッドで文字列 表現を返します。pointsメソッドは、カードのポイントを計算します。
class Card
attr_reader :value, :suit
def initialize(value, suit)
@value = value
@suit = suit
end
def to_s
"#{@value}#{@suit}"
end
def points
case @value
when "A"
11
when "K", "Q", "J"
10
else
@value.to_i
end
end
end
(2) Deckクラスについて
Deckクラスは、52枚のカードが含まれたデッキを表現します。initializeメソッドでカードの生成とシャッフルを行い、drawメソッドでカードを1枚引きます。
class Deck
SUITS = ["♠︎", "♣︎", "♦︎", "♥︎"]
VALUES = (2..10).to_a + ["J", "Q", "K", "A"]
def initialize
@cards = []
SUITS.each do |suit|
VALUES.each do |value|
@cards << Card.new(value, suit)
end
end
@cards.shuffle!
end
def draw
@cards.pop
end
end
(3) Handクラスについて
Handクラスは、プレイヤーの手札を表現します。add_cardメソッドでカードを追加し、pointsメソッドで手札のポイントを計算します。エースは1または11として計算されます。
class Hand
attr_accessor :cards
def initialize
@cards = []
end
def add_card(card)
@cards << card
end
def points
total_points = 0
aces_count = 0
@cards.each do |card|
if card.value == "A"
aces_count += 1
total_points += 11
else
total_points += card.points
end
end
while total_points > 21 && aces_count > 0
total_points -= 10
aces_count -= 1
end
total_points
end
end
(4) Playerクラスについて
Playerクラスは、ゲームに参加するプレイヤーを表現し、それぞれのプレイヤーが持っている手札やバストしたかどうかを管理できるように設計しています。
class Player
attr_accessor :hand
def initialize
@hand = Hand.new
end
def busted?
@hand.points > 21
end
end
(5) Dealerクラスについて
Dealerクラスは、ディーラーを表現するクラスで、Playerクラスを継承しています。show_facedown_cardメソッドで伏せカードを表現する文字(ここでは "X")を返します。
class Dealer < Player
def show_facedown_card
"X"
end
end
(6) Gameクラスについて
Gameクラスは、ブラックジャックゲーム全体を制御します。ゲームの進行、手札の表示、プレイヤーとディーラーの手番、勝敗の判定などが含まれています。
class Game
def initialize
@deck = Deck.new
@player = Player.new
@dealer = Dealer.new
end
def initial_deal
2.times do
@player.hand.add_card(@deck.draw)
@dealer.hand.add_card(@deck.draw)
end
end
def display_initial_hands
puts "Player: #{@player.hand.cards.join(' ')} Total: #{@player.hand.points}"
puts "Dealer: #{@dealer.hand.cards.first} #{@dealer.show_facedown_card}"
end
def player_turn
while !@player.busted?
puts "Do you want to hit? (y/n)"
answer = gets.chomp.downcase
break if answer == "n"
card = @deck.draw
@player.hand.add_card(card)
puts "Player drew: #{card}"
puts "Player: #{@player.hand.cards.join(' ')} Total: #{@player.hand.points}"
end
end
def dealer_turn
while @dealer.hand.points < 17
card = @deck.draw
@dealer.hand.add_card(card)
puts "Dealer drew: #{card}"
end
end
def display_final_hands
puts "Player: #{@player.hand.cards.join(' ')} Total: #{@player.hand.points}"
puts "Dealer: #{@dealer.hand.cards.join(' ')} Total: #{@dealer.hand.points}"
end
def determine_winner
if @player.busted?
puts "Player busted! Dealer wins!"
elsif @player.hand.points > @dealer.hand.points
puts "Player wins!"
elsif @player.hand.points < @dealer.hand.points
puts "Dealer wins!"
else
puts "It's a tie!"
end
end
def play
initial_deal
display_initial_hands
player_turn
dealer_turn unless @player.busted?
display_final_hands
determine_winner
end
end
(7) gameインスタンスを作成・実行
最後に、ゲームを開始するコードを示します。game = Game.new
game.play