paiza プログラミング

[Ruby]paiza クラス・構造体メニュー 格闘ゲーム (paizaランク B 相当)の解説

fighting_game

こんにちは!じゃいごテックのあつしです。

今回はpaiza クラス・構造体メニュー からSTEP3: 格闘ゲームという問題を解説します。

この問題集はクラスの応用的な使い方に関する4個のSTEP問題(B~A級)FINAL問題(A級)で構成されていて、STEP問題を解いて行けばFINAL問題も解けるはず!となっています。

STEP3:  格闘ゲーム(paizaランク B 相当)を解いてみる

※ paizaレベルアップ問題集 クラス・構造体メニューより

問題

友達の家で N 人で遊んでいる paiza 君は格闘ゲームを遊ぶことにしました。
格闘ゲームのルールは次の通りです。

  • 各プレイヤーは 決まった hp と 3 種類の技を持っていて、技には強化系の技と攻撃の技があり、各攻撃技には技を出すための発生フレーム F とダメージ A が設定されている。
  • hp が 0 になったプレイヤーは退場となる。
  • あるプレイヤー 1 が、他のプレイヤーにある技 T_1 (発生フレーム F_1 , 攻撃力 A_1) を使って攻撃した場合、攻撃を受けたプレイヤー 2 は反撃の技 T_2 (発生フレーム F_2 , 攻撃力 A_2) を選ぶ。その後、次のようなルールに従っていずれかのプレイヤーにダメージが入る。

どちらか片方でもプレイヤーが退場している場合、互いに何も起こらない。

強化系の技を使った場合、使ったプレイヤーの他の全ての技の発生フレーム(最短 1 フレーム) を -3 , 攻撃力を +5 する。
相手が攻撃技を使っていた場合、その攻撃の攻撃力分 hp が減少する。

互いに攻撃技を使った場合

  • F_1 < F_2 のとき、プレイヤー 2 の hp が A_1 減る
  • F_1 > F_2 のとき、プレイヤー 1 の hp が A_2 減る
  • F_1 = F_2 のとき、何も起こらない

各プレイヤーの持っている技についての情報と、技が出された回数、使われた技の詳細が与えられるので、全ての技が使われた後に場に残っているプレイヤーの人数を答えてください。

入力される値

N K
hp_1 F1_1 A1_1 F2_1 A2_1 F3_1 A3_3
...
hp_N F1_N A1_N F2_N A2_N F3_N A3_N
P1_1 T1_1 P2_1 T2_1
...
P1_K T1_K P2_K T2_K

  • 1 行目では、プレイヤー数 N と攻撃回数 K が与えられます。
  • 続く N 行のうち i 行目(1 ≦ i ≦ N)では、
    i 番目のプレイヤーの hp である hp_i,
    技 1 の発生フレーム F1_i , 攻撃力 A1_i
    技 2 の発生フレーム F2_i , 攻撃力 A2_i
    技 3 の発生フレーム F3_i , 攻撃力 A3_i が半角スペース区切りで与えられます。
    ただし、発生フレーム・攻撃力が共に 0 である技は強化技であることを表しています。
  • 続く K 行のうち、 i 行目では i 回目の攻撃内容が与えられます。
    技を使ったプレイヤーの番号 P1_i と P1_i が選んだ技の番号 T1_i
    技を使ったプレイヤーの番号 P2_i と P2_i が選んだ技の番号 T2_i
    が半角スペース区切りで与えられます。

入力値最終行の末尾に改行が1つ入ります。

期待する出力

場に残っているプレイヤーの人数を 1 行で出力してください。

条件

すべてのテストケースにおいて、以下の条件をみたします。

  • 1 ≦ N , K ≦ 1000
  • 1 ≦ hp_i ≦ 100 (1 ≦ i ≦ N)
  • 0 ≦ F1_i , F2_i , F3_i ≦ 60 (1 ≦ i ≦ N)
  • 0 ≦ A1_i , A2_i , A3_i ≦ 30 (1 ≦ i ≦ N)
  • 1 ≦ P1_i , P2_i ≦ N , P1_i ≠ P2_i (1 ≦ i ≦ K)
  • T1_i , T2_i は 1 , 2 , 3 のいずれか (1 ≦ i ≦ K)
  • 強化技は各プレイヤーに最大 1 つまで
