CHaser解説(Python編)

資料

チュートリアル解答例集

⚠️ ネタバレ注意

このページには全ステージの解答例が掲載されています。自分で組んだプログラムと見比べたいときに開いてください。

第2部では、いくつかの代表的なステージだけを取り上げて、Pythonのコードを紹介しました。この付録では、チュートリアルのステージ1〜8(1-1〜8-3、全37ステージ)について、実際にクリアしたときのPythonのコードを1つずつ掲載します。

ステージ9(9-1〜9-3)は、解答例を載せていません。 ここまでの力で、自分で組み立てるステージだからです(入門編の付録2も同じです)。

どのコードも「これが唯一の正解」というわけではなく、あくまで一例です。動きが同じなら、書き方が違っても間違いではありません。自分のコードと見比べて、「同じ考え方で書けているか」「もっと短く書けそうか」を確認する材料として使ってください。

  • コードは、チュートリアルのPython画面(/tutorial-python)に、そのまま入力して試せる書き方です。命令はplayer.を付けずに書きます(本番の対戦ではplayer.が必要です)。
  • 実機のチュートリアルで、すべてクリアできることを確かめています(乱数のあるステージは、複数回試しました)。
  • ブロックの解答例は、入門編の付録2にあります。

ステージ1 移動

本文第2部1章に対応します。上下左右への移動(walk)を並べる練習です。

1-1 キャラクターを移動させよう1

解答例を見る

考え方:アイテムは、真上に2マス先 → 1回の移動で、進めるのは1マス → だから、「上へ移動」を2回

walk(Up)
walk(Up)

1-2 キャラクターを移動させよう2

解答例を見る

考え方:アイテムは、右に2マス先 → 向きだけが、1-1とちがう → 「右へ移動」を2回

walk(Right)
walk(Right)

1-3 いろいろな方法に移動させよう

解答例を見る

考え方:縦に2マス、横に4マス → 縦と横を、分けて数える → 動きを順に並べる(合計6回)

walk(Up)
walk(Up)
walk(Right)
walk(Right)
walk(Right)
walk(Right)

1-4 移動の順序を考えよう

解答例を見る

考え方:まっすぐ行くと、ブロックにぶつかる → 通れる道を、先に決める → 道のとおりに、向きごとに数える → 右1 → 上2 → 右4 の順に並べる

walk(Right)
walk(Up)
walk(Up)
walk(Right)
walk(Right)
walk(Right)
walk(Right)

ブロックにぶつからない道を先に決めて、その順番どおりに書きます。

ステージ2 繰り返し

本文第2部1章に対応します。forで、同じ動きをまとめます。

2-1 繰り返しを使おう

解答例を見る

考え方:同じ向きに4マス進む → 1回ずつ書かず、繰り返しでまとめる → 「4回繰り返す」の中に「右へ移動」

for i in range(4):
    walk(Right)

繰り返したい命令は、forの次の行から字下げして書きます。

2-2 繰り返しをつなげよう

解答例を見る

考え方:向きが変わる区間が、3つある → 区間ごとに、繰り返しを1つ使う → 3つを、上から順につなげる

for i in range(4):
    walk(Down)
for i in range(4):
    walk(Right)
for i in range(4):
    walk(Up)

2-3 繰り返すところを考えよう1

解答例を見る

考え方:右に6マス、そのあと上に1マス → 繰り返すのは「右」だけ → 上への1回は、繰り返しの外に置く

for i in range(6):
    walk(Right)
walk(Up)

2-4 繰り返すところを考えよう2

解答例を見る

考え方:「右・下」の2手が、くり返されている → 間に「下」が1回だけ入る → 「繰り返し → 下1回 → 繰り返し」の3部構成

for i in range(3):
    walk(Right)
    walk(Down)
walk(Down)
for i in range(3):
    walk(Right)
    walk(Down)

2-5 繰り返しを繰り返そう

解答例を見る

考え方:「右2・上2」が、ひと組 → ひと組を、2回くり返す → 内側で右2回と上2回、外側でひと組を2回

for i in range(2):
    for j in range(2):
        walk(Right)
    for j in range(2):
        walk(Up)

内側のforと外側のforで、変数の名前を変えています。

ステージ3 フィールド情報と条件分岐

本文第2部2章に対応します。map_infoで周りを見て、if・elif・elseで動きを選びます。

3-1 周りを見て行動しよう1

解答例を見る

