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