入力例1
3 6
10 1 1 2 2 3 3
10 0 0 6 1 7 2
10 0 0 7 5 8 3
1 1 2 2
1 2 3 2
1 3 2 3
2 2 3 1
2 3 3 1
1 2 3 2
出力例1
2
入力例2
5 10
8 2 24 40 25 42 26
59 48 13 21 13 56 2
5 59 7 57 5 25 24
99 28 6 32 5 23 2
62 24 19 11 19 7 21
2 1 3 2
2 1 3 2
5 1 3 1
5 3 1 2
1 1 2 2
4 2 3 1
5 3 3 2
2 3 3 2
4 1 5 3
2 3 3 2
出力例2
3
攻略ポイント

ポイント

  • プレイヤーの情報をクラスにまとめる
    • hpをインスタンス変数に格納する
    • 手持ちスキルをインスタンス変数に格納する
    • ダメージを受けたときの処理を考える
      • 退場の処理を考える(今回はhp=0で退場とする)
      • hpがマイナスにならないようにする
    • インスタンスメソッドで強化技を実装する
  • ゲームの流れを管理するクラスを考える
    • ターンスキップの処理
      • プレイヤーのどちらかが退場している
      • 攻撃技同士でスピードが同じ場合
    • 攻撃処理の条件分岐
      • 両方が攻撃技のとき
        • スピードが速い方が攻撃できる
      • 片方が強化技の時
        • 強化技を実行する
        • 相手の攻撃を受ける
      • 両方が強化技の時
        • 両方が強化技を実行する

参考記事

クラス

メソッドの定義と呼び出し

標準入力・標準出力

問題を解く流れ

入出力例をコピペしてヒアドキュメントで変数に代入し、データを確認します。正しく受け取れていれば確認用コードは削除します。

 

INPUT1 = <<~"EOS"
  3 6
  10 1 1 2 2 3 3
  10 0 0 6 1 7 2
  10 0 0 7 5 8 3
  1 1 2 2
  1 2 3 2
  1 3 2 3
  2 2 3 1
  2 3 3 1
  1 2 3 2
EOS
OUTPUT1 = <<~"EOS"
  2
EOS

INPUT2 = <<~"EOS"
  5 10
  8 2 24 40 25 42 2
  59 48 13 21 13 56 2
  5 59 7 57 5 25 24
  99 28 6 32 5 23 2
  62 24 19 11 19 7 21
  2 1 3 2
  2 1 3 2
  5 1 3 1
  5 3 1 2
  1 1 2 2
  4 2 3 1
  5 3 3 2
  2 3 3 2
  4 1 5 3
  2 3 3 2
EOS
OUTPUT2 = <<~"EOS"
  3
EOS

# 確認用コード
pp INPUT1
# > "3 6\n" +
# > "10 1 1 2 2 3 3\n" +
# > "10 0 0 6 1 7 2\n" +
# > "10 0 0 7 5 8 3\n" +
# > "1 1 2 2\n" +
# > "1 2 3 2\n" +
# > "1 3 2 3\n" +
# > "2 2 3 1\n" +
# > "2 3 3 1\n" +
# > "1 2 3 2\n"
p OUTPUT1
# > "2\n"
pp INPUT2
# > "5 10\n" +
# > "8 2 24 40 25 42 26\n" +
# > "59 48 13 21 13 56 2\n" +
# > "5 59 7 57 5 25 24\n" +
# > "99 28 6 32 5 23 2\n" +
# > "62 24 19 11 19 7 21\n" +
# > "2 1 3 2\n" +
# > "2 1 3 2\n" +
# > "5 1 3 1\n" +
# > "5 3 1 2\n" +
# > "1 1 2 2\n" +
# > "4 2 3 1\n" +
# > "5 3 3 2\n" +
# > "2 3 3 2\n" +
# > "4 1 5 3\n" +
# > "2 3 3 2\n"
p OUTPUT2
# > "3\n"

続いてPlayerクラス・FightingGameクラスと問題を解くsolveメソッドを変数の下に定義します。

まず、Playerクラスを定義します。

