皆さんこんにちは。ローグライクゲームのマップは、できるだけ全てのマップをくまなく探索してから次のマップに移動したい小幡です。
前回はクラスの作成を中心に作業をしてきました。前回の記事はコチラからどうぞ。
前回のおさらいを簡単にすると・・・
- クラスとは何かを知る
- プレイヤークラスを作る
- プレイヤーインスタンスを作る
という事をしてきました。これによって、今までおおまかな流れを文字で表現するだけだったゲームにプレイヤーという概念が誕生しました。
今回はマップの概念をゲームに作り、マップを文字列で表現するところまでを説明していきます。
2Dゲームのマップと言えば、グラフィックがありプレイヤーは移動したい方向に合わせてグラフィックが細かく動くイメージですが、今回はコマンドラインでの動作ですので、文字列しか扱うことができません。
しかし、その文字列だけでも十分にマップとして機能させることが可能です。イメージ言うと、2Dゲームのミニマップです。ゲームのメイン画面の隅っこに小さくマップが表示されるゲームがあるかと思います。そのマップは大まかな通れる道や、プレイヤーの位置情報、宝箱やゴールの概要をアイコンで表示しているパターンが多いです。
そのミニマップを今回のRPGではメイン画面で大きく表示するというイメージです。それでは実際にどのようにして導入していくかを説明していきます。
フィールドマップを配列(リスト)で表現する
先ほども説明した通り、コマンドラインでのマップ表示は全て文字列で表現します。文字列で表現する場合でも、様々な表現方法があるかとおもいますが今回は配列を使います。
今回の記事では最終的に小さなフィールドマップを作成するところまで作業します。結果的には次の画像の様な状態まで表示できるようになります。
それでは、まずは配列の紹介をします。
配列とは、前回までに使っていた変数とは違い、「1つのもの」だけではなく、「複数のもの」を入れる事ができるデータ構造のです。Pythonの場合はリスト、タプル、辞書が存在します。
今回はこのリストを使用します。
リストはインデックスと呼ばれるデータに紐づける番号をもつデータ構造です。そしてのインデックスの番号は0から1つずつ増加していきます。試しにリストを1つ作ってみます。
item_list = ['薬草', '勇者の剣', '勇者の盾']
上の例では、”item_list”という名前を付けたリストを作成し、その中身は、薬草、勇者の剣、勇者の盾の3つです。このリストを前回作成したプレイヤーに持たせることができれば、プレイヤーはアイテムを所持できる!というイメージを掴むことができれば良いと思います。
このとき、コードには表現されていませんが、自動的にインデックスが各アイテムに振られているということを頭に入れておきましょう。具体的に言うと薬草は0、勇者の剣は1、勇者の盾は2のインデックスが割り振られています。
for文の説明
このインデックスを確認するために、for文を利用してみます。
for文とは、ループ処理の事です。まず例を示します。
for item in ['薬草', '勇者の剣', '勇者の盾']:
print(item)
print('おわり')
1行目を見てみると”for item in リスト型”と記述されていることがわかります。末尾のリスト型は先ほど説明した通り、3つのアイテムが入っているリストと見ることができます。
そして”for item in”と記述することで、「リスト型の中身の先頭から順番にitemという変数に入れて、以下の処理を実行する」という意味を持ちます。今回は末尾に単純なリスト型が記述されていますので、そのまま1つずつ処理するだけです。
コードの流れに沿って見て行きます。まず1行目にリスト型の先頭のアイテム”薬草”が”item”の中に入れられます。そして2行目の”print(item)”が実行されます。
for文の構造は、末尾の”:”(コロン)がスタートの目印になっていて、for文の処理の中身はコロン以下のスペース4つ分かインデントされた行が対象になりますので、3行目、4行目には進みません。4行目にはスペース4つ分が無いのが目印です。これで、「ここはfor文の構造の外なんだ」ということがわかります。
1回目の処理が終わりましたら、また1行目に戻ります。そして次のアイテムが取り出されて、また”item”の中に入れられます。そして同じように2行目の”print(item)”が実行されます。
さきほどと同じように、3行目、4行目には行かずに、処理が終了したので1行目に戻り、またリストからアイテムが取り出され”item”の中に入れられます。そして2行目が実行されます。
ようやくリストの中身3つが処理されました。しかし、プログラム上では再び1行目に戻ります。ここでリストの4つ目を取り出そうとした時に、4つ目が無いと判断された後にようやくループから抜け出します。
ループから抜けた先に、”おわり”を表示する記述がありますので、これを実行すると以下のようになります。
薬草
勇者の剣
勇者の盾
おわり
1行に1つのアイテムが出力されているのがわかるかと思います。最後にはループを抜けて”おわり”が標準出力されています。
こうして無事にリスト型から1つずつアイテムを取り出す方法が理解できたかと思います。
先ほど説明した配列に付与されている番号(インデックス)も同じような処理で確認することができます。インデックスの確認は”index()”を使います。以下に例を示します。
item_list = ['薬草', '勇者の剣', '勇者の盾']
for item in item_list:
print(item_list.index(item))
print('おわり')
これを実行するとどうなるでしょうか?先ほどは、”item_list”から取り出したアイテムの名前を、そのままprintで出力していましたが、今回は”item_list.index(item)”という様に少し複雑な形になっています。
これは前から順番に見ていくと「”item_list”のインデックスを調べます。調べるインデックスに付与されている名前は”item”です」という感じです。先ほどのループ処理の復習になりますが、最初に取り出されるのはリストの先頭にある”薬草”です。”item”の中に”薬草”が入れられた状態で確認してみると・・・
「”item_list”のインデックスを調べます。調べるインデックスに付与されている名前は”薬草”です」ということになりますので、薬草のインデックスが取得できるということになります。そしてリスト型のインデックスは0から始まるというのを思い出してください。
以上の事を踏まえて実行結果を見てみましょう。以下に示します。
0
1
2
おわり
いかがでしょうか?リストのインデックスが0から1、2と順番に表示されているのがわかるかと思います。先ほどと同じようにループが終わり、最後の標準出力”おわり”も確認できるかと思います。
以上で、リスト型をfor文を使って1つずつ取り出して処理する方法の説明は終わりです。
2次元配列をfor文で処理する
続きまして、リスト型の中にリスト型が入った2次元配列の紹介をします。これを乗り切ればフィールドマップの表示を理解できるようになると思います。
理屈はfor文で1つずつ取り出すというのを2重にしているだけです。以下に例を示します。
item_lists = [['薬草', '毒消し草'], ['勇者の剣', '勇者の盾']]
この例では、薬草と毒消し草を1つの配列に、勇者の剣と勇者の盾を1つの配列にまとめ、その2つの配列を”item_list”という配列に入れています。1行で書くと見にくいので、以下のように修正します。内容は全く同じです。
item_lists = [
['薬草', '毒消し草'],
['勇者の剣', '勇者の盾'],
]
このように、中に入っている配列を1つの塊として見た時に、”,”(カンマ)で区切って改行するとわかりやすいと思います。”item_list = [”という大きな配列が示された後に、2つの配列が入れられていのが、わかるかと思います。
これを先ほど説明したfor文で取り出してみます。少し奇妙な感じを受けると思いますが、以下のように取り出すことが可能です。最終的に”単体単体”という様に出力しているのは、後で登場するフィールドマップを表示するために必要な処理だからです。先にフィールドマップの出力を見て貰った方が理解し易いかもしれません。
# 配列(複数形)から配列(単体)を取り出す
for item_list in item_lists:
string = ''
# 配列(単体)からアイテム(単体)を取り出す
for item in item_list:
# アイテム(単体)を結合する -> ”単体単体”
string = string + item
# ”単体単体” が出力される
print(string)
9行目の処理は、1度目は空の文字列と取り出したアイテムを結合させ変数に入れており、2度目は空文字とアイテムを結合させた変数に、さらに取り出したアイテムを結合している処理になっています。つまり、1度目は””と”薬草”を結合し、2度目は”薬草”と”毒消し草”を結合していますので、”string”には”薬草毒消し草”が入っている状態で一度printで出力されます。
出力結果を見てください。以下に示します。
薬草毒消し草
勇者の剣勇者の盾
この処理には疑問が残るかもしれませんが、とりあえず今は2次元配列もfor文二回で全て取り出すことが出来るという認識を持って頂ければと思います。
さて、リストについてわかったところで、マップをリストで表現するというのはどういうことでしょうか?これは例を見て貰った方が早いと思いますので、以下に示します。
MAP = [
['B', 'B', 'B', 'B', 'B', 'B'],
['B', 'P', 'E', 'E', 'E', 'B'],
['B', 'E', 'E', 'E', 'E', 'B'],
['B', 'E', 'E', 'E', 'G', 'B'],
['B', 'B', 'B', 'B', 'B', 'B'],
]
例を示してみましたが、どのように見えるでしょうか?
Bはブロック、Pはプレイヤー、Eは空(エンプティ)、Gはゴールを表しています。
まだどういうマップを表しているかわからないかと思いますので、これをコマンドライン上に標準出力して並べてみたいと思います。このリストはMAPという大きい配列に、5つの配列が入っている2次元配列になっているので、標準出力させるのは先ほど説明したfor文2回が必要です。
# マップから配列を取り出す
for array in MAP:
string = ''
# 配列から文字を取り出す
for character in array:
# 文字を結合して文字列にする
string = string + character
print(string)
これは先ほど疑問が残った文字列の結合を同じように行っています。
すると結果はどうなるでしょうか?
5つの配列の文字列が全て横並びに標準出力されますので、以下のように出力されます。
BBBBBB
BPEEEB
BEEEEB
BEEEGB
BBBBBB
まだまだ見ずらいかと思いますが、少しずつフィールドマップの形に近づいてきたのではないでしょうか?後は空を意味する”E”を空欄にして表示したりブロックの”B”をもう少しわかりやすく”#”にしてみたり、半角英文字だと少し細く見えてしまうので、全角文字にしてみたりと、細かい工夫を加えていくと、第一回で示したフィールドマップとして表示することができます。
他にも”薬”、”剣”、”盾”などのアイテムも配置したいと思いますので、どうしたら今回作成した2次元配列から適切なフィールドの文字に変換できるでしょうか?
次回はその疑問を解決しつつ、フィールドマップを完成させたいと思います。
まとめ
お疲れ様でした。今回は配列と2次元配列、for文で中身を取り出したり、文字列を結合したりと、一気に作業量が増えましたが、無事に簡単なマップを表示できたでしょうか?
配列を扱えるようになるとプログラミングで出来る事が一気に増えていきますので、その構造と利用方法をマスターできるように頑張りましょう。
先だしになってしまいましたが、プレイヤーが持つアイテム一覧もリスト型ですので、また後で使う事になりますし、様々な場面で登場してくると思います。
2次元配列とfor文はゆっくり読み解くことができれば大丈夫です。そもそもfor文が2回続けて登場することでスペースの数(タブ)が増えてしまう事を”ネスト”と呼んだりしますが、これはあまり良い事ではありません。「絶対にこうしなければならない」という訳ではなく、他にも2次元配列から中身を取り出す方法もありますので、興味のある方は調べてみてください。
それではまた次回、みなさんにお会いできることを楽しみにしております。次回は以下リンクよりどうぞ
以下のDiscordサーバでも質問を受け付けています。お気軽に声をかけて頂けると幸いです!