paiza プログラミング

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

rpg

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

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

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

STEP2: RPG (paizaランク B 相当)を解いてみる

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

問題

paiza 村にたびたび魔物が訪れるため、 1 〜 N 番の番号が割り当てられた N 人の勇者を雇うことにしました。
勇者には次のようなステータスを持ちます。

レベル l_i
体力 h_i
攻撃力 a_i
防御力 d_i
素早さ s_i
賢さ c_i
運 f_i

また、各勇者には次のようなイベントが発生します。

levelup h a d s c f
レベルが 1 上昇
体力が h 上昇
攻撃力が a 上昇
防御力が d 上昇
素早さが s 上昇
賢さが c 上昇
運が f 上昇

muscle_training h a
体力が h 上昇
攻撃力が a 上昇

running d s
防御力が d 上昇
素早さが s 上昇

study c
賢さが c 上昇

pray f
運が f 上昇

各勇者の初期ステータスと起こるイベントの回数、
また、起こるイベントとその対象の勇者の番号が与えられるので、
全てのイベントが終わった後の各勇者のステータスを出力してください。

入力される値

入力は以下のフォーマットで与えられます。

N K
l_1 h_1 a_1 d_1 s_1 c_1 f_1
...
l_N h_N a_N d_N s_N c_N f_N
to_1 event_1
...
to_K event_K

  • 1 行目では、勇者の人数 N と起こるイベントの回数 K が与えられます。
  • 続く N 行のうち i 行目(1 ≦ i ≦ N)では、問題文の通り、番号 i の勇者のステータスが与えられます。
  • 続く K 行では、イベントの対象の勇者の番号 to と、イベントの内容 event が問題文の通り与えられます。

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

期待する出力

i 行目に番号 i の勇者のステータスを以下の形式で出力してください。

l_1 h_1 a_1 d_1 s_1 c_1 f_1
...
l_N h_N a_N d_N s_N c_N f_N

条件

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

与えられる値は全て整数

  • 1 ≦ N , K ≦ 10^5
  • 1 ≦ l_i , h_i , a_i , d_i , s_i , c_i , f_i ≦ 1000(1 ≦ i ≦ N)
  • 1 ≦ to_i ≦ N (1 ≦ i ≦ K)

event_i (1 ≦ i ≦ K) は以下のいずれかの形式で与えられる。

  • levelup h a d s c f (1 ≦ h , a , d , s , c , f ≦ 1000)
  • muscle_training h a (1 ≦ h , a ≦ 1000)
  • running d s (1 ≦ d , s ≦ 1000)
  • study c (1 ≦ c ≦ 1000)
  • pray f (1 ≦ f ≦ 1000)
入力例1
1 3
23 128 533 552 44 69 420
1 muscle_training 565 241
1 study 132
1 levelup 379 585 4 145 276 8
出力例1
24 1072 1359 556 189 477 428
入力例2
10 20
161 295 842 678 857 640 702
703 973 816 584 474 996 452
640 929 296 484 617 785 968
621 946 565 298 297 17 963
82 75 684 44 706 828 615
509 27 178 957 156 705 150
224 247 745 338 11 969 218
343 25 757 600 277 478 814
217 537 596 50 807 363 299
123 296 770 108 25 500 938
4 muscle_training 367 195
8 pray 229
10 levelup 683 829 497 446 843 38
3 pray 505
6 pray 488
6 muscle_training 280 653
4 study 630
10 muscle_training 111 609
8 levelup 846 819 872 906 126 58
7 muscle_training 75 112
3 levelup 750 656 95 557 50 95
7 study 771
3 muscle_training 251 458
8 study 888
4 study 52
3 pray 912
10 study 264
2 pray 886
5 muscle_training 1000 676
9 study 125
出力例2
161 295 842 678 857 640 702
703 973 816 584 474 996 1338
641 1930 1410 579 1174 835 2480
621 1313 760 298 297 699 963
82 1075 1360 44 706 828 615
509 307 831 957 156 705 638
224 322 857 338 11 1740 218
344 871 1576 1472 1183 1492 1101
217 537 596 50 807 488 299
124 1090 2208 605 471 1607 976
攻略ポイント