Playerクラス

  • インスタンス変数
    • 体力 @hp : Integer(外部から参照可)
    • 所持スキル @skills : Array(外部から参照可)
  • initializeメソッド
    • initialize(hp, skills)
      • @hp を hp で初期化する
      • @skills を skills で初期化する
  • インスタンスメソッド
    •  reinforce
      • 強化技以外の所持スキルを強化する
        • 早さ(speed) を -3 (下限を1とする)
        • 攻撃(power) を +5 インスタンスメソッド
    • take_damage(damage)
      • @hp から damage を引く(下限を0とする)
class Player
  attr_reader :hp, :skills

  def initialize(hp, skills)
    @hp = hp
    @skills = skills
  end

  def reinforce
    @skills.each do |skill|
      if skill[:speed] > 0
        skill[:speed] = [1, skill[:speed] - 3].max
        skill[:power] += 5
      end
    end
  end

  def take_damage(damage)
    @hp = [0, @hp - damage].max
  end
end

# 確認用コード
# インスタンスの生成
skills = [{ speed: 0, power: 0 },
          { speed: 3, power: 10 },
          { speed: 10, power: 20 }]
player = Player.new(100, skills)

# インスタンス変数の確認
pp player
# > #<Player:0x00007ffff2b95588
# >  @hp=100,
# >  @skills=
# >   [{:speed=>0, :power=>0}, {:speed=>3, :power=>10}, {:speed=>10, :power=>20}]>
p player.hp
# > 100
p player.skills
# > [{:speed=>0, :power=>0}, {:speed=>3, :power=>10}, {:speed=>10, :power=>20}]

# reinforce の確認
p player.reinforce
# > [{:speed=>0, :power=>0}, {:speed=>1, :power=>15}, {:speed=>7, :power=>25}]
p player.reinforce
# > [{:speed=>0, :power=>0}, {:speed=>1, :power=>20}, {:speed=>4, :power=>30}]
p player.reinforce
# > [{:speed=>0, :power=>0}, {:speed=>1, :power=>25}, {:speed=>1, :power=>35}]

# take_gamage の確認
p player.take_damage(80)
# > 20
p player.take_damage(999)
# > 0

続いて、FightingGameクラスを定義します。

FightingGameクラス

  • インスタンス変数
    • 参加プレイヤー @players : Player(外部から参照可)
  • initializeメソッド
    • initialize(players)
      • @players を players で初期化する
  • インスタンスメソッド
    • fighting(p_no1, s_no1, p_no2, s_no2)
      • 引数の情報を基に戦うプレイヤーと使うskillを選択する
        • プレイヤー player = players[p_no - 1]
        • 使用スキル skill = player.skills[s_no - 1]
      • turn_end? メソッドでターンスキップを確認する
      • スキルのスピード順で並び替える(player1が早い、player2が遅い)
      • 戦闘の処理を行う
        • player1が攻撃技ならskill1でplayer2のtake_damageメソッドを実行してターン終了
        • player1が強化技ならplayer1がreinforce メソッドを実行してplayer2の手番
          • player2が攻撃技ならskill2でplayer1のtake_damageメソッドを実行してターン終了
          • player2が強化技ならplayer2がreinforceメソッドを実行してターン終了
    • turn_end?(player1, skill1, player2, skill2)(外部から呼び出し不可)
      • ターンスキップの判定を行う
        • どちらかのhpが0ならtrue
        • 両方が攻撃技でスピードが同じならtrue
        • 上記どちらにも当てはまらなければfalse
