【初心者向け】KivyによるWindowsアプリ作成32 タイプされたキーの受付処理の適正化

今回は、前回の終わりに頭出しした通り、ユーザが「Shift」+アルファベットキーを押したとき、アプリ側で正しく大文字がインプットされたと認識できるよう、プログラムを修正していきます。

<スポンサーリンク>

修正方針の検討

キー入力時にアプリに渡される値の確認

修正方針を決めるに先立ち、ユーザがキーをタイプした際、実際にはどのような値がアプリに渡されるのか、確認してみましょう。

タイプされたキーに関して、アプリに渡される情報はkeyboardDownメソッドの引数にて指定されています。具体的に、引数には以下の通り、self~modifiersまでの5つがあります。

def keyboardDown(self, keyboard, keycode, text, modifiers):

このうち、今回実装する処理上重要なものは、keycode, text, modifiersの3つです。

これら引数の詳細は以下の記事にも書いているのですが、今回の記事の内容をより理解頂くため、キーを押した時の各変数の値を例示します。

今回から数回に分けて、キーボードから文字をタイプされた時の処理を少しずつ実装していきたいと思います。 まず今回は、キーボードからタイプ...

「a」を押した場合

  • keycode -> (97, ‘a’)
  • text -> a
  • modifiers -> []

シンプルに1つキーを押した場合、keycodeにはキーに割り振られたコードと文字がタプル形式で、textには実際に表示される文字が、そして、modifiersには空のリストが入力されます。(ちなみにmodifierは「修飾子」という意味で、aやbなどの通常文字を装飾するShiftやCtrlが該当します。)

キーボード上、左側の「Shift」を押した場合

  • keycode -> (304, ‘shift’)
  • text -> İ
  • modifiers -> []

Shiftを押した場合も、基本的には通常の文字をタイプした場合と同じように値が渡されます。

textには、Shiftを表す特殊記号が入ります。

キーボード上、右側の「Shift」を押した場合

  • keycode ->  (303, ‘rshift’)
  • text -> į
  • modifiers -> []

keycodeの値が、左Shiftとは異なりますね。プログラム内部では、左右のShiftを別のキーとして捉えていることが分かります。

「Shift」(左側)+「a」を押した場合

  • keycode -> (97, ‘a’)
  • text -> a
  • modifiers -> [‘shift’]

Shiftはmodifierとして扱われ、modifiersリストに格納されます。そして、keycodeとtextには、メインの文字である「a」の情報が入ります。

なお、Shiftとaを完全に同じタイミングで押すことは、人間にはほぼ不可能です。大文字を入力する場合はもちろん、先にShiftを押して、その後aを押すのが通常です。

この場合、一旦Shiftだけを押した時の情報がkeyboardDownメソッドに送られ、その直後に上記のShift + aを押したときの情報が送られます。

「Shift」(右側)+「a」を押した場合

  • keycode -> (97, ‘a’)
  • text -> a
  • modifiers -> [‘shift’]

左側のShiftを押した時と結果は変わりません。Shiftは、他のキーと同時押しされてmodifierとして使われる場合、左右のShiftのどちらが押されても全く同じようにプログラム側では処理されているようですね。

「Shift」+「Ctrl」(左側)を押した場合

  • keycode -> (305, ‘lctrl’)
  • text -> ı
  • modifiers -> [‘shift’]

modifierが2つ押された場合、後に押されたキーのkeycodeとtextが取得され、先に押されたキーのみがmodifiersリストに入ります。

左Shift + 右Shiftでも、全く同様の振る舞いをします。

「Shift」+「Ctrl」+「a」を押した場合

  • keycode -> (97, ‘a’)
  • text -> a
  • modifiers -> [‘ctrl’, ‘shift’]

この場合、Ctrlキーもmodifierとして扱われ、modifiersリストの1要素としてプログラムでは扱われることになります。

それではこれらも踏まえて、どのような処理を追加すべきか、少し検討してみましょう。

追加する処理の検討

現在のプログラムでは、タイプされた文字をkeyboardDownメソッド内のtextという変数に入れ、それとゲーム画面に表示されている文字(Button Widget)のtext属性を比較して、正解タイプかどうかを判定していました。

ですので、今回追加する処理では、基本的にShiftと通常文字が同時にタイプされた際に、タイプ文字を大文字に変換してメソッド内のtext変数に入れてやる処理を実装してやれば、その他既存のコードは修正しなくても良さそうです。

加えて、CtrlやTabなど、明らかに表示されている文字とは無関係のキーがタイプされた際に、文字比較を行う前にミスタイプとみなして減点してやれば、文字比較のループを回す必要もなくなり、ベターだと思います。

では、この方針でもう少し検討を進めて行きましょう。

今回の追加処理を検討する上でバグの発生を最小限に抑えるためには、ユーザがタイプし得るすべてのパターンを網羅的に洗い出し、それぞれに対してどのような対応を行うべきかを決める必要があります。

ここでは、modifiersを軸として、考えられるパターンを洗い出してみたいと思います。

