【typehandler】最短9行で実装！タイピングゲーム用のPythonモジュールを作ってみた

[![PyPI Downloads](https://static.pepy.tech/badge/typehandler)](https://pepy.tech/projects/typehandler)

## はじめに
　今回は、タイピングゲームのためのPythonモジュールを作ったので、紹介します。そもそも作ろうと思ったきっかけについて話しておきます。筆者が高校生のころから寿司打ヘビーユーザーというのもあり、タイピングゲームに興味があったこと。Pythonで、日本語の複数パターンの入力に対応したタイピングゲームを作っている人が、見られなかったこと。この２つです。特に後者について、日本語に対応したタイピングゲームを検索すると、他の言語が出てくるか、複数パターンの入力に対応していないかのどちらかでした。欲しい情報があまり得られず、結局他の言語での実装部分を参考にしました。

:::note
この記事では、詳しい処理の部分は解説していませんが、モジュールを作ったのだから、もはや誰も処理から実装する必要はないよね！というレベルを目指しているためです。既に日本語、英数字、記号の全てをこのモジュールで扱えますが、その他の部分で、現在も適宜機能を追加中です。（v0.5.0でいえばnext機能）
:::

## コンセプト
　**とにかく使いやすさを重視しています**。`print('HelloWorld!!')`レベルの手軽さが理想ですが、ゲーム画面の描画と、キーイベントの検知もこちらで実装すると、あまりにも自由度が低すぎます。したがって、これらの機能をどのモジュールで実装するかは、各自に委ねる形としました。音声なども扱えますし、個人的には`pygame`がおすすめです。（`typehandler`の機能的に、`pygame`と相性が良いという事はありません）

## 参考にしたサイト
https://mekiku.com/view.php?a=53

　日本語の複数パターン入力に対応するため、こちらのサイトを真似させてもらい、チャンクという物を使った方法を採用しています。端的に言えば、ひらがなを一文字ずつ判定するのではなく、まとまりごとに判定するということです。どうやって判定していくのかについては、こちらのサイトに載っているので、割愛させてもらいます。筆者がメインにやったことは、それらをPythonに落とし込んで、機能ごとに使いやすくまとめるってだけです。とりあえずタイピングゲームを作りたいだけだから、モジュールがあるならそれでいいや、という方はこのままお進みください。自分で実装したいんだ！という人はこちらのサイトへどうぞ。（もしくは[最後に](#最後に)からGitHubのコードをご覧ください。）

## インストール方法
以下の名前でpipできます。
```
pip install typehandler
```

:::note warn
最新版は0.5.0なので、もしも0.1や0.2がインストールされていたら、以下のようにしてください（0.1と0.2はPyPIに不慣れ故に動かなかったやつです）
```
pip install typehandler==0.5.0
```
:::
## メソッドの一覧

:::note
とりあえず使いたいだけの人は、次の項目にサンプルコードを置いてあるので、この項目は飛ばしてください。あと細かい使い方はREADMEの方に載せてあります。
:::

アプデ後の最新情報はREADMEをご覧ください

https://github.com/Prosamo/typehandler/

#### divide(hurigana: str) -> List[str]
ひらがなだけの文字列を受け取って、入力パターンのリストを返します。自分で別の判定方法などを実装したい場合に、こちらを活用していただけます。

<details><summary>他のメソッドも見る</summary>

#### set_new_sentence(words: Dict[str, str] = None) ->None
別の文章に変更するためのものです。例えば時間制限に引っかかったり、文章を打ち終わったら、これを呼び出すみたいな使い方を想定しています。引数に辞書を渡すと、その辞書から新しい文章を選びます。

#### set_new_words(words: Dict[str, str]) ->None
別の辞書をセットします。これを呼び出すと、その時点で文章も再設定されます。

#### check_correct_input(key: str, shift: bool) ->bool
キーの名前を受け取って、そのキーが入力パターンにあれば`True`, なければ`False`を返します。`if`の条件として使うことを想定しています。shift入力に対応したい時は、シフトが押されている状態のときだけ第二引数に`True`を渡してください。（pygameなど、そもそもシフト入力が判定できるものを使っている場合は、第一引数にシフト入力後の文字を渡せばOKです。）

#### check_chunk_completion() ->bool
こちらも`if`の条件として使うことを想定しています。`check_correct_input`が`True`を返した後、こちらでひらがなを打ち終わったかどうか判定します。ひらがなを打ち終わっていたら`True`、いなければ`False`を返します。

#### check_sentence_completion() ->bool:        
こちらも`if`の条件として使うことを想定しています。`check_chunk_completion`が`True`を返した後、こちらで文章を打ち終わったかどうか判定します。文章を打ち終わっていたら`True`、いなければ`False`を返します。

#### update_show_roman() ->str
現在入力してあるところまでを基に、入力パターンの一例を返します。常に最新の状態にするため、ゲームループ中で毎回呼び出すことをお勧めします。

#### main(key: str, shift: bool) ->int
特に音声などを付けないのであれば、これを呼び出すだけで、正誤判定から文章の打ち終わりまで判定して文章の更新もしてくれます。

### その他変数
#### input: str
既に入力済みのローマ字
#### show_roman: str
入力パターンの一例
#### sentence: str
お題の文章
#### next: str
次のお題の文章

これらにアクセスできますので、画面にこれらの情報を描画したい時にお使いください。

</details>

## 使用例
```python:minimum.py
import tkinter, typehandler
words = dict(リンゴ = 'りんご',バナナ = 'ばなな',ブドウ = 'ぶどう',レモン = 'れもん')
root = tkinter.Tk()
game = typehandler.Process(words)
def key_pressed(event):
    game.main(event.keysym)
    print(f'{game.input}\n{game.sentence}')
root.bind('<Key>', key_pressed)
root.mainloop()
```
↑最小だと、こんな感じで実装できます。(wordsを変数として宣言しなければ8行になりますが、見づらいので止めました)

```python:sample.py
import pygame, sys
from typehandler import Process

pygame.init()
screen = pygame.display.set_mode((600, 500))
pygame.display.set_caption('example')

font =  pygame.font.SysFont("MSP Gothic", 32)

def vocalize(se):
    #音声を再生する
    pygame.mixer.init(frequency=44100)
    pygame.mixer.set_num_channels(32)
    sound_key = pygame.mixer.Sound(se)
    sound_key.play()

words = dict(リンゴ = 'りんご',
             ブドウ = 'ぶどう',
             レモン = 'れもん',
             バナナ = 'ばなな',
             )

def main():
    clock = pygame.time.Clock()
    process = Process(words)
    while True:
        process.update_show_roman()
        screen.fill((255, 255, 255))
        text_roman = font.render(process.show_roman, True, (192, 192, 192))
        text_input = font.render(process.input, True, (0, 0, 0))
        text_sentence = font.render(process.sentence, True, (0, 0, 0))
        pygame.draw.line(screen, (0, 128, 255), (0, 50), (600, 50), 5)    #青い線を描画
        pygame.draw.line(screen, (255, 128, 0), (0, 150), (600, 150), 5)    #オレンジの線を描画
        screen.blit(text_roman, (30,60))
        screen.blit(text_input, (30,60))
        screen.blit(text_sentence, (30, 100))
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                correct_input = process.check_correct_input(pygame.key.name(event.key))    #ミスタイプを判定
                if correct_input:
                    vocalize('input.mp3')
                    chunk_conpleted = process.check_chunk_completion()    #文の打ち終わりを判定
                    if chunk_conpleted:
                        sentence_completed = process.check_sentence_completion()
                        if sentence_completed:
                            vocalize('next.mp3')
                            process.set_new_sentence()    #新しい文を用意
                else:
                    vocalize('miss.mp3')
        pygame.display.update()
        clock.tick(50)

if __name__ == '__main__':
    main()
```
もう少しまともな例を用意しました。音声の再生とかをするなら、このようになります。音声ファイルを用意するのが面倒な方は、コメントアウトして、動きだけどんな感じか見てください。先ほどは`tkinter`で今度は`pygame`を使っている通り、キー入力の判定さえできれば、何のモジュールを使っていただいても構わないです。

:::note
READMEにも例を載せてあるので、そちらも併せてご覧ください
:::

　ちなみにですが、fpsは50くらいあった方がいいと思います。30も試しましたが、筆者の入力速度でも、たまに追いついてきてくれませんでした。筆者より速い人は60くらい必要だったりするんでしょうか...？世の中には化け物みたいに速い人もいますからね....

 参考までに筆者のレベルが分かるものを置いておきます
 <details><summary>スクリーンショットを見る</summary>
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3922095/e702ec7e-d7f0-c0c0-fdb8-b081ec9c8d46.png)


</details>

## 最後に
　ここまで読んでいただきありがとうございました。PyPIに登録してpipできるようにするのが初めてだったので、ちょっと大変でした。ひらがなの辞書の部分を除けば250行くらいしかないので、ブラウザゲームを作りたいから、外部モジュール使えないなーって人は、以下のページから`process.py`の中身をコピペして使っちゃってください。
 
https://github.com/Prosamo/typehandler/blob/main/typehandler/process.py

ちなみにブラウザゲームの公開方法の記事も出しているので、タイピングゲームを自作して公開までを行いたい方は、この記事と併せてご覧いただけると嬉しいです。(ちゃっかり宣伝)

https://qiita.com/Prosamo/items/de12a90ba026b7517d2a

実際に筆者が公開してみたタイピングゲームはこちらから遊べます

https://prosamo.github.io/wind_typing/