画面とキー入力制御の自由度を高める curses ライブラリを用いると、高機能な対話的プログラムが作れる。
cursesをプログラム内に導入し、利用宣言すると以下のことが可能になる。
その一方、画面出力・キー入力がすべてcursesのものになるため、 これまでのプログラムでできている以下のことが できなくなる。
puts, print, printf
     を使った文字列出力(期待どおりの場所に出ない)
出力が画面下端に行った場合の画面の自動スクロール (最下行で止まる)
getsなどの入力関数(データを送れない)
これらのことに気をつけ、それぞれのメソッドをcurses専用の ものを必ず利用するように気をつけていれば問題ない。
cursesを利用したプログラムは以下のような流れで作成する。
#!/usr/koeki/bin/ruby # -*- coding: utf-8 -*- require 'curses' include Curses init_screen # スクリーンを初期化する begin … 必要な処理本体 … ensure close_screen # スクリーンを元に戻す end
エスケープシーケンス を用いても画面の任意の位置に文字出力できるが、位置決めを頻繁に行なう ようなプログラムではcursesを用いる方がプログラムを作りやすい。
たとえば、画面上「5行目、10桁目」に「こんにちは」と表示する プログラムは以下のようになる。
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'curses'
include Curses
init_screen                     # スクリーンを初期化する
begin
  setpos(4, 9)                  # 5行目の10桁目(0から数えるため)
  addstr("こんにちは")
  refresh                       # 出力を画面に反映させる
  sleep 3                       # これがないとすぐ消えてしまうため
ensure
  close_screen
end
エスケープシーケンスを用いて、球に見立てた●を動かすプログラム
pitch.rbと同じ動きをする
ものを、cursesを利用して作ってみると以下のようになる。
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
# cursesを用いて●を動かす
require 'curses'
include Curses
init_screen			# 画面も消える
ball = "●"
kesu = "  "
wait = 0.05			# タイマー
x = 60				# 60桁目の
y = 10				# 10行目から
begin
  while x > 10			# 右から左へ
    setpos(y-1, x-1)		# カーソルを今の位置へ
    addstr(kesu)		# 前のボールを消す
    x -= 1
    setpos(y-1, x-1)		# カーソルを次の位置へ
    addstr(ball)		# ボールを書く
    setpos(0,0)			# カーソルを邪魔でないところへ
    refresh                     # これをしないと画面に反映されない
    sleep(wait)			# 一定時間休む
  end
  while y > 1			# 下から上へ
    setpos(y-1, x-1)		# カーソルを今の位置へ
    addstr(kesu)		# 前のボールを消す
    y -= 1
    setpos(y-1, x-1)		# カーソルを次の位置へ
    addstr(ball)		# ボールを書く
    setpos(0, 0)		# カーソルを邪魔でないところへ
    refresh                     # 見せたいものが揃ったら必ずrefresh
    sleep(wait*2)		# 一定時間休む
  end
  setpos(y, 0)
  addstr("おしまい\n")
  refresh                       # 最後も忘れずに
  sleep 3
ensure
  close_screen
end
エスケープシーケンス版とほとんどプログラム構成は同じだが、
カーソル位置を決める部分が、setpos メソッドの呼び出し
で分かりやすく書けることが分かる。
これまで作成してきたようなデータの入力を主目的とするプログラムでは、
意味のある文字列を打ち込んで最後にReturnキーを押して
初めてデータがプログラム(getsメソッド)に渡る。
データ入力では間違えずに入れることが大切なのでこれでよいが、たとえば
「準備ができたら何かキーを押してください」、
「yかnを押してください」のような場合はReturn
を押さずに進めた方が利用者にしてみれば快適である。
Cursesライブラリに含まれる getch メソッドは入力キー
(の文字コード)を1字文だけ読み取るもので、これを呼ぶ前に
cbreak メソッドを呼んでおくと、Return
を押さなくてもデータが getch メソッドに伝わるようになる。
以下の2つの例を試してみよ。
Returnキーを押すまで進まない:
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'curses'
include Curses
init_screen                     # スクリーンを初期化する
nocbreak                        # Returnでデータをまとめて送る
begin
  addstr("何かキーを押してください: ")
  x = getch                     # 1字読み取る(文字コードが返る)
  addstr("\nさようなら(3秒後に終わります)")
  refresh                       # 出力を画面に反映させる
  sleep 3                       # 結果がしばらく見えるようにする
