Pythonロボティクスコース レッスン 25
テーマ.6-4 ボタン操作でプログラミングできる車型ロボットの制作
ボタン操作で車型ロボットの動きをプログラミングしてみよう
チャプター1
このレッスンで学ぶこと
このレッスンでは、特殊なメソッドのオーバーライドとクラスメソッドとインスタンスメソッドのちがいについて学習します。レッスンの後半では、これまでの学習のまとめとして、ボタン操作で動きをプログラミングできる車型ロボットを製作します。
チャプター2
新しいPython文法の学習
ここでは、簡単なサンプルプログラムを通して、特殊なメソッドのオーバーライドと、クラスメソッドとインスタンスメソッドのちがいについて学習しましょう。
2. 1 特殊なメソッドのオーバーライド
Pythonでは、「+」や「-」などの演算子に対する処理や、str()
やlen()
のような標準関数に対する処理を定義した特殊なメソッド名が用意されています。次の表はその一覧です。
【 比較に関する特殊メソッド名の例 】
演算子 | 特殊メソッド名 |
---|---|
== (等しい) | __eq__(self, other) |
!= (異なる) | __ne__(self, other)) |
< | __lt__(self, other) |
> | __gt__(self, other) |
<= | __le__(self, other) |
>= | __ge__(self, other) |
参考までに 対応表
記号 | メソッド | 英語表記 |
== | eq | equal |
!= | ne | not equal |
<= | le | less than or equal to |
< | lt | less than |
>= | ge | greater than or equal to |
> | gt | greater than |
【 計算に関する特殊メソッド名の例 】
演算子 | 特殊メソッド名 |
---|---|
+ | __add__(self, other) |
- | __sub__(self, other) |
* | __mul__(self, other) |
// | __floordiv__(self, other) ※小数点以下切り捨てで整数値で返す |
/ | __truediv__(self, other) |
% | __mod__(self, other) |
** | __pow__(self, other) ※累乗 |
【 標準関数に関する特殊メソッド名の例 】
関数名 | 特殊メソッド名 |
---|---|
str() | __str__(self) |
len() | __len__(self) |
このように、コンストラクタの「__init__()メソッド」と同じように、特殊なメソッドは名前の前後に「_(アンダースコア)」が2つ付きます。
※「初期化」とか「初めに」とかを意味する「initialize」とか「initial」とかの略だから「イニット」と読む。
実は、これまでの学習の中でもこの特殊メソッドのオーバーライドを利用して、使い勝手を良くしているクラスを利用していました。それが、StuduinoBitImage
クラスです。
StuduinoBitImage
クラスでは「+
演算子(__add__()
メソッド)」をオーバーライドして、2つのイメージを合成した新たなイメージを返すようにしています。
例えば次のコードでは、最終的に欲しいイメージを色ごとに分けてインスタンスとして作成し、+
演算子でそれらのイメージを合成した新たなインスタンスを得ています。
【 __add__()
メソッドのオーバーライドを利用した演算処理の例 】
img_yellow = StuduinoBitImage("10000:01000:00000:01010:00000", color=(31, 31, 0)) img_brown = StuduinoBitImage("10000:01000:00000:01010:00000", color=(4, 1, 1)) img_giraffe = img_yellow + img_brown
では実際に、特殊なメソッドをオーバーライドする練習をしてみましょう。
■ 特殊なメソッドのオーバーライドの練習
例として、あるアンケートを学校で実施し、クラスごとに集計した結果をまとめる場面を考えてみましょう。このアンケートでは「A」「B」「C」の3つの回答があり、それぞれに何人が投票したのかを調査しています。「class 1」と「class 2」は次のようになり、2つを合計した回答数を計算してみました。
同じ作業をすべての学年のすべてのクラスで行うのはとても大変です。そこで、これらの結果をPythonの辞書(dict
)にまとめて「+
演算子」で足し合わせることを思い付きました。
しかし、辞書では+
演算子に対応するメソッドがなく、思いついたことは実行できません。そこで、辞書をプロパティとしてもつ独自のクラスData
を作成し、+
演算子に対応する__add__()
メソッドをオーバーライドして、この計算を行えるようにします。
まずは、Data
クラスを宣言し、コンストラクタ(__init__()
メソッド)で辞書を引数として受け取り、プロパティとして格納する処理を書きましょう。
class Data:
def __init__(self, _dict):
self._dict = _dict
※dict
は、Pythonの予約語であるため_dict
としています。
次に、__add__()
メソッドをオーバーライドします。ここでは引数として、同じData
クラスのインスタンスを受け取ります。そして、順番に辞書のキーを取り出し、2つの辞書の値を足し合わせた結果を新たに作成した辞書new
に追加します。そしてこの辞書new
を戻すことで、考えた計算が行えるようになります。
追加【5行目~9行目】
class Data:
def __init__(self, _dict):
self._dict = _dict
def __add__(self, other):
new = {}
for key in self._dict.keys():
new.[key] = self._dict[key] + other._dict[key]
return Data(new)
実際に、2つのクラスのデータを用意して、+
演算子で足し合わせた結果を表示してみましょう。
【 サンプルコード 2-1-1 】
追加【12行目~15行目】
class Data:
def __init__(self, _dict):
self._dict = _dict
def __add__(self, other):
new = {}
for key in self._dict.keys():
new[key] = self._dict[key] + other._dict[key]
return Data(new)
class_1 = Data({"A" : 10, "B" : 12, "C" : 14})
class_2 = Data({"A" : 12, "B" : 8, "C" : 16})
total = class_1 + class_2
print(total._dict)
(実行結果)
{'A': 22, 'C': 30, 'B': 20}
このように、特殊な処理に対応するメソッドをオーバーライドすることで、既存のクラスを損なうことなく、補足したい機能を追加したクラスを作成することもできます。
2. 2 インスタンスメンバ変数とクラスメンバ変数
次に、クラスやオブジェクトに関する2種類の変数(プロパティ)として、「インスタンスメンバ変数」と「クラスメンバ変数」のちがいについて見ていきましょう。
まず、クラスメンバ変数はクラス自身が所有する変数で、インスタンスメンバ変数は各インスタンスごとに所有する変数です。例として次のコードを見てみましょう。
【 サンプルコード 2-2-1 】
class Dog:
voice = "Bow!" # クラスメンバ変数
def __init__(self, name, age):
self.name = name # インスタンスメンバ変数
self.age = age # インスタンスメンバ変数
このクラスでは、voice
がクラスメンバ変数として、name
やage
がインスタンスメンバ変数として定義されています。つまり、ざっくり言うとクラスの直下で定義されたものがクラスメンバ変数となり、メソッド内で定義されたものがインスタンスメンバ変数となります。
クラスメンバ変数はクラスオブジェクトから直接呼び出すことができます。一方で、インスタンスメンバ変数はクラスオブジェクトからは呼び出すことはできません。インスタンスメンバ変数は、インスタンスのオブジェクトからのみ呼び出すことができます。
【 クラスメンバ変数の呼び出し 】
>>> print(Dog.voice) Bow!
【 インスタンスメンバ変数の呼び出し 】
>>> print(Dog.name) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: type object 'Dog' has no attribute 'name'
クラスメンバ変数はクラスオブジェクトと同じものがインスタンスオブジェクトにも紐づけられています。そのため、以下のコードのようにクラスメンバ変数を別の値に書き換えると、インスタンスから呼び出した変数voice
もその値に変わっています。
【 サンプルコード 2-2-2 】
追加【9行目~12行目】
class Dog:
voice = "Bow!"
def __init__(self, name, age):
self.name = name
self.age = age
dog = Dog("Shiro", 3)
print(dog.voice)
Dog.voice = "Wow!" # クラスメンバ変数の書き換え
print(dog.voice) # 書き換えられたクラスメンバ変数を表示
(実行結果)
Bow! Wow!
ただし、インスタンスから変更を行った場合は、クラスメンバ変数への参照がはずれて、新しい値が格納されるため、他のインスタンスが影響されることはありません。
【 サンプルコード 2-2-3 】
追加・変更【9行目~13行目】
class Dog:
voice = "Bow!"
def __init__(self, name, age):
self.name = name
self.age = age
dog_1 = Dog("Shiro", 3)
dog_2 = Dog("Taro", 3)
print(dog_2.voice) # クラスメンバ変数を表示
dog_1.voice = "Wow!" # クラスメンバ変数への参照がはずれ、新しい値を格納
print(dog_2.voice) # クラスメンバ変数を表示
(実行結果)
Bow! Bow!
2. 3 インスタンスメソッドとクラスメソッド
変数と同様にメソッドについても「インスタンスメソッド」と「クラスメソッド」のちがいがあります。クラスメソッドとして定義する場合は、直前に@classmethod
デコレータを記述します。以下のコードでは、bark_cl()
メソッドをクラスメソッドとして定義しています。
【 サンプルコード 2-3-1 】
class Dog:
voice = "Bow!"
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod # デコレータ
def bark_cl(self): # クラスメソッド
print(self.voice)
def bark(self): # インスタンスメソッド
print(self.voice)
インスタンスメソッドをクラスオブジェクトから実行する場合は、引数self
への受け渡しを省略できませんが、クラスメソッドは、これを省略することができます。
引数self
が指定されていないため、エラーが発生。
>>> Dog.bark() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: function takes 1 positional arguments but 0 were given
引数self
にクラスオブジェクト自身を渡すと実行できる。
>>> Dog.bark(Dog) Bow!
クラスメソッドの場合は引数self
への受け渡しが省略できる。
>>> Dog.bark_cl() Bow!
このようなちがいがあるのは、@classmethod
デコレータが付いた場合、メソッドの実行時に自動的にクラス自身が引数として渡されるようになるためです。また、インスタンスからインスタンスメソッドが呼び出された場合も、自動的にインスタンス自身が引数として渡されるようになっています。
チャプター3
車型ロボットの組み立て
このレッスンでは、レッスン22で組み立てた車型ロボットをそのまま使います。分解している場合は、以下の組立説明書を確認して、再度組み立てを行ってください。
チャプター4
ボタンでプログラミングできる車型ロボットの制作
ここではボタンを使って、前進/後進/右回転/左回転の4つの動作を登録して実行できる車型ロボットのプログラムを作成します。
【 製作するロボットの動作 】
このプログラムは大きく分けると、「メイン」「動作の登録」「動作の実行」の3つの処理で構成されています。
【 プログラムの構成 】
この中で最も複雑な処理が「動作の登録」です。動作を登録するときは、Aボタンを押して動きを選択し、Bボタンで決定します。このとき、どの動作が選択されているのかが分かるように、動きに対応した矢印のイメージをLEDディスプレイに表示します。
【 選択された動きに対応して表示する矢印のイメージ 】
Bボタンで決定した動作はリストに記録していきます。play()
関数では、そのリストに貯められた情報を順番に取り出し、対応する動作を行うVehicleRobot
クラスのメソッド(move_forward()
やrotate_right()
など)を実行します。
4. 1 動作に関する情報を管理する`Direction`クラスの定義
ここではまず、動作に関する情報をまとめて管理するDirection
クラスを用意します。このDirection
クラスには、次のプロパティとメソッドを持たせます。
※ 「direction
」は「方向」の意味を表す英単語です。
【 Direction
クラスのプロパティ 】
- 動作を表す定数
動作 | 名前 | 値 |
---|---|---|
前進 | FORWARD | 10 |
右回転 | RIGHT | 20 |
後進 | BACKWARD | 30 |
左回転 | LEFT | 40 |
directions
:動作を表す定数をまとめたタプル
directions = (FORWARD, RIGHT, BACKWARD, LEFT)
images
:動作に対応する矢印のイメージをまとめた辞書
キー(プロパティ) | 値 |
---|---|
FORWARD | StuduinoBitImage.ARROW_N (上向きの矢印) |
RIGHT | StuduinoBitImage.ARROW_E (右向きの矢印) |
BACKWARD | StuduinoBitImage.ARROW_S (下向きの矢印) |
LEFT | StuduinoBitImage.ARROW_W (左向きの矢印) |
【 Direction
クラスのメソッド 】
get_direction()
メソッド
引数として、プロパティdirections
に指定するインデックスを受け取り、そのデータを返します。get_image()
メソッド
引数として、プロパティFORWARD
、RIGHT
、BACKWARD
、LEFT
のいずれかを受け取り、プロパティimages
のキーとして指定し、その値(イメージ)を返します。get_len()
メソッド
プロパティdirections
の長さを返します。
いきなり色々な情報が出てきたので頭の中を整理するのが大変ですが、ここからは実際にコードを書きながら、それぞれのプロパティやメソッドをなぜ用意しているのかを説明していきます。
■ 動作を表す定数の定義
動作をリストに登録するときに、「"move_forward"
」や「"rotate_right"
」のように動作を表す文字列を記録することもできますが、ここではあえて文字列ではなく、代わりに数値を使用します。これには次のような理由があります。
Studuino:bitのようなマイコンは、PCと比べてとても容量が小さいメモリ(データを一時的に記憶するための部品)でプログラムを処理しています。基本的に数値と文字列では記録に必要なデータ容量にちがいがあり、文字列は数値とくらべてより多くの容量を必要とします。そのため、マイコンのプログラムにおいては、代替できる場合も文字列ではなく数値を使用する慣習があります。Pythonでは、C言語のように古くから使われているプログラム言語ほどの差はありませんが、それでも文字列は数値の2倍程度かそれ以上の容量を必要とします。塵も積もれば山となるということわざにもあるように、多くのデータを記録していく場合は、その差は馬鹿にできません。
そこで、今回は練習も兼ねて数値を使用してみたいと思います。ただし、数値を扱う場合は、文字列と違ってコードを読んでもすぐにそれが何の代わりに使われた数値なのか判断することができません。そこで、定数として名前を付けて定義することで、読んで理解できるようにします。
では、前置きが少し長くなりましたが、Direction
クラスを定義して、次のようにコードを書きましょう。
class Direction:
FORWARD = 1
RIGHT = 2
BACKWARD = 3
LEFT = 4
directions = (FORWARD, RIGHT, BACKWARD, LEFT)
4つの定数は、このクラスのクラスメンバ変数として扱います。これによって、クラスから直接アクセスして値を取り出すことができます。
print(Direction.FORWARD)
また、7行目ではdirections
プロパティとして、これら4つの定数をタプルにまとめておきましょう。
■ 動作に対応する矢印のイメージをまとめた辞書の定義
次に、それぞれの動作に対応するイメージをまとめた辞書を用意します。矢印のイメージはStuduinoBitImageクラスにあらかじめ用意されていますので、インポートして使用しましょう。
追加【1行目、11行目~16行目】
from pystubit.board import Image
class Direction:
FORWARD = 1
RIGHT = 2
BACKWARD = 3
LEFT = 4
directions = (FORWARD, RIGHT, BACKWARD, LEFT)
images = {
FORWARD: Image.ARROW_N, # 上向きの矢印
RIGHT: Image.ARROW_E, # 右向きの矢印
BACKWARD: Image.ARROW_S, # 下向きの矢印
LEFT: Image.ARROW_W, # 左向きの矢印
}
■ 3つのメソッドの定義
最後に3つのメソッドを定義します。
get_direction()
メソッドget_image()
メソッドget_len()
メソッド
これらは、後のregist()
関数でボタンを押して動作の選択を切り替える処理を書くときに使います。今はまだこれらをメソッドとして用意した理由がピンと来ないかもしれませんが、後々その便利さが分かってきます。また、これら3つのメソッドはクラスから直接呼び出せるように、クラスメソッドとして定義しましょう。
まずは1つめのget_direction()
メソッドから書いていきます。このメソッドは、引数としてdirections
プロパティに指定するインデックス(index
)を受け取ります。そして、このインデックスのデータを返します。
追加【16行目~18行目】
class Direction:
FORWARD = 10
RIGHT = 20
BACKWARD = 30
LEFT = 40
directions = (FORWARD, RIGHT, BACKWARD, LEFT)
images = {
FORWARD: Image.ARROW_N,
RIGHT: Image.ARROW_E,
BACKWARD: Image.ARROW_S,
LEFT: Image.ARROW_W,
}
@classmethod
def get_direction(self, index):
return self.directions[index]
2つめとして、指定された動作に対応する矢印のイメ―ジを返すget_image()
メソッドを定義します。このメソッドは引数として、このクラスがプロパティとしてもつ4つの定数「FORWARD
/RIGHT
/BACKWARD
/LEFT
」を受け取り、辞書images
のキーとして指定し、その値を返します。
追加【20行目~22行目】
@classmethod
def get_direction(self, index):
return self.directions[index]
@classmethod
def get_image(self, direction):
return self.images[direction]
最後の3つめに、directions
プロパティの長さを返すget_len()
メソッドを定義します。
追加【24行目~26行目】
@classmethod
def get_direction(self, index):
return self.directions[index]
@classmethod
def get_image(self, direction):
return self.images[direction]
@classmethod
def get_len(self):
return len(self.directions)
4. 2 動作を登録する`regist()`関数の定義
regist()
関数では、ボタンを押して動作の選択と登録を行い、リストに追加していきます。この関数の処理の流れは次のようになります。
【 regist()
関数の処理の流れ 】
regist()
関数ではボタンAとB、それからLEDディスプレイを使用するため、これらを追加でインポートしましょう。また、空のリストcommands
もグローバル変数として用意します。
追加・変更【1行目、3行目】
from pystubit.board import Image, button_a, button_b, display
commands = [] # 登録した動作を記憶するためのリスト
class Direction():
■ リストを空にする
では、regist()
関数を定義していきます。この中で、さきほど用意したグローバル変数のcommands
を使いますので、最初にglobal宣言をします。また、この登録処理を呼び出すたびに、前に記憶していた情報は削除します。そのため、リストのclear()
メソッドで空にしておきましょう。
追加・変更【32行目~34行目】
def regist():
global commands
commands.clear()
■ 初期選択の動作を決めディスプレイに表示する
次に、最初に選択する動作を決めて、LEDディスプレイに表示します。ここでは、4つある動作の中で、インデックスが0のものを最初は選ぶようにしておきましょう。
変数index
を用意し、その値を0にします。これをDirection
クラスのget_direction()
メソッドの引数に指定して、動作を表す値を取得し、その値を変数selected_direction
に格納します。そして、Direction
クラスのget_image()
メソッドに変数selected_direction
を指定してイメージを取得し、display
オブジェクトのshow()
メソッドで表示しましょう。
追加・変更【36行目~38行目】
def regist():
global commands
commands.clear()
index = 0
selected_direction = Direction.get_direction(index)
display.show(Direction.get_image(selected_direction), delay=0)
■ ボタンAを押すたびに次の動作を選択してディスプレイに表示する
ボタンBが長押しされるまでは、ボタンが押されたことを調べる処理を繰り返し行いますので、ここでは、変数loop_flag
を用意して、この値がTrue
からFalse
に変更されるまで繰り返すというコードを書きます。
追加【40行目、41行目】
def regist():
global commands
commands.clear()
index = 0
selected_direction = Direction.get_direction(index)
display.show(Direction.get_image(selected_direction), delay=0)
loop_flag = True
while loop_flag:
ボタンAが押されことを判定して、押された場合は変数index
を1ずつ変えます。また、ボタンを押し続けることで連続して処理されるのを防ぐためにボタンが離されるまで待ってからindex
を変更します。そして、index
の値がDirection
クラスのdirection
プロパティの最大のインデックス番号(長さ - 1)を超えるとエラーが発生するため、条件式でその場合は0に戻すようにします。それから、変更後のインデックスで動作の値と対応する矢印のイメージを取得して、LEDディスプレイに表示します。
追加【42行目~49行目】
loop_flag = True
while loop_flag:
if button_a.is_pressed():
while button_a.is_pressed():
pass
index = index + 1 if index < Direction.get_len() - 1 else 0
selected_direction = Direction.get_direction(index)
display.show(
Direction.get_image(selected_direction), delay=0
)
■ ボタンBを押して選択中の動作を登録する/ボタンBの長押しでループを抜ける
ボタンBは短く押された場合と、長押しされた場合で行う処理が変わります。まずは、押された時間の長さを計測するためにtime
モジュールを利用するため、プログラムの先頭でインポートしましょう。
追加【2行目】
from pystubit.board import Image, button_a, button_b, display
import time
commands = []
まずは、ボタンBが押された時間の長さを計測し、2秒以上押し続けられたらloop_flag
をFalse
に変更して、ループを抜ける処理を書きます。現在のStuduino:bit内の時間はtime
モジュールのticks_ms()
関数で取得できます。ボタンBが押され、時間の計測を開始する前に一度この関数を実行し、その時間を変数start
に格納します。
追加【51行目、52行目】
loop_flag = True
while loop_flag:
if button_a.is_pressed():
while button_a.is_pressed():
pass
index = index + 1 if index < Direction.get_len() - 1 else 0
selected_direction = Direction.get_direction(index)
display.show(
Direction.get_image(selected_direction), delay=0
)
if button_b.is_pressed():
start = time.ticks_ms()
ボタンBが押されている間は時間の計測を繰り返します。この繰り返しの中で、time
モジュールのticks_diff()
関数で、取得した最新の時間(ticks_ms()
)と計測開始時の時間(start
)の差を求めます。この差が2秒(2000ミリ秒)を超えている場合は、変数loop_flag
をFalse
に変更し、LEDディスプレイの表示を消します。そして、ボタンが離されるまで待ち、break
文でこの階層のwhile
文を抜けます。
追加【54行目~59行目】
if button_b.is_pressed():
start = time.ticks_ms()
while button_b.is_pressed():
if time.ticks_diff(time.ticks_ms(), start) > 2000:
loop_flag = False
display.clear()
while button_b.is_pressed(): # ボタンBが離されるまで待つ
pass
break # beak文で抜けることに注意
break
文でwhile
文を抜けるのは理由があります。2秒が経過する前に、ボタンBが離されたとき、つまり正常にwhile文を抜けたときだけ、else
文を使用して現在選択している動作をリストに追加するためです。このとき、リストに動作を追加したことが使用者に分かるように、短く(200ミリ秒ほど)LEDディスプレイを点滅させるようにしましょう。
追加【60行目~66行目】
if button_b.is_pressed():
start = time.ticks_ms()
while button_b.is_pressed():
if time.ticks_diff(time.ticks_ms(), start) > 2000:
loop_flag = False
display.clear()
while button_b.is_pressed():
pass
break
else: # インデントの位置に注意してください。
commands.append(selected_direction)
display.clear() # LEDディスプレイを点滅する。
time.sleep_ms(200)
display.show(
Direction.get_image(selected_direction), delay=0
)
少し長くなりましたが、これでregist()
関数が定義できました。
4. 3 登録した動作を実行するplay関数の定義
play()
関数では、リストcommands
に登録されている動作を表す値を順番に取り出して、その値によって、VehicleRobot
クラスのメソッドを実行して車型ロボットを動かします。この関数の処理の流れは次のようになります。
この関数では、車型ロボットを制御するため、VehicleRobot
クラスのインスタンスを用意しておきましょう。
追加【3行目、6行目】
from pystubit.board import button_a, button_b, display, Image
import time
from vehicle import VehicleRobot
commands = []
robo = VehicleRobot(pin_l="M1", pin_r="M2")
では、play()
関数を定義していきます。まずは、リストから情報(動作を表す値)を取り出して、LEDディスプレイに表示するコードを書きましょう。
追加【70行目~74行目】
def play():
global commands, robo # ここでは、グローバル宣言は必ずしも必要はありませんが、明示しておくことでコードが読みやすくなります。
for direction in commands:
display.show(Direction.get_image(direction), delay=0)
次に、この値(direction
)によって、ロボットの動作を制御します。次のようにコードを書きましょう。Direction
クラスのプロパティを使用して値を比較することで、コードが読みやすくなっていることがわかります。
追加【75行目~83行目】
def play():
global commands, robo
for direction in commands:
display.show(Direction.get_image(direction), delay=0)
if direction == Direction.FORWARD:
robo.move_forward()
elif direction == Direction.BACKWARD:
robo.move_backward()
elif direction == Direction.LEFT:
robo.rotate_left()
else:
robo.rotate_right()
time.sleep_ms(1000) # 動かす時間は自由に変えてください。
robo.brake() # すべての動作を実行し終えたら停止する。
4. 4 メインの処理を行うmain関数の定義
最後に、ボタンAとBのどちらが押されたかを見て、regist()
関数とplay()
関数を実行するメインの処理を行うmain()
関数を定義します。この関数の処理の流れは次のようになります。
この処理は、コードとして次のように書くことができます。
追加【86行目~95行目】
def main():
while True:
if button_a.is_pressed():
while button_a.is_pressed(): # ボタンAが離されてから関数を実行
pass
play()
if button_b.is_pressed():
while button_b.is_pressed(): # ボタンBが離されてから関数を実行
pass
regist()
また、待機中はそのことが分かるように、下の図のような笑った顔のイメージを表示するようにしておきましょう。
このイメージは、StuduinoBitImage
クラスにHAPPY
というプロパティ名であらじめ用意されています。また、点灯させるときの色の定義もあらかじめStuduinoBitImage
クラスに用意されており、黄色の場合はYELLOW
というプロパティがあります。そのため、以下のようにコードを書くだけで上の図のイメージをLEDディスプレイに表示することができます。
display.show(Image.HAPPY, color=Image.YELLOW)
このコードを以下の3か所に追加しましょう。
追加【87行目、93行目、98行目】
def main():
display.show(Image.HAPPY, color=Image.YELLOW) # 追加
while True:
if button_a.is_pressed():
while button_a.is_pressed():
pass
play()
display.show(Image.HAPPY, color=Image.YELLOW) # 追加
if button_b.is_pressed():
while button_b.is_pressed():
pass
regist()
display.show(Image.HAPPY, color=Image.YELLOW) # 追加
4. 5 動作を確認する
これでプログラムが完成しました。プログラムの末尾に、main()
関数を実行するコードを追加して、プログラムを実行しましょう。
main()
プログラムがうまく動作しない場合は、以下のサンプルコードと見比べて誤りがないかをチェックしてください。
【 サンプルコード 4-5-1 】
from pystubit.board import button_a, button_b, display, Image
import time
from vehicle import VehicleRobot
commands = []
robo = VehicleRobot(pin_l="M1", pin_r="M2")
class Direction():
FORWARD = 1
RIGHT = 2
BACKWARD = 3
LEFT = 4
directions = (FORWARD, RIGHT, BACKWARD, LEFT)
images = {
FORWARD: Image.ARROW_N,
RIGHT: Image.ARROW_E,
BACKWARD: Image.ARROW_S,
LEFT: Image.ARROW_W,
}
@classmethod
def get_direction(self, index):
return self.directions[index]
@classmethod
def get_image(self, direction):
return self.images[direction]
@classmethod
def get_len(self):
return len(self.directions)
def regist():
global commands
commands.clear()
index = 0
selected_direction = Direction.get_direction(index)
display.show(Direction.get_image(selected_direction), delay=0)
loop_flag = True
while loop_flag:
if button_a.is_pressed():
while button_a.is_pressed():
pass
index = index + 1 if index < Direction.get_len() - 1 else 0
selected_direction = Direction.get_direction(index)
display.show(
Direction.get_image(selected_direction), delay=0
)
if button_b.is_pressed():
start = time.ticks_ms()
while button_b.is_pressed():
if time.ticks_diff(time.ticks_ms(), start) > 2000:
loop_flag = False
display.clear()
while button_b.is_pressed():
pass
break
else:
commands.append(selected_direction)
display.clear()
time.sleep_ms(200)
display.show(
Direction.get_image(selected_direction), delay=0
)
def play():
for direction in commands:
display.show(Direction.get_image(direction), delay=0)
if direction == Direction.FORWARD:
robo.move_forward()
elif direction == Direction.BACKWARD:
robo.move_backward()
elif direction == Direction.LEFT:
robo.rotate_left()
else:
robo.rotate_right()
time.sleep_ms(1000)
robo.brake()
def main():
display.show(Image.HAPPY, color=Image.YELLOW)
while True:
if button_a.is_pressed():
while button_a.is_pressed():
pass
play()
display.show(Image.HAPPY, color=Image.YELLOW)
if button_b.is_pressed():
while button_b.is_pressed():
pass
regist()
display.show(Image.HAPPY, color=Image.YELLOW)
main()
チャプター5
課題:クラスではなくモジュールとして`Direction`を定義する
【 サンプルコード 4-5-1 】では、動作に関連する情報をDirection
クラスにまとめて定義していました。しかし、クラスとしてではなく、モジュールとして定義しても同じように使用することができます。この課題では実際に、新しくファイルを作成してコードを移し、Direction(.py)
という名前でStuduino:bit内に保存することで、モジュールとして利用できるようにしてみましょう。
5. 1 プログラムの作成例
まずは新しくファイルを作成し、Direction(.py)
という名前を付けてPCに保存しましょう。
次に、元のファイルから以下のコードをコピーして、新しいファイルに貼り付けます。貼り付け終わったら元のファイルからこのコードを削除するか、もしくはコメント化しましょう。
class Direction:
FORWARD = 1
RIGHT = 2
BACKWARD = 3
LEFT = 4
directions = (FORWARD, RIGHT, BACKWARD, LEFT)
images = {
FORWARD: Image.ARROW_N,
RIGHT: Image.ARROW_E,
BACKWARD: Image.ARROW_S,
LEFT: Image.ARROW_W,
}
@classmethod
def get_direction(self, index):
return self.directions[index]
@classmethod
def get_image(self, direction):
return self.images[direction]
@classmethod
def get_len(self):
return len(self.directions)
新しいファイルの方で、上のコードを次のように変更します。ファイルが分かれるため、StuduinoBitImageクラス(省略形のImage
)を先頭でインポートするようにし、また@classmethod
の修飾と引数self
を削除して、インデントを調整しましょう。
【 Direction.py 】
from pystubit.board import Image
FORWARD = 1
RIGHT = 2
BACKWARD = 3
LEFT = 4
directions = (FORWARD, RIGHT, BACKWARD, LEFT)
images = {
FORWARD: Image.ARROW_N,
RIGHT: Image.ARROW_E,
BACKWARD: Image.ARROW_S,
LEFT: Image.ARROW_W,
}
def get_direction(index):
return directions[index]
def get_image(direction):
return images[direction]
def get_len():
return len(directions)
このファイルをStuduino:bit内に保存します。メニューのファイルをクリックして、ウィンドウを開き、PCからStuduino:bitへ写しましょう。
できたら、元のファイルへ戻り、このDirection(.py)
をモジュールとしてインポートしましょう。
追加【4行目】
from pystubit.board import button_a, button_b, display, Image
import time
from vehicle import VehicleRobot
import Direction
これで変更完了です。プログラムを実行して、【 サンプルコード 4-5-1 】と同じように動作することを確認しましょう。
5. 2 クラスとモジュールの使い分けについて
クラスとモジュールには似ている部分があるため、今回は上で確かめたように、モジュールとして定義し直しても、元のコードをほとんど変えずにそのまま実行することができました。
では、クラスとモジュールはどのように使い分ければ良いでしょうか。これについて、簡単なガイドラインを以下にまとめていますので、参考にしてください。
【 クラスとモジュールを使い分けるときのガイドライン 】
- メソッドは同じでも、プロパティの値がちがう複数のインスタンスが必要な場合は、クラスを使用する方が良い。
- 反対に、インスタンスを1つだけしか作成しないようなときは、モジュールの方が適当なケースもある。(例えば、Pythonの場合、モジュールはプログラムの中で何度インポート参照されても1つのコピーしかできないため、メモリの消費が抑えられるというメリットがある。)
- 継承する可能性があるものは、クラスを使用する方が良い。(モジュールは継承ができないため。)
- 複数の値をもつ変数があり、これらを複数の関数に引数として渡せるときは、変数をプロパティとして、関数をメソッドとしてもつクラスを定義した方がコードがすっきりとする。
チャプター6
おわりに
6. 1 このレッスンのまとめ
このレッスンでは、演算子や標準関数に対応する特殊なメソッドのオーバーライドと、クラスやインスタンスに所属する変数とメソッドのちがいについて紹介しました。このレッスンでクラスについての基礎学習は終わりですが、これから先のレッスンではクラスを多用してコードを作成していきますので、分からなくなったときはこのテーマ.6を振り返るようにしてください。
6. 2 次のレッスンについて
テーマ.7では、様々なゲームを製作していきます。これまでよりもさらに複雑なプログラムに取り組んでいきますが、頑張って学習していきましょう。