CHaser解説(Python編)

資料

演習・確認クイズの解答

⚠️ ネタバレ注意

このページには第1部・第2部・第3部の演習問題・確認クイズの解答が含まれています。

各部末の「確認クイズ」と、各章末の「演習問題」の解答をまとめました。

ここに載せているのはあくまで解答の一例です。とくにプログラムを書く問題は、同じ動きをする書き方がいくつもあります。自分の答えと違っていても、動きが同じであれば間違いではありません。「なぜその答えになるのか」の部分を読んで、考え方が合っていたかを確かめてください。

「自分で考えて書き出す」タイプの問題には、解答の代わりに確認のポイントを示しています。


第1部 確認クイズの解答

1章 CHaserとは

  1. 直接操作できません。試合が始まったら、事前に書いたプログラムだけがキャラクターを動かします。
  2. 相手より多くアイテムを集めることです(ただし、アタック勝ち・閉じ込め勝ちという特別勝利条件もあります)。

2章 フィールドの構成要素

  1. プレイヤー(Cool)、プレイヤー(Hot)、アイテム、ブロックの4種類です。
  2. アイテムのマスは通れて、通ると取得できます。ブロックのマスは通れません(移動しようとすると衝突して敗北します)。

3章 プレイヤーの行動

  1. 2種類です(移動と、ブロック設置)。
  2. 行えません。どちらも上下左右の4方向だけです。斜めに進みたいときは「上に1マス、右に1マス」のように2手に分けます。

4章 アイテム取得とブロック出現

  1. そのマスにブロックが出現します。
  2. 自分の周りがブロックだらけになり、身動きが取れなくなって「自縛」で敗北する危険があります。

5章 勝利条件

  1. 2種類で、「アタック勝ち」と「閉じ込め勝ち」です(それぞれ相手側から見ると「衝突」「自縛」という敗北になります)。
  2. アタック勝ちは、相手がいるマスにブロックを設置したときに起こります。衝突は、自分がブロックのあるマスに移動しようとしたときに起こります。

6章 Pythonを書く場所

  1. ステージ選択画面の右上にある「ブロック/Python」の切り替えを、「Python」にしてから、ステージを選びます。
  2. ブロックをPythonに書き直したときの形を、表示することだけができます。コードを書いたり、編集したりはできません。

第2部 演習問題の解答

0章 Pythonの基礎と、CHaserの書き方

  1. 次のとおりです。
walk(Up)
walk(Up)

命令は、player.を付けず、名前だけで書きます(player.walk(Up)と書くと、エラーになります)。 2. 確認のポイント:上がブロックでない間は、上へ進み続けます。上がブロックになると、右へ1マス進みます。次のターンには、また上を確認して、上がブロックでなければ上へ進みます。「毎ターン、get_ready()で最新の情報を受け取ってから、判断している」ことに気づけていれば十分です。 3. 上がブロックのときだけplayer.walk(Up)を実行し、そうでないときは右へ進むようになります。つまり、ブロックのマスへ、自分から動こうとして衝突し、負けてしまいます。!=(等しくない)と==(等しい)を取り違えると、動きが逆になる、という例です。

1章 移動と繰り返し

  1. 次のとおりです。
walk(Left)
walk(Left)
walk(Down)
walk(Left)
walk(Left)
  1. 次のように書けます(変数名は自由です)。
for count in range(3):
    for count2 in range(3):
        walk(Right)
    walk(Down)

内側のforで右に3マス進み、そのあと下に1マス進む、というセットを、外側のforが3回繰り返します。

  1. 右に6回、上に6回移動します(内側がそれぞれ2回、外側が3回なので、2×3=6)。

2章 情報の読み取りと条件分岐

  1. 次のとおりです。
if map_info[Right] == Item:
    walk(Right)
elif map_info[Down] == Block:
    walk(Up)
else:
    walk(Down)
  1. 「左にアイテムがあり、かつ上もブロック」という場合に結果が変わります。1行目と2行目を入れ替えると、map_info[Up] == Blockが先に成り立ってしまい、walk(Right)が実行されて、左のアイテムを取り逃します。

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

  1. search_info[2]です(0番目が1マス先なので、3マス先は2番)。
