モンキーハンティングのシミュレータ(2)

昨日のVPythonを使ったモンキーハンティングのコードを拡張して、GUI画面でパラメータの設定ができるようにした。いつものことながらwxPythonを使用。ちょっとしたモンキーハンティングのシミュレーションソフトになった。使用例を図1に示す。パラメータ設定画面と計算の可視化画面が開く。

図1:本アプリケーション使用例

まず、ファイルMonkeyHunt.pyをダブルクリックするとアプリケーションが起動する。図2が起動画面。これがパラメータの設定画面になる。

図2:パラメータ設定画面

この画面で必要な計算条件を設定する。それらは、

  • ターゲットまでの距離[m]: 2つの物体の水平距離を指定する。
  • ターゲットの高さ[m]: ターゲットの初期位置の地面からの高さを指定する。
  • 弾丸の初期速度[m/s]:打ち出されたときの弾丸の速度の大きさを指定する。
  • 時間刻み[s]:計算の時間刻みを指定する。小さすぎると計算に時間がかかり、大きすぎると正確な軌道が計算できない。

これらを設定した後、Fireボタンを押す。可視化画面が開き軌道が表示される(図3)。赤が弾丸、緑がターゲットを表す。

図3:計算結果の可視化画面

VPythonの機能で、マウスにより表示の回転や拡大が可能(図4)。


図4:表示を回転、拡大する。

赤と緑がぶつかれば、パラメータ設定画面の左下に"You got it."と表示され、はずれると
"You miss it"と表示される。
とりあえずここまで。http://python.sunnycoder.com/にファイルと説明書を置く。

ソースコードMonkeyHunt.pyは以下の通り。

# ---------------------------------------------------------------------------
# program name: Monkey Hunting             
# file name   : MonkeyHunt.py
# version     : 1.00
# date        : Thu Jan 22 12:53:54 JST 2009
# ---------------------------------------------------------------------------
import wx
import os
from visual import *

# constant
gconst = 9.8
eps = 1.0e-2

class MainFrame(wx.Frame):
    def __init__(self, id, title):
        width, height = -1, -1
        wx.Frame.__init__(self, id, title="Monkey Hunting",
                          size=wx.Size(width, height))
        self.current = "  "
        self.sb = self.CreateStatusBar(2)
        self.sb.SetStatusWidths([-1, 150])
        self.sb.SetStatusText(self.current)

        Pan = wx.Panel(self, -1)

        Sizer0 = wx.BoxSizer(wx.VERTICAL)
        Sizer1 = wx.BoxSizer(wx.HORIZONTAL)
        Sizer2 = wx.BoxSizer(wx.HORIZONTAL)
        Sizer3 = wx.BoxSizer(wx.HORIZONTAL)
        Sizer4 = wx.BoxSizer(wx.HORIZONTAL)

        StTxt1 = wx.StaticText(Pan, -1, "Distance to Target [m]:")
        StTxt2 = wx.StaticText(Pan, -1, "Height of Target [m]:")
        StTxt3 = wx.StaticText(Pan, -1, "Initial velocity of bullet [m/sec]:")
        StTxt4 = wx.StaticText(Pan, -1, "Time step size [sec]:")
        self.TxtCtrl1 = wx.TextCtrl(Pan, -1, "10", size=(50, -1))
        self.TxtCtrl2 = wx.TextCtrl(Pan, -1, "10", size=(50, -1))
        self.TxtCtrl3 = wx.TextCtrl(Pan, -1, "10", size=(50, -1))
        self.TxtCtrl4 = wx.TextCtrl(Pan, -1, "0.001", size=(50, -1))
        self.RBtn = wx.Button(Pan, -1, "Fire!")
        self.RBtn.Bind(wx.EVT_BUTTON, self.Fire)

        Sizer1.Add(StTxt1, 0, wx.ALL, 2)
        Sizer1.Add(self.TxtCtrl1, 0, wx.ALL, 2)
        Sizer2.Add(StTxt2, 0, wx.ALL, 2)
        Sizer2.Add(self.TxtCtrl2, 0, wx.ALL, 2)
        Sizer3.Add(StTxt3, 0, wx.ALL, 2)
        Sizer3.Add(self.TxtCtrl3, 0, wx.ALL, 2)
        Sizer4.Add(StTxt4, 0, wx.ALL, 2)
        Sizer4.Add(self.TxtCtrl4, 0, wx.ALL, 2)
        Sizer0.Add(Sizer1, 0, wx.ALL, 2)
        Sizer0.Add(Sizer2, 0, wx.ALL, 2)
        Sizer0.Add(Sizer3, 0, wx.ALL, 2)
        Sizer0.Add(Sizer4, 0, wx.ALL, 2)
        Sizer0.Add(self.RBtn, 0, wx.ALL, 2)
        Pan.SetSizer(Sizer0)
        Sizer0.Fit(self)

    def Fire(self, event):
        dist = float(self.TxtCtrl1.GetValue())
        hght = float(self.TxtCtrl2.GetValue())
        ballv= float(self.TxtCtrl3.GetValue())
        dt   = float(self.TxtCtrl4.GetValue())
        start_sim = VPython_anim(dt, dist, hght, ballv)
        start_sim.simulate()
        if start_sim.result == 0:
            self.sb.SetStatusText("You got it.")
        else:
            self.sb.SetStatusText("You miss it!")