class FightingGame
  attr_reader :players

  def initialize(players)
    @players = players
  end

  def fighting(p_no1, s_no1, p_no2, s_no2)
    # プレイヤーとスキルを選択
    player1 = @players[p_no1 - 1]
    skill1 = player1.skills[s_no1 - 1]
    player2 = @players[p_no2 - 1]
    skill2 = player2.skills[s_no2 - 1]

    # ターンスキップの条件を満たしているなら何もせず終了
    if turn_end?(player1, skill1, player2, skill2)
      # 確認用コード
      puts "スキップ"
      puts "---------------------------------"
      return
    end

    # スピード順で並び替える
    if skill1[:speed] > skill2[:speed]
      player1, player2 = player2, player1
      skill1, skill2 = skill2, skill1
    end

    # ///// 確認用コード /////
    puts <<~"EOS"
           < order >
           player#{p_no1}, skill_no: #{s_no1}
           player#{p_no2}, skill_no: #{s_no2}

           < fighting >
           player#{p_no1} hp:#{@players[p_no1 - 1].hp}
           skills: #{@players[p_no1 - 1].skills}
           skill_params: #{@players[p_no1 - 1].skills[s_no1 - 1]}

           player#{p_no2} hp:#{@players[p_no2 - 1].hp}
           skills: #{@players[p_no2 - 1].skills}
           skill_params: #{@players[p_no2 - 1].skills[s_no2 - 1]}

         EOS
    # ///////////////////////

    # 戦闘の処理
    if skill1[:power] > 0
      # player1 が攻撃
      player2.take_damage(skill1[:power])
    else
      # player1 が強化
      player1.reinforce if skill1[:power] == 0
      if skill2[:power] == 0
        # player2 が強化
        player2.reinforce
      else
        # player2 が攻撃
        player1.take_damage(skill2[:power])
      end
    end

    # ///// 確認用コード /////
    puts <<~"EOS"
           < result >
           player#{p_no1} hp:#{@players[p_no1 - 1].hp}
           skills: #{@players[p_no1 - 1].skills}

           player#{p_no2} hp:#{@players[p_no2 - 1].hp}
           skills: #{@players[p_no2 - 1].skills}
           ---------------------------------
         EOS
    # ///////////////////////
  end

  private

  # ターンスキップの判定
  def turn_end?(player1, skill1, player2, skill2)
    # どちらかが倒れていれば何もしない
    return true if player1.hp == 0 || player2.hp == 0

    # 両方攻撃技でspeedが同じなら何もしない
    return true if skill1[:power] > 0 && skill2[:power] > 0 &&
                   skill1[:speed] == skill2[:speed]
    false
  end
end

# 確認用コード
p1 = Player.new(10, [{ speed: 1, power: 1 },
                     { speed: 2, power: 2 },
                     { speed: 3, power: 3 }])
p2 = Player.new(10, [{ speed: 0, power: 0 },
                     { speed: 6, power: 1 },
                     { speed: 7, power: 2 }])
p3 = Player.new(10, [{ speed: 0, power: 0 },
                     { speed: 7, power: 5 },
                     { speed: 8, power: 3 }])
game = FightingGame.new([p1, p2, p3])

# 両方が攻撃技でスピードが違うとき
game.fighting(1, 1, 2, 2)
# > < order >
# > player1, skill_no: 1
# > player2, skill_no: 2
# >
# > < fighting >
# > player1 hp:10
# > skills: [{:speed=>1, :power=>1}, {:speed=>2, :power=>2}, {:speed=>3, :power=>3}]
# > skill_params: {:speed=>1, :power=>1}
# >
# > player2 hp:10
# > skills: [{:speed=>0, :power=>0}, {:speed=>6, :power=>1}, {:speed=>7, :power=>2}]
# > skill_params: {:speed=>6, :power=>1}
# >
# > < result >
# > player1 hp:10
# > skills: [{:speed=>1, :power=>1}, {:speed=>2, :power=>2}, {:speed=>3, :power=>3}]
# >
# > player2 hp:9
# > skills: [{:speed=>0, :power=>0}, {:speed=>6, :power=>1}, {:speed=>7, :power=>2}]
# > ---------------------------------

# 両方が攻撃技でスピードが同じとき
game.fighting(2, 3, 3, 2)
# > スキップ
# > ---------------------------------

# 片方がが強化技のとき
game.fighting(2, 1, 1, 1)
# > < order >
# > player2, skill_no: 1
# > player1, skill_no: 1
# >
# > < fighting >
# > player2 hp:9
# > skills: [{:speed=>0, :power=>0}, {:speed=>6, :power=>1}, {:speed=>7, :power=>2}]
# > skill_params: {:speed=>0, :power=>0}
# >
# > player1 hp:10
# > skills: [{:speed=>1, :power=>1}, {:speed=>2, :power=>2}, {:speed=>3, :power=>3}]
# > skill_params: {:speed=>1, :power=>1}
# >
# > < result >
# > player2 hp:8
# > skills: [{:speed=>0, :power=>0}, {:speed=>3, :power=>6}, {:speed=>4, :power=>7}]
# >
# > player1 hp:10
# > skills: [{:speed=>1, :power=>1}, {:speed=>2, :power=>2}, {:speed=>3, :power=>3}]
# > ---------------------------------