search_info = search(Right)
search_info[2]   # 3マス先
  1. look_info[UpLeft]は、探査の中心から見て、左上のマスの情報です。look(Up)なら、中心は自分の2マス先なので、UpLeftは「自分から見て、上に3マス、左に1マス」のマスです。自分から見た左上ではない点に注意してください。
  2. 次のようになります。
if (search_info[0] == Item or search_info[1] == Item or search_info[2] == Item
    or search_info[3] == Item or search_info[4] == Item or search_info[5] == Item
    or search_info[6] == Item or search_info[7] == Item or search_info[8] == Item):

inは、この9個の比較をorでつないだのと同じことを、たった1つの書き方で済ませてくれている、ということが分かります。

  1. andをorに変えると、「右上か左上のどちらか一方でもブロックなら危険」という判定になってしまいます。片側だけふさがっていても、実際には挟まれないので、危険でない場面まで迂回するようになり、取れるはずのアイテムを取り逃すことが増えます。

  2. 「上下左右を順に調べる」の節のコードが、答えの一例です。forで4方向を順にsearchし、Item in search_infoが成り立ったらbreakで終わり、あとのwhile True:で、そのdirectionへ進みます。

4章 ターンの仕組みと、攻撃

  1. 次の2つを見比べてください。
# 外に1回だけ書いた場合(よくない例)
map_info = get_ready()
while True:
    if map_info[Right] == Enemy:
        put(Right)
    else:
        walk(Right)
# 中に毎回書いた場合(基本の形)
while True:
    map_info = get_ready()
    if map_info[Right] == Enemy:
        put(Right)
    else:
        walk(Right)

前者はmap_infoが最初の1回から更新されないため、相手が右隣に来ても気づけず、ずっと同じ判断を繰り返します。後者は、毎ターン最新の情報で判断できます。

  1. 次のとおりです。
while True:
    map_info = get_ready()
    if map_info[Right] == Enemy:
        put(Right)
  1. 4章の「同じ動きを、短く書く」のコードが、答えの一例です。見張る方角をdirections = [Down, Right, Up, Left]とリストにし、番号iで、いま見張っている方角を覚えます。ふさがった方角ではiを進めます(減らしません)。

    ちなみに、for direction in (Down, Right, Up, Left):で、毎ターン下から順に調べ直す書き方も考えられますが、こちらは、いったんふさがった方角が空くと、前の方角に戻ってしまうので、動きが変わります。実機のチュートリアル(Python)で、この書き方を8回試すと、5回クリアできました。8回中8回クリアできた、番号iを使う書き方のほうが、元のコードに近い動きです。

  2. 不要です。初めの距離が奇数なら、移動だけを繰り返しても、距離は奇数のままなので、距離1で隣り合える可能性があります。先頭でputを入れると、かえって距離の偶奇が偶数にずれて、隣り合えなくなってしまいます。偶奇をずらすputが必要なのは、初めの距離が偶数のときだけです。

5章 変数と関数

  1. Item in search_infoは「あるか・ないか」だけを教えてくれます。first_index(search_info, Item)は、それに加えて「何番目にあるか」という位置まで教えてくれます。位置が分かると、「あと何マス進めば届くか」「相手より自分のほうが近いか」といった判断に使えます。ただし、first_indexが返す位置は、1から数えた番号(見つからなければ0)で、search_info[番号]の0から数える番号とは、ずれている点に注意が必要です。
  2. 一例です。
def item_directions():
    result = []
    for direction in (Up, Down, Left, Right):
        if map_info[direction] == Item:
            result.append(direction)
    return result

random_moveとの違いは、条件が「ブロックでない」から「アイテムである」に変わった点と、その場で移動せずに、リストを返すだけにした点です。 3. 変わりません。returnだけを書いても、return Noneと同じく、Noneが返ります。関数の最後までreturnに出会わなかったときも、Noneが返ります。

6章 総合演習

  1. 一例です。
