LoginSignup
17
12

More than 5 years have passed since last update.

コンソールでキー入力を一文字づつ読み込む

Last updated at Posted at 2016-07-15

例えば簡単なCLIのゲームやpecoのようなキーボード入力が一回あるたびに何か動作させるものを作りたい場合は、OSの機能に働きかけ、端末の動作を変えることで実現できます。

Rubyでの場合を紹介しますが、
基本的にどのプログラミング言語でもこの端末の機能に触ることができれば実現できるはずです。

ルーク、io/consoleをつかえ

Rubyでは、標準添付ライブラリーio/consoleで使えるようになるIO#rawを使用します。
主要なOSに対応しているのでwindowsでもlinuxでもmacで同じように使えるでしょう。

「ふつうにIO#getcでいいじゃん」と思う方はirbなどでSTDIN.getcを実行してみましょう。

$ irb
irb(main):001:0> STDIN.getc
asdf...

エンターキーを入力するまで文字の読み込みが始まらないことが分かります。

ではrequire 'io/console'してからSTDIN.raw(&:getc)を実行してみましょう。

$ irb
irb(main):001:0> require 'io/console'
=> true
irb(main):002:0> STDIN.raw(&:getc)
=> "a"

一文字入力した途端にirbに戻ってきます。

しくみ

端末には一般的にrawモードcookedモードがあるようです。(他にもnoechoモードなどもあるが省略)

私たちが普段使っているようなものはcookedモードで、

  • CTRL+Cなどのようにシグナルを送れる
  • 一行分バッファリングするので途中でカーソル移動したり消したりできる
  • 端末に入力した文字が表示される

という特徴があります。

逆にrawモードは

  • シグナルを送れない
  • バッファリングしない
  • 端末に入力した文字を表示しない

という特徴があります。

Rubyでもこのモード切り替えが簡単に操作できるようになっていて、

  • IO#raw: ブロックの間だけrawモードにする
  • IO#cooked: ブロックの間だけcookedモードにする

と使い分けることができます。

以降ずっとrawモードにするIO#raw!などもあるのですが、rawモードのままプログラムを終了すると、
コンソールの表示が変な感じになったりします。
ブロック付きで使うと、たとえエラーが起ころうとブロックを抜けるときに自動的に元の状態に戻してくれるのでおすすめです。

以下は入力したキーボードのバイト値を、
キーボードを打つ毎に端末の左上に表示するサンプルです。

#! /usr/bin/env ruby

require 'io/console'

$stdin.raw do |io|
  while true
    ch = io.readbyte
    exit 0 if ch == 3 # CTRL+Cで終了
    print "\e[H" # カーソルをコンソールの左上に移動する
    print "\e[0K" # 前の文字が残らないようにする
    print ch
  end
end

このようにエスケープシーケンスなどと組み合わせれば、タイピングゲームやオレオレエディタなんかも作れて夢が広がります。

Cでの実装

基本的に#include <termios.h>でつかえるAPIを利用します。

実践的な実装は https://github.com/ksss/mruby-io-console/blob/master/src/io-console.c あたりをご参考ください。
macでしか動作確認してない & win対応してないですが……。

17
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
12