ポイント

  • プレイヤーの情報をクラスにまとめる
    • ステータス情報をインスタンス変数に格納する
    • イベントに対応するインスタンスメソッドを定義する

参考記事

クラス

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

標準入力・標準出力

問題を解く流れ

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

INPUT1 = <<~"EOS"
  1 3
  23 128 533 552 44 69 420
  1 muscle_training 565 241
  1 study 132
  1 levelup 379 585 4 145 276 8
EOS
OUTPUT1 = <<~"EOS"
  24 1072 1359 556 189 477 428
EOS

INPUT2 = <<~"EOS"
  10 20
  161 295 842 678 857 640 702
  703 973 816 584 474 996 452
  640 929 296 484 617 785 968
  621 946 565 298 297 17 963
  82 75 684 44 706 828 615
  509 27 178 957 156 705 150
  224 247 745 338 11 969 218
  343 25 757 600 277 478 814
  217 537 596 50 807 363 299
  123 296 770 108 25 500 938
  4 muscle_training 367 195
  8 pray 229
  10 levelup 683 829 497 446 843 38
  3 pray 505
  6 pray 488
  6 muscle_training 280 653
  4 study 630
  10 muscle_training 111 609
  8 levelup 846 819 872 906 126 58
  7 muscle_training 75 112
  3 levelup 750 656 95 557 50 95
  7 study 771
  3 muscle_training 251 458
  8 study 888
  4 study 52
  3 pray 912
  10 study 264
  2 pray 886
  5 muscle_training 1000 676
  9 study 125
EOS
OUTPUT2 = <<~"EOS"
  161 295 842 678 857 640 702
  703 973 816 584 474 996 1338
  641 1930 1410 579 1174 835 2480
  621 1313 760 298 297 699 963
  82 1075 1360 44 706 828 615
  509 307 831 957 156 705 638
  224 322 857 338 11 1740 218
  344 871 1576 1472 1183 1492 1101
  217 537 596 50 807 488 299
  124 1090 2208 605 471 1607 976
EOS

# 確認用コード
pp INPUT1
# > "1 3\n" +
# > "23 128 533 552 44 69 420\n" +
# > "1 muscle_training 565 241\n" +
# > "1 study 132\n" +
# > "1 levelup 379 585 4 145 276 8\n"
pp OUTPUT1
# > "24 1072 1359 556 189 477 428\n"
pp INPUT2
# > 10 20\n" +
# > "161 295 842 678 857 640 702\n" +
# > "703 973 816 584 474 996 452\n" +
# > "640 929 296 484 617 785 968\n" +
# > "621 946 565 298 297 17 963\n" +
# > "82 75 684 44 706 828 615\n" +
# > "509 27 178 957 156 705 150\n" +
# > "224 247 745 338 11 969 218\n" +
# > "343 25 757 600 277 478 814\n" +
# > "217 537 596 50 807 363 299\n" +
# > "123 296 770 108 25 500 938\n" +
# > "4 muscle_training 367 195\n" +
# > "8 pray 229\n" +
# > "10 levelup 683 829 497 446 843 38\n" +
# > "3 pray 505\n" +
# > "6 pray 488\n" +
# > "6 muscle_training 280 653\n" +
# > "4 study 630\n" +
# > "10 muscle_training 111 609\n" +
# > "8 levelup 846 819 872 906 126 58\n" +
# > "7 muscle_training 75 112\n" +
# > "3 levelup 750 656 95 557 50 95\n" +
# > "7 study 771\n" +
# > "3 muscle_training 251 458\n" +
# > "8 study 888\n" +
# > "4 study 52\n" +
# > "3 pray 912\n" +
# > "10 study 264\n" +
# > "2 pray 886\n" +
# > "5 muscle_training 1000 676\n" +
# > "9 study 125\n"
pp OUTPUT2
# > "161 295 842 678 857 640 702\n" +
# > "703 973 816 584 474 996 1338\n" +
# > "641 1930 1410 579 1174 835 2480\n" +
# > "621 1313 760 298 297 699 963\n" +
# > "82 1075 1360 44 706 828 615\n" +
# > "509 307 831 957 156 705 638\n" +
# > "224 322 857 338 11 1740 218\n" +
# > "344 871 1576 1472 1183 1492 1101\n" +
# > "217 537 596 50 807 488 299\n" +
# > "124 1090 2208 605 471 1607 976\n"

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

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

