CHaser解説(Python編)

第2部 Python編

3章 探査と、行動の決め方

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のマップとクリア条件

ステージ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のマップとクリア条件

ステージ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)を、ペアで目で追いましょう。

演習問題

  1. 右方向に9マス先までsearchしたとき、「3マス先」の情報を取り出すコードを書いてみましょう。
  2. look(Up)を実行したとき、look_info[UpLeft]は、自分から見てどの位置のマスの情報ですか。
  3. Item in search_infoという条件を、search_infoの9個の要素すべてをorでつないだ形で書き直してみましょう(実際にはこう書く必要はありませんが、inが何をしているかの理解に役立ちます)。
  4. 4-4のコードで、andをorに変えてしまうと、判定の意味はどう変わりますか。
  5. チュートリアルのステージ4-1を、Pythonで解いてみましょう。

YouTubeチャンネル

U-15プロコン舞鶴のYouTubeチャンネルを開設しました。大会の情報などを発信しています。 チャンネルを見る