Pythonロボティクスコース レッスン 27
テーマ.7-2 ペイントゲームを製作しよう
加速度センサーを利用してゲームを製作しよう!
チャプター1このレッスンで学ぶこと
このレッスンでは、前のレッスンより少し複雑なゲームプログラムの製作に取り組みます。また、リストや辞書を使うプログラムを短くまとめるときに便利な「内包表記」について新たに学習します。
チャプター2
新しいPython文法の学習
リストには「1, 2, 3, 4, 5, …」や「2, 4, 6, 8, 10, ….」のように、ある一定の規則で並ぶ値を用意して格納するときに便利な「内包表記」という書き方があります。ここでは、簡単なサンプルプログラムを通して、この内包表記について学習しましょう。
2. 1 リストの内包表記
内包表記の基本は、式とfor
文とシーケンスなデータを組み合わせて次のように書きます。
[ 変数1を使用した式 for 変数1 in range()関数やリスト、タプルなどのシーケンスなデータ ]
例えば、0~9の数字を格納したリストを作成したい場合は、次のように書くことができます。
【 サンプルコード 2-1-1 】
_list = [num for num in range(1, 10)]
print(_list)
このプログラムを実行すると、しっかりとリストに1~9までの数字が格納されていることが確認できます。
(実行結果)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
※Pythonのrange
関数は、開始値から終了値までの範囲を生成しますが、終了値は含まれません。具体的には、range(1, 10)
は1から9までの整数を生成しますが、10は含まれません。
式では様々な演算を行うことができ、例えば【 サンプルコード 2-1-1 】で、式「num
」を「num * 3
」に変えると、3の倍数の並びになります。
【 サンプルコード 2-1-2 】
_list = [num * 3 for num in range(1, 10)]
print(_list)
(実行結果)
[3, 6, 9, 12, 15, 18, 21, 24, 27]
内包表記はこれだけでなく、複数のfor
文を組み合わせたたり、if
文と組み合わせたりすることができます。それぞれサンプルコードを例に見ていきましょう。
■ for文を複数組み合わせた内包表記の書き方
複数のfor
文を組み合わせることで、複数のシーケンスなデータにもとづいた値を順番に格納していくことができます。例えば、2つのfor
文を組み合わせる場合は次のように書きます。
[ 変数1と変数2を使用した式 for 変数1 in シーケンスなデータ for 変数2 in シーケンスなデータ ]
では実際に、「11, 12, 21, 22, 31, 32」という数字の並びを、2つのfor文を組み合わせてリストに格納してみましょう。
【 サンプルコード 2-2-1 】
_list = [i * 10 + j for i in range(1, 4) for j in range(1, 3)]
print(_list)
(実行結果)
[11, 12, 21, 22, 31, 32]
【 サンプルコード 2-2-1 】を内包表記を使用せずに書くと、以下のようなコードになります。
_list = []
for i in range(1,4):
for j in range(1,3):
_list.append(i * 10 + j)
print(_list)
変数i
と変数j
の2重のループになっており、i * 10
で10の桁を、j
で1の桁を表して、リストに数値を追加しています。
append
の利点
- 簡単にリストに要素を追加できる。
- リストの末尾に追加されるため、リストの順序を保持したまま新しい要素を追加できる。
これがappend
メソッドの基本的な使い方と特性です。
_list = []
for i in range(1, 4): # 外側のループ:iは1から3まで
for j in range(1, 3): # 内側のループ:jは1から2まで
_list.append(i * 10 + j) # i*10にjを足した値を_listに追加
print(_list)
■ if文を組み合わせた内包表記の書き方
for
文の後に続けてif
文を書くことで、条件式によって、シーケンスなデータから取り出した値から式を評価して、結果をリストに格納するかどうかを決めることができます。
[ 変数1を使用した式 for 変数1 in シーケンスなデータ if 式を評価するかどうかを決める条件式]
例えば、数値が格納されたあるリストから、5より大きい値を取り出したい場合、次のように書くことができます。
【 サンプルコード 2-3-1 】
mylist = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
_list = [num for num in mylist if num > 5]
print(_list)
(実行結果)
[6, 7, 8, 9, 10]
【 サンプルコード 2-3-1 】を内包表記を使用せずに書くと、以下のようなコードになります。
mylist = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
_list = []
for num in mylist:
if num > 5:
_list.append(num)
print(_list)
内包表記を使用する事で記述するコードがシンプルになり、読みやすくなるだけでなく、処理速度が速くなるなどのメリットもあります。
2. 2 辞書の内包表記
内包表記はリストだけでなく、辞書にも適用できます。(残念ながらタプルには適用できません。)基本の書き方は次の通りで、キーと値をそれぞれシーケンスなデータの要素を使用した式で決めることができます。
{ キーを決める式:値を決める式 for 変数 in シーケンスなデータ }
例えば、リストに格納している各要素へ、順番に「2000-
」から始まる番号をキーとして付与したい場合、以下のように書くことができます。
【 サンプルコード 2-4-1 】
fruits = ["banana", "apple", "strawberry", "pineapple"]
_dict = { "2000-"+str(num): fruits[num] for num in range(len(fruits)) }
print(_dict)
(実行結果)
{'2000-2': 'strawberry', '2000-3': 'pineapple', '2000-0': 'banana', '2000-1': 'apple'}
そして、リストのときと同様に、複数のfor
文を組み合わせたたり、if
文と組み合わせたりすることもできます。
■ 2つのfor文を使用した内包表記
例えば以下のサンプルコードでは、「1または2」と「4または5」の組み合わせの掛け算の式をキーに、その計算結果を値にもつ辞書を、2つのfor
文を組み合わせて作成しています。
【 サンプルコード 2-4-2 】
_dict = {str(i)+"*"+str(j): i*j for i in range(1, 3) for j in range(4, 6)}
print(_dict)
(実行結果)
{'2*5': 10, '2*4': 8, '1*4': 4, '1*5': 5}
このコードの意味は、i
と j
の組み合わせに対して、i
と j
を掛け算した結果を、キーとしてその掛け算式を文字列で表したものとともに辞書に格納することです。
■ if文を使用した内包表記
また、次のサンプルコードではif
文と組み合わせることで、リスト内から5文字以上の単語のみを取り出し、その単語をキーに文字数を要素にもつ辞書を作成しています。
【 サンプルコード 2-4-3 】
mywords = ["bike", "apple", "milk", "sugar", "violin"]
_dict = {word: len(word) for word in mywords if len(word) >= 5}
print(_dict)
(実行結果)
{'apple': 5, 'violin': 6, 'sugar': 5}
内包表記は慣れるまで少し、時間が掛かるかもしれませんが、使いこなせるようになると、Pythonらしいプログラムが書けるようになります。もし、これから先にリストや辞書を使ったプログラムを作成する場合は、積極的に内包表記が利用できないか考えるようにしましょう。ここまでできたらクリック
チャプター3
ペイントゲームの製作
ここからは、Studuino:bitのLEDディスプレイと内蔵の加速度センサーを使用した「ペイントゲーム」を製作します。まずは次の動画を見て、このゲームの動作を確認しましょう。
【 ゲームのプレイ動画 】
3. 1 ペイントゲームの遊び方
このゲームでは、5×5マスのLEDディスプレイを1枚の画用紙に見立てます。プレイヤーはその上で筆に見立てた赤色のLEDを移動させ、通ったマスに色を塗ります。筆はStuduino:bitを傾けた方向へ移動するので、上手に操作して制限時間の10秒以内にすべてのマスを塗りつぶすことができればゲームクリアとなります。
3. 2 ゲームプログラムの作成
このゲームには、主に次の機能が必要になります。
【 ペイントゲームに必要な機能 】
- Studuino:bitを傾けた向きに筆(赤色のLED)を移動する機能
- 筆が通ったマスのLEDを緑色に点灯する機能
- すべてのマスを塗りつぶすことができたかどうかを判定する機能
- 経過した時間を確認する機能
これらの機能はgame()
関数としてまとめ、次の流れで処理を行います。
【 プログラムの主な処理の流れ 】
では、それぞれの機能を順番に作成していきましょう。
■ 使用するモジュールやオブジェクトのインポート
準備として、プログラムの中で使用する各種モジュールやオブジェクトをインポートしておきましょう。ボタンAは押すとゲームを開始するという設定で使用します。また、前のレッスンでStuduino:bit内に保存したsound
モジュールを今回も使用します。
from pystubit.board import button_a, display, accelerometer
import sound
import time
もし、Studuino:bit内からsound
モジュールを削除している場合は、以下のリンク先からダウンロードしてもう一度Studuino:bitに保存しましょう。
※ リンクの上にカーソルを合わせて右クリックし、「名前を付けてリンク先を保存」を選択してください。
■ Studuino:bitを傾けた向きに筆を移動する機能/筆が通ったマスのLEDを緑色に点灯する機能
筆はゲーム開始時にLEDディスプレイの中央に配置します。また、移動後の座標は現在の位置から求めるため、変数brush_x
とbrush_y
を用意して、座標を記録するようにします。
ゲームの主な処理は、game()
関数にまとめます。この関数を宣言し、冒頭で筆の初期位置として中央の座標(x=2
, y=2
)をbrush_x
とbrush_y
に入れて、LEDを赤色に点灯させましょう。
追加【5行目~8行目】
from pystubit.board import button_a, display, accelerometer
import sound
import time
def game():
brush_x = 2
brush_y = 2
display.set_pixel(brush_x, brush_y, (31, 0, 0))
続けて、Studuino:bitを傾けることで、筆を前後左右の4つの方向へ移動できるようにします。
これら4つの方向へ移動できるようにすると、その組み合わせで斜め方向へも移動できるようになります。
左右の傾きは加速度センサーのX軸の値を、前後の傾きはY軸の値を調べることで分かります。
【 加速度センサーの各軸の方向 】
では、加速度センサーの値を確認するため、新しくプログラムを作成し、以下のコードをコピーして実行しましょう。
【 サンプルコード 3-2-1 】
from pystubit.board import accelerometer
import time
while True:
print(accelerometer.get_values())
time.sleep_ms(500)
確認した値から、左や右、前や後ろに傾いたと判定する条件を考えましょう。この先では、次のように条件を決めたものとしてプログラムを作成していきます。
向き | 判定の条件 |
---|---|
左に傾いている | X軸の値が-2より小さい |
右に傾いている | X軸の値が2より大きい |
前に傾いている | Y軸の値が-2より小さい |
後ろに傾いている | Y軸の値が2より大きい |
元のプログラムに戻り、分岐処理を書いていきます。筆がLEDディスプレイの端まで移動している場合(X軸やY軸の値が「0」や「4」のとき)は、座標を変更しないように条件を組み合わせましょう。
追加【10行目~19行目】
from pystubit.board import button_a, display, accelerometer
import sound
import time
def game():
dot_x = 2
dot_y = 2
display.set_pixel(dot_x, dot_y, (31, 0, 0))
while True:
vals = accelerometer.get_values()
if vals[0] < -2 and not brush_x == 0: # 左に傾けたとき
brush_x -= 1
elif vals[0] > 2 and not brush_x == 4: # 右に傾けたとき
brush_x += 1
if vals[1] < -2 and not brush_y == 0: # 前に傾けたとき
brush_y -= 1
elif vals[1] > 2 and not brush_y == 4: # 後ろに傾けたとき
brush_y += 1
16行目がelif
文ではなくif
文になっていることに注意してください。ここをelif
文にしてしまうと、X軸方向とY軸方向へ同時に移動させることできなくなり、斜めの移動が行えなくなります。
次に、移動した先のLEDを赤色に点灯させます。移動していない場合は、点灯を行う必要がないため、あらかじめ移動前の座標を記録しておき、それと見比べて変更があった場合のみコードが実行されるようにしましょう。
追加【11行目、12行目、22行目~24行目】
def game():
brush_x = 2
brush_y = 2
display.set_pixel(brush_x, brush_y, (31, 0, 0))
while True:
old_brush_x = brush_x # 移動前のX座標を記録
old_brush_y = brush_y # 移動前のY座標を記録
vals = accelerometer.get_values()
if vals[0] < -2 and not brush_x == 0:
brush_x -= 1
elif vals[0] > 2 and not brush_x == 4:
brush_x += 1
if vals[1] < -2 and not brush_y == 0:
brush_y -= 1
elif vals[1] > 2 and not brush_y == 4:
brush_y += 1
if brush_x != old_brush_x or brush_y != old_brush_y:
display.set_pixel(brush_x, brush_y, (31, 0, 0))
そして、移動前のマスはLEDを緑色に点灯します。
追加【24行目】
if brush_x != old_brush_x or brush_y != old_brush_y:
display.set_pixel(brush_x, brush_y, (31, 0, 0))
display.set_pixel(old_brush_x, old_brush_y, (0, 31, 0))
最後に、筆が移動する速さを設定します。この速さは次の移動を行うまでに待つ時間の長さで決まります。ここでは100ミリ秒としておき、あとでゲームをプレイしてみてから調整するようにしてください。
追加【25行目】
※ インデントの位置に注意してください。
if brush_x != old_brush_x or brush_y != old_brush_y:
display.set_pixel(brush_x, brush_y, (31, 0, 0))
display.set_pixel(old_brush_x, old_brush_y, (0, 31, 0))
time.sleep_ms(100)
ここまでのプログラムを実行して動作を確認します。画面上部の「実行」をクリックした後、ターミナルからgame()
関数を実行しましょう。
>>> game()
■ すべてのマスを塗りつぶすことができたかどうかを判定する機能
制限時間内にすべてのマスを通り、LEDを緑色に点灯できればゲームクリアとなります。ここでは、そのための判定を行う処理を書いていきましょう。
ここでは、1つ1つのマスに対して、通ったかどうかをTrue
またはFalse
のブール値で管理するようにします。こうすることで、すべてのマスがTrue
になった時点で塗りつぶしが完了したと判定できます。
game()
関数のはじめに、それぞれのマスのX座標とY座標のタプルをキーとし、True
またはFalse
のブール値を値とする辞書paper
を用意します。初期状態では、上の左の図のような配置となるように、以下のように内包表記を利用してコードを書きましょう。
追加【9行目、10行目】
def game():
brush_x = 2
brush_y = 2
display.set_pixel(brush_x, brush_y, (31, 0, 0))
paper = {(x, y): False for x in range(5) for y in range(5)} # 内包表記
paper[(brush_x, brush_y)] = True # ゲーム開始時点で中央は塗りつぶし済み
このように辞書のキーにはタプルを指定することもできます。また、内包表記を利用したことで、コードを短くまとめることができました。
次に、筆の移動先になるマスの値をTrue
に変更するコードを追加します。キーがX座標とY座標のタプルとなっているため、呼び出すときも[]
内にタプルを指定します。
追加【27行目】
while True:
old_brush_x = brush_x
old_brush_y = brush_y
vals = accelerometer.get_values()
if vals[0] < -2 and not brush_x == 0:
brush_x -= 1
elif vals[0] > 2 and not brush_x == 4:
brush_x += 1
if vals[1] < -2 and not brush_y == 0:
brush_y -= 1
elif vals[1] > 2 and not brush_y == 4:
brush_y += 1
if brush_x != old_brush_x or brush_y != old_brush_y:
display.set_pixel(brush_x, brush_y, (31, 0, 0))
display.set_pixel(old_brush_x, old_brush_y, (0, 31, 0))
paper[(brush_x, brush_y)] = True
time.sleep_ms(100)
塗りつぶしが完了したかどうかの判定は、筆の移動後に行います。辞書paper
の値を1つずつ確認し、1つでもFalse
があれば、塗りつぶしが完了していないと判断します。塗りつぶしが完了した場合はゲーム終了として、外側のwhile
文を抜けるようにします。
追加【30行目~33行目】
while True:
old_brush_x = brush_x
old_brush_y = brush_y
vals = accelerometer.get_values()
if vals[0] < -2 and not brush_x == 0:
brush_x -= 1
elif vals[0] > 2 and not brush_x == 4:
brush_x += 1
if vals[1] < -2 and not brush_y == 0:
brush_y -= 1
elif vals[1] > 2 and not brush_y == 4:
brush_y += 1
if brush_x != old_brush_x or brush_y != old_brush_y:
display.set_pixel(brush_x, brush_y, (31, 0, 0))
display.set_pixel(old_brush_x, old_brush_y, (0, 31, 0))
paper[(brush_x, brush_y)] = True
time.sleep_ms(100)
for val in paper.values():
if val is False:
break # 30行目のfor文を抜ける
else: # 32行目のbreak文で30行目のfor文を抜けなかった場合のみ実行
break # 12行目のwhile文を抜ける
上で追加したコードでは、30行目のfor
文の中で、順番に辞書paper
の値がFalse
になっているかどうかを調べ、1つ目が見つかった時点で32行目のbreak
文でこのfor
文を抜けるようになっています。この場合、33行目のelse
文は処理されませんので、12行目のwhile
文の次のループに移ります。反対に、32行目のbreak
文が終わりまで実行されなかった場合、33行目のelse
文が処理され、34行目のbreak
文で12行目のwhile
文を抜けます。
最後に、12行目のwhile
文を抜ける前に、現在の筆の位置を示すマスを緑色に点灯させるコードを追加して、LEDディスプレイの全面が緑色に点灯するようにします。
追加【34行目】
for val in paper.values():
if val is False:
break
else:
display.set_pixel(brush_x, brush_y, (0, 31, 0))
break
ここまでのプログラムを実行して動作を確認しましょう。何度か動作を確認する場合は、game()
関数を実行する前にdisplay.clear()
を実行して、LEDディスプレイの表示をリセットしておきましょう。
>>> display.clear() >>> game()
■ 時間の経過を確認する機能
ここでは、時間の経過を計測して、制限時間内にゲームをクリアできたかどうかを判定する機能を追加していきます。
まずは、定数として制限時間を定義します。はじめは簡単にクリアできるように10000ミリ秒(10秒)としておきましょう。
追加【5行目】
from pystubit.board import button_a, display, accelerometer
import sound
import time
LIMIT_TIME = 10000
次に、game()
関数のはじめで、time
モジュールのticks_ms()
関数で現在の時間を取得して変数start_time
に格納するコードを追加します。そしてwhile
文の条件を「経過時間(time.ticks_diff(time.ticks_ms(), start_time)
)が制限時間(LIMIT_TIME
)を下回っている」に変更します。
追加・変更【14行目、15行目】
def game():
brush_x = 2
brush_y = 2
display.set_pixel(brush_x, brush_y, (31, 0, 0))
paper = {(x, y): False for x in range(5) for y in range(5)}
paper[(brush_x, brush_y)] = True
start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), start_time) < LIMIT_TIME:
old_brush_x = brush_x
old_brush_y = brush_y
これで、制限時間を超える前に塗りつぶしを完了できた場合は、break
文でこのwhile
文が強制的に終了され、制限時間を超えた場合は正常にwhile
文を抜けることになります。このことを利用して、次のようにコードを書くことで、ゲームクリアに成功した場合と失敗した場合に分けて処理を行うことができます。
追加【39行目~45行目】
start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), start_time) < LIMIT_TIME:
old_brush_x = brush_x
old_brush_y = brush_y
vals = accelerometer.get_values()
if vals[0] < -2 and not brush_x == 0:
brush_x -= 1
elif vals[0] > 2 and not brush_x == 4:
brush_x += 1
if vals[1] < -2 and not brush_y == 0:
brush_y -= 1
elif vals[1] > 2 and not brush_y == 4:
brush_y += 1
if brush_x != old_brush_x or brush_y != old_brush_y:
display.set_pixel(brush_x, brush_y, (31, 0, 0))
display.set_pixel(old_brush_x, old_brush_y, (0, 31, 0))
paper[(brush_x, brush_y)] = True
time.sleep_ms(100)
for val in paper.values():
if val is False:
break
else:
display.set_pixel(brush_x, brush_y, (0, 31, 0))
break
else: # 失敗の場合(38行目のbreak文が実行されず正常に15行目のwhile文を抜けた場合)
sound.failed() # クリア失敗音(soundモジュールの関数を利用)
display.clear()
return # 失敗の場合はこの行でgame()関数を抜ける
# 成功の場合(42行目のreturn文でgame()関数を抜けなかった場合)
sound.conguratulation() # クリア成功音(soundモジュールの関数を利用)
display.clear()
39行目のelse
文は15行目のwhile
文が38行目のbreak
文で強制終了されなかった場合(クリア失敗の場合)のみ実行されます。そして、42行目のreturn
文でgame()
関数を抜けます。return
文は通常関数の戻り値を渡す場合に使用しますが、このように戻り値がなくても使用することができます。return
文が実行されると、その後ろに続く関数内のコードは実行されないため、43行目以降は、成功の場合のみ実行されることになります。
これでゲームの主な機能ができました。折角なので最後に、ターミナルからgame()
関数を実行するのではなく、ボタンAを押してカウントダウン後にゲームを開始できるようにコードを追加しましょう。
追加【47行目~54行目】
def main():
while True:
if button_a.is_pressed():
sound.countdown()
game()
main()
完成したプログラムは以下のようになります。プログラムを実行してうまくいかない場合は、これと見比べて誤りがないかどうかを確認しましょう。
【 サンプルコード 3-2-2 】
from pystubit.board import button_a, display, accelerometer
import sound
import time
LIMIT_TIME = 10000
def game():
brush_x = 2
brush_y = 2
display.set_pixel(brush_x, brush_y, (31, 0, 0))
paper = {(x, y): False for x in range(5) for y in range(5)}
paper[(brush_x, brush_y)] = True
start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), start_time) < LIMIT_TIME:
old_brush_x = brush_x
old_brush_y = brush_y
vals = accelerometer.get_values()
if vals[0] < -2 and not brush_x == 0:
brush_x -= 1
elif vals[0] > 2 and not brush_x == 4:
brush_x += 1
if vals[1] < -2 and not brush_y == 0:
brush_y -= 1
elif vals[1] > 2 and not brush_y == 4:
brush_y += 1
if brush_x != old_brush_x or brush_y != old_brush_y:
display.set_pixel(brush_x, brush_y, (31, 0, 0))
display.set_pixel(old_brush_x, old_brush_y, (0, 31, 0))
paper[(brush_x, brush_y)] = True
time.sleep_ms(100)
for val in paper.values():
if val is False:
break
else:
display.set_pixel(brush_x, brush_y, (0, 31, 0))
break
else:
sound.failed()
display.clear()
return
sound.conguratulation()
display.clear()
def main():
while True:
if button_a.is_pressed():
sound.countdown()
game()
main()
チャプター4
課題:ゲームの難易度を変更できる機能の追加
市販のシューティングゲームやアクションゲームの中には、ゲームの難易度をプレイヤーが選択できるものがあります。今回製作したペイントゲームも様々な方法でゲームの難易度を変更することができます。その中でも簡単な方法が、「制限時間を変える」ことです。つまり、定数LIMIT_TIME
の値を変更するだけで難易度が調整できます。
from pystubit.board import button_a, display, accelerometer
import sound
import time
LIMIT_TIME = 10000 # 制限時間が短いほどゲームの難易度が上がる
しかし、このままでは事前に難易度は調整できても、プレイヤーに難易度を選択させることはできません。そこでこの課題では、ゲームの開始前に、「Easy
」「Normal
」「Hard
」の3つのモードからプレイヤーが難易度を選択できるように【 サンプルコード 3-2-2 】を改造します。さらに、プレイヤーの選択した難易度が分かるように、ゲーム中のLEDの点灯色もそれぞれ次のように変えましょう。
【 各難易度の制限時間 】
モード(難易度) | 制限時間 |
---|---|
Easy (やさしい) | 10000ミリ秒(10秒) |
Normal (ふつう) | 8000ミリ秒(8秒) |
Hard (むずかしい) | 6000ミリ秒(6秒) |
【 各モード選択時のLEDの点灯色 】
また、モードはボタンBを押して選択します。初期選択は「Easy
」とし、main()
関数の中に選択を切り替える処理を書きましょう。
4. 1 プログラムの作成例
【 サンプルコード 3-2-2 】に、次の2つの役割をもつコードを追加します。
- ボタンBを押してモードを選択する
- 選択されたモードで制限時間とLEDの点灯色を設定する
それでは、順番に書いていきましょう。
■ ボタンBを押してモードを選択する
プログラムのはじめに、3つのモードと、それぞれのモードの制限時間とLEDの点灯色(筆で塗りつぶす色)を定数として定義します。
定数名 | データ型 | 値 |
---|---|---|
MODE | タプル | ("Easy", "Normal", "Hard") |
LIMIT_TIME | 辞書 | それぞれのモードの制限時間 {"Easy":10000, "Normal":8000, "Hard":6000} |
PAINT_COLOR | 辞書 | それぞれのモードのLEDの点灯色 {"Easy":(31, 0, 0), "Normal":(0, 31, 0), "Hard":(31, 0, 31)} |
そして、変数selected_mode
を定義して、現在選択中のモードを格納するようにします。また、button_b
オブジェクトのインポートも忘れないように注意してください。
追加・変更【1行目、5行目~8行目】
from pystubit.board import button_a, button_b, display, accelerometer
import sound
import time
MODE = ("Easy", "Normal", "Hard") # モード
LIMIT_TIME = {"Easy": 10000, "Normal": 8000, "Hard": 6000} # モード別の制限時間
PAINT_COLOR = {"Easy": (0, 31, 0), "Normal": (0, 0, 31), "Hard": (31, 0, 31)} # モード別の塗りつぶし色
selected_mode = MODE[0] # 最初は"Easy"を選択
次に、main()
関数内でボタンBが押されると、選択中のモードを切り替える処理を書いていきます。ここでは、タプルがもつ「要素の値からインデックスを調べる」index()
メソッドを利用します。このメソッドで、現在選択中のモードのタプルMODE
内でのインデックスを調べ、その次のインデックスを求めます。ただし、最後のインデックスの場合は「0」に戻さなければいけないことに注意してください。また、変更後のモードがプレイヤーに分かるようにdisplay
オブジェクトのscroll()
メソッドで表示します。
追加【 42行目、43行目、49行目~53行目 】
def main():
global selected_mode # 関数内で値を変更する場合はは、グローバル宣言が必要
display.scroll(selected_mode, delay=20, color=PAINT_COLOR[selected_mode]) # 最初に選択されているモードを表示
while True:
if button_a.is_pressed():
sound.countdown()
game()
if button_b.is_pressed(): # ボタンBが押されるとモードを変更
index = MODE.index(selected_mode) # 現在選択中のモードのインデックスを取得
index = index + 1 if index < len(MODE) - 1 else 0 # 変更後のモードのインデックスを取得
selected_mode = MODE[index] # モードの変更
display.scroll(selected_mode, delay=20, color=PAINT_COLOR[selected_mode]) # LEDディスプレイに変更後のモードを表示
selected_mode
はグローバル変数のため、main()
関数内で値を変更する場合は、42行目のようにglobal宣言が必要です。また、初期選択がどのモードになっているのかが分かるように、main()
関数の呼び出し直後にもLEDディスプレイに表示するようにしておきましょう。
■ 選択されたモードで制限時間とLEDの点灯色を設定する
モードの選択ができるようになったので、後は選択されたモードによって制限時間とLEDの点灯色が切り替わるようにプログラムを変更します。【 サンプルコード 3-2-2 】からLIMIT_TIME
をLIMIT_TIME[selected_mode]
に、(0, 31, 0)
をPAINT_COLOR[selected_mode]
へそれぞれ変更しましょう。これでプログラムの完成です。
【 サンプルコード 4-1-1 】
変更【18行目、32行目、40行目】
from pystubit.board import button_a, button_b, display, accelerometer
import sound
import time
MODE = ("Easy", "Normal", "Hard")
LIMIT_TIME = {"Easy": 10000, "Normal": 8000, "Hard": 6000}
PAINT_COLOR = {"Easy": (0, 31, 0), "Normal": (0, 0, 31), "Hard": (31, 0, 31)}
selected_mode = MODE[0]
def game():
brush_x = 2
brush_y = 2
display.set_pixel(brush_x, brush_y, (31, 0, 0))
paper = {(x, y): False for x in range(5) for y in range(5)}
paper[(brush_x, brush_y)] = True
start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), start_time) < LIMIT_TIME[selected_mode]: # モードによって制限時間が変わる
old_brush_x = brush_x
old_brush_y = brush_y
vals = accelerometer.get_values()
if vals[0] < -2 and not brush_x == 0:
brush_x -= 1
elif vals[0] > 2 and not brush_x == 4:
brush_x += 1
if vals[1] < -2 and not brush_y == 0:
brush_y -= 1
elif vals[1] > 2 and not brush_y == 4:
brush_y += 1
if brush_x != old_brush_x or brush_y != old_brush_y:
display.set_pixel(brush_x, brush_y, (31, 0, 0))
display.set_pixel(old_brush_x, old_brush_y, PAINT_COLOR[selected_mode]) # 選択したモードの色で塗りつぶす
paper[(brush_x, brush_y)] = True
time.sleep_ms(100)
for val in paper.values():
if val is False:
break
else:
display.set_pixel(brush_x, brush_y, PAINT_COLOR[selected_mode]) # 選択したモードの色で点灯する
break
else:
sound.failed()
display.clear()
return
sound.conguratulation()
display.clear()
def main():
global selected_mode
display.scroll(selected_mode, delay=20, color=PAINT_COLOR[selected_mode])
while True:
if button_a.is_pressed():
sound.countdown()
game()
if button_b.is_pressed():
index = MODE.index(selected_mode)
index = index + 1 if index < len(MODE) - 1 else 0
selected_mode = MODE[index]
display.scroll(selected_mode, delay=20, color=PAINT_COLOR[selected_mode])
main()
チャプター5
おわりに
5. 1 このレッスンのまとめ
このレッスンでは、リストや辞書の「内包表記」について新たに学習しました。内包表記を利用することで、Pythonらしくプログラムを簡潔にまとめることができるようになります。
また、前回に引き続きゲーム製作として「ペイントゲーム」の作成に取り組みました。少し構造が複雑になりましたが、課題で取り組んだように難易度の設定ができることで、より長くプレイヤーに楽しんでもらえるゲームになります。
この先のレッスンで作成するゲームや前のレッスンで作成したゲームでも、ぜひこの難易度設定を導入できないか考えてみてください。
5. 2 次のレッスンについて
次回のレッスンでは、さらに構造が複雑なゲームプログラムに取り組みます。ビデオカメラで撮影した動画やゲームの映像で用いられる「フレームの処理」の考え方を応用した「複数オブジェクトの同時制御手法」を紹介します。