これまでに習得したプログラミング技術を使って、 簡単な物理シミュレータを作ってみよう (Fig. 1)。

Figure 1: (fig:fig110_010) CAPTION

Figure 1: (fig:fig110_010) CAPTION

1 シミュレーション

シミュレーションといっても難しいことはない。 諸君らはシミュレータの1つを既に作成した。 前回の課題であるドラクエ風戦闘シミュレータがそれだ。 さて、シミュレーションを行うには、以下の要素をどのように定めるかが重要である。

1.1 ドラクエ風戦闘シミュレータの復習

まず、前回作ったドラクエ風戦闘シミュレータでは、 それぞれの要素がどのようであったかを復習してみよう。

1.1.1 目的

ドラクエ風戦闘シミュレータの目的は、 戦ったらどちらが勝つかということを知ることである。

1.1.2 対象物のモデル化

ドラクエ風戦闘シミュレータでは、シミュレーションの対象物は人間である。 人間の能力を「攻撃力・防御力・HP」という3つのパラメータだけで表現した。

実際の人間にはその他に「素早さ」や「体重」など、様々な要素があるが、 それら全てを無視して、実に大雑把な近似を行った。 また、ダメージを受けても攻撃力や防御力が低下しないというのは リアルな感覚とも乖離しているが、 ドラクエではそういうことを気にしてはいけない。

1.1.3 タイムステップ

ドラクエ風戦闘シミュレータでは、ターンという概念で時間の単位を表現した。 ターンに具体的に何秒という取り決めはなく、 「全員が1回ずつ行動できる程度の時間」という感じで捉えられる。 とにかくドラクエでは、 時間の単位であるターンの積み重ねで時間の経過が表現されている。 実際の戦闘ではターンという縛りなどあるはずもないが、 ドラクエではそういうことを気にしてはいけない。

1.1.4 状態遷移

1ターン内の処理のこと。 次のターン(時間)の状態を変化させていくということで、 具体的には 「攻撃力と防御力からダメージを算出し、HP を減じるという処理を 双方について行う」ということ。

1.1.5 初期条件

両者に最初に設定した攻撃力・防御力・HP のこと。

たとえて言えば、 状態遷移はその世界のルールと言える。 対して初期条件はそのルールに則って動くモノタチであると言える。

1.1.6 終了条件

どちらかが HP が 0 になったところで戦闘能力を喪失し、 状況変化が終了する。 あるいは 30ターンで審判が戦闘を止める。

1.2 弾道シミュレータ

ここは バケツに入るモンスター(バケットモンスター、バケモン)のいる世界。 バケモンを捕えるためには怪物玉というボールを投げ当てる必要がある。 怪物玉を投げる速さと角度を制御して、バケモン ゲットだぜ!

1.2.1 目的

本シミュレーションの目的は、 怪物玉がバケモンに衝突するかどうかを判定することと設定する。

1.2.2 対象物のモデル化

投射された玉が関係する物理にはさまざまなものがあるが、 そのどれを取り込むかは要求される精度次第である。 今回は精度よりもシミュレータを作るということ自体が目的のため、 複雑な現象は取り込まない。 すなわち、

重力のみがかかる単純な放物線運動を想定する場合、 玉の質量はパラメータとして不要である。 真空中では羽根も鉄球も同じ速度で落下することは当然知っているだろう。 しかしたとえば空気抵抗がかかる場合、 玉の質量は重要なパラメータとなる。 ゴルフボールとピンポン玉はサイズは同程度であっても質量が異なる。 これらを同じ速度・角度で打ち出した場合、 ゴルフボールの方が遠くに飛ぶことは実感として理解できるだろう。 ゴルフボールの方がよく飛ぶ理由には表面の窪みの効果もある。 この辺は流体力学の問題になる。 勿論、今回はこの効果も無視する。

以上のことから玉が任意の時刻 \(n\) に持つべきパラメータは以下の通りとなる。 (Fig. 2)

Figure 2: (fig:fig110_020) 瞬間ごとの座標・速度と次の瞬間の状態演算を示した模式図。