item_count = 0
while True:
    map_info = get_ready()
    if item_count == 10:
        put(Right)          # 10個集めたら、特別な行動をする
    elif map_info[Up] == Item:
        walk(Up)
        item_count += 1            # 取ったら、数を1増やす
    else:
        walk(Right)

item_countを0から始め、アイテムを取った直後に1を足しているのがポイントです。数えるための変数は、繰り返しの外で0にしておかないと、毎ターン0に戻ってしまいます。また、1回の番で行動できるのは1回だけなので、if・elif・elseにして、どれか1つだけが実行されるようにしています。

  1. 確認のポイント:うまくいかなかった原因が、「Pythonの書き方が分からなかった」のか、「そもそも手順を思いつかなかった」のかを、区別してみてください。前者なら、第2部の対応する章とBlockly⇔Python対応表を、後者なら、入門編の第2部の同じ内容の章を読み返すのが近道です。

第2部 確認クイズの解答

0章 Pythonの基礎と、CHaserの書き方

  1. チュートリアルのPython画面では、walk(Up)のように、名前だけで書きます。対戦用のPython画面では、player.walk(Up)のように、player.を付けて書きます。
  2. while True:の中と、自分で作る関数(def mainより上)です(対戦用の骨組みの場合)。
  3. direction = Rightは、「directionという変数にRightを入れる」という代入です。direction == Rightは、「directionの中身がRightと等しいかどうか」を調べる比較です。
  4. :(コロン)です。

1章 移動と繰り返し

  1. walk(Left)です。
  2. 0, 1, 2, 3という、4つの数字の並びを作ります。

2章 情報の読み取りと条件分岐

  1. 自分の左隣のマスに何があるか(Floor・Block・Item・Enemyのいずれか)を表します。
  2. ifはその条件が成り立ったとき、elifはそれより上の条件がすべて成り立たず、かつ自分の条件が成り立ったとき、elseはどの条件も成り立たなかったときに実行されます。

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

  1. lookの結果は、look_info[Up]のように方角の名前で、searchの結果は、search_info[0]のように番号で取り出します。
  2. 指定した方向の2マス先のマスの情報です(lookの範囲の、真ん中のマスです)。
  3. エラー(IndexError)になります。search_infoには、0番目から8番目までの9個しかなく、9番目は存在しないためです。

4章 ターンの仕組みと、攻撃

  1. 「自分の番が来るまで待つ」ことと、「番が来たときの、最新の周辺情報を受け取る」ことの2つです。
  2. 1回だけです。walk・put・look・searchは、どれも1回の行動で、使うとその番は終わります。
  3. 自分も相手も毎ターン1マスずつ動くので、2人の距離の偶奇は変わりません。勝つには、距離1(奇数)で隣り合う必要があるので、初めの距離が偶数だと、隣り合えません。勝つには、プログラムの先頭で、putを1回入れます。putは自分が動かないので、偶奇がずれます。

5章 変数と関数

  1. defです。
  2. free = []は、中身が空のリストを作っています。free.append(Up)は、そのリストの最後に、Upという値を追加しています。
  3. global 変数名のように、globalを書いてから、変数名を宣言します。書かないと、関数の中だけの別の変数として扱われ、外の変数は変わりません。

6章 総合演習

  1. =の右側のcount + 1が先に計算され、その結果が、左側のcountに代入されます。
  2. count += 1と書けます。

第3部 演習問題の解答

3章 やりたいことをプログラムにする

  1. 確認のポイント:3つの関数は、take_turnの中で呼ぶ関数名を差し替えるだけで切り替えられます。「どの選択肢が強いか」よりも、相手や場面によって結果が変わるかに注目してください。負けた試合では、そのとき敵がどこにいて、自分がどう動いたかを思い出して、原因を1つ書き出してみましょう。
  2. 一例です。待った回数を変数で数え、上限を超えたらFalseを返して、次の判断(アイテム取得や探索)に回します。
wait_count = 0    # 連続で待った回数

