3章 探査と、行動の決め方
入門編の第2部5・6章(ステージ4)に対応します。
CHaserのしくみの要点
- 周辺情報(
map_info)で分かるのは、自分のすぐ隣のマスまでです。もっと遠くを見るための命令が、look(近隣探査)とsearch(遠方探査)の2つです。 lookは、指定した方向の2マス先を中心とした、周囲9マスを調べます。 たとえばlook(Up)なら、上に1〜3マス先の範囲を、横に3マス分です。searchは、指定した方向に一直線に、9マス分を調べます。- 探査は、移動やブロック設置と同じく1回の行動です。探査を実行したターンは、移動もアイテム取得もできません。むやみに使うと、アイテムを集める速さが落ちます。
look:近隣探査
look_info = look(Right)
look(方向)は、指定した方向の2マス先を中心とした、周囲9マスの情報を返します。結果はlook_infoという変数に入れます。map_infoと同じように、方角の名前で取り出せます。
look_info[Center] # 2マス先のマス(探査の中心)
look_info[Up] # 中心の上のマス
look_info[DownRight] # 中心の右下のマス
方角の名前は、探査した範囲を、地図と同じ向きで見た3×3のマスに対応します。look(Up)の場合は、次のとおりです。
| 左 | 中 | 右 | |
|---|---|---|---|
| 3マス先 | look_info[UpLeft] |
look_info[Up] |
look_info[UpRight] |
| 2マス先 | look_info[Left] |
look_info[Center] |
look_info[Right] |
| 1マス先 | look_info[DownLeft] |
look_info[Down] |
look_info[DownRight] |
| 自分 | ★ |
look_info[UpLeft]は、「自分から見た左上」ではなく、「探査の中心から見た左上」のマスです。この違いを間違えないようにしましょう。
search:遠方探査
search_info = search(Left)
search(方向)は、指定した方向に一直線に9マス分の情報を返します。結果はsearch_infoに入れます。
search_info[0] # 1マス先(隣)
search_info[4] # 5マス先
search_info[8] # 9マス先(いちばん遠い)
search_infoは、方角の名前ではなく、番号で取り出します。番号は0から数えるので、search_info[0]が隣、search_info[8]がいちばん遠いマスです。「9マス先」を見たいのにsearch_info[9]と書くと、エラーになります(0〜8の9個しかないため)。
lookは正方形で方角の名前、searchは一直線で番号、と覚えると、取り出し方を間違えにくくなります。
| 命令 | 調べる範囲 | 取り出し方 | 向いている場面 |
|---|---|---|---|
look(方向) |
2マス先を中心とした周囲9マス | look_info[Center]など、方角の名前 |
進んだ先の安全確認 |
search(方向) |
一直線に9マス | search_info[3]など、番号 |
遠くのアイテムや相手を見つける |
探査した情報で行動を決める(ステージ4-1)
ステージ4-1のマップとクリア条件
| クリア条件 | 内容 |
|---|---|
| アイテム | 1個ゲットする |
| ターン数 | 100ターン以内 |
黄色の枠が自分(Cool)のスタート位置です。すべての条件を同時に満たすとクリアです。アイテムの位置は毎回変わります(マップの半透明の表示は、出現位置の候補です)。
まず、番号で取り出す形の練習として、「右方向をsearchして、9マス先にアイテムがあれば右へ、なければ左へ、ずっと進む」プログラムを見てみましょう。
map_info = get_ready()
search_info = search(Right)
if search_info[8] == Item:
while True:
map_info = get_ready()
walk(Right)
else:
while True:
map_info = get_ready()
walk(Left)
探査するのは最初の1回だけで、その結果に応じて選ばれた、どちらかのwhile True:だけが、ずっと動き続けます。インデントが3段階になっている点に注目してください。
このコードのget_ready()は、CPUのいないチュートリアルのステージでは、省略できます。本番では、行動の前に必ずget_ready()を呼びます(4章)。上のコードは、その形で入れてあります(本番では、命令の前にplayer.を付けます)。
ただし、これはステージ4-1の答えではありません。 実機のステージ4-1では、アイテムは、上下左右のいちばん遠いところに、毎回どれか1か所だけ置かれます。しかも、いちばん遠いといっても、上下は8マス先、左右は7マス先で、9マス先(search_info[8])は、フィールドの外です(値はBlockになります)。だから、search_info[8] == Itemは、このステージでは成り立ちません。実際のステージでは、次のinを使います。
「どこかにあるか」を調べる:in
アイテムが何マス先にあるかに関係なく、「9マスのどこかにあるか」を調べるには、inを使います。
search_info = search(Right)
if Item in search_info: # 9マスのどこかにアイテムがある
direction = Right
else:
direction = Left
inは、リストの中に、その値が含まれているかどうかを調べる演算子です。「何か in リスト」で、含まれていればTrue、なければFalseになります。位置は分かりませんが、「あるか・ないか」だけを知りたいなら、これがいちばん短く書けます。
上下左右を順に調べる(ステージ4-1・4-2の答え)
ステージ4-1・4-2では、アイテムがどの方向にあるか分かりません。上下左右を順にsearchして、見つかった方向へ進み続けます。
for direction in (Up, Down, Left, Right):
search_info = search(direction) # 1回の行動を使う
if Item in search_info: # 9マスのどこかにアイテムがある
break # 見つかったので、調べるのをやめる
while True:
walk(direction)
for direction in (Up, Down, Left, Right):は、4つの方向を順にdirectionに入れて繰り返します。breakは、繰り返しをそこで終わらせる命令です。アイテムが見つかった時点で終わるので、directionには、見つかった方向が入ったままです。あとのwhile True:で、その方向へ進み続けます。
このコードは、実機のチュートリアル(Python)で、ステージ4-1を3回、4-2を6回試して、すべてクリアできました。本番で使うときは、searchのたびに、その前にget_ready()が必要です。
Blocklyから変換されたコードに出てくるfirst_index
入門編の4-2では、「リスト〔 〕で以下の〔 〕の最初の出現箇所を検索」ブロックを、≠ 0と比べて使いました。これをPythonに変換すると、次のようになります。
if first_index(search_info, Item) > 0: # アイテムがあるか
first_indexは、Blocklyが変換のときに補う補助関数で、Pythonの画面の骨組みには含まれていません。自分で書くときは、inで十分です。変換されたコードを読むときのために、次のことだけ知っておいてください(定義は5章で説明します)。
first_indexが返す位置は1から数えた番号です(隣が1、いちばん遠いマスが9)。- 見つからなければ
0を返します。だから> 0が「どこかにあった」の意味になります。 search_info[番号]は0から数えるので、数え方が違います。
条件を組み合わせる:and・or・not
2つの条件を組み合わせるには、and(かつ)・or(または)を使います。条件を反対にするには、notを使います。
# 右上も左上もブロックのとき
if map_info[UpRight] == Block and map_info[UpLeft] == Block:
# 右上か左上のどちらかがブロックのとき
if map_info[UpRight] == Block or map_info[UpLeft] == Block:
# 上がブロックでないとき(!= と同じ)
if not (map_info[Up] == Block):
日本語で条件を声に出してみて、「〜も〜も」ならand、「〜か〜か」ならorと考えると、選び間違えにくくなります。
自縛を避ける(ステージ4-4)
ステージ4-4のマップとクリア条件
| クリア条件 | 内容 |
|---|---|
| アイテム | 7個ゲットする |
| ターン数 | 20ターン以内 |
黄色の枠が自分(Cool)のスタート位置です。すべての条件を同時に満たすとクリアです。
アイテムを取りに行った結果、自分の周りが両側からブロックで挟まれてしまうことがあります(第1部4章)。上にアイテムがあるとき、それを取っても大丈夫か、lookで先を見て確かめるプログラムです。
while True:
map_info = get_ready()
if map_info[Up] == Item:
# 上にアイテムがある場合、両脇がブロックかを確認
if map_info[UpRight] == Block and map_info[UpLeft] == Block:
look_info = look(Up)
map_info = get_ready()
if look_info[Center] == Block:
walk(Right) # 先がブロックなら迂回
else:
walk(Up) # 安全なら上へ
else:
walk(Up)
else:
walk(Right)
「上にアイテムがあるか」→「両脇(UpRight・UpLeft)がブロックか」→「lookで、先(look_info[Center]、2マス先)がブロックか」と、ifを入れ子にして、1つずつ確かめています。lookは1回の行動なので、その直後にget_ready()を呼んで、次のターンの情報を受け取ってから、walkしています。
インデントは3段階になりますが、1段ずつ「何を確認しているか」を読み解けば、難しい構造ではありません。
注意したいこと
search_info[9]と書く:番号は0〜8です。9番目は存在せず、エラーになります。lookの結果を、番号で取り出そうとする:look_info[0]のような書き方はできません。lookの結果は、方角の名前で取り出します。look_info[UpLeft]を、自分から見た左上と取り違える:探査の中心から見た左上です。inと==を混同する:「特定の位置と等しいか」は==、「リストのどこかに含まれているか」はinです。andとorを取り違える:日本語の条件文を先に書き出してから、どちらか判断しましょう。ifの入れ子が深くなって、対応するelseが分からなくなる:同じ深さのifとelse(elif)を、ペアで目で追いましょう。
演習問題
- 右方向に9マス先までsearchしたとき、「3マス先」の情報を取り出すコードを書いてみましょう。
look(Up)を実行したとき、look_info[UpLeft]は、自分から見てどの位置のマスの情報ですか。Item in search_infoという条件を、search_infoの9個の要素すべてをorでつないだ形で書き直してみましょう(実際にはこう書く必要はありませんが、inが何をしているかの理解に役立ちます)。- 4-4のコードで、
andをorに変えてしまうと、判定の意味はどう変わりますか。 - チュートリアルのステージ4-1を、Pythonで解いてみましょう。