Heroクラス

  • インスタンス変数
    • レベル @lv : Integer
    • 体力 @hp : Integer
    • 攻撃力 @ap : Integer
    • 防御力 @dp : Integer
    • 速さ @sp : Integer
    • 賢さ @cp : Integer
    • 運 @fp : Integer
  • initializeメソッド
    • initialize(lv, hp, ap, dp, sp, cp, fp)
      • @lv を lv で初期化する
      • @hp : hp で初期化する
      • @ap : ap で初期化する
      • @dp : dp で初期化する
      • @sp : sp で初期化する
      • @cp : cp で初期化する
      • @fp : fp で初期化する
  •  インスタンスメソッド
    • info
      • @lv, @hp, @ap, @dp, @sp, @cp, @fp を半角スペースで連結した文字列を返す
    • levelup(hp, ap, dp, sp, cp, fp)
      • @lv をインクリメントする
      • muscle_trainingメソッドを呼び出す
      • runningメソッドを呼び出す
      • studyメソッドを呼び出す
      • prayメソッドを呼び出す
    • muscle_training(hp, ap)
      • @hp に hp を加算する
      • @ap に ap を加算する
    • running(dp, sp)
      • @dp に dp を加算する
      • @sp に sp を加算する
    • study(cp)
      • @cp に cp を加算する
    • pray(fp)
      • @fp に fp を加算する
class Hero
  def initialize(lv, hp, ap, dp, sp, cp, fp)
    @lv = lv
    @hp = hp
    @ap = ap
    @dp = dp
    @sp = sp
    @cp = cp
    @fp = fp
  end

  def info
    [@lv, @hp, @ap, @dp, @sp, @cp, @fp].join(" ")
  end

  def levelup(hp, ap, dp, sp, cp, fp)
    @lv += 1
    muscle_training(hp, ap)
    running(dp, sp)
    study(cp)
    pray(fp)
  end

  def muscle_training(hp, ap)
    @hp += hp
    @ap += ap
  end

  def running(dp, sp)
    @dp += dp
    @sp += sp
  end

  def study(cp)
    @cp += cp
  end

  def pray(fp)
    @fp += fp
  end
end

# 確認用コード
hero = Hero.new(1, 100, 100, 100, 100, 100, 100)
p hero
# > #<Hero:0x00007fce70051ef0 @lv=1, @hp=100, @ap=100, @dp=100, @sp=100, @cp=100, @fp=100>
hp, ap = 10, 20
hero.muscle_training(hp, ap)
dp, sp = 30, 40
hero.running(dp, sp)
cp = 50
hero.study(cp)
fp = 60
hero.pray(fp)
p hero
# > #<Hero:0x00007febfe085b78 @lv=1, @hp=110, @ap=120, @dp=130, @sp=140, @cp=150, @fp=160>
hp, ap, dp, sp, cp, fp = 1, 2, 3, 4, 5, 6
hero.levelup(hp, ap, dp, sp, cp, fp)
p hero
# > #<Hero:0x00007fe4809497d0 @lv=2, @hp=111, @ap=122, @dp=133, @sp=144, @cp=155, @fp=166>

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

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

def solve(input_lines)
  # 入力データ受け取り
  input_lines = input_lines.split("\n")
  n, k = input_lines.shift.split.map(&:to_i)
  heros = input_lines.shift(n).map { |line| line.split.map(&:to_i) }
  events = input_lines.shift(k).map do |line|
    idx, event, *params = line.split
    [idx.to_i, event, params.map(&:to_i)]
  end

  # 確認用コード
  [heros, events]
end

