5章 変数と関数
入門編の第2部9章(ステージ7)に対応します。
CHaserのしくみの要点
- CHaserのプログラムは、前のターンに見たことを、勝手には覚えていません。
map_infoも、毎ターン新しく作り直されます。あとで使いたい情報は、変数に自分で入れておきます。 - プログラムが長くなると、判断が何行にもなって、読みにくくなります。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のマップとクリア条件
| クリア条件 | 内容 |
|---|---|
| アイテム | 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のマップとクリア条件
| クリア条件 | 内容 |
|---|---|
| アイテム | 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なしで代入する:関数の中だけの、別の変数になってしまい、外の変数は変わりません。
演習問題
Item in search_infoと、first_index(search_info, Item) > 0は、どちらも「アイテムがあるか」を調べています。first_indexを使うと、inより何が分かるでしょうか。random_moveを参考に、「ブロックのない方向」ではなく「アイテムのある方向」だけをリストに集める関数を考えてみましょう。item_directionのreturn Noneを、returnの1行に書き換えると、どうなりますか。