画面とキー入力制御の自由度を高める 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