Figure 2: (fig:fig110_020) 瞬間ごとの座標・速度と次の瞬間の状態演算を示した模式図。

なお、地面は水平で高低差がないものとする。

1.2.3 タイムステップ

シミュレーションを行う時間単位を \(t\) 秒とする。 とりあえずは \(t=0.1\) 秒としておこう。 課題ができた後で余裕があれば、\(t = 0.01\)\(t = 1\) などとして 精度にどの程度の影響が出るか 試してみると良い。

1.2.4 状態遷移

ある瞬間 \(n+1\) における玉の状態は、 \(t\) 秒前の瞬間 \(n\) のパラメータを用いて以下の式で表せるものとする。 1 (Fig. 2)

\[\begin{aligned} x_{n+1} &=& x_n + v_{x, n} t \\ y_{n+1} &=& y_n + v_{y, n} t \\ v_{x, n+1} &=& v_{x, n} \\ v_{y, n+1} &=& v_{y, n} - g t\end{aligned} \]

ここで、 \(g\) は重力加速度である。 本プログラムでは \(g = 9.8\) [m/s\(^2\)] であるとする。

1.2.5 初期条件

君が投げられる球速は、 10〜40 [m/s] とする。 投げ出す角度 \(\theta\) は任意。 リリースポイントの高さは地表から +1.5m の高さであるとする。

1.2.6 終了条件

地面に衝突したとき ( すなわち \(y \le 0\) となったとき ) を終了条件とする。

1.3 描画

シミュレーションでは、 最終的な結果だけでなく、 その途中の状態遷移も重要である。 プログラムを実行したあと、 「玉は 1.2 秒後に距離 25.34 m の地点で地面と衝突しました。」 とだけ出力されたならば、 計算結果が正しいのか何となく不安になる。

一つの方法は、ループごとに座標、時間の数値を画面に表示するということである。

t =   0.0, x =      0, y =      0
t =   0.1, x =     70, y =     71
t =   0.2, x =    141, y =    142
:
t = 144.4, x = 102106, y =      6
t = 144.5, x = 102176, y =    -64

しかし軌跡というものはできればグラフィカルに表示したいものだ。 コンソールで簡単に表示しようと思うが、 描画の問題は本授業の範囲から越える。 サンプルコードを以下に示すので、余力があれば解読を試みられたい。

CONSOLE_WIDTH  = 45 # コンソールの幅, 文字数
MAX_X  = 30.0       # 描画できる最大距離 [m]
CONSOLE_HEIGHT = 15 # コンソールの高さ, 文字数
MAX_Y  = 30.0       # 描画できる最大高さ [m]
# コンソールでグラフィカルに表示する。
# x, y はそれぞれ距離、高さの数値 [ m ] 。
def view( x, y )
x_show = x.to_i / ( MAX_X / CONSOLE_WIDTH )
y_show = y.to_i / ( MAX_Y / CONSOLE_HEIGHT )
CONSOLE_HEIGHT.downto(0) do |i| #縦軸は、上から描画
  if y_show == i
    CONSOLE_WIDTH.times do |j| #横軸は、左から描画
      if x_show == j
        print "*"
      else
        print " "
      end
    end
  end
  print "\n"
end
print "|" + "--------------------------------------------|#{MAX_X}\n"
# ↑ - の数は CONSOLE_WIDTH - 1個
end

while ( y > 0.0 )
# x の値を更新
# y の値を更新
system( "cls" ) # コンソール画面のクリア。
print "[t = #{t} seconds]\n"
print "[x = #{x} m]\n"
print "[y = #{y} m]\n"
view(x, y)
end

2 練習問題

君の前方 20 m 離れた位置にバケモン『電気鼠』がいる。 電気鼠に命中させるための 怪物玉を投げる速度と角度を見つけ出せ。 ただし、電気鼠の前後 0.3 m の範囲で着地すれば命中するものとする。 この速度と角度をソースコードに書き込むこと。 シミュレーションに用いるタイムステップは 0.1 秒とする。


  1. これも粗い近似である。 台形近似などを行うと近似の精度が上がるが、 本授業の範囲ではないのでここでは扱わない。 この辺は数値計算法で習うだろう。