# 確認用コード
pp solve(INPUT1)
# > [[[23, 128, 533, 552, 44, 69, 420]],
# >  [[1, "muscle_training", [565, 241]],
# >   [1, "study", [132]],
# >   [1, "levelup", [379, 585, 4, 145, 276, 8]]]]
pp solve(INPUT2)
# > [[[161, 295, 842, 678, 857, 640, 702],
# >   [703, 973, 816, 584, 474, 996, 452],
# >   [640, 929, 296, 484, 617, 785, 968],
# >   [621, 946, 565, 298, 297, 17, 963],
# >   [82, 75, 684, 44, 706, 828, 615],
# >   [509, 27, 178, 957, 156, 705, 150],
# >   [224, 247, 745, 338, 11, 969, 218],
# >   [343, 25, 757, 600, 277, 478, 814],
# >   [217, 537, 596, 50, 807, 363, 299],
# >   [123, 296, 770, 108, 25, 500, 938]],
# >  [[4, "muscle_training", [367, 195]],
# >   [8, "pray", [229]],
# >   [10, "levelup", [683, 829, 497, 446, 843, 38]],
# >   [3, "pray", [505]],
# >   [6, "pray", [488]],
# >   [6, "muscle_training", [280, 653]],
# >   [4, "study", [630]],
# >   [10, "muscle_training", [111, 609]],
# >   [8, "levelup", [846, 819, 872, 906, 126, 58]],
# >   [7, "muscle_training", [75, 112]],
# >   [3, "levelup", [750, 656, 95, 557, 50, 95]],
# >   [7, "study", [771]],
# >   [3, "muscle_training", [251, 458]],
# >   [8, "study", [888]],
# >   [4, "study", [52]],
# >   [3, "pray", [912]],
# >   [10, "study", [264]],
# >   [2, "pray", [886]],
# >   [5, "muscle_training", [1000, 676]],
# >   [9, "study", [125]]]]

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

  • 配列herosの先頭から順にHerosクラスでインスタンス化した配列で上書きします。
  • 配列eventsの値に従ってイベントを実行します。
    • idx-1で対象のheroを選択する
    • eventを調べて該当メソッドを実行する
  • イベント終了後、配列herosの先頭から順にinfoメソッドを実行した結果を配列に格納します。
def solve(input_lines)
  # 入力データ受け取り
  input_lines = input_lines.split("\n")
  n, k = input_lines.shift.split.map(&:to_i)
  heros = input_lines.shift(n).map { |line| line.split.map(&:to_i) }
  events = input_lines.shift(k).map do |line|
    idx, event, *params = line.split
    [idx.to_i, event, params.map(&:to_i)]
  end

  # Heroクラスでインスタンス化して上書き
  heros.map! { |params| Hero.new(*params) }

  # イベントを実行する
  events.each do |idx, event, params|
    hero = heros[idx - 1]
    case event
    when "levelup"
      hero.levelup(*params)
    when "muscle_training"
      hero.muscle_training(*params)
    when "running"
      hero.running(*params)
    when "study"
      hero.study(*params)
    when "pray"
      hero.pray(*params)
    end
  end

  result = heros.map(&:info)

  # 確認用コード
  result
end

# 確認用コード
pp solve(INPUT1)
# > ["24 1072 1359 556 189 477 428"]
p solve(INPUT1) == OUTPUT1
# > false
pp solve(INPUT2)
# > ["161 295 842 678 857 640 702",
# >  "703 973 816 584 474 996 1338",
# >  "641 1930 1410 579 1174 835 2480",
# >  "621 1313 760 298 297 699 963",
# >  "82 1075 1360 44 706 828 615",
# >  "509 307 831 957 156 705 638",
# >  "224 322 857 338 11 1740 218",
# >  "344 871 1576 1472 1183 1492 1101",
# >  "217 537 596 50 807 488 299",
# >  "124 1090 2208 605 471 1607 976"]
p solve(INPUT2) == OUTPUT2
# > false

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

def solve(input_lines)
  # 入力データ受け取り
  input_lines = input_lines.split("\n")
  n, k = input_lines.shift.split.map(&:to_i)
  heros = input_lines.shift(n).map { |line| line.split.map(&:to_i) }
  events = input_lines.shift(k).map do |line|
    idx, event, *params = line.split
    [idx.to_i, event, params.map(&:to_i)]
  end

  # Heroクラスでインスタンス化して上書き
  heros.map! { |params| Hero.new(*params) }

  # イベントを実行する
  events.each do |idx, event, params|
    hero = heros[idx - 1]
    case event
    when "levelup"
      hero.levelup(*params)
    when "muscle_training"
      hero.muscle_training(*params)
    when "running"
      hero.running(*params)
    when "study"
      hero.study(*params)
    when "pray"
      hero.pray(*params)
    end
  end

  result = heros.map(&:info)

  # result を改行で連結し末尾に改行を追加
  result.join("\n") << "\n"