考え方:アイテムは上方向。途中にブロックがある → 上がブロックなら右、そうでなければ上 → 毎ターン判断するので、ずっと繰り返す

while True:
    map_info = get_ready()
    if map_info[Up] == Block:
        walk(Right)
    else:
        walk(Up)

3-2 周りを見て行動しよう2

解答例を見る

考え方:状況は3つ:左がアイテム/上がブロック/それ以外 → アイテムを逃さないよう、左を先に判定 → 条件は上から調べ、最初に成り立つものだけ実行

while True:
    map_info = get_ready()
    if map_info[Left] == Item:
        walk(Left)
    elif map_info[Up] == Block:
        walk(Right)
    else:
        walk(Up)

条件は上から順に調べるので、アイテムを逃さないよう、左のアイテムを先に判定しています。

3-3 アイテムをたどってみよう

解答例を見る

考え方:8方向を見て、アイテムの方向を判断 → 上下左右は1回、斜めは2手で取る → どれもなければ下へ進み、下の斜めも確認

while True:
    map_info = get_ready()
    if map_info[Up] == Item:
        walk(Up)
    elif map_info[Left] == Item:
        walk(Left)
    elif map_info[Right] == Item:
        walk(Right)
    elif map_info[UpRight] == Item:
        walk(Up)
        walk(Right)
    elif map_info[UpLeft] == Item:
        walk(Up)
        walk(Left)
    else:
        walk(Down)
        if map_info[DownRight] == Item:
            walk(Right)
        elif map_info[DownLeft] == Item:
            walk(Left)

3-4 周りだけじゃわからない?

解答例を見る

考え方:隣だけ見ても、正解が分からない場所がある → 最初の動きは、先に決めて書いておく → そのあとは「下がブロックなら左、そうでなければ下」

walk(Up)
walk(Up)
walk(Up)
walk(Up)
walk(Left)
while True:
    map_info = get_ready()
    if map_info[Down] == Block:
        walk(Left)
    else:
        walk(Down)

3-5 ジグザグの迷路を進もう

解答例を見る

考え方:通れる道に、アイテムが並んでいる → 上・下・左・右のアイテムを順に判定 → アイテムの向きに進めば、迷路を自然にたどれる

while True:
    map_info = get_ready()
    if map_info[Up] == Item:
        walk(Up)
    elif map_info[Down] == Item:
        walk(Down)
    elif map_info[Left] == Item:
        walk(Left)
    elif map_info[Right] == Item:
        walk(Right)

3-6 市松模様の壁を進もう

解答例を見る

考え方:アイテムは、左右の壁ぎわに現れる → 右 → 左 の順に調べ、なければ下へ → ターン数が短い。むだな動きは、しない

while True:
    map_info = get_ready()
    if map_info[Right] == Item:
        walk(Right)
    elif map_info[Left] == Item:
        walk(Left)
    else:
        walk(Down)

ステージ4 探査(look・search)の活用

本文第2部3章に対応します。lookとsearchで、遠くを調べてから動きます。

4-1 遠くを見てみよう

解答例を見る

考え方:アイテムは、上下左右のどこか1か所 → 4方向を順に search(遠方探査)する → 見つかった方向を覚えて、進み続ける

for direction in (Up, Down, Left, Right):
    search_info = search(direction)
    if Item in search_info:
        break
while True:
    walk(direction)

forで4方向を順にsearchし、アイテムが見つかった時点でbreakします。そのあと、directionに入ったままの方向へ進み続けます。

4-2 情報をリストにまとめよう

解答例を見る

考え方:4-1と同じ:アイテムの方向を探す → search の結果は、9マスぶんのリスト → 「リストに含まれるか」で、位置は気にしない

for direction in (Up, Down, Left, Right):
    search_info = search(direction)
    if Item in search_info:
        break
while True:
    walk(direction)

4-3 自分の位置を確かめよう

解答例を見る

考え方:スタート位置は、毎回ちがう → 上を search。壁が見えれば、上半分にいる → 右を search して、見えるまで縦に進む → 見えたら、右へ進み続ける

search_info = search(Up)
if Block in search_info:
    vertical = Down
else:
    vertical = Up
while True:
    search_info = search(Right)
    if Item in search_info:
        break
    walk(vertical)
while True:
    walk(Right)

4-4 自縛を回避しよう

解答例を見る