class VPython_anim:
    def __init__(self, dt, dist, hght, ballv):
        self.dt = dt
        self.dist = dist
        self.hght = hght
        self.ballv= ballv
        angl = atan(hght/dist)

        self.f0= box(pos=(0,0.5*hght,-0.5*hght), length=dist*1.2, height=hght,
                     width=dist*1.0E-4, color=color.yellow)
        self.f = box(pos=(0,0,0), length=dist*1.2, height=dist*1.0E-4,
                     width=hght, color=color.blue)

        self.b1 = sphere(pos=(-0.5*dist,0,0), radius=dist/50, color=color.red)
        self.b1.velocity = vector(ballv*cos(angl),ballv*sin(angl),0)
        self.b1.trail = curve(color=self.b1.color)

        self.b2 = sphere(pos=(0.5*dist,hght,0), radius=dist/50, color=color.green)
        self.b2.velocity = vector(0,0,0)
        self.b2.trail = curve(color=self.b2.color)

        self.result = 0

    def simulate(self):
        while True:
            rate (100)
            self.b1.pos = self.b1.pos + self.b1.velocity * self.dt
            self.b2.pos = self.b2.pos + self.b2.velocity * self.dt
            self.b1.trail.append(pos=self.b1.pos)
            self.b2.trail.append(pos=self.b2.pos)
            if self.b2.pos.y < self.f.pos.y:
                self.b2.velocity.y = -self.b2.velocity.y
            else:
                self.b2.velocity.y = self.b2.velocity.y - gconst*self.dt
            self.b1.velocity.y = self.b1.velocity.y - gconst*self.dt
        
            if fabs(self.b1.pos.y - self.f.pos.y)/self.b1.pos.y < eps:
                self.result = -1
                break

            if fabs(self.b1.pos.x - self.b2.pos.x)/self.b1.pos.x < eps and\
               fabs(self.b1.pos.y - self.b2.pos.y)/self.b1.pos.y < eps:
                self.result = 0
                break


class Application(wx.App):
    def OnInit(self):
        frame = MainFrame(None, -1)
        frame.Show(True)
        self.SetTopWindow(frame)
        return True

def main():
    import time
    app = Application(0)
    print "info> start-up time: %s." % time.ctime(time.time())
    appl_boot_path = os.getcwd()
    print "info> start-up directory: %s." % appl_boot_path
    app.MainLoop()

if __name__ == '__main__':
    main()