B10 [宿題]

B10_1 (スクリプト)

ねらい: 新しいデータ型を作る前に,データ型に実装する機能を整理し,実装する.ねらいは課題A10_1と同じ.

※この課題では,新しいデータ型の作成は行わない.

以下の手順に従って,タートルグラフィクスのタートルを等速直線運動させよ.

  1. 変数 x, y に位置,変数 vx, vy に速度を保持するものとし,初期位置として (x, y) = (0, 100),初期速度として (vx, vy) = (10, 0) を設定する.
  2. turtle モジュールをインポートし,turtle.Turtle型のインスタンス kame を生成する.タートルの形状やサイズは好みで変更してよい.
  3. (初期位置への移動) ペンを上げてタートルを位置 (x, y) に移動させる.
  4. 描画処理1回あたりの経過時間を保持する変数 dt を用意し,dt = 0.1 とする.
  5. 以下の描画処理を無限ループで繰り返す.
    1. (位置の更新) 位置 x, y に時間 dt の間の移動量 vx * dt, vy * dt を加える.
    2. (軌跡の描画) ペンを下げ,現在位置から前のステップで更新された位置 (x, y) へタートルを移動させて線を描画する.

B10_2 (スクリプト)

ねらい: 新しいデータ型を作成する方法を習得する.データ型を使用しない場合との相違を確認する.ねらいは課題A10_2と同じ.

課題B10_1に対応させて,2次元平面上を運動する質点を表す新しいデータ型 PointMass を作成せよ.

データ型 PointMass の仕様は以下の通りである.

作成したデータ型を用いて,課題B10_1と同様の動作を実現せよ. 作成後のプログラムの一部を示す.データ型 PointMass を定義した後,以下のようにしてデータ型 PointMass を利用するものとする.

import turtle

class PointMass:
    def __init__(self):
        self.x = 0

(中略)

point = PointMass()
point.setup()
while True:
  dt = 0.1
  point.move(dt)

B10_3 (スクリプト)

ねらい: 初期化メソッドを使用して,インスタンスの生成時に必要な設定を行う方法を習得する.ねらいは課題A10_3と同じ.

課題B10_2で作成したデータ型 PointMass の初期化メソッドを,以下の仕様に変更せよ.

なお,本変更を加えた場合に,課題B10_2におけるインスタンスの生成部分が point = PointMass() のままだとタートルが中央で静止したままになる(理由は考えてみよ).動作を確認するために,PointMass 型のインスタンスを生成する際に,適当な初期値を与えるようにせよ.


B10_4 (スクリプト)

ねらい: データ型を作成することで,複数のインスタンスを簡単に扱えることを実感する.

課題B10_3で作成したデータ型 PointMass を用いて,複数のタートルを同時に動かすことを考える.

まず,PointMass 型のインスタンスを10個生成し,生成されたインスタンスをリスト list_points で保持せよ(※). 初期値は以下のようにランダムに指定せよ.

次に,無限ループ内で,リスト list_points の要素として保持されている PointMass 型のインスタンス全てについて,移動処理を行え(※※).

余力のある人は,メソッド move() を改良し,タートルの向きを進行方向に対して一致させよ.ヒント: 逆三角関数を使う.

(※)のヒント: 空リストを作りインスタンスを後ろに追加(append)していけばよい.

(※※)のヒント: for文を使うとリストの要素を1つずつに対して処理を行える.リストの要素が PointMass 型でも同じである.


コラム: turtle.Turtle 型の移動を速くするには

本課題のように10個(匹?)のタートルの描画を同時に行うと,デフォルトの速度設定ではかなり遅く感じられる.

高速化する方法1: turtle.Turtle 型のメソッド speed() を用いて速度を設定する.引数に0を与えると,途中のアニメーションを省略するため最高速になる.

self.kame.speed(0)

高速化する方法2: turtle モジュールの関数 delay() を用いて描画の遅延を設定する.引数に0を与えると最高速になる.

turtle.delay(0)

見違えるほど高速になり気持ちがよいので,一度試してみてほしい.


コラム: 関数 range() を使って繰り返す際の変数の名前

本課題では,PointMass 型を複数生成する際に,for 文と関数 range() を使っていることと思う(もし10回同じ行をコピー&ペーストしていたら,for 文の使い方を復習せよ).

繰り返し変数を繰り返し処理内で使用しない場合も,文法上,何らかの変数名を指定する必要がある.このような場合は,変数名を _ とすることがPythonでは慣例的である.

for _ in range(10):
   :
   (ブロック内では変数 _ を使わない暗黙の了解)

変数名を _ とする他の例として,関数やメソッドから複数の値がタプルで返ってくるが,一部の値を利用しない場合に,戻り値を代入する先として用いられるなどの使い方がなされる.


B10_5 (スクリプト,オプション)

課題B10_4までで作成したデータ型 PointMass に,重力加速度による影響を加えよ.


B10_6 (スクリプト,オプション)

更なる発展として,以下のうち任意の要素を実装せよ.複数実装してもよい.


B10_7 (スクリプト,オプション)

ねらい: 作成したプログラムが適切に動作しているかを確認する.

本課題は課題A10_4の続きである.

データ型 Dice が適切に動作していることを確認するために,最大値と最小値をチェックすることを考えてみる.

  1. n面サイコロを作成する.
  2. 10000回振り,出目を記録する.
  3. 得られた出目の最大値と最小値を求める.
  4. 最大値および最小値に基づいて,出目が適切であれば「n面サイコロ - OK!」,そうでなければ「n面サイコロ - NG!」と表示する.NGの場合は次の行に,どのようにおかしいのかを表示する.

まず,上記の手順を,面数が 1, 2, ..., 100 であるサイコロを順に生成して行うスクリプトを作成して実行し,結果を確認せよ.この段階では,OKしか出ないはずである.

次に,メソッド shoot() に対して,以下の変更を加えよ.

【誤り生成機能】 生成されたランダムな値に対して,(1) 10000分の1の確率で1を加える (2) 10000分の1の確率で1を引く,の各処理を行った後,戻り値として返す.

再度スクリプトを実行し,結果を確認せよ.

提出するスクリーンショットは,Anaconda Prompt の画面をスクロールさせて探し,NG!の項目が表示されており,かつ最大値がおかしいものと最小値がおかしいものが両方入っている箇所とせよ.スクリーンショットに適した箇所がない場合は,何度か実行せよ.


B10_8 (スクリプト,オプション)

ねらい: 更に複雑なデータ属性やメソッドを作成する方法を習得する.

※課題B10_7を実施した場合には,メソッド shoot() に【誤り生成機能】を追加していると思われるが,本課題の実施にあたっては削除しておくこと.

これまでに作成したサイコロに,「いかさまモード」を追加する.このモードの仕様は以下の通りである.

また,いかさまモードが正しく動作しているかを確認するために,以下のメソッドを追加する.

面数が同じ Dice 型のインスタンスを2つ生成し,片方をいかさまモードにした後,それぞれを10000回実行し,各サイコロの出目の回数を表示して,いかさまモードが正しく動作していることを確認せよ.

コメント: メソッド aggregate() の実装について,aggregate()が呼ばれるたびに全履歴から回数を集計する方法と,あらかじめ各出目の回数をカウントする辞書を用意しておき,サイコロを振る(shoot())たびにカウントする方法が考えられる(他にもあるかもしれない)が,一長一短である.前者はデータの管理が楽だが頻繁に呼び出されると速度が遅い.後者は高速だが履歴と各出目の回数の2つのデータを常に整合させておかなければならない.