ensure
  close_screen
end
Returnキーを押さなくても進む:
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'curses'
include Curses
init_screen                     # スクリーンを初期化する
cbreak                          # リターンキーなしでも入力させる
noecho                          # 入力文字のエコーバックを無しにする
begin
  addstr("何かキーを押してください(打った文字は見えません): ")
  x = getch                     # 1字読み取る(文字コードが返る)
  addstr("\nさようなら(3秒後に終わります)")
  refresh                       # 出力を画面に反映させる
  sleep 3                       # 結果がしばらく見えるようにする
ensure
  close_screen
end
後者で利用した noecho は、入力した文字を
画面に出す(エコーバックという)ことをやめる。
Curses.timeout 変数にミリ秒単位の整数を指定すると
gets メソッドで入力を待つ制限時間となる。たとえば、
入力待ちを2.5秒で打ち切るには Curses.timeout = 2500 とする。
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'curses'
include Curses
cbreak
Curses.timeout = 2500           # 2.5秒でアウト
init_screen
srand
alphabet = "abcdefghijklmnopqrstuvwxyz"
ans = alphabet[rand(alphabet.length)]
begin
  setpos(5,10)
  addstr(sprintf("%cを押せ! : ", ans))
  refresh
  key = getch
  setpos(6,10)
  if key == ans
    addstr("正解! また会おう")
  else
    addstr("出直してこい")
  end
  refresh
  sleep 2
ensure
  close_screen
end
Cursesライブラリを用いた対話的なプログラムをしっかり作るためには 以下のメソッドや変数を覚えておくよい。
cbreak
     Return キーなしで即入力データを渡す。
nocbreak
     Return キー入力を待ってから入力データを渡す。
echo
     タイプした文字を画面にエコーバックする。
noecho
     タイプした文字を画面にエコーバックしない。
clear
     画面全体を表すウィンドウをクリアする。
closed?
     すでにcurses画面制御が終了したかを返す。
cols
     画面に表示可能な桁数を返す。
lines
     画面に表示可能な行数を返す。cols
     とともに利用して、想定する処理を行なうのに必要な画面サイズが
     あるかあらかじめ確認しておくのが望ましい。
     例:
require 'curses'
include Curses
if lines < 25 then
  STDERR.puts("端末を25行以上にしてから起動してください")
  exit 1
elsif cols < 80
  STDERR.puts("端末を桁幅80桁以上にしてから起動してください")
  exit 1
end
     doupdate
     refreshよりも効率的に画面更新を行なう。
getstr
     文字列を読み込みその値を返す。
cursesを用いたアクション型プログラムの例を示す。
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
# cursesを用いて●を動かす
require 'curses'
include Curses
noecho                          # エコーバックなし
cbreak                          # Returnなしで即入力
Curses.timeout = 0		# 入力は待たない
init_screen			# 画面も消える
ball = "●"
kesu = "  "
wait = 0.03			# タイマー
x = 0
y = lines-2			# 下から2行目
j = 0                           # ジャンプの高さ
jmax = 6                        # 2ステップ分高度をあげる
jnow = 0                        # 現在のステップ(0 - 3)
setpos(1, 0)
addstr("SPCでジャンプ!")
setpos(y-5, cols/2+rand(3))     # ランダムに決めた位置に
addstr("★")                    # ★を置く
begin
  h = y-1                       # 高さの初期値をセットしておく
  while x < cols		# 右から左へ
    setpos(h, x-1)		# カーソルを今の位置へ
    addstr(kesu)		# 前のボールを消す
    x += 1
    h = y-1 - ((jmax-jnow)*jnow/2) # ジャンプは2次曲線
    setpos(h, x-1)		# カーソルを次の位置へ
    addstr(ball)		# ボールを書く
    setpos(0,0)			# カーソルを邪魔でないところへ
    refresh                     # これをしないと画面に反映されない
    if jnow > 0 then
      jnow -= 1                 # ジャンプ中の処理
      getch                     # ジャンプ中に押されたキーは捨てる
    else
      key = getch
      if key == " "[0]          # SPCだったら
        jnow = jmax             # ジャンプ開始
      end
    end
    sleep(wait)			# 一定時間休む
  end
  setpos(y-1, 0)
  addstr("おしまい\n")
  refresh                       # 最後も忘れずに
  sleep 3
ensure
  close_screen
end