考え方:上にアイテムがあるときだけ、危険を確認 → 左上・右上がブロックなら、挟まれる恐れ → look で2マス先を見て、ブロックなら右へ迂回 → 安全なら、そのまま取る

while True:
    map_info = get_ready()
    if map_info[Up] == Item:
        if map_info[UpLeft] == Block and map_info[UpRight] == 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)

lookは1回の行動なので、直後にget_ready()で情報を取り直してからwalkしています。

4-5 斜め方向をlookで見つけよう

解答例を見る

考え方:アイテムは、斜め方向にある → 上・右・下・左の順に look(近隣探査) → 見つかった向きと位置から、3手を決める

look_info = look(Up)
if look_info[Right] == Item:
    walk(Up)
    walk(Up)
    walk(Right)
else:
    look_info = look(Right)
    if look_info[Up] == Item:
        walk(Right)
        walk(Right)
        walk(Up)
    else:
        look_info = look(Down)
        if look_info[Left] == Item:
            walk(Down)
            walk(Down)
            walk(Left)
        else:
            walk(Left)
            walk(Left)
            walk(Down)

4-6 近くはlook、遠くはsearch

解答例を見る

考え方:近くは look、遠くは search → look で4方向を調べ、近くのアイテムを取る → なければ search で、一直線を調べる → 見つかった方向へ、進み続ける

found = False
for direction, side, path in (
        (Up, Right, (Up, Up, Right)),
        (Right, Down, (Right, Right, Down)),
        (Down, Left, (Down, Down, Left)),
        (Left, Up, (Left, Left, Up))):
    look_info = look(direction)
    if look_info[side] == Item:
        for step in path:
            walk(step)
        found = True
        break
if not found:
    for direction in (Up, Down, Right, Left):
        search_info = search(direction)
        if Item in search_info:
            break
    while True:
        walk(direction)

ステージ5 ターンの仕組み

本文第2部4章に対応します。CPUがいるステージです。毎ターンget_ready()で、自分の番を待ちます。

5-1 自分の番を待ってみよう1

解答例を見る

考え方:CPUがいるので、毎ターン自分の番を待つ → get_ready()してから、動く → ずっと繰り返す

while True:
    map_info = get_ready()
    walk(Right)

5-2 自分の番を待ってみよう2

解答例を見る

考え方:最初に、上へ1マス → アイテムが上下にあれば、2マス続けて進む → 1手動くたびに、get_ready()

get_ready()
walk(Up)
while True:
    map_info = get_ready()
    if map_info[Down] == Item:
        walk(Down)
        get_ready()
        walk(Down)
    elif map_info[Up] == Item:
        walk(Up)
        get_ready()
        walk(Up)
    else:
        walk(Right)

5-3 たくさんアイテムをゲットしよう

解答例を見る

考え方:アイテムが50個ある → 上・下・左にアイテムがあれば、取りに行く → どれもなければ、右へ進む

while True:
    map_info = get_ready()
    if map_info[Up] == Item:
        walk(Up)
    elif map_info[Down] == Item:
        walk(Down)
    elif map_info[Left] == Item:
        walk(Left)
    else:
        walk(Right)

5-4 CPUとアイテムを取り合おう

解答例を見る

考え方:アイテムの場所は決まっている → CPUと取り合うので、最短の道を決めておく → 1手ごとにget_ready()してから動く

for direction in (Right, Right, Down, Down, Right, Up, Up, Up, Up, Left):
    get_ready()
    walk(direction)

アイテムの場所が決まっているステージなので、道順を先に決めておきます。ただし、CPUに先に取られると失敗することがあります(実機で15回中13回クリア)。

5-5 番を待ちつつ遠くを探そう

解答例を見る

考え方:アイテムは、上下左右のどこか → 毎回get_ready()してから、search する → 見つかった方向へ、進み続ける

for direction in (Up, Down, Left, Right):
    get_ready()
    search_info = search(direction)
    if Item in search_info:
        break
while True:
    get_ready()
    walk(direction)

ステージ6 ブロック設置とアタック

本文第2部4章に対応します。putで相手にブロックを置いて勝ちます。

6-1 相手にブロックを置いてみよう1

解答例を見る

考え方:相手(CPU)は、動かない → 右にいたら、ブロックを置く → いなければ、右へ進む

while True:
    map_info = get_ready()
    if map_info[Right] == Enemy:
        put(Right)
    else:
        walk(Right)

6-2 広いマップでブロックを置こう

解答例を見る