# 両方強化技の時
game.fighting(2, 1, 3, 1)
# > < order >
# > player2, skill_no: 1
# > player3, skill_no: 1
# >
# > < fighting >
# > player2 hp:8
# > skills: [{:speed=>0, :power=>0}, {:speed=>3, :power=>6}, {:speed=>4, :power=>7}]
# > skill_params: {:speed=>0, :power=>0}
# >
# > player3 hp:10
# > skills: [{:speed=>0, :power=>0}, {:speed=>7, :power=>5}, {:speed=>8, :power=>3}]
# > skill_params: {:speed=>0, :power=>0}
# >
# > < result >
# > player2 hp:8
# > skills: [{:speed=>0, :power=>0}, {:speed=>1, :power=>11}, {:speed=>1, :power=>12}]
# >
# > player3 hp:10
# > skills: [{:speed=>0, :power=>0}, {:speed=>4, :power=>10}, {:speed=>5, :power=>8}]
# > ---------------------------------

# K.O.
game.fighting(2, 2, 3, 3)
# > < order >
# > player2, skill_no: 2
# > player3, skill_no: 3
# >
# > < fighting >
# > player2 hp:8
# > skills: [{:speed=>0, :power=>0}, {:speed=>1, :power=>11}, {:speed=>1, :power=>12}]
# > skill_params: {:speed=>1, :power=>11}
# >
# > player3 hp:10
# > skills: [{:speed=>0, :power=>0}, {:speed=>4, :power=>10}, {:speed=>5, :power=>8}]
# > skill_params: {:speed=>5, :power=>8}
# >
# > < result >
# > player2 hp:8
# > skills: [{:speed=>0, :power=>0}, {:speed=>1, :power=>11}, {:speed=>1, :power=>12}]
# >
# > player3 hp:0
# > skills: [{:speed=>0, :power=>0}, {:speed=>4, :power=>10}, {:speed=>5, :power=>8}]
# > ---------------------------------

# どちらかが hp=0 のとき
game.fighting(2, 2, 3, 3)
# > スキップ
# > ---------------------------------

クラスの動作が良さそうなら、確認用コードを削除してFightingGameクラスの下にsolveメソッドを記述していきます。

まず、入力データを受け取る処理を書きます。

def solve(input_data)
  # 入力データ受け取り
  input_data = input_data.split("\n")
  n, k = input_data.shift.split.map(&:to_i)
  players = input_data.shift(n).map do |player_params|
    hp, *skill_params = player_params.split.map(&:to_i)
    skills = skill_params.each_slice(2).map do |speed, power|
      { speed: speed, power: power }
    end
    [hp, skills]
  end
  requests = input_data.shift(k).map { |request| request.split.map(&:to_i) }

  # 確認用コード
  [players, requests]
end

# 確認用コード
pp solve(INPUT1)
# > [[[10,
# >    [{:speed=>1, :power=>1}, {:speed=>2, :power=>2}, {:speed=>3, :power=>3}]],
# >   [10,
# >    [{:speed=>0, :power=>0}, {:speed=>6, :power=>1}, {:speed=>7, :power=>2}]],
# >   [10,
# >    [{:speed=>0, :power=>0}, {:speed=>7, :power=>5}, {:speed=>8, :power=>3}]]],
# >  [[1, 1, 2, 2],
# >   [1, 2, 3, 2],
# >   [1, 3, 2, 3],
# >   [2, 2, 3, 1],
# >   [2, 3, 3, 1],
# >   [1, 2, 3, 2]]]
pp solve(INPUT2)
# > [[[8,
# >    [{:speed=>2, :power=>24},
# >     {:speed=>40, :power=>25},
# >     {:speed=>42, :power=>26}]],
# >   [59,
# >    [{:speed=>48, :power=>13},
# >     {:speed=>21, :power=>13},
# >     {:speed=>56, :power=>2}]],
# >   [5,
# >    [{:speed=>59, :power=>7},
# >     {:speed=>57, :power=>5},
# >     {:speed=>25, :power=>24}]],
# >   [99,
# >    [{:speed=>28, :power=>6},
# >     {:speed=>32, :power=>5},
# >     {:speed=>23, :power=>2}]],
# >   [62,
# >    [{:speed=>24, :power=>19},
# >     {:speed=>11, :power=>19},
# >     {:speed=>7, :power=>21}]]],
# >  [[2, 1, 3, 2],
# >   [2, 1, 3, 2],
# >   [5, 1, 3, 1],
# >   [5, 3, 1, 2],
# >   [1, 1, 2, 2],
# >   [4, 2, 3, 1],
# >   [5, 3, 3, 2],
# >   [2, 3, 3, 2],
# >   [4, 1, 5, 3],
# >   [2, 3, 3, 2]]]

