第2部では、CHaserのプログラムをPythonで書くための道具を、順番に学びます。
この部の読み方
入門編では、ステージごとに「課題 → ヒント → 挑戦 → 解答例」の順で、自分でブロックを組む練習をしました。この本では、その考え方をPythonの文法でどう書くかを中心に説明します。だから、ステージの課題を1つずつ解く形ではなく、書き方の解説と、コードの例、つまずきやすい点を中心に進めます。
この部のコードは、チュートリアルのPython画面にそのまま入力できる書き方(walk(Up))で書きます。本番用(Pythonの画面・対戦)では、命令の前にplayer.を付けます。違いは、0章で説明します。
各章は、入門編の次の章に対応しています。
| この本の章 | 内容 | 入門編の章(ステージ) |
|---|---|---|
| 0章 | Pythonの基礎と、CHaserの書き方(チュートリアルと本番の違い) | 第2部0章 |
| 1章 | 移動と繰り返し | 第2部1・2章(ステージ1・2) |
| 2章 | 情報の読み取りと条件分岐 | 第2部3・4章(ステージ3) |
| 3章 | 探査と、行動の決め方 | 第2部5・6章(ステージ4) |
| 4章 | ターンの仕組みと、攻撃 | 第2部7・8章(ステージ5・6) |
| 5章 | 変数と関数 | 第2部9章(ステージ7) |
| 6章 | 総合演習と、Blockly⇔Python対応表 | 第2部10章(ステージ8) |
入門編を読んでいない人のために、各章の最初に「CHaserのしくみの要点」を置きました。入門編を読んだ人は、飛ばして構いません。
0章 Pythonの基礎と、CHaserの書き方
この章では、CHaserのプログラムをPythonで書く場所と、その書き方(2通り)、Pythonの基本ルールを確認します。Pythonを知っている人は、「Pythonを書く2つの場所」から「CHaserで使う名前」までを読めば十分です。
Pythonを書く2つの場所
CHaserには、Pythonのコードを書く場所が2つあります。
| チュートリアル(Python) | Pythonの画面・対戦 | |
|---|---|---|
| 開き方 | ステージ選択画面の右上を「Python」に切り替えて、ステージを選ぶ | トップ画面の「Python」 |
| 目的 | ステージをPythonでクリアする練習 | 対戦用のプログラムを書く・対戦する |
| 命令の書き方 | 名前だけ:walk(Up) |
player.を付ける:player.walk(Up) |
| 準備 | いらない(importも、接続もなし) |
骨組み(下)を使う |
この本の第2部のコードは、チュートリアルにそのまま入力できる書き方(walk(Up))で書きます。本番用に使うときは、命令の前にplayer.を付け、骨組みの中に入れます(walk(Up) → player.walk(Up))。第3部の実践では、本番用の書き方で書きます。
チュートリアルのPythonは「簡易版」
チュートリアルのPython画面は、walk(Up)と書くだけで、キャラクターを動かせるように作られた簡易版です。書いたコードは、画面が、CHaserのステージ用に変換してから動かしています。だから、書いたコードは、普通のPythonとしては動きません。たとえば、walk(Up)だけを書いたファイルを、パソコンのPythonで動かすと、「walkという名前がありません(NameError)」というエラーになります。walkなどの命令や、方角の名前は、この画面が用意しているからです。
普通のPythonにはない、次のような決まりと制限があります。
- 命令の結果が、
map_infoなどの変数に、自動で入る(普通のPythonでは、map_info = ...と書かない限り、変数は変わりません)。 - CPUのいないステージでは、開始時点の
map_infoが最初から入っていて、get_ready()を書かなくても動く。 importなしで、UpやBlockが使える。- 次の書き方は、エラーになる(実機で確かめた):関数の外の
return、[walk(Up) for ...]のような内包表記やlambdaの中で命令を使うこと、f = walkのように( )を付けずに命令の名前だけを使うこと。命令は、for文やdef(関数)の中でも、walk(Up)のように書きます。
対戦用のPythonの画面(第3部)は、こちらが正式な形です。命令をplayer.walk(Up)と書き、骨組みの中にコードを書き、map_info = player.get_ready()で自分で受け取ります。チュートリアルで書いたコードは、そのまま本番に貼っても動きません。命令の前にplayer.を付けるなど、書き換えが必要です。チュートリアルは、考え方を練習するための簡易版、本番は、実際に対戦するためのプログラム、と使い分けてください。
チュートリアルのPython画面
ステージ選択画面の右上にある「ブロック/Python」の切り替えを「Python」にしてからステージを選ぶと、Pythonのチュートリアル画面が開きます。切り替えは、次に開いたときにも残ります。ステージのクリア済みの表示は、ブロックとPythonで別々に管理されます。
画面は、左のエディタ、右の盤面と「残りターン数」、その下の出力欄でできています。ヘッダーのボタンは「実行」「停止」「保存」「開く」で、実行中は「実行」が「リセット」に変わります。「保存」「開く」は、Pythonのファイル(.py)の書き出しと読み込みです。
この画面には、次の決まりがあります。
- 命令は名前だけで書く:
walk(Up)・look(Up)・search(Up)・put(Up)・get_ready()です。player.walk(Up)と書くと、「playerという名前はまだ決まっていません」というエラーになります。 importなしで、方角(Upなど)とマスの種類(Floor・Block・Item・Enemy)が使える:乱数を使うときだけ、先頭にimport randomと書きます。- 命令の結果は、自動で変数に入る:
walk・put・get_readyのあとの周辺情報はmap_infoに、lookの結果はlook_infoに、searchの結果はsearch_infoに入ります。map_info = get_ready()のように、戻り値として受け取る書き方でも構いません。 get_ready()が必要なのは、相手(CPU)のいるステージ:CPUのいないステージ(ステージ1〜4、7、8など)では、開始時点の周辺情報がすでにmap_infoに入っていて、get_ready()を書かなくても動きます(書いても動きます)。CPUのいるステージ(5-1、6-1など)では、毎ターンget_ready()が必要です。書かないと、最初の1回動いたところで止まります。- ブロック数の目標は、判定しない:ステージ2-1〜2-5には行数の目安があり、超えていてもクリアはできます(クリア後に、「くり返し(for)や関数(def)を使うと、もっと短く書けるよ!」と表示されます)。
first_indexは使えない:Blocklyの「最初の出現箇所を検索」ブロックをPythonに変換すると出てくるfirst_index(…)は、この画面では定義されていません。かわりにinを使います(3章)。- 出力欄:
print()の結果や、エラー(行番号つき)、「<< 停止しました >>」などが表示されます。10秒たっても次の行動(walkなど)が出てこないと、止まります。
本番用の骨組み(Pythonの画面・対戦)
ここまで説明してきた、チュートリアルのPythonは、簡易版でした。ここからが、対戦で実際に使う、正式なPythonのプログラムの形です。
Pythonの画面(第3部1章)を開くと、最初から次のようなプログラムが書かれています。これが、本番用のCHaserのプログラムの骨組みです。
import argparse
from lib.pyCHaser import * # lib/pyCHaser.py
player = None
map_info = []
def main(port, name, host):
global player, map_info
# サーバーと接続
player = Client(port=port, name=name, host=host)
while True:
# 準備完了 & 周辺探査
map_info = player.get_ready()
# ブロックがなければ上へ、あれば右へ
if map_info[Up] != Block:
player.walk(Up)
else:
player.walk(Right)
その下に、プログラムを起動するための、決まった書き方が続きます。ここは書き換えません。
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--port", default=2009)
parser.add_argument("-n", "--name", default="player")
parser.add_argument("-i", "--host", default="localhost")
args = parser.parse_args()
main(port=args.port, name=args.name, host=args.host)
全部を理解する必要はありません。自分で書き換えるのは、while True:の中だけです。ほかの部分は、次のような役割です。
from lib.pyCHaser import *:CHaserを動かすための道具を読み込みます。これで、Client、方角の名前(Upなど)、マスの種類の名前(Blockなど)、乱数の道具(random)が使えるようになります。player = Client(...):ゲームに接続して、キャラクターを動かすためのplayerを作ります。以降、player.walk(Up)のように書いて命令を出します。チュートリアルの画面では、このplayerがないので、walk(Up)と書きます。global player, map_info:playerとmap_infoは、プログラムの一番上(mainの外)で作った変数です。globalと書くことで、mainの中での代入がその変数に反映されます。だから、自分で作る関数(5章)の中でも、map_infoとplayerをそのまま使えます。while True::ずっと繰り返す、という意味です(後で説明します)。CHaserのプログラムは、何ターン続くか分からないので、基本的にこの繰り返しで全体を包みます。map_info = player.get_ready():自分の番が来るまで待って、来たら自分の周りの9マスの情報を受け取ります。本番では、map_infoに自動では入らないので、この形で受け取ります。
チュートリアルで書いたコードを、本番に持っていくときは、命令の前にplayer.を付けて、while True:の中身は骨組みのwhile True:の中に、自分で作る関数はdef mainより上に、貼り付けてください。 また、本番では、行動の前に必ずmap_info = player.get_ready()が必要です(4章)。
CHaserで使う名前
CHaserのプログラムで使う名前は、次のとおりです。
命令
| チュートリアルの書き方 | 本番用の書き方 | 意味 |
|---|---|---|
get_ready() |
player.get_ready() |
自分の番を待ち、周辺情報(周囲9マス)を返す |
walk(方向) |
player.walk(方向) |
方向に1マス移動する |
put(方向) |
player.put(方向) |
方向のマスにブロックを置く |
look(方向) |
player.look(方向) |
方向の2マス先を中心とした周囲9マスを調べる(近隣探査) |
search(方向) |
player.search(方向) |
方向に一直線9マスを調べる(遠方探査) |
方向とマスの種類(どちらの書き方でも同じ)
| 種類 | 名前 |
|---|---|
| 方向(移動・設置・探査の指定に使える) | Up・Down・Left・Right |
| 方角(周辺情報の取り出しに使う) | 上の4つ、UpLeft・UpRight・DownLeft・DownRight・Center(自分自身のマス) |
| マスの種類 | Floor(何もない)・Block(ブロック)・Item(アイテム)・Enemy(相手) |
これらの名前は、クォート(")で囲まない、あらかじめ用意された値です。実際には、方角の名前はUpLeft = 0、Up = 1、…、DownRight = 8という整数の別名で、map_infoは9個の要素を持つただのリストです。だからmap_info[Up]は、リストの1番目(Upは1)を取り出しています。
Pythonの基本ルール
変数と代入
値に名前を付けて覚えておく仕組みを変数といいます。
direction = Right
これは「directionという名前の変数に、Rightという値を入れる(代入する)」という意味です。左側が変数名、=が代入の記号、右側が入れたい値です。数学の「等しい」という意味の=とは違うので注意してください。
比較演算子
「等しいかどうか」を調べるときは、=を2つ重ねた==を使います。等しくないかは!=、大小は>・<・>=・<=です。
map_info[Up] == Block # 上がブロックか(TrueかFalseになる)
=(代入)と==(比較)を混同するのは、初心者がもっともよくやってしまう間違いの1つです。
if文(条件分岐)と、インデント
「もし〇〇なら」を表すのがifです。
if map_info[Up] == Block:
player.walk(Right)
elif map_info[Left] == Item:
player.walk(Left)
else:
player.walk(Up)
ifの条件の最後には:(コロン)を付けます。条件が成り立ったときに実行したい処理は、次の行から字下げ(インデント)して書きます。Blocklyでは、ブロックの「内側」にはめ込む形で表していた「中に入っているかどうか」を、Pythonではインデントの深さで表します。同じ深さの行は同じグループで、字下げが浅くなると、そのグループから抜けたことになります。本書では、1段につき半角スペース4つで揃えます。elifは「そうでなくもし」、elseは「どれにも当てはまらなければ」です。上から順に調べて、最初に成り立ったところだけが実行されます。
for文とwhile文(繰り返し)
回数が決まっている繰り返しにはfor、回数が決まっていない繰り返しにはwhileを使います。
for count in range(4): # 4回繰り返す
player.walk(Right)
while True: # ずっと繰り返す
player.walk(Right)
range(4)は「0, 1, 2, 3」という4つの数字の並びを作ります。for count in range(4):は、その4つの数字を順にcountに入れながら、インデントされた部分を4回繰り返します。while 条件:は、条件が成り立つ間、繰り返します。while True:は条件が常に成り立つので、ずっと繰り返します。
コメント
#から行の終わりまでは、プログラムの動作に影響しない、人間が読むためのメモ(コメント)です。判断の意図を書き残しておくと、後で読み返すときに役に立ちます。
リストと、番号の指定
複数の値を順に並べてまとめて扱うものをリストといいます。map_infoもリストです。角かっこ[ ]に番号か名前(Upなど)を書いて、1つの値を取り出します。
free = [] # 空のリスト
free.append(Up) # 最後に追加する
free[0] # 最初の要素を取り出す(番号は0から数える)
番号は0から数え始めます。searchの結果(search_info[0]が隣、search_info[8]がいちばん遠い)も、この数え方です(3章)。
変数名について(ローマ字の名前)
チュートリアルの画面で、日本語の名前を付けたブロックの変数や関数をPythonに変換すると、ido_hoko(移動方向)のようなローマ字の名前になります。この本のコードでは、directionのように英単語の名前を使いますが、変換されたコードを読むときは「もとは日本語の名前だった」と考えると、意味が追いやすくなります。
注意したいこと
=と==を混同する:「代入」と「比較」は見た目が似ていますが、まったく違う意味です。ifの条件の中では必ず==を使います。- インデントがずれる:コピー&ペーストをしたときや、タブとスペースを混ぜたときに、インデントが揃わずエラーになることがあります。字下げの深さを目で確認する癖をつけましょう。
:(コロン)を忘れる:if、elif、else、for、while、後で学ぶdefの行末には、必ず:が必要です。- チュートリアルで
player.を付けてしまう/本番でplayer.を忘れる:チュートリアルではplayer.walk(Up)が、本番ではwalk(Up)が、それぞれエラーになります。エラーの行番号で、命令の書き方を見直しましょう。 while True:の外に、行動を書いてしまう:骨組みのwhile True:の外に書いた命令は、1回しか実行されません。毎ターン実行したい処理は、while True:の中に書きます。
演習問題
- チュートリアルのステージ1-1をPythonで解いてみましょう(ステージ選択画面の右上を「Python」に切り替えます)。上に2マス進むには、どう書きますか。
- 本番用の骨組みを、Pythonの画面で動かして、キャラクターがどう動くか観察してみましょう。「上がブロックでないとき」と「ブロックのとき」で、どう動きが変わりますか。
- 骨組みの
if map_info[Up] != Block:を==に書き換えると、動きはどう変わりますか。実際に動かして確かめてみましょう。