考え方:相手が、ランダムに動く広いマップ → 最初に1個ブロックを置き、距離の偶奇をずらす → 隣に相手がいたら置く → いなければ、右へ進む

map_info = get_ready()
put(Left)
while True:
    map_info = get_ready()
    if map_info[Up] == Enemy:
        put(Up)
    elif map_info[Down] == Enemy:
        put(Down)
    elif map_info[Right] == Enemy:
        put(Right)
    else:
        walk(Right)

先頭のput(Left)は、距離の偶奇をずらすためのものです(第2部4章)。これがないとクリアできません。

6-3 相手にブロックを置いてみよう2

解答例を見る

考え方:相手は下にいる。右にブロックがある → 下にいたら、ブロックを置く → 右がふさがれたら、上か下へよける → そうでなければ、右へ進む

while True:
    map_info = get_ready()
    if map_info[Down] == Enemy:
        put(Down)
    elif map_info[Right] == Block:
        if map_info[Up] == Block:
            walk(Down)
        else:
            walk(Up)
    else:
        walk(Right)

6-4 動く相手にブロックを置いてみよう

解答例を見る

考え方:動く相手:毎ターン、位置が変わる → 最初に1個置いて、距離の偶奇をずらす → 4方向を順に見張り、相手がいたら置く → ふさがった向きは、次の向きに切り替える

directions = [Down, Right, Up, Left]
i = 0
map_info = get_ready()
put(Up)
while True:
    map_info = get_ready()
    while i < 3 and map_info[directions[i]] not in (Enemy, Floor):
        i += 1
    d = directions[i]
    if map_info[d] == Enemy:
        put(d)
    elif map_info[d] == Floor:
        walk(d)
    else:
        walk(Right)

先頭のput(Up)は、距離の偶奇をずらすためのものです(第2部4章)。そのあと、見張る方向を順に切り替えます。

ステージ7 変数と関数

本文第2部5章に対応します。変数に覚えさせ、関数(def)に処理をまとめます。

7-1 変数を使ってみよう

解答例を見る

考え方:アイテムの方向を、変数に覚えておく → 4方向を search し、見つけたら変数に入れる → ずっと、変数の方向へ進む

move_dir = Right
for direction in (Up, Down, Left, Right):
    search_info = search(direction)
    if Item in search_info:
        move_dir = direction
        break
while True:
    walk(move_dir)

7-2 関数を使ってみよう

解答例を見る

考え方:処理を、2つの関数に分ける → アイテムゲット:隣にあれば取る(結果を返す) → ランダム移動:進める向きから、ランダムに選ぶ → メイン:取れなければ、ランダム移動

import random


def get_item():
    for direction in (Up, Down, Left, Right):
        if map_info[direction] == Item:
            walk(direction)
            return True
    return False


def random_move():
    free = []
    for direction in (Up, Down, Left, Right):
        if map_info[direction] != Block:
            free.append(direction)
    if free:
        walk(random.choice(free))


while True:
    map_info = get_ready()
    if not get_item():
        random_move()

関数は、defの行の下に、字下げして処理を書きます。map_infoは関数の外で作った変数ですが、関数の中から読めます。

7-3 関数を使ってみよう2

解答例を見る

考え方:アイテム方向:アイテムのある向きを返す → ランダム方向:進める向きからランダムに選ぶ → メイン:向きが決まらなければ、ランダム

import random


def random_move():
    free = []
    for direction in (Up, Down, Left, Right):
        if map_info[direction] != Block:
            free.append(direction)
    if free:
        walk(random.choice(free))


def item_direction():
    for direction in (Up, Down, Left, Right):
        if map_info[direction] == Item:
            return direction
    return None


while True:
    map_info = get_ready()
    direction = item_direction()
    if direction is None:
        random_move()
    else:
        walk(direction)

ステージ8 総合演習

本文第2部6章に対応します。これまでの道具を組み合わせる、チュートリアルの集大成です。

8-1 障害物を回避しよう

解答例を見る

考え方:ブロックがランダムに置かれている → 進行方向を決め、ふさがったら左へよける → よけた回数を、変数に覚えておく → 安全になったら、右へ戻る

search_info = search(Up)
if Item in search_info:
    forward = Up
else:
    forward = Down
side = 0
dodging = False
while True:
    map_info = get_ready()
    if side > 0 and map_info[Right] != Block and not dodging:
        walk(Right)
        side -= 1
    elif map_info[forward] != Block:
        walk(forward)
        dodging = False
    else:
        walk(Left)
        side += 1
        dodging = True

