Newer
Older
Ruby / orbit_rocket.rb
@MAEDA Keiji MAEDA Keiji on 13 Nov 2021 3 KB 2021-11-13 19:28:05
#!/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