【後編】初学者がRubyで「トランプの戦争」を作ってみた【備忘録】
前編では、ルールから役割を分解してCard/Player/Dealerを作ったものの
- 「Playerがプレイヤーっぽくない(手札がない)」
- 「Dealerが全部やることになりそう」
という違和感が出てきました。
ただ、前編の通り「動くこと」を最優先にしていたので、まずは作り切った結果を備忘録として残します。
そして、作り終えた今の視点で、「何を改善したくなったか」を整理しておきます。
4. まず「Playerをプレイヤーにする」ために手札を持たせた
前編の時点では Player は名前しか持っていませんでした。
これだと「カードを出す」も「勝ったカードを受け取る」もできないので、最初に手札を持てるようにしました。
class Player
attr_reader :name, :hand_cards
attr_writer :hand_cards
def initialize(name = 1)
@name = "プレイヤー#{name}"
@hand_cards = []
end
def put_down_card
@hand_cards.pop
end
end
この時点でようやく「プレイヤーっぽい」状態になりました。
なお、attr_writer :hand_cardsを付けていますが、結局この実装ではhand_cards=は使っていません。
また、外部から手札を差し替えられる形にもなるので、ここは不要でした。
5. 「Dealerが全部やる」前提でとにかくゲームを回すことにした
クラス構成はCard/Player/Dealerにすると決めていたので、それ以外の処理はDealerに寄せて進めました。
理由は単純で、現時点での理解だと「手続きとしての進行」が一番書けそうだったからです。
そのため、次の処理をDealerに集約してゴールを目指しました。
- カード生成
- 配布
- 1ターンの進行
- 勝敗判定
- 勝者への回収
- 終了判定
6. 山札(52枚)は「カード1枚=配列」で作る
カードがないとゲームが始まらないので、まずは山札を作りました。
事前に作ったCardインスタンスが持つrankとsuitを使って、[suit, rank]の配列としてカードを表現しています。
def make_cards(card)
card.suit.each do |s|
card.rank.each do |r|
@playing_cards << [s, r]
end
end
end
これで「作る→配る→出す」が一気に書きやすくなりました。
一方でCardクラスが「カード1枚」ではなくカードを作る材料っぽくなってしまいました。
7. 配布は % を使って交互に配る
配る処理は、人数に応じて順番に配れるようにしました。
def deal_cards
players = @players_list.size
@playing_cards.shuffle.each_with_index do |card, num|
@players_list[num % players].hand_cards << card
end
end
ただ、ここでhand_cardsをDealerが直接操作してしまっています。
結果として「Playerが手札を管理する」という役割が外に漏れてしまい、責務が崩れた形になりました。
8. 引き分けルールは “簡略化”
前編では「引き分けならもう一度カードを出す」と書きましたが、
今回の実装では、引き分け分をused_cardにプールして、次の勝負に持ち越す形にしています。
結果として引き分け後もゲームは続き、どこかの勝負で決着がついたタイミングで、プール分もまとめて回収されます。
if set_card.strength[played[0][:rank].to_sym] == set_card.strength[played[1][:rank].to_sym]
puts "引き分けです。"
@used_card.concat(battlefield)
battlefield.clear
end
9. 動かして分かったこと:違和感が“確信”に変わった
最後まで回したことで、前編の違和感が「やっぱりここがしんどい」に変わりました。
改善ポイント1 Dealerが重すぎる
game_startにロジックも表示も詰まって、修正するのが怖い状態になります。
勝ち/負け/引き分けで似たコードが増えて、読み返すだけで疲れます。
game_start
def game_start(set_card)
puts "戦争を開始します。"
puts "カードが配られました。"
battlefield = []
while (@players_list[0].hand_cards.empty? || @players_list[1].hand_cards.empty?) == false do
puts "戦争!"
played = []
@players_list.each_with_index do |player, num|
card = players_list[num].put_down_card
battlefield << card
suit, rank = card
played << { player: player.name, hand_cards: player.hand_cards, suit: suit, rank: rank }
end
if set_card.strength[(played[0][:rank].to_sym)] == set_card.strength[(played[1][:rank].to_sym)]
puts "#{played[0][:player]}のカードは#{played[0][:suit]}の#{played[0][:rank]}です。"
puts "#{played[1][:player]}のカードは#{played[1][:suit]}の#{played[1][:rank]}です。"
@used_card.concat(battlefield)
battlefield.clear
puts "引き分けです。"
elsif set_card.strength[(played[0][:rank].to_sym)] > set_card.strength[(played[1][:rank].to_sym)]
puts "#{played[0][:player]}のカードは#{played[0][:suit]}の#{played[0][:rank]}です。"
puts "#{played[1][:player]}のカードは#{played[1][:suit]}の#{played[1][:rank]}です。"
puts "#{played[0][:player]}の勝ちです。"
puts "#{played[0][:player]}はカードを#{battlefield.size + @used_card.size}枚もらいました。"
played[0][:hand_cards].concat(@used_card).concat(battlefield).shuffle!
@used_card.clear
battlefield.clear
else
puts "#{played[0][:player]}のカードは#{played[0][:suit]}の#{played[0][:rank]}です。"
puts "#{played[1][:player]}のカードは#{played[1][:suit]}の#{played[1][:rank]}です。"
puts "#{played[1][:player]}の勝ちです。"
puts "#{played[1][:player]}はカードを#{battlefield.size + @used_card.size}枚もらいました。"
played[1][:hand_cards].concat(@used_card).concat(battlefield).shuffle!
@used_card.clear
battlefield.clear
end
end
if @players_list[0].hand_cards.empty?
puts "#{@players_list[0].name}の手札がなくなりました。"
puts "#{@players_list[1].name}の手札は#{@players_list[1].hand_cards.size}枚です。#{@players_list[0].name}の手札は0枚です。"
puts "#{@players_list[1].name}が1位、#{@players_list[0].name}が2位です。"
elsif @players_list[1].hand_cards.empty?
puts "#{@players_list[1].name}の手札がなくなりました。"
puts "#{@players_list[0].name}の手札は#{@players_list[0].hand_cards.size}枚です。#{@players_list[1].name}の手札は0枚です。"
puts "#{@players_list[0].name}が1位、#{@players_list[1].name}が2位です。"
end
puts "戦争を終了します。"
end
→ 次はメソッドを分けて、機能を細かく作ります。
改善ポイント2 Playerの手札を外から触りすぎ
配布も回収もhand_cards <<やconcatを外からやっています。
「Playerが手札を管理する」という役割が崩れてしまいました。
→ 次はPlayerクラスで手札を操作するようにし、Dealerは手札配列に触れないようにします。
改善ポイント3 Cardが“カード1枚”じゃなく、材料の集まりになっている
Cardにrank/suit/strengthが集まり、カードは配列で扱っています。
今のルール範囲なら十分シンプルで扱いやすい一方、ルールを増やすならカード表現は見直しポイントになりそうです。
→ 次はCardを1枚オブジェクトとして切り出します。
まとめ 今回の成果は「動かしたから改善点が見えた」こと
設計もコードも全く綺麗ではありません。
でも、動くところまでやったことで
- どこが重いか(Dealer)
- どこが漏れてるか(Playerの手札)
- どこが役割ズレしてるか(Card)
が初学者なりに見えるようになりました。
Discussion