def wait_diagonal_enemy():
    global wait_count
    for diagonal in APPROACH:
        if map_info[diagonal] == Enemy:
            if wait_count >= 3:          # 3回待っても動かなければ、待つのをやめる
                wait_count = 0
                return False
            wait_count += 1
            player.look(APPROACH[diagonal][0])
            return True
    wait_count = 0                       # 斜めに敵がいなければ、数え直す
    return False

wait_countを関数の外で0にしておくのと、globalを書くのがポイントです。上限の数字を変えて、動きがどう変わるかも試してみましょう。

  1. <= 4を大きくすると、敵を見てからlookし続ける期間が長くなります。>= 3を小さくすると、lookの間隔が短くなります。どちらもlookを撃つ回数が増えます。lookは1ターンを使い、その間は移動もアイテム取得もできないので、撃ちすぎるとアイテムを集める速さが落ちます。
  2. 確認のポイント:数字を大きくすると遠くのアイテムまで追いかけるようになる一方、探索が進みにくくなることがあります。小さくすると、目の前のアイテムしか追わなくなります。どの数字のとき、自分の動きが「ちょうどよい」と感じたかを、理由といっしょに書き留めておきましょう。
  3. 一例です。探索の終点を「まだ見ていないマス」から「覚えているアイテムのマス」に変えます。memory.get(next_cell)の結果がItemならそこが目標です。まだ見ていないマス(None)は、通れるか分からないので、先へ広げないようにします。
                cell = memory.get(next_cell)
                if cell == Item:
                    return step                      # 覚えているアイテムに着いた
                if cell is not None and cell != Block:
                    first_dir[next_cell] = step      # 通れると分かっているマスだけ広げる
                    queue.append(next_cell)
  1. Noneです。(3, 1)がブロックだと、(2, 1)から右へは広がれません。回を追うと、1回目に(1, 2)と(2, 1)がqueueに入り、2回目の(1, 2)は行き止まり、3回目の(2, 1)は上下がブロック・右もブロックで、追加されるマスがありません。queueが空になってwhileが終わり、未知のマス(4, 1)にはたどり着けないので、return Noneになります。このときexploreは、random_walk()に切り替わります。
  2. 0です。(5, 3)から見て、上・下・右がブロックで、左の元のマスもアイテムを取るとブロックになるため、出口が1つもありません。min_exitsが2でも1でも0 >= min_exitsは成り立たないので、そのアイテムは取られません。

第3部 確認クイズの解答

1章 Pythonの画面の使い方

  1. while True:の中と、自分で作る関数(def mainより上)です。importの行や、mainの定義、if __name__ == "__main__":以降は、そのままにしておきます。
  2. 出力欄です。エラーメッセージが出ていないか、出ていたら、どの行で、どんな種類のエラーが起きたかを確認します。

2章 対戦ルームの使い方

  1. VS対戦(人と人との対戦)と、CPU対戦(コンピューターが操作する相手との対戦)の2種類です。
  2. Pythonの画面で「ダウンロード」した、Pythonのファイル(.py)を、対戦ルームの「ファイルを選択」ボタンから読み込みます。

3章 やりたいことをプログラムにする

  1. ①やりたいことを1文で書く、②あいまいな言葉を数えられるものに直す、③日本語のまま小さな手順に分ける、④手順を1つずつコードの部品に置き換える、の4つです。
  2. 何を数えたり比べたりすれば言えるのかを考え、数字の比較に直します。たとえば「最近敵を見た」は、「敵を最後に見てから何ターンたったか」を数えて、turn_counter - last_enemy_turn <= 4のような式にします。
  3. returnは、その関数をそこで終わらせる命令だからです。if 判断():の中でreturnすると、それより下に書いた判断は実行されません。そのため、上に書いた判断ほど先に実行され、優先されます。
  4. 指定した方向の2マス先のマスの情報です。lookは、指定した方向の2マス先を中心とする周囲9マスを返し、look_info[Center]はその真ん中のマスを表します。
  5. global my_xのように、globalを書いてから変数名を宣言します。書かないと、関数の中だけの別の変数として扱われ、外のmy_xは変わりません。

YouTubeチャンネル

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