#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
# Curses でロケットの運動を描画する
# 作者:山本裕樹
require "curses"
# 万有引力定数 G = 6.67430×10^-11 [m^3/kg/s^2]
# 地球の質量 ME = 5.9724×10^24 [kg]
# 地球の半径 RE = 6.3781x10^6 [m]
$G = 6.67430e-11
$ME = 5.9724e24
$RE = 6.3781e6
# 現実の座標から端末上の座標に変換するための x, y 方向のスケール
$xscale = 10.0
$yscale = 5.0
# 初期値
# ($x0, $y0):ロケットの発射位置(単位 [m])
# ($u0, $v0):ロケットの発射速度(単位 [m/s])
# $dt:時間の刻み(単位 [s])
$x0 = $RE + 10
$y0 = 0
$u0 = 6000
$v0 = 6000
$dt = 60
# 原点から (x, y) までの距離
def r(x, y)
return Math.sqrt(x ** 2 + y ** 2)
end
# 運動方程式の右辺1
def f(x, y)
return - $G * $ME * x / r(x, y) ** 3
end
# 運動方程式の右辺2
def g(x, y)
return - $G * $ME * y / r(x, y) ** 3
end
# ku, kv, kx, ky の計算
def calc_k(u, v, x, y, du, dv, dx, dy)
return f(x + dx, y + dy) * $dt,
g(x + dx, y + dy) * $dt,
(u + du) * $dt,
(v + dv) * $dt
end
# ルンゲ=クッタ法で $dt 後の (x,y) と (u,v) を求める
def next_step(u, v, x, y)
ku1, kv1, kx1, ky1 = calc_k(u, v, x, y, 0, 0, 0, 0)
ku2, kv2, kx2, ky2 = calc_k(u, v, x, y,
ku1 * 0.5, kv1 * 0.5, kx1 * 0.5, ky1 * 0.5)
ku3, kv3, kx3, ky3 = calc_k(u, v, x, y,
ku2 * 0.5, kv2 * 0.5, kx2 * 0.5, ky2 * 0.5)
ku4, kv4, kx4, ky4 = calc_k(u, v, x, y, ku3, kv3, kx3, ky3)
return u + (ku1 + 2 * ku2 + 2 * ku3 + ku4) / 6.0,
v + (kv1 + 2 * kv2 + 2 * kv3 + kv4) / 6.0,
x + (kx1 + 2 * kx2 + 2 * kx3 + kx4) / 6.0,
y + (ky1 + 2 * ky2 + 2 * ky3 + ky4) / 6.0
end
# 現実の座標 (x,y) から端末上の座標 (cx,cy) へ変換
def x2cx(x) ($cox + x / $RE * $xscale).round end
def y2cy(y) ($coy - y / $RE * $yscale).round end
# 端末上の座標 (cx,cy) から現実の座標 (x,y) へ変換
def cx2x(cx) (cx - $cox) * $RE / $xscale end
def cy2y(cy) -(cy - $coy) * $RE / $yscale end
# (0, 0) を中心に地球を描く
def draw_earth
# 地球の色の設定
Curses.attrset(Curses.color_pair(2))
# 端末上の全ての座標をスキャンして地球の半径内なら地球を出力
0.upto(Curses.cols - 1) do |cx|
0.upto(Curses.lines - 1) do |cy|
if r(cx2x(cx), cy2y(cy)) < $RE
Curses.setpos(cy, cx)
Curses.addch(' ')
end
end
end
end
# 現実の座標 (x,y) から端末上の座標へ変換して文字 ch を出力
def puts_char(x, y, ch)
cx = x2cx(x)
cy = y2cy(y)
if (cx >= 0 && cx <= Curses.cols - 1) &&
(cy >= 0 && cy <= Curses.lines - 1)
Curses.setpos(cy, cx)
Curses.addch(ch)
end
end
# Cursesの初期化
Curses.init_screen
begin
# 色の設定
Curses.start_color
Curses.init_pair(1, Curses::COLOR_WHITE, Curses::COLOR_BLACK)
Curses.init_pair(2, Curses::COLOR_BLACK, Curses::COLOR_BLUE)
Curses.init_pair(3, Curses::COLOR_CYAN, Curses::COLOR_BLACK)
Curses.init_pair(4, Curses::COLOR_BLACK, Curses::COLOR_RED)
# 端末の中央の座標
$cox = ((Curses.cols - 1) * 0.5).round + 0.5
$coy = ((Curses.lines - 1) * 0.5).round + 0.5
# カーソルを非表示
Curses.curs_set(0)
# キー入力で待つ時間(ミリ秒)
Curses.stdscr.timeout = 60
# 初期値を代入
x = $x0
y = $y0
u = $u0
v = $v0
# 時間
i = 0
# ロケットの軌道を描くループ
while 1
# 地球を出力
draw_earth
# デフォルトの色
Curses.attrset(Curses.color_pair(1))
# 時刻の出力
Curses.setpos(Curses.lines - 1, 0)
Curses.addstr(sprintf("t=%d[s]\n", i * $dt))
if (r(x, y) > $RE)
# ロケットを出力
puts_char(x, y, 'R')
else
# 爆発を出力
Curses.attrset(Curses.color_pair(4))
puts_char(x, y, 'X')
Curses.refresh
# ループを抜ける
break
end
# キー入力があるか
if Curses.getch != nil
# ループを抜ける
break
end
# (x, y) にロケットの跡を出力
Curses.attrset(Curses.color_pair(3))
puts_char(x, y, '.')
# 画面表示の更新
Curses.refresh
# $dt 後の (x,y) を求める
u, v, x, y = next_step(u, v, x, y)
i += 1
end
# キー入力があるまでずっと待つ
Curses.stdscr.timeout = -1
Curses.getch
ensure
# Cursesの終了処理
Curses.close_screen
end