Next.jsでタイピングゲームを作ってみた

# はじめに

初投稿です。
最近TypeScriptを勉強したので、next.jsを使ってタイピングゲームを作りました。

# 作ったもの
デモ
https://typing-game-neco75.netlify.app

github
https://github.com/neco75/TypingGame-next.js

# 実装
今回は最低限の機能を実装することを目標にして、以下の要素を実装しました。

- スコア機能
- 制限時間
- タイプした文字列の正誤判定

よくある1タイプごとに判定して、間違えている場合入力できない機能を実装したかったのですがうまくいきませんでした…

## プロジェクトのセットアップ

最初に、新しいNext.jsプロジェクトを作成

```bash
npx create-next-app typing-game
cd typing-game
```

必要なライブラリをインストール
```bash
npm install react react-dom next
npm install typescript @types/react @types/node
npm install random-words
```
## コード内容
プロジェクトの使わないファイルを消去、変更します。今回はcomponentsディレクトリを作成し、その下にTypingGame.tsxを用意しました。以下がindex.tsxとtypingGame.tsxのコードです
##### index.tsx
```
import React from 'react';
import TypingGame from '../components/TypingGame';

const Home: React.FC = () => {
  return (
    <div>
      <TypingGame />
    </div>
  );
};

export default Home;

```
##### TypingGame.tsx
```
import randomWords from 'random-words';
import React, { useState, useEffect } from 'react';

const TypingGame: React.FC = () => {
  const [word, setWord] = useState<string>('');
  const [input, setInput] = useState<string>('');
  const [score, setScore] = useState<number>(0);
  const [missCount, setMissCount] = useState<number>(0);
  const [time, setTime] = useState<number>(30);
  const [gameOver, setGameOver] = useState<boolean>(false);

  useEffect(() => {
    generateNewWord();
  }, []);

  useEffect(() => {
    let timer: NodeJS.Timeout;

    if (time > 0 && !gameOver && missCount < 5) {
      timer = setTimeout(() => {
        setTime(time - 1);
      }, 1000);
    } else if ((time === 0 || missCount >= 5) && !gameOver) {

      setGameOver(true);
      alert("Game Over");
    }

    return () => clearTimeout(timer);
  }, [time, gameOver, missCount]);

  const generateNewWord = () => {
    const newWord = randomWords();
    setWord(newWord);
    setInput('');
  };

  const reset = () => {
    setInput('');
    setScore(0);
    setMissCount(0);
    setTime(30);
    setGameOver(false);
    document.getElementById("text")!.style.backgroundColor = "white";
    generateNewWord();
  };

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const userInput = e.target.value;
    setInput(userInput);

    if (!gameOver && missCount < 5) {
      if (userInput === word) {
        document.getElementById("text")!.style.backgroundColor = "white";
        setScore(score + 10);
        setTime(time + 2);
        generateNewWord();
      } else if (userInput.length >= word.length) {
        document.getElementById("text")!.style.backgroundColor = "pink";
        setInput('');
        setScore(Math.max(score - 1, 0));
        setMissCount(missCount + 1);

        if (missCount + 1 >= 5) {
          setGameOver(true);
          alert("Game Over");
        }

        setTime(Math.max(time - 3, 0));
      }
    }
  };

  return (
    <div>
      <h1>Typing Game</h1>
      <p>Time: {time}</p>
      <p>Score: {score}</p>
      <p>MissCount: {missCount}</p>
      <h2>Word: {word}</h2>
      <input id="text" type="text" value={input} onChange={handleInputChange} />
      <input id="reset" type="button" value="Reset" onClick={reset} />
    </div>
  );
};

export default TypingGame;

```

## ゲームのロジック
ゲームの基本的なロジックは次のようになります。

- タイピングされた文字列が正しい場合、スコアを増加させ、新しい単語を表示。
- タイピングされた文字列が不正確な場合、スコアを減少させ、ミスカウントを増加
- タイマーがゼロになるか、ミスカウントが5以上になるとゲームオーバーとなる

## 起動
サーバーを起動します。
```
npm run dev
```
デフォルトの http://localhost:3000 で実行されるはずです。

# 最後に
React自体殆ど触ったことなくてなかなか形にするのが大変でした。まだまだ改善点があるので、もう少し勉強したいです。