データが正しく受け取れていれば、solve メソッドに処理を追加していきます。

  • 配列playersの先頭から順にPlayerクラスでインスタンス化した配列で上書きします。
  • 配列requestsの値に従ってgame.fightingメソッドを実行します。
    game.fighting(player1の番号, player1が使うスキルの番号, player2の番号, player2が使うスキルの番号)
  • 戦闘終了後、配列playersのうちhp が残っている人数をカウントします。
def solve(input_data)
  # 入力データ受け取り
  input_data = input_data.split("\n")
  n, k = input_data.shift.split.map(&:to_i)
  players = input_data.shift(n).map do |player_params|
    hp, *skill_params = player_params.split.map(&:to_i)
    skills = skill_params.each_slice(2).map do |speed, power|
      { speed: speed, power: power }
    end
    [hp, skills]
  end
  requests = input_data.shift(k).map { |request| request.split.map(&:to_i) }

  # players をインスタンス化して上書き
  players.map! { |hp, skills| Player.new(hp, skills) }

  # playersを渡してFightingGameクラスでインスタンス game を生成
  game = FightingGame.new(players)
  # game に request を渡してゲームを進める
  requests.each do |request|
    game.fighting(*request)
  end

  # ゲーム終了時に hp が残っている player の人数を返す
  game.players.count { |player| player.hp > 0 }
end

# 確認用コード
p solve(INPUT1)
# > 2
p solve(INPUT1) == OUTPUT1
# > false
p solve(INPUT2)
# > 3
p solve(INPUT2) == OUTPUT2
# > false

後は出力形式を整えれば完成です。
p solve(INPUT1) == OUTPUT1 -> true を確認するために、配列resultを改行で連結して末尾に改行を加えます。

def solve(input_data)
  # 入力データ受け取り
  input_data = input_data.split("\n")
  n, k = input_data.shift.split.map(&:to_i)
  players = input_data.shift(n).map do |player_params|
    hp, *skill_params = player_params.split.map(&:to_i)
    skills = skill_params.each_slice(2).map do |speed, power|
      { speed: speed, power: power }
    end
    [hp, skills]
  end
  requests = input_data.shift(k).map { |request| request.split.map(&:to_i) }

  # players をインスタンス化して上書き
  players.map! { |hp, skills| Player.new(hp, skills) }

  # playersを渡してFightingGameクラスでインスタンス game を生成
  game = FightingGame.new(players)
  # game に request を渡してゲームを進める
  requests.each do |request|
    game.fighting(*request)
  end

  # ゲーム終了時に hp が残っている player の人数を返す
  game.players.count { |player| player.hp > 0 }.to_s << "\n"
end

# 確認用コード
p solve(INPUT1)
# > "2\n"
p solve(INPUT1) == OUTPUT1
# > true
p solve(INPUT2)
# > "3\n"
p solve(INPUT2) == OUTPUT2
# > true

入出力例での動作確認が出来ましたので、標準入力からのデータ受け取りに変更して、手動で動作確認をしたら提出します。
複数行のデータ受け取りなので STDIN.read を使います。(入力終了は Ctrl+D 又は Ctrl+Z)

解答コード
class Player
  attr_reader :hp, :skills

  def initialize(hp, skills)
    @hp = hp
    @skills = skills
  end

  def reinforce
    @skills.each do |skill|
      if skill[:speed] > 0
        skill[:speed] = [1, skill[:speed] - 3].max
        skill[:power] += 5
      end
    end
  end

  def take_damage(damage)
    @hp = [0, @hp - damage].max
  end
