diff --git a/orbit_rocket.rb b/orbit_rocket.rb new file mode 100644 index 0000000..15d708b --- /dev/null +++ b/orbit_rocket.rb @@ -0,0 +1,161 @@ +#!/usr/koeki/bin/ruby +# -*- coding: utf-8 -*- + +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 + +# 地球を描く +def draw_earth + Curses.attrset(Curses.color_pair(2)) + 0.upto(Curses.cols - 1) do |cpx| + 0.upto(Curses.lines - 1) do |cpy| + if (r((cpx - $cox) / $xscale.to_f, (cpy - $coy) / $yscale.to_f) < 1) + Curses.setpos(cpy, cpx) + Curses.addch(' ') + end + end + end + Curses.attrset(Curses.color_pair(1)) +end + +# (x, y) から端末上の座標へ変換して文字 ch を出力 +def puts_char(x, y, ch) + cpx = $cox + x / $RE * $xscale + cpy = $coy - y / $RE * $yscale + if (cpx >= 0 && cpx <= Curses.cols - 1) && + (cpy >= 0 && cpy <= Curses.lines - 1) + Curses.setpos(cpy, cpx) + 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_BLACK, Curses::COLOR_RED) + + # 端末の中央の座標 + $cox = ((Curses.cols - 1) * 0.5).round + $coy = ((Curses.lines - 1) * 0.5).round + + # カーソルを非表示 + Curses.curs_set(0) + + # キー入力で待つ時間(ミリ秒) + Curses.stdscr.timeout = 60 + + # 地球を出力 + draw_earth + Curses.refresh + + # 初期値を代入 + x = $x0 + y = $y0 + u = $u0 + v = $v0 + i = 0 + + # 無限ループ + while 1 + # 時刻の出力 + Curses.setpos(Curses.lines - 1, 0) + Curses.addstr(sprintf("t=%d[s]\n", i * $dt)) + + # (x, y) に文字を出力 + if (r(x, y) >= $RE) + # ロケットを出力 + puts_char(x, y, 'R') + Curses.refresh + else + # 爆発を出力 + Curses.attrset(Curses.color_pair(3)) + puts_char(x, y, 'X') + Curses.refresh + break + end + + # キー入力 + if Curses.getch != nil + break + end + + # (x, y) にロケットの跡を出力 + 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 \ No newline at end of file