こんにちは!じゃいごテックのあつしです。
今回は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 で初期化する
- initialize(lv, hp, ap, dp, sp, cp, 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 を加算する
- info
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はパラメータやメソッドが沢山ありましたが、基本的なクラス・インスタンス変数・インスタンスメソッドを使って解くことが出来ました。
シミュレーション系の問題は問題文をそのまま実装すればよいので簡単ですね!