end

class FightingGame
  attr_reader :players

  def initialize(players)
    @players = players
  end

  def fighting(p_no1, s_no1, p_no2, s_no2)
    # プレイヤーとスキルを選択
    player1 = @players[p_no1 - 1]
    skill1 = player1.skills[s_no1 - 1]
    player2 = @players[p_no2 - 1]
    skill2 = player2.skills[s_no2 - 1]

    # ターンスキップの条件を満たしているなら何もせず終了
    return if turn_end?(player1, skill1, player2, skill2)

    # スピード順で並び替える
    if skill1[:speed] > skill2[:speed]
      player1, player2 = player2, player1
      skill1, skill2 = skill2, skill1
    end

    # 戦闘の処理
    if skill1[:power] > 0
      # player1 が攻撃
      player2.take_damage(skill1[:power])
    else
      # player1 が強化
      player1.reinforce if skill1[:power] == 0
      if skill2[:power] == 0
        # player2 が強化
        player2.reinforce
      else
        # player2 が攻撃
        player1.take_damage(skill2[:power])
      end
    end
  end

  private

  # ターンスキップの判定
  def turn_end?(player1, skill1, player2, skill2)
    # どちらかが倒れていれば何もしない
    return true if player1.hp == 0 || player2.hp == 0

    # 両方攻撃技でspeedが同じなら何もしない
    return true if skill1[:power] > 0 && skill2[:power] > 0 &&
                   skill1[:speed] == skill2[:speed]
    false
  end
end

def solve(input_data)
  # 入力データ受け取り
  input_data = input_data.split("\n")
  n, k = input_data.shift.split.map(&:to_i)
  players = input_data.shift(n).map do |player_params|
    hp, *skill_params = player_params.split.map(&:to_i)
    skills = skill_params.each_slice(2).map do |speed, power|
      { speed: speed, power: power }
    end
    [hp, skills]
  end
  requests = input_data.shift(k).map { |request| request.split.map(&:to_i) }

  # players をインスタンス化して上書き
  players.map! { |hp, skills| Player.new(hp, skills) }

  # playersを渡してFightingGameクラスでインスタンス game を生成
  game = FightingGame.new(players)
  # game に request を渡してゲームを進める
  requests.each do |request|
    game.fighting(*request)
  end

  # ゲーム終了時に hp が残っている player の人数を文字列に変換して末尾に改行を追加
  game.players.count { |player| player.hp > 0 }.to_s << "\n"
end

puts solve(STDIN.read)

今回のまとめ

Playerクラスと、ゲームを管理するFightingGameクラスを作って問題を解いてみました。
一見複雑そうな問題に見えますが、クラス別に動作を確認して最後に全体をまとめる感じで解くことが出来ました。

オブジェクト指向プログラミングらしくなってきましたね!
次からはA級問題となります!



【PR】アルゴリズム学習でお世話になった本


アルゴリズム関連の技術書は大抵C/C++で書かれていますが、ある程度プログラムが出来れば、処理の流れは理解することが出来ます。






通称: 螺旋本。C++で解説されています。AOJ(Aizu Online Judge)を運営している会津大学の渡辺准教授が書いた本です。データ構造や計算量の内容から丁寧に書いてありますのでアルゴリズム学習の最初の参考書としてオススメです。







通称: 蟻本。C++で解説されています。競技プログラミング中級者の定番書と言われていて、競技プログラミングで利用できる色々なアルゴリズムを学ぶことが出来ます。かなり高度な内容も含まれているので1冊分を完全に理解するのにはかなりの時間がかかりそうですが、手元に置いて何度も読み返しています。







通称: チーター本。C#, C++, JAVAでTopcoderの問題を解説しています。初心者~中級者向けに書かれているので解説が非常に丁寧です。







Python3でアルゴリズムを解説している本です。講義スタイルの本で、図やフローチャートを使ってアルゴリズムとデータ構造についてしっかりと説明されています。代わりにコードは少なめです。Pythonコードが読めなくても十分理解できると思います。


-paiza, プログラミング
-,

© 2024 じゃいごテック