end

# 確認用コード
pp solve(INPUT1)
# > "24 1072 1359 556 189 477 428\n"
p solve(INPUT1) == OUTPUT1
# > ture
pp solve(INPUT2)
# > "161 295 842 678 857 640 702\n" +
# > "703 973 816 584 474 996 1338\n" +
# > "641 1930 1410 579 1174 835 2480\n" +
# > "621 1313 760 298 297 699 963\n" +
# > "82 1075 1360 44 706 828 615\n" +
# > "509 307 831 957 156 705 638\n" +
# > "224 322 857 338 11 1740 218\n" +
# > "344 871 1576 1472 1183 1492 1101\n" +
# > "217 537 596 50 807 488 299\n" +
# > "124 1090 2208 605 471 1607 976\n"
p solve(INPUT2) == OUTPUT2
# > true

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

解答コード
class Hero
  def initialize(lv, hp, ap, dp, sp, cp, fp)
    @lv = lv
    @hp = hp
    @ap = ap
    @dp = dp
    @sp = sp
    @cp = cp
    @fp = fp
  end

  def info
    [@lv, @hp, @ap, @dp, @sp, @cp, @fp].join(" ")
  end

  def levelup(hp, ap, dp, sp, cp, fp)
    @lv += 1
    muscle_training(hp, ap)
    running(dp, sp)
    study(cp)
    pray(fp)
  end

  def muscle_training(hp, ap)
    @hp += hp
    @ap += ap
  end

  def running(dp, sp)
    @dp += dp
    @sp += sp
  end

  def study(cp)
    @cp += cp
  end

  def pray(fp)
    @fp += fp
  end
end

def solve(input_lines)
  # 入力データ受け取り
  input_lines = input_lines.split("\n")
  n, k = input_lines.shift.split.map(&:to_i)
  heros = input_lines.shift(n).map { |line| line.split.map(&:to_i) }
  events = input_lines.shift(k).map do |line|
    idx, event, *params = line.split
    [idx.to_i, event, params.map(&:to_i)]
  end

  # Heroクラスでインスタンス化して上書き
  heros.map! { |params| Hero.new(*params) }

  # イベントを実行する
  events.each do |idx, event, params|
    hero = heros[idx - 1]
    case event
    when "levelup"
      hero.levelup(*params)
    when "muscle_training"
      hero.muscle_training(*params)
    when "running"
      hero.running(*params)
    when "study"
      hero.study(*params)
    when "pray"
      hero.pray(*params)
    end
  end

  result = heros.map(&:info)

  # result を改行で連結し末尾に改行を追加
  result.join("\n") << "\n"
end

puts solve(STDIN.read)
他の解答例

solveメソッドevent処理部分はeventがメソッド名になっているので、public_sendメソッドを使うと短く書けます。

def solve(input_lines)
  # 入力データ受け取り
  input_lines = input_lines.split("\n")
  n, k = input_lines.shift.split.map(&:to_i)
  heros = input_lines.shift(n).map { |line| line.split.map(&:to_i) }
  events = input_lines.shift(k).map do |line|
    idx, event, *params = line.split
    [idx.to_i, event, params.map(&:to_i)]
  end

  # Heroクラスでインスタンス化して上書き
  heros.map! { |params| Hero.new(*params) }

  # イベントを実行する
  events.each do |idx, event, params|
    heros[idx - 1].public_send(event, *params)
  end

  result = heros.map(&:info)

  # result を改行で連結し末尾に改行を追加
  result.join("\n") << "\n"
end

puts solve(STDIN.read)

今回のまとめ

STEP2はパラメータやメソッドが沢山ありましたが、基本的なクラス・インスタンス変数・インスタンスメソッドを使って解くことが出来ました。

シミュレーション系の問題は問題文をそのまま実装すればよいので簡単ですね!



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


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






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







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







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







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


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

© 2024 じゃいごテック