Pythonロボティクスコース レッスン 26
テーマ.7-1 忍者ゲームを制作しよう
モジュールを利用してゲームを制作しよう!
チャプター1
このレッスンで学ぶこと
テーマ.7では、これまで学習してきた文法を活用して、少し処理が複雑なゲームのプログラムを作成します。このレッスンでは、ゲームの製作に共通で必要な機能をモジュール化したり、モジュールをパッケージにまとめて管理したりする方法について学びます。
チャプター2
新しいPython文法の学習
はじめに、モジュールやパッケージについて、それぞれどういったものであったか改めて確認していきましょう。
2. 1 モジュールとは?
モジュールとは「関連性のあるプログラムの部品をひとつにまとめたもの」です。プログラム中に何度も出てくるコードをひとつのファイルにまとめ、名前を付けてモジュール化します。これまで使用してきた、Python/Studuino:bit/ArtecRoboのそれぞれのモジュール例を下の表にまとめていますので、おさらいしましょう。
【 Pythonで標準的に用意されているモジュール例 】
モジュール名 | 説明 |
---|---|
time | 現在の日時を取得したり、時間の経過を計測したりする関数がまとめられたモジュール |
random | ランダムな処理を行う場合に使用できる関数がまとめられたモジュール |
【 Studuino:bit専用のモジュール例 】
モジュール名 | 説明 |
---|---|
dsply | LEDディスプレイを制御するためのクラスが定義されているモジュール |
image | LEDディスプレイ上に表示するイメージを扱うためのクラスが定義されているモジュール |
bzr | ブザーを制御するためのクラスが定義されているモジュール |
button | ボタンを扱うためのクラスが定義されているモジュール |
board | Studuino:bitの各種パ―ツを扱うクラスから作成したインスタンスが定義されているモジュール |
【 ArtecRobo専用のモジュール例 】
モジュール名 | 説明 |
---|---|
parts | ロボット用の各種パーツを制御するためのクラスが定義されているモジュール |
2. 2 パッケージとは?
パッケージとは複数のモジュールを束ねたものです。
パッケージの構造を工具棚に例えると、工具棚(パッケージ)に複数の工具ケース(モジュール) があり、さらにその中にそれぞれの工具(変数/関数/クラス)が収納されているという関係です。つまり、「パッケージ > モジュール > 変数・関数・クラス」の階層構造になっています。
さきほど確認したStuduino:bit用のモジュールやArtecRobo用のモジュールも、それぞれ以下のパッケージに梱包されています。
【 Studuino:bitやArtecRobo用のモジュールを梱包しているパッケージ 】
パッケージ名 | 梱包されている代表的なモジュール |
---|---|
pystubit | dsply、image、bzr、button、board |
pyatcrobo2 | parts |
2. 3 パッケージの中身の確認
では、実際にMuでpyatcrobo2
パッケージの内容を確認してみましょう。
Studuino:bitをUSBケーブルで接続し、メニューの「ファイル」をクリックします。開いたウィンドウからpyatcrobo2
フォルダを探し、クリックして展開すると、その中にparts
を含むいくつかのモジュールが存在していることが確認できます。
このように、フォルダをパッケージとして扱いたいときは、__init__.py
という名前のファイルをフォルダ内に含めます。これでそのフォルダはPythonからパッケージとして認識されるようになります。
※ __init__.py
の中身は空でも構いません。
2. 4 モジュールやモジュール内の変数・関数・クラスのインポートについて
ここでは、改めてモジュールやその中の変数・関数・クラスを読み込む方法について確認しましょう。
モジュール内の変数や関数、クラスを使用したい場合、モジュールをインポートして呼び出すのか、それともモジュール内で使用したい特定の変数や関数、クラスのみをインポートするのかを選択できます。
例として、pystubit
パッケージのdsply
モジュールにあるStuduinoBitDisplay
クラスを使用する場合のサンプルプログラムで確認しましょう。
【 モジュールをインポートしてクラスを呼び出す 】
from pystubit import dsply
display = dsply.StuduinoBitDisplay()
display.scroll("Hello!")
【 モジュール内のクラスのみをインポート 】
from pystubit.dsply import StuduinoBitDisplay display = StuduinoBitDisplay()display.scroll("Hello!")
変数や関数、クラスを指定してインポートした場合、モジュール名が省略できるというメリットがあります。しかし一方で、複数のモジュールから変数や関数、クラスをインポートした場合、同じ名前のものが含まれているとプログラム上で衝突してしまい、エラーの原因となる可能性があります。もし、同じ名前の変数や関数、クラスをインポートしたい場合は、モジュールをインポートして、そこから呼び出すようにしましょう。
2. 5 モジュール化やパッケージ化するメリット
1つのファイルに何百、何千行と続くコードを書くよりも、まとまったコードの固まりをモジュール化したり、パッケージにまとめたりする事で、コードが整理でき、管理しやすくなります。また、それによってプログラムの修正を行うときの作業が効率化できたり、機能を拡張しやすくなったりするなどのメリットもあります。
チャプター3
忍者ゲームの製作
ここからは、新しいモジュールを利用して、ゲームを製作していきます。このゲームでは、Muのターミナルウィンドウをディスプレイとして利用します。ゲームのテーマは「忍者」で、ランダムにディスプレイに表示される敵と味方を見分けて、敵が表示されたら手裏剣を投げて倒します。手裏剣を投げるときは、Studuino:bitの光センサーの上で手を通過させます。
まずは下の動画を見て、どのようにこのゲームをプレイするのかを確認しましょう。
【 ゲームのプレイ動画 】
3. 1 ゲームで登場するキャラクターの紹介
プレイヤーはターミナルウィンドウに表示された「アスキーアート」のキャラクターが「敵」か「味方」かを瞬時に判断して攻撃を行います。
※ アスキーアート(ASCII art)とは・・・コンピューター上で扱える文字や記号を用いた表現のこと。1行の顔文字から複数行に渡る大掛かりな絵まで様々なものが作られています。
【 敵のキャラクター 】
- 敵忍者A
X ∠ ̄\∩ |/゚U゚Lノ ~~( ニ⊃ ( ヽ/ ノ>ノ
- 敵忍者B
∩ ⊂ \ /ヽ ) ⊂二 ,)~~ /|,∩,/| U\_フ
【 味方のキャラクター 】
- 姫様
∧M∧ (゚ー゚*) ⊂(|0|)⊃ ノ~~~ヽ ノ~~~~~ヽ ∪ ∪
- 殿様
∧_∧ ( ゚∀゚) /~~〉〈/つ ノ ノ| |、 ~(__)__)ゝ~~~ ~~~~~~~~~~~
また、手裏剣を投げる動作を行うと、プレイヤーが操作する忍者のキャラクターが手裏剣を投げる姿が表示されます。
【 プレイヤーのキャラクター 】
∧_∧ ∩ <#・+・>彡 -==卍 / 三三つニつ-=≡卍 / /ミヽ -=卍 -==卍 //~> > ∪ -=≡卍 \) (_)
3. 2 ゲームのルール説明
敵が表示されたときは手裏剣を投げて攻撃し、味方が表示されたときは攻撃をしなければ、それぞれポイントが入ります。反対に、敵を攻撃しなかったり、誤って味方を攻撃してしまったりすると減点されます。
敵忍者A | 敵忍者B | 姫様 | 殿様 | |
---|---|---|---|---|
攻撃した | +1 | +1 | -1 | -1 |
攻撃していない | -1 | -1 | +1 | +1 |
はじめは、プレイ時間を30秒に設定し、ゲーム終了時に7点以上のポイントを獲得できていればゲームクリアとしましょう。
3. 3 ゲーム製作用に使うモジュールの準備
ゲーム開発用のソフトウェアには、あらかじめキャラクターの画像やゲーム内で流す音楽など様々な素材が用意されているものがあります。これらの素材を利用することで、絵を描いたり、曲を制作したりすることができない人でも、ゲーム製作ができるようになっています。また、インターネット上で自分の描いた絵や作曲した音楽を無償で提供するサイトもあり、それらを利用して多くのゲームが開発されています。
そこで今回は、ゲームを素早く制作できるように、以下のアスキーアートと簡単な効果音をまとめたモジュールを利用してみましょう。
■ ターミナルエリアに表示するアスキーアートのモジュール
以下のリンクから「picture.py
」をダウンロードして、Studuino:bit内に保存してください。
※ リンクの上にカーソルを合わせて右クリックし、「名前を付けてリンク先を保存」を選択してください。
このモジュールには、以下のアスキーアートの文字列が格納された変数(定数)が定義されています。
- 数字
左から「ZERO
,ONE
,TWO
,THREE
,FOUR
,FIVE
,SIX
,SEVEN
,EIGHT
,NINE
」
┏━━┓ ┏┓ ┏━━┓ ┏━━┓ ┏┓┏┓ ┏━━┓ ┏━━┓ ┏━━┓ ┏━━┓ ┏━━┓ ┃┏┓┃ ┃┃ ┗━┓┃ ┗━┓┃ ┃┃┃┃ ┃┏━┛ ┃┏━┛ ┃┏┓┃ ┃┏┓┃ ┃┏┓┃ ┃┃┃┃ ┃┃ ┏━┛┃ ┏━┛┃ ┃┗┛┃ ┃┗━┓ ┃┗━┓ ┗┛┃┃ ┃┗┛┃ ┃┗┛┃ ┃┃┃┃ ┃┃ ┃┏━┛ ┗━┓┃ ┗━┓┃ ┗━┓┃ ┃┏┓┃ ┃┃ ┃┏┓┃ ┗━┓┃ ┃┗┛┃ ┃┃ ┃┗━┓ ┏━┛┃ ┃┃ ┏━┛┃ ┃┗┛┃ ┃┃ ┃┗┛┃ ┏━┛┃ ┗━━┛ ┗┛ ┗━━┛ ┗━━┛ ┗┛ ┗━━┛ ┗━━┛ ┗┛ ┗━━┛ ┗━━┛
- キャラクター
左から「NINJA_A
,NINJA_B
,PRINCESS
,KING
,SHURIKEN
」
X ∩ ∧M∧ ∧_∧ ∧_∧ ∩ ∠ ̄\∩ ⊂ \ (゚ー゚*) ( ゚∀゚) <#・+・>彡 -==卍 |/゚U゚Lノ /ヽ ) ⊂(|0|)⊃ /~~〉 〈/つ / 三三つニつ-=≡卍 ~~( ニ⊃ ⊂二 ,)~~ ノ~~~ヽ ノ ノ| |、 / /ミヽ -=卍 -==卍 ( ヽ/ /|,∩,/| ノ~~~~~ヽ ~(__)__)ゝ~~~ //~> > ∪ -=≡卍 ノ>ノ U\_フ ∪ ∪ ~~~~~~~~~~~ \) (_)
- 文字
左から「OK
,NG
」
***** * * * * ***** * * * * ** * * * * ** * * * * *** * * * * * ** * * ***** * * * * *****
- 結果の表示用
上から「SUCCESS
,FAILED
」
∧_∧ / ̄ ̄ ̄ ̄ /\( ・∀・)/ヽ < {}points! Congratulation! ( ☆ ⊂ つ ☆ ) \____ \/⊂、 ノ \ノ し’ ∧_∧ / ̄ ̄ ̄ ̄ ( ・_・) < {}points. Please retry. ( つ と) \____ と_)_)
■ 簡単な効果音のモジュール
ゲームで使用できる簡単な効果音をまとめたモジュールです。以下のリンクから「sound.py
」をダウンロードして、Studuino:bit内に保存してください。このモジュールは次回以降のレッスンでも使用します。
※ リンクの上にカーソルを合わせて右クリックし、「名前を付けてリンク先を保存」を選択してください。
このモジュールには以下の関数が定義されています。
関数名 | 内容 |
---|---|
success() | 「ピンポンピンポーン」という成功音 |
failed() | 「ブッブー」という失敗音 |
operation() | 「ビ」という操作音 |
incorrect() | 「ブー」という不正解音 |
congratulation() | 「ドレミファソラシド」が順番に鳴るメロディ |
countdown() | 「3・2・1・0」とLEDディスプレイに数字を表示しながら音を鳴らすカウントダウン |
3. 4 忍者ゲームのプログラムの作成
上記のモジュールを利用して、ゲームのプログラムを作成します。まずは、このゲームに必要な機能を確認しましょう。
【 ゲームに必要な主な機能 】
- ゲーム開始までをカウントダウンする機能
- ゲーム開始から経過した時間を計測する機能
- ランダムにキャラクターを選択し、ターミナルウィンドウに表示する機能
- 光センサーの上を手が通過したかどうかを検知する機能
- ポイントの獲得や減点を判定する機能
- 制限時間の経過後にポイントが目標点を超えたかどうかを判定する機能
また、このプログラムは以下の流れで処理を行います。プログラムの主な処理はstart_game()
関数としてまとめ、ターミナルから呼び出して実行できるようにします。
【 プログラムの処理の主な流れ 】
では、順番に各機能を作成していきましょう。
■ 各種モジュールのインポート
はじめに、プログラム内で使用する各種オブジェクトやモジュールをインポートしましょう。新たにStuduino:bit上に保存したモジュールpicture(.py)
とsound(.py)
に加えて、ランダムな動作を行わせるためにrandom
モジュールと、経過時間を確認するためにtime
モジュールをインポートします。
from pystubit.board import lightsensor, buzzer
import picture # 新たにStuduino:bit上に保存したモジュール
import sound # 新たにStuduino:bit上に保存したモジュール
import random
import time
■ ゲーム開始までをカウントダウンする機能
ゲーム開始の合図として、ターミナルウィンドウに3から0までのアスキーアートの数字を順番に表示します。このカウントダウンを行うプログラムを短くまとめるために、使用するアスキーアートの数字を表示順にタプルnumbers
に格納します。
追加【7行目~12行目】
from pystubit.board import lightsensor, buzzer
import picture
import sound
import random
import time
numbers = (
picture.THREE,
picture.TWO,
picture.ONE,
picture.ZERO
)
次に、start_game()
関数を宣言して、作成したタプルnumbers
から順番に要素を取り出してターミナルに表示するコードを追加します。このときターミナルウィンドウへの表示以外に、ブザー音("C6"
)を600ミリ秒間鳴らし、次の数字の表示までさらに600ミリ秒待つようにしてみましょう。
追加【14行目~18行目】
from pystubit.board import lightsensor, buzzer
import picture
import sound
import random
import time
numbers = (
picture.THREE,
picture.TWO,
picture.ONE,
picture.ZERO
)
def start_game():
for number in numbers:
print(number)
buzzer.on("C6", duration=600)
time.sleep_ms(600)
このstart_game()
関数は、ターミナルから呼び出します。動作を確認する前に、以下の手順でターミナルウィンドウの高さを調整しましょう。
- エディタエリアの下部にある横線にカーソルを合わせると形が変化します。そこでカーソルをドラッグすると、ターミナルウィンドウの高さを調整することができます。
- あらかじめ、「Enterキー」を何度か押して「
>>>
」でターミナルウィンドウを埋めておき、ちょうど7行分の高さになるように「1の作業」を行って調整します。
アスキーアートはすべて6行の高さで用意しており、print()
関数で表示すると最後に1つ改行文字が追加されるため、ターミナルエリアの高さを7行とすることで、常に1つのアスキーアートだけが見えるようになります。
■ 経過時間を確認する機能
次に、ゲーム開始から経過した時間を確認する機能を追加します。まずは、制限時間の30秒(30000ミリ秒)を定数LIMIT_TIME
として定義します。
追加【7行目】
from pystubit.board import lightsensor, buzzer
import picture
import sound
import random
import time
LIMIT_TIME = 30000
続けて、start_game()
関数の中でカウントダウンを終えた後に、ゲームを開始した時間を記録するコードを追加します。そして、経過時間が制限時間を超えない間は処理を繰り返すコードも追加します。このwhile
文の中にキャラクターの表示やポイントの獲得や減点などを行うコードを書いていきます。
追加【22行目~24行目】
def start_game():
for number in numbers:
print(number)
buzzer.on("C6", duration=600)
time.sleep_ms(600)
start_time = time.ticks_ms() # ゲームの開始時間を記録する
while time.ticks_diff(time.ticks_ms(), start_time) < LIMIT_TIME: # 開始時間と現在の時間の差分を計算して、制限時間と比較する
pass # まだ中身がないため、pass文を書いています
■ ランダムに選んだキャラクターを表示する機能
ターミナルウィンドウに表示するキャラクターをランダムに選ぶため、random
モジュールのchoice()
関数を使います。このchoice()
関数は引数としてタプルを受け取り、その要素の中からランダムにひとつを選びます。
そこで、ターミナルに表示するキャラクターをタプルcharacters
にまとめます。ただし、後の判定で使用するために、敵の場合は"enemy"
を、味方の場合は"ally"
を2つめの要素として持たせたタプルとして追加します。
※ enemy
は英語で「敵」を意味し、ally
は英語で「味方」を意味します。
追加【14行目~19行目】
from pystubit.board import lightsensor, buzzer
import picture
import sound
import random
import time
LIMIT_TIME = 30000
numbers = (
picture.THREE,
picture.TWO,
picture.ONE,
picture.ZERO
)
characters = (
(picture.NINJA_A, "enemy"),
(picture.NINJA_B, "enemy"),
(picture.PRINCESS, "ally"),
(picture.KING, "ally")
)
続けて、キャラクターをランダムに選び表示します。キャラクターを表示するときはsound
モジュールからoperation()
関数を実行して、効果音も加えてみましょう。
追加・変更【29行目~31行目】
def start_game():
for number in numbers:
print(number)
buzzer.on("C6", duration=600)
time.sleep_ms(600)
start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), start_time) < LIMIT_TIME:
character = random.choice(characters)
sound.operation() # 効果音
print(character[0]) # タプルの1番目の要素がアスキーアートの文字列であることに注意
※ while
文の下にあったpass
文は削除しています。
さらに、キャラクターを表示するまでに待つ時間も0.5秒(500ミリ秒)から1.5秒(1500ミリ秒)の間の50ミリ秒きざみの時間からランダムに選んでみましょう。
追加【30行目、31行目】
def start_game():
for number in numbers:
print(number)
buzzer.on("C6", duration=600)
time.sleep_ms(600)
start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), start_time) < LIMIT_TIME:
character = random.choice(characters)
wait_time = random.randint(10, 30) * 50 # 10~30の間でランダムに選んだ数字に50を掛けることで、
time.sleep_ms(wait_time) # 500~1500まで50きざみでランダムな数字を選ぶことができる。
sound.operation()
print(character[0])
50ミリ秒きざみとするために、乱数は10から30の間で選び、それに50ミリ秒をかけています。これによって、「500, 550, …. , 1450, 1500」の中からランダムに選ぶのと同じ結果となります。
■ 光センサーの上を手が通過したかどうかを感知する機能
光センサーの上を手が通っている間はセンサー値が小さくなっています。このことから、一度でも設定したしきい値を下回った場合、手が通過したとみなすことにします。
では、しきい値を決めるために、新しいプログラムを作成して以下の【 サンプルコード 3-4-1 】を実行しましょう。手で覆い隠したときと、そうでないときの光センサーの値を確認してください。
【 サンプルコード 3-4-1 】
from pystubit.board import lightsensor
import time
while True:
print(lightsensor.get_value())
time.sleep_ms(500)
調べた値から決めたしきい値は、元のプログラムに定数THRESHOLD
として定義しましょう。
追加【8行目】
from pystubit.board import lightsensor, buzzer
import picture
import sound
import random
import time
LIMIT_TIME = 30000
THRESHOLD = 2000 # 自分で決めたしきい値を設定してください
続けて、定義したしきい値を使って光センサーの上を手が通過したかどうかを判定する処理を書いていきます。
光センサーの上を手が通過した場合は、手裏剣を投げて攻撃したことになります。攻撃が行われたかどうかを判断するためのフラグとして変数is_attacked
を用意します。この変数is_attacked
には、はじめFalse
を入れておき、光センサーの上を手が通過した場合はTrue
を入れます。
また、プレイヤーへ攻撃するかどうかを瞬時に判断させるために、600ミリ秒間だけこの検知を行います。この間に光センサーの上を手が通過しなかった場合は、攻撃しなかったものとして処理します。
追加【36行目~39行目】 下の4行
def start_game():
for number in numbers:
print(number)
buzzer.on("C6", duration=600)
time.sleep_ms(600)
start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), start_time) < LIMIT_TIME:
character = random.choice(characters)
wait_time = random.randint(10, 30) * 50
time.sleep_ms(wait_time)
sound.operation()
print(character[0])
is_attacked = False # 手裏剣を投げて攻撃したかどうかの判定用フラグ
input_start_time = time.ticks_ms() # 600ミリ秒の計測を始めた開始時間を記録
while time.ticks_diff(time.ticks_ms(), input_start_time) < 600: # 600ミリ秒間だけ検知
pass # まだ中身がないため、pass文を書いています
while
文の中で、光センサーの値を取得し、しきい値と比較します。光センサーの上を手が通過した場合は、is_attacked
をTrue
に変え、手裏剣を投げるアスキーアートpicture.SHURIKEN
を600ミリ秒間表示しましょう。
追加・変更【39行目~43行目】
is_attacked = False
input_start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), input_start_time) < 600:
val = lightsensor.get_value() # 光センサーの値を取得
if val < THRESHOLD: # 設定したしきい値と比較
is_attacked = True # 手裏剣を投げたと判断して、フラグを立てる
print(picture.SHURIKEN) # 手裏剣を投げるアスキーアートの表示
time.sleep_ms(600) # 600ミリ秒間ターミナルウィンドウに表示
■ ポイントの獲得と減点を判定する機能
攻撃(手裏剣を投げたこと)を検知したときは、表示したキャラクターによってポイントを獲得するのか、それとも減点するのかを判定します。この判定の組み合わせは以下の表のようになります。
攻撃した is_attacked == True | 攻撃していない is_attacked == False | |
---|---|---|
敵 "enemy" | +1 | -1 |
味方 "ally" | -1 | +1 |
では、獲得したポイントを記録しておくための変数score
を定義します。start_game()
関数の先頭で初期値として「0」を入れておきましょう。
追加・変更【23行目】
def start_game():
score = 0
for number in numbers:
print(number)
buzzer.on("C6", duration=600)
time.sleep_ms(600)
47行目からポイントの獲得と減点の判定を行うコードを追加します。上の表を参考にして、分岐処理を書きましょう。また、ポイントを獲得したときはアスキーアートpicture.OK
を表示し、sound
モジュールからcorrect()
関数を実行しましょう。反対に、減点になったときはアスキーアートpicture.NG
を表示し、sound
モジュールからincorrect()
関数を実行しましょう。
追加【47行目~64行目】 ※if is_attacked: # 攻撃した場合 から追加
is_attacked = False
input_start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), input_start_time) < 600:
val = lightsensor.get_value()
if val < THRESHOLD:
is_attacked = True
print(picture.SHURIKEN)
time.sleep_ms(600)
if is_attacked: # 攻撃した場合
if character[1] == "enemy": # タプルの2番目の要素で敵か味方かを判断
score += 1 # 成功の場合1点追加
print(picture.OK) # 「OK」のアスキーアートを表示
sound.correct() # 成功音
else:
score -= 1 # 失敗の場合1点減点
print(picture.NG) # 「NG」のアスキーアートを表示
sound.incorrect() # 失敗オン
else: # 攻撃していない場合
if character[1] == "enemy":
score -= 1
print(picture.NG)
sound.incorrect()
else:
score += 1
print(picture.OK)
sound.correct()
そして、次のループに移るまで600ミリ秒待ち、さらに5行分の改行文字を表示してターミナルウィンドウの表示を空にするコードを追加しましょう。
追加【66行目、67行目】 ※下の2行
else:
if character[1] == "enemy":
score -= 1
print(picture.NG)
sound.incorrect()
else:
score += 1
print(picture.OK)
sound.correct()
time.sleep_ms(600)
print("\n\n\n\n\n") # ターミナルウィンドウの表示を空にする。
■ 結果を判定して表示する機能
最後に、獲得したポイントがゲームクリアの基準とする「7点」以上かどうかを判定し、結果を表示する処理を追加します。まずは、このゲームクリアの基準となる得点を定数CLEAR_SCORE
として定義しましょう。
追加【9行目】 ※最後の1行
from pystubit.board import lightsensor, buzzer
import picture
import sound
import random
import time
LIMIT_TIME = 30000
THRESHOLD = 2000
CLEAR_SCORE = 7
経過時間が制限時間を超えたかどうかを判定しているwhile
文を抜けたあとに、ゲームクリアの判定を行います。ここで、ゲームの結果を表示するためのアスキーアートとしてpicture.SUCCESS
とpicture.FAILED
を用意していたことを思い出してください。
picture.SUCCESS
∧_∧ / ̄ ̄ ̄ ̄ /\( ・∀・)/ヽ < {}points! Congratulation! ( ☆ ⊂ つ ☆ ) \____ \/⊂、 ノ \ノ し’
picture.FAILED
∧_∧ / ̄ ̄ ̄ ̄ ( ・_・) < {}points. Please retry. ( つ と) \____ と_)_)
このアスキーアートの文字列の中には、「{}
」が含まれています。{}
は文字列のformat()
メソッドで指定した別の数値や文字列に変換することができました。そこで、獲得したポイントをformat()
メソッドの引数として渡し、その戻り値をターミナルウィンドウへ表示します。
さらに、ゲームをクリアしたときと、失敗したときでそれぞれsound
モジュールに用意されている関数を実行して音を鳴らしましょう。
追加【69行目~74行目】
※ インデントの位置に注意してください。
time.sleep_ms(600)
print("\n\n\n\n\n")
if score >= CLEAR_SCORE: # ゲームクリア成功
print(picture.SUCCESS.format(score)) # 得点を追加してクリア成功のアスキーアートを表示
sound.success()
else: # ゲームクリア失敗
print(picture.FAILED.format(score)) # 得点を追加してクリア失敗のアスキーアートを表示
sound.failed()
完成したプログラムは以下のようになります。作成したプログラムと見比べて誤りがないかを確認して、プログラムを実行しましょう。
【 サンプルコード 3-4-2 】
from pystubit.board import lightsensor, buzzer
import picture
import sound
import random
import time
LIMIT_TIME = 30000
THRESHOLD = 2000 # 自分で決めたしきい値を設定してください
CLEAR_SCORE = 7
numbers = (
picture.THREE,
picture.TWO,
picture.ONE,
picture.ZERO
)
characters = (
(picture.NINJA_A, "enemy"),
(picture.NINJA_B, "enemy"),
(picture.PRINCESS, "ally"),
(picture.KING, "ally")
)
def start_game():
score = 0
for number in numbers:
print(number)
buzzer.on("C6", duration=600)
time.sleep_ms(600)
start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), start_time) < LIMIT_TIME:
character = random.choice(characters)
wait_time = random.randint(10, 30) * 50
time.sleep_ms(wait_time)
sound.operation()
print(character[0])
is_attacked = False
input_start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), input_start_time) < 600:
val = lightsensor.get_value()
if val < THRESHOLD:
is_attacked = True
print(picture.SHURIKEN)
time.sleep_ms(600)
if is_attacked:
if character[1] == "enemy":
score += 1
print(picture.OK)
sound.correct()
else:
score -= 1
print(picture.NG)
sound.incorrect()
else:
if character[1] == "enemy":
score -= 1
print(picture.NG)
sound.incorrect()
else:
score += 1
print(picture.OK)
sound.correct()
time.sleep_ms(600)
print("\n\n\n\n\n")
if score >= CLEAR_SCORE:
print(picture.SUCCESS.format(score))
sound.success()
else:
print(picture.FAILED.format(score))
sound.failed()
チャプター4
課題:新しいアクションを追加してゲームの難易度を上げよう
【 サンプルコード 3-4-2 】では、味方の「姫様」や「殿様」が表示されたときは、何もしないことでポイントが入るようになっていましたが、この課題ではゲームの難易度を上げるために、姫様が表示されたときはボタンAを押すというアクションを追加してみましょう。
- 姫様 ⇒ ボタンAを押す
∧M∧ (゚ー゚*) ⊂(|0|)⊃ ノ~~~ヽ ノ~~~~~ヽ ∪ ∪
- 殿様 ⇒ 何もしない
∧_∧ ( ゚∀゚) /~~〉〈/つ ノ ノ| |、 ~(__)__)ゝ~~~ ~~~~~~~~~~~
これによって、ポイントの獲得や減点の設定を以下のように変更します。攻撃したときは同じですが、攻撃していないときにボタンAを押したかどうかでポイントが変わります。また、味方には"ally"
という値を設定していましたが、姫様と殿様を見分けるために、それぞれ"princess"
と"king"
に変更します。
攻撃した | 攻撃していない かつ ボタンAを押した | 攻撃していない かつ ボタンAを押していない | |
---|---|---|---|
敵 "enemy" | +1 | -1 | -1 |
姫様 "princess" | -1 | +1 | -1 |
殿様 "king" | -1 | -1 | +1 |
4. 1 プログラムの作成例
【 サンプルコード 3-4-2 】へコードの追加と変更を順番に行います。まずは、ボタンAを新たに使用するため、プログラムの先頭でbutton_a
オブジェクトをインポートしましょう。
変更【1行目】
from pystubit.board import button_a, lightsensor, buzzer
次にキャタクターのアスキーアートに付けている値を変更します。"ally"
からそれぞれ"princess"
と"king"
に変更しましょう。
変更【19行目、20行目】
characters = (
(picture.NINJA_A, "enemy"),
(picture.NINJA_B, "enemy"),
(picture.PRINCESS, "princess"),
(picture.KING, "king")
)
ボタンAが押されたかどうかは、button_a
オブジェクトのwas_pressed()
メソッドを利用して判定します。このメソッドは過去にボタンAが押されたかどうかを調べるものですが、攻撃の判定と関係のないところでボタンAを押したときにも記録されてしまいますので、攻撃の判定処理の直前で実行しておき、記録を消去しておきましょう。
追加【40行目】
is_attacked = False
button_a.was_pressed() # ボタンAが押された記録を消去
input_start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), input_start_time) < 600:
val = lightsensor.get_value()
最後に攻撃判定によって、ポイントの獲得か減点かを判定するコードを変更します。攻撃があった場合(変数is_attacked
がTrue
の場合)に変更はありません。攻撃がなかった場合(変数is_attacked
がFalse
の場合)は、"enemy"
だけでなく"princess"
と"king"
でも処理を分けます。
次の表を参考にして、コードを書きましょう。
攻撃していない かつ ボタンAを押した | 攻撃していない かつ ボタンAを押していない | |
---|---|---|
敵 "enemy" | -1 | -1 |
姫様 "princess" | +1 | -1 |
殿様 "king" | -1 | +1 |
追加・変更【63行目~80行目】
if is_attacked:
if character[1] == "enemy":
score += 1
print(picture.OK)
sound.correct()
else: # "princess"も"king"もどちらもこの中のコードが実行される。
score -= 1
print(picture.NG)
sound.incorrect()
else:
if character[1] == "enemy":
score -= 1
print(picture.NG)
sound.incorrect()
elif character[1] == "princess": # 姫様のとき
if button_a.was_pressed(): # ボタンAが押された
score += 1
print(picture.OK)
sound.correct()
else: # ボタンAが押されていない
score -= 1
print(picture.NG)
sound.incorrect()
else: # "殿様"のとき
if button_a.was_pressed(): # ボタンAが押された
score -= 1
print(picture.NG)
sound.incorrect()
else: # ボタンAが押されていない
score += 1
print(picture.OK)
sound.correct()
これでプログラムの完成です。全体のプログラムは次のようになります。うまく動作しない場合は、こちらと見比べて、誤りがないかどうか確認してください。
【 サンプルコード 4-1-1 】
from pystubit.board import button_a, lightsensor, buzzer
import picture
import sound
import random
import time
LIMIT_TIME = 30000
THRESHOLD = 2000
CLEAR_SCORE = 7
numbers = (
picture.THREE,
picture.TWO,
picture.ONE,
picture.ZERO
)
characters = (
(picture.NINJA_A, "enemy"),
(picture.NINJA_B, "enemy"),
(picture.PRINCESS, "princess"),
(picture.KING, "king")
)
def start_game():
score = 0
for number in numbers:
print(number)
buzzer.on("C6", duration=600)
time.sleep_ms(600)
start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), start_time) < LIMIT_TIME:
character = random.choice(characters)
wait_time = random.randint(10, 30) * 50
time.sleep_ms(wait_time)
sound.operation()
print(character[0])
is_attacked = False
button_a.was_pressed()
input_start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), input_start_time) < 600:
val = lightsensor.get_value()
if val < THRESHOLD:
is_attacked = True
print(picture.SHURIKEN)
time.sleep_ms(600)
if is_attacked:
if character[1] == "enemy":
score += 1
print(picture.OK)
sound.correct()
else:
score -= 1
print(picture.NG)
sound.incorrect()
else:
if character[1] == "enemy":
score -= 1
print(picture.NG)
sound.incorrect()
elif character[1] == "princess":
if button_a.was_pressed():
score += 1
print(picture.OK)
sound.correct()
else:
score -= 1
print(picture.NG)
sound.incorrect()
else:
if button_a.was_pressed():
score -= 1
print(picture.NG)
sound.incorrect()
else:
score += 1
print(picture.OK)
sound.correct()
time.sleep_ms(600)
print("\n\n\n\n\n")
if score >= CLEAR_SCORE:
print(picture.SUCCESS.format(score))
sound.success()
else:
print(picture.FAILED.format(score))
sound.failed()
チャプター5
おわりに
5. 1 このレッスンのまとめ
このレッスンでは、モジュールとパッケージについて改めて学習をしました。そして、あらかじめ用意されたモジュールを利用することで、効率良くゲームを製作できることも確認しました。
Pythonでは作成したモジュールやパッケージをインターネット上に公開して共有するコミュニティがあります。その中には優れたものも数多くあり、必要なものを探して利用することで、効率的にプログラムを書くことができます。このコミュニティの利用方法についてはテーマ11以降で紹介していきます。
5. 2 次のレッスンについて
次回のレッスンでは、Studuino:bitの加速度センサーを使用したゲームを製作します。また、「1, 2, 3, 4, 5」のようにある一定の規則で並ぶ値をリストに格納するときに便利な「内包表記」について新たに学習します。