にじさんじのタイピングゲームを作ってみた

## はじめに
およそ2年前にタイピングゲームを作りました。
http://hiroshihp.html.xdomain.jp/typing/

Vtuberグループ「にじさんじ」の**英語名**のタイピングです。にじさんじを知らなくてもプレイできますので、仕様の確認がてらプレイしてみてください。

大文字小文字は区別します（「S」であればshift + S）
スペースも入力対象になります。
英語名のタイピングなので「月ノ美兎（英語名:Tsukino Mito）」を「**Tu**kino Mito」のように打つことはできません。

タイピングゲームは結構ありふれた題材かと思いますが、「そういえばこれって自力で実装したんだったな」ということで、このタイピングゲームの実装をここに記したいと思います。

## 実装方法
### まずはinputに直接入力する
本ゲームは目に見えないinputタグに文字を入力し、その値を用いて判定をしています。
この方法により、「入力すべき文字が大文字のときは同時にShiftキーを押しているかを判定する」などの記述をなくすことができました。
```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="js/jquery-3.6.0.min.js"></script>
</head>
<body>
  <!-- お題を表示する場所 -->
  <div id="themeArea"></div>

  <!-- input -->
  <input id="inputArea" type="text">

  <script src="js/main.js"></script>
</body>
</html>
```

jsはこちらになります。記述を短くするためjQueryを用いています。
```javascript:main.js
let theme = 'Tsukino Mito'; // 現在のお題
let strNum = 0; // 入力すべき文字位置

// themeArea に theme をセット
$('#themeArea').html(theme);

// 入力時
$('#inputArea').on('input', () => {
  // タイプした文字をvalに格納
  const val = $('#inputArea').val();

  // タイプが合っている場合
  if (val === theme[strNum]) {
    strNum++;

  // タイプが間違っている場合
  } else {
    console.log(`ミス！打つべき文字は「${theme[strNum]}」です`)
  }

  //inputを空に
  $('#inputArea').val('')
})
```
#### 解説
```javascript
const val = $('#inputArea').val();
```
valにはinputに入力された文字が格納されます。inputに「S」が入力されればvalには「大文字のS」が格納されることになります。

```javascript
let strNum = 0; // 入力すべき文字位置
```
strNumには入力すべき文字位置が格納されます。inputに入力された際に```val === theme[strNum]```のようにしてタイプされた文字と入力すべき文字が合っているかどうか判定します。
合っている場合、入力すべき文字は次の文字になるのでstrNumをインクリメントします。

```javascript
$('#inputArea').val('')
```
判定を終えたらすぐにinputの値を空にします。そうすることでタイプするごとに、いまタイプした文字一文字を判定に利用することができます。

### スタイリングをする
現状では何文字目を打つべきなのかわかりにくいため、入力した文字は赤色になるようにしていきます。
一文字ずつスタイリングする必要があるため、そのままthemeをセットするのではなく、各文字をspanタグで囲むことにします。
```diff_html:index.html
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="js/jquery-3.6.0.min.js"></script>
+ <style>
+   .correct {
+     color: red;
+   }
+ </style>
</head>
```

```diff_javascript:main.js
  let theme = 'Tsukino Mito'; // 現在のお題
  let strNum = 0; // 入力すべき文字位置

+ // theme を一文字ずつ分解し配列に
+ let themeArr = theme.split('');
+
+ // theme を一文字ずつ span で囲み themeArea にセット
+ themeArr.forEach(spell => {
+   $("#themeArea").append(`<span>${spell}</span>`);
+ })


  // 入力時
  $('#inputArea').on('input', () => {
    // タイプした文字をvalに格納
    const val = $('#inputArea').val();

    // タイプが合っている場合
    if (val === theme[strNum]) {
+     $(`#themeArea span:nth-child(${strNum+1})`).addClass("correct");
      strNum++;

    // タイプが間違っている場合
    } else {
      console.log(`ミス！打つべき文字は「${theme[strNum]}」です`)
    }

    //inputを空に
    $('#inputArea').val('')
  })
```
#### 解説
```javascript
let themeArr = theme.split('');
// themeArr = ['T', 's', 'u', 'k', 'i', 'n', 'o', ' ', 'M', 'i', 't', 'o']
```
一文字ずつspanで囲むために、spritメソッドを使って分割します。

```javascript
$(`#themeArea span:nth-child(${strNum+1})`).addClass("correct");
```
nth-childを用いることで、現在タイプした文字にcorrectクラスを付与することができます。nth-childと配列の添字は1つ数字がズレるため注意です。

### inputを非表示にしてタイピングゲームっぽくする
現時点でもだいぶタイピングゲームっぽいですが、inputを非表示にしてさらに近づけていきましょう。
また、今のままだとinputにフォーカスを当てて入力する必要があるため、その操作を不要にしていきます。
```diff_html:index.html
  <style>
    .correct {
      color: red;
    }
+   #inputArea {
+     opacity: 0;
+   }
  </style>
```
```diff_html:index.html
  <!-- input -->
+ <input id="inputArea" type="text" autofocus>
```
```diff_javascript:main.js
  // 入力時
  $('#inputArea').on('input', () => {
      // 省略
  })

+ // フォーカスを固定する
+ $('#inputArea').on('blur',()=>{
+   $('#inputArea').focus();
+ })
```

#### 解説
```html
<input id="inputArea" type="text" autofocus>
```
autofocus属性を付与することで、ページが読み込まれた際に自動でフォーカスが当たるようにします

```javascript
 $('#inputArea').on('blur',()=>{
   $('#inputArea').focus();
 })
```
フォーカスが外れた際にもフォーカスを戻すことで、フォーカスを固定しています。

### その他
以上で一つのお題に対する処理を実装することができました。あとは「お題を入力し終えたら次のお題を表示する」などの実装を進めていくことでさらにタイピングゲームを発展させることができます。

## おわりに
日本語のタイピングゲームであればローマ表記の揺れ（つ　→　tsu,tu　どちらでも可）を考慮することが多いと思いますが、~~なんかめんどくさそう~~今回の実装では難しそうでしたので、思い切って**英語名**をタイピングするということにしました。
タイピングゲームの実装方法はいろいろあると思うので、ぜひ調べてみてはいかがでしょうか。