8-2 行き止まりを回避しよう

解答例を見る

考え方:アイテムは、4つの行き止まりのどれか → search で、進む向きを決める → ふさがったら、よける向きへ進む → 行き止まりなら、よける向きを反対にする

for forward in (Up, Down, Left, Right):
    search_info = search(forward)
    if Item in search_info:
        break
back = {Up: Down, Down: Up, Left: Right, Right: Left}[forward]
if forward in (Up, Down):
    left_or_up, right_or_down = Left, Right
else:
    left_or_up, right_or_down = Up, Down
slide = left_or_up
pos = 0
depth = 0
bad = []
fresh = False
while True:
    map_info = get_ready()
    if depth > 0 and map_info[forward] == Block and map_info[left_or_up] == Block and map_info[right_or_down] == Block:
        bad.append(pos)
        for i in range(depth):
            walk(back)
        depth = 0
    elif map_info[forward] == Block or (pos in bad and depth == 0):
        if map_info[slide] == Block:
            if slide == left_or_up:
                slide = right_or_down
            else:
                slide = left_or_up
        walk(slide)
        if slide == left_or_up:
            pos += 1
        else:
            pos -= 1
        fresh = True
    elif fresh:
        walk(forward)
        depth += 1
        fresh = False
    elif pos > 0 and map_info[right_or_down] != Block:
        walk(right_or_down)
        pos -= 1
        depth = 0
    elif pos < 0 and map_info[left_or_up] != Block:
        walk(left_or_up)
        pos += 1
        depth = 0
    else:
        walk(forward)
        if pos != 0:
            depth += 1
    if pos == 0:
        bad = []

進む向き(forward)がふさがったら、横(slide)へよけます。よけた量(pos)を覚えておいて、障害物を通り過ぎたら元の線に戻ります。行き止まりに入ったら、その位置(pos)をbadに覚えて、別のすき間を探します。

8-3 見つけた情報を記録しよう

解答例を見る

考え方:自分の座標を、動くたびに更新する → 4方向を search し、アイテムの座標をメモする → いちばん近いメモへ向かう → 着いたらメモから消す。逆戻りはしない

x = 0
y = 0
memo = []
before = None
opposite = {Up: Down, Down: Up, Left: Right, Right: Left}
step = {Up: (0, -1), Down: (0, 1), Left: (-1, 0), Right: (1, 0)}
while True:
    for direction in (Up, Right, Down, Left):
        search_info = search(direction)
        dx, dy = step[direction]
        for k in range(1, 10):
            cell = search_info[k - 1]
            if cell == Block:
                break
            if cell == Item and (x + dx * k, y + dy * k) not in memo:
                memo.append((x + dx * k, y + dy * k))
    map_info = get_ready()
    choice = None
    if memo:
        memo.sort(key=lambda p: abs(p[0] - x) + abs(p[1] - y))
        tx, ty = memo[0]
        if ty > y and map_info[Down] != Block and before != Up:
            choice = Down
        elif ty < y and map_info[Up] != Block and before != Down:
            choice = Up
        elif tx > x and map_info[Right] != Block and before != Left:
            choice = Right
        elif tx < x and map_info[Left] != Block and before != Right:
            choice = Left
    if choice is None:
        for direction in (Up, Right, Down, Left):
            if map_info[direction] != Block and before != opposite[direction]:
                choice = direction
                break
    walk(choice)
    before = choice
    dx, dy = step[choice]
    x += dx
    y += dy
    if (x, y) in memo:
        memo.remove((x, y))

自分の座標(x, y)を、動くたびに更新します。searchで見つけたアイテムの座標をmemoに記録して、いちばん近いものへ向かいます。着いたら、memoから消します。beforeは直前の向きで、すぐに引き返さないために使います。


ステージ1から8まで、こうして見比べてみると、使っている道具は、移動(walk)・繰り返し(for・while)・条件分岐(if)・探査(look・search)・変数・関数という、本文で学んだ数種類だけです。ステージが進むにつれてコードが長くなるのは、道具の種類が増えているからではなく、その道具をどう組み合わせるかの工夫が積み重なっているからです。行き詰まったときは、長いコードを一度に理解しようとせず、「これはどの道具とどの道具の組み合わせでできているか」を1つずつ分解して読んでみてください。

YouTubeチャンネル

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