①タイプされたキー情報にmodifiersが付いていない場合

この場合はさらに、Shiftが押された場合、英数字が入力された場合、それら以外の、CtrlやTabなどの特殊記号が押された場合の3パターンに分けることができます。

まず、Shiftが押された場合ですが、次に入力される文字により最終的にユーザが入力する文字が決まりますので、入力を一旦無視するようにします。

次に、英数字がタイプされた場合、text変数を書き換える必要がありませんので、そのまま既存のコードを実行すれば良いだけです。

最後に上記2つのいずれにも当てはまらない場合は、正解タイプとなる可能性がゼロですので、減点処理をします。

②タイプされたキー情報にmodifiersが1つだけ指定されている場合

これは指定されたmodifierがShiftかそうでないかによって、まず、大きな2つのパターンに分けられます。

簡単な方から行きましょう。

指定されたものがCtrlなどShift以外のものであれば、Ctrlが押されている時点でミスタイプとみなすべきですが、この場合、先にCtrl単体でのキー入力がされているはずです。ですので、①で既に減点されているので、無視します。

次に、指定されたmodifierがShiftである場合を考えましょう。この場合は、さらに2パターンに分けることができます。

まず、Shiftと同時に押されたものがアルファベットであれば、ユーザは大文字入力を意図しているはずですので、同時押しされている文字を大文字化してtext変数に代入してやります。

次のパターンですが、同時押されたキーがアルファベット以外である場合を考えます。

かなりイレギュラーですが、右Shift + 左Shiftのように、2つのShiftが押された場合が1つ考えられます。この場合、一旦無視して、次の入力で入力値を確定させるか、ミスタイプとするか悩ましいですが、今回は「不要なタイプはすべてミスタイプとする」という厳しい方針で、減点することにします。

一方、Shiftと同時押しされたキーが、CtrlやTabなどのShift以外の特殊文字と数字の場合も、不要なタイプが行われたということで減点処理とします。

③タイプされたキー情報にmodifiersが2つ以上指定されている場合

最後に、Modifiersが2つ以上の場合を考えましょう。

といっても話は簡単で、この場合は明らかにShift以外の不要なmodifierが入力されていますので、減点となるべきです。ただし、この場合、どんなケースでも①、②で既に減点処理がなされていますので、対応は「無視する」となります。

タイプされ得るパターンと、それぞれの対応方針については以上ですが、少し複雑になりましたので、表に整理しておきます。

modifier有無 条件1 条件2 対応
なし タイプ文字がShift 無視
タイプ文字が英数字 既存コード実行
それ以外 減点
1つ ModifierがShift以外 無視
ModifierがShift アルファベットを同時押し 大文字化してtextに入れる
アルファベット以外を同時押し 減点
2つ 無視

実装

それでは、上記の通り検討した結果を踏まえて、実装を行っていきましょう。

まずは、いきなり追記すべきコードを掲載させて下さい。

def keyboardDown(self, keyboard, keycode, text, modifiers):
    missFlag = True

    #ここから追加
    if len(modifiers) == 0:
        if keycode[1] == "shift" or keycode[1] == "rshift":
            return
        elif text.isalnum():
            pass
        else:
            self.score -= 10
            self.ids["score"].text = str(self.score)
            return

    if len(modifiers) == 1:
        if modifiers[0] != "shift":
            return
        else:
            if text.isalpha():
                text = text.upper()
            else:
                self.score -= 10
                self.ids["score"].text = str(self.score)
                return

    if len(modifiers) >= 2:
        return
    #ここまで追加

    for target in self.targets:
        ・・・

内容的には先ほど整理した表を忠実に再現しているだけですので、大体のイメージはつくと思いますが、少しだけ補足します。

まず、対応が「無視する」となったパターンについては、「return」という記述を入れています。

returnを使うことによって、実行中のメソッドから抜けることができます。これにより、後続の処理をすべてスキップして、次の入力を再び待ち受けることができます。

このreturnは減算処理の中でも使っており、これにより減算後に文字比較等の処理を行わないようにしています。

次に、isalnum()などの見慣れない関数がいくつかありますが、これらは文字列の操作に使われる組み込み関数です。

関数名 用途
isalnum() 対象の文字列が英数字であればTrueを、そうでなければFalseを返す
isalpha() 対象の文字列がアルファベットであればTrueを、そうでなければFalseを返す
upper()  文字列中の小文字を大文字に変換する

今回の実装は、これで完了です。

テスト実行

それではプログラムを実行し、難易度をNormalかHardにしてゲームを始めてみましょう。

大文字のタイプはできましたでしょうか?また、Shiftを含め、特殊文字を入力した際の処理は正しく行われていますでしょうか?

これらが確認できれば、プログラムの修正は正しく行われたことになります。

終わりに

ゲーム難易度の調整処理については、今回で大体固めることができました。

難易度に関してはあと1点、難易度調整画面の見た目を整えたいと思いますが、それは次回対応していきたいと思います。

<スポンサーリンク>

シェアする

フォローする