CHaser解説(Python編)

第2部 Python編

5章 変数と関数

5章 変数と関数

入門編の第2部9章(ステージ7)に対応します。

CHaserのしくみの要点

  • CHaserのプログラムは、前のターンに見たことを、勝手には覚えていません。map_infoも、毎ターン新しく作り直されます。あとで使いたい情報は、変数に自分で入れておきます。
  • プログラムが長くなると、判断が何行にもなって、読みにくくなります。1つの判断を、名前を付けた関数にまとめると、全体が短く読みやすくなります。

変数に、決めた方針を覚えさせる(ステージ7-1)

ステージ7-1のマップとクリア条件

ステージ7-1のマップ
ステージ7-1のマップ
クリア条件 内容
アイテム 1個ゲットする
ターン数 100ターン以内

黄色の枠が自分(Cool)のスタート位置です。すべての条件を同時に満たすとクリアです。アイテムの位置は毎回変わります(マップの半透明の表示は、出現位置の候補です)。

「右方向をsearchして、アイテムがあれば右、なければ左へ、ずっと進む」というプログラムを、変数で書いてみます。

map_info = get_ready()
search_info = search(Right)
if Item in search_info:
    direction = Right
else:
    direction = Left

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

最初に1回だけ探査して、その結果を変数directionに入れます。あとのwhile True:の中では、判断をやり直さずに、directionの中身の方向へ、ずっと歩き続けます。変数は、「値を入れておく箱」と考えると分かりやすくなります。箱の中身は、別の値を代入するまで、そのまま保たれます。

3章の4-1と比べると、ifの中にwhile True:が2つあった形が、1つのwhile True:にまとまっています。「方針を決める」部分と「動く」部分を分けると、プログラムが読みやすくなります。

変数の値を更新する

変数の値を、今の値に1を足したものに置き換えるには、次のように書きます。

count = count + 1
count += 1          # 同じ意味の、短い書き方

=の右側が先に計算され、その結果が左の変数に代入されます。「回数を数える」ときに使います。

Blocklyから変換されたfirst_indexの定義

3章で触れたfirst_indexは、次のような関数です。Blocklyから変換されたコードには、この関数の定義が含まれています。

def first_index(my_list, elem):
  try: index = my_list.index(elem) + 1
  except: index = 0
  return index

大事なのは、最後の+ 1と、見つからなかったときの0です。同じsearch_infoでも、数え方が2種類あるので、注意してください。

書き方 数え方 隣のマス いちばん遠いマス 見つからないとき
search_info[番号] 0から数える search_info[0] search_info[8] —
first_index(search_info, Item) 1から数える 1 9 0

first_indexが「見つからない」を0で表すために、位置のほうを1からにずらしている、と考えると筋が通ります。first_indexの結果をsearch_info[ ]の番号に使うなら、1を引きます。

関数にまとめる(ステージ7-2)

ステージ7-2のマップとクリア条件

ステージ7-2のマップ
ステージ7-2のマップ
クリア条件 内容
アイテム 10個ゲットする
ターン数 100ターン以内

黄色の枠が自分(Cool)のスタート位置です。すべての条件を同時に満たすとクリアです。

Pythonでは、関数をdef(define:定義する)で作ります。「上下左右にアイテムがあれば取りに行く」判断を、関数にしてみます。

def get_item():
    """上下左右にアイテムがあれば取りに行って True、なければ False を返す"""
    for direction in (Up, Down, Left, Right):
        if map_info[direction] == Item:
            walk(direction)
            return True
    return False

def get_item():が関数の始まりで、インデントされた範囲が中身です。かっこの中には、受け取りたい値(引数)を書きます(この関数は、受け取る値がないので空です)。returnは、その関数をそこで終わらせて、値を呼び出し元に返す命令です。ここでは、アイテムを見つけて取りに行けたらTrue、どこにもなければFalseを返します。

もう1つ、「ブロックのない方向を、ランダムに選んで歩く」関数です。

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))

free = []は、中身が空のリストです。ブロックでない方向を、.append(方向)で1つずつ加えていきます。random.choice(リスト)は、リストの中から、ランダムに1つ選ぶ命令です(チュートリアルの画面では、先頭にimport randomが必要です。本番用の骨組みでは、from lib.pyCHaser import *で使えるので、書かなくても動きます)。if free:は、リストが空でないときだけ、という意味です。

メインの処理は、この2つの関数を呼び出すだけになります。

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

「アイテムを取りに行けなかったときだけ、ランダムに歩く」と、日本語のように読めます。それぞれの関数の中身がどれだけ複雑でも、メインの処理は短く読めるのが、関数を使う最大の利点です。

戻り値を、行動ではなく「答え」にする(ステージ7-3)

ステージ7-3のマップとクリア条件

ステージ7-3のマップ
ステージ7-3のマップ
クリア条件 内容
アイテム 10個ゲットする
ターン数 100ターン以内

黄色の枠が自分(Cool)のスタート位置です。すべての条件を同時に満たすとクリアです。

get_itemは、判断と移動を、1つの関数の中で行いました。関数の役割を「進む方向を答えるだけ」にすると、もっと使い回しがききます。

def item_direction():
    """アイテムのある方向を返す。なければ None"""
    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)

Noneは、「該当なし」を表す、Pythonの特別な値です。値がNoneかどうかは、is Noneで調べます。判断(どの方向か)と行動(歩く)を分けておくと、方向の答えを、歩く以外(たとえば、ブロックを置く)にも使えます。

関数の外の変数

関数の中から、関数の外の変数(map_infoなど)は、読むだけなら、そのまま使えます。関数の中で、外の変数に新しい値を代入するときは、関数の先頭で、global 変数名と宣言する必要があります(第3部3章の例2で、初めて使います)。

注意したいこと

  • defの行末の:を忘れる:ifやwhileと同じく、def 関数名():にも:が必要です。
  • returnがないと、値は返らない:値を答えにしたい関数には、returnで返す値を指定します。何も返さない関数はNoneが返ります。
  • 関数を定義しただけで、呼び出し忘れる:defで定義しただけでは、何も起きません。get_item()のように、関数名の後に丸かっこを付けて呼び出してはじめて、中身が実行されます。
  • リストと変数の役割を混同する:1つの値を覚えておくのが変数、複数の値を集めておくのがリストです。
  • random.choiceに、空のリストを渡す:エラーになります。if free:で、空でないことを確かめてから使います。
  • 関数の外の変数に、globalなしで代入する:関数の中だけの、別の変数になってしまい、外の変数は変わりません。

演習問題

  1. Item in search_infoと、first_index(search_info, Item) > 0は、どちらも「アイテムがあるか」を調べています。first_indexを使うと、inより何が分かるでしょうか。
  2. random_moveを参考に、「ブロックのない方向」ではなく「アイテムのある方向」だけをリストに集める関数を考えてみましょう。
  3. item_directionのreturn Noneを、returnの1行に書き換えると、どうなりますか。

YouTubeチャンネル

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