ビットパターンを使ったキー入力の判定

こんにちは、めのんです!

久しぶりに今回は「PHPプログラマーのためのC講座」以外の話題です。
内容は「ビットパターンを使ったキー入力の判定」です。

先日のことですけど、あるプログラミング初心者の方から質問を受けました。
キー入力の判定方法がよくわからないというのです。

「確かに初心者の方にはちょっと難しいかな?」と思ったので、せっかくなのでブログの記事にすることにしました。

プログラミング経験者にとっては何てことない内容ですけど、どうぞお付き合いください。

想定する操作パネル

今回の話題を進めるにあたって、想定する操作パネルを定義しておきたいと思います。
操作パネルには、OK、Cancel、上、下、左、右の6つのキーがあるものとしましょう。
ゲーム機のコントローラのようなものを想像していただいてもかまいません。

イメージをつかみやすいように画像を貼っておきますね。

この操作パネルからキーの押下状態(押されているかどうかの状態)を30分の1秒ごとに読み取ることができるとしましょう。

キーの入力状態は、OK、Cancel、上、下、左、右キーそれぞれの状態が、最下位ビットから順に割り付けられているものとします。

たとえば、2進数で000001であればOKキーだけが押されていることになります。
同じく2進数で010100であれば上キーと左キーが押されていることになります。

キー押下状態の変化を検出する

30分の1秒周期で読み取ることができるのは、あくまでもその瞬間のキーの押下状態です。
押し続けた時間だけ繰り返し処理するならいいのですが、普通、OKやCancelは押したときだけ反応してほしいですよね。

そういうときはキーの押下状態が変化したかどうかをまず調べる必要があります。

これを実現するには、直前のキー押下状態を覚えておいて、今回のキー押下状態と比較します。
ただし、ここでの比較方法は等価演算子や関係演算子ではなく、ビット単位の排他的論理和を使います。

実際にサンプルコードを書いてみますね。

int previous_keys;  // 直前のキー押下状態
prevous_keys = get_keys();  // 開始時のキー押下状態を取得
for (;;)
{
  sync();  // 30分の1秒の同期
  int keys = get_keys();  // 今回のキー押下状態を取得
  int changed = keys ^ prevous_keys;  // キー押下状態の変化を検出
  previous_keys = keys;
  ...  // 必要な処理
}

ざっとこんな感じです。

念のため排他的論理和のおさらいをしておきましょう。

C = A ⊕ B の真理値表は次のようになります。

A B C
0 0 0
0 1 1
1 0 1
1 1 0

排他的論理和では、AとBの値が異なるときだけCが1になるんでしたね。
直前の押下状態と今回の押下状態が異なる(=変化した)ときに1になるんです。

つまり、changedには、キーの押下状態が変化したところだけ1が格納されます。

たとえば、直前の周期でキーを読み込んだときには何も押されていなかったとしましょう。
直前のキー押下状態はprevious_keysに格納されますので、previous_keysは0になります。

ここでCancelキーを押すと、今回のキー押下状態keysには2進数で000010が格納されます。
previous_keysとkeysのビット単位の排他的論理和を求めると2進数で000010になりますから、Cancelキーが変化したことがわかります。

次にCancelキーを離しましょう。
keysには000000が格納されます。
previous_keysには000010が格納されていますから、ビット単位の排他的論理和を求めると2進数で000010になります。

つまり、changedだけではどのキーの押下状態が変化したかはわかりますが、押されたのか離されたのかまではわかりません。

具体的な操作を調べる

キー押下状態の変化はchangedで調べることができました。
今度は、キーが押されたのか離されたのかを調べてみましょう。

キーが押されたことを検出する

キーが押されたことを検出するには、keysとchangedのビット単位の論理積を求めます。
具体的には次のようにします。

int changed = keys ^ prevous_keys;  // キー押下状態の変化を検出
int pressed_keys = keys & changed;  // 今回押されたキーを検出

論理積については大丈夫でしょうか?
両辺が1の場合だけ結果も1になり、それ以外は0になるんでしたね(真理値表は省略しますので、ご自身で作ってみてください)。

pressed_keyは、キー押下状態が変化していて、かつ今回押下されている(=今回押された)キーに対応するビットだけが1になります。

キーが離されたことを検出する

今回は先ほどの応用になります。

chanedとkeysをそのままビット単位の論理積を求めると今回押されたことを検出できるので、keysの0と1を反転してやれば今回離されたことを検出できそうです。
具体的には次のようにします。

int released_keys = ~keys & changed; // 今回離されたキーを検出

CやCから派生したプログラミング言語では、チルダ(~)はビット単位の否定を表します。
否定というのは、もとが0なら1に、1なら0にする演算です。
ビットを反転させるといってもいいでしょう。

keysに対してビット単位の否定演算を行っておくことで、今回押下されていないキーに対応するビットだけが1になります。
これとchangedのビット単位の論理積を求めてあげれば、今回離されたキーに対応するビットだけが1になります。

組込み機器やゲーム機など、キーの制御を行うプログラムでは頻繁に書く処理なんですけど、なじみのない方にとってはほとんど見かけないかもしれませんね。

こんな感じで、今回は久々にいつもとは違う話題を取り上げてみました。