8
6

「じゃんけん脳トレアプリ」を作りながらReactとTypeScriptを学べるチュートリアル

Last updated at Posted at 2024-10-03

こんにちは、とまだです。

みなさん、頭の回転は早い方ですか?

私は30代前半ですが、数秒前にやろうとしていたことを忘れることがあり、さすがに不安になってきました。

そんなときには、脳トレが効果的らしいですね!!!!

そこで今回は、ReactとTypeScriptを使って「じゃんけん脳トレアプリ」を作るチュートリアルをご紹介します。

作るアプリのイメージ

簡単にいうと「指示通りの手を出すじゃんけん」です。

このアプリでは指示に従って勝つ負ける引き分けるの3つのパターンがあります。

image.png

普通、じゃんけんと言えば常に勝つことを目指しますが、その常識・ルールを覆すような指示が出されるので、脳トレになるという寸法です。
(よくありますよね)

image.png

また、正解数と不正解数が表示されるので、自分の脳トレの成果を確認できます。

では、早速作り方を見ていきましょう!

対象となる読者

  • ReactとTypeScriptを使ってアプリを作ってみたい初心者
  • React に興味がある方
  • TypeScript に興味がある方

1. プロジェクトの作成

まずは、新しいReactプロジェクトを作成しましょう。今回は、高速な開発環境を提供する「Vite」というツールを使います。

ターミナル(コマンドプロンプトやPowerShell)を開いて、以下のコマンドを実行してください。

npm create vite@latest janken-brain-training -- --template react-ts

このコマンドは、「janken-brain-training」という名前のプロジェクトを作成します。また、ReactとTypeScriptを使用するテンプレートを選択しています。

コマンドを実行すると、新しいフォルダが作成されます。そのフォルダに移動しましょう。

cd janken-brain-training

2. 依存関係のインストール

プロジェクトのフォルダに移動したら、必要なパッケージをインストールします。以下のコマンドを実行してください。

npm install

次に、スタイリングを簡単に行えるTailwind CSSをプロジェクトに追加します。

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

3. Tailwind CSSの設定

Tailwind CSSを使用するための設定ファイルを作成します。以下のコマンドを実行してください。

npx tailwindcss init -p

次に、tailwind.config.jsファイルを開いて、以下のように編集します。

tailwind.config.js
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

最後に、src/index.cssファイルを開いて、以下の内容で置き換えます。

src/index.css
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

これで、Tailwind CSSの設定が完了しました!

4. 型定義の作成

TypeScriptの魅力の1つは、型定義によってコードの安全性を高められることです。じゃんけん脳トレアプリで使用する型を定義しましょう。

src/types.tsファイルを作成し、以下の内容を記述します。

src/types.ts
// じゃんけんの手を表す型
export type Hand = 'rock' | 'paper' | 'scissors';

// プレイヤーへの指示を表す型
export type Instruction = 'win' | 'lose' | 'draw';

// ゲームの結果を表す型
export type Result = 'correct' | 'incorrect';

// ゲームの状態を表すインターフェース
export interface GameState {
  playerHand: Hand | null;  // プレイヤーの選んだ手
  computerHand: Hand | null;  // コンピューターの手
  instruction: Instruction | null;  // 現在の指示
  result: Result | null;  // ゲームの結果
  score: {  // スコア
    correct: number;  // 正解数
    incorrect: number;  // 不正解数
  };
}
型定義の詳細説明
  • Hand 型:じゃんけんの手(グー、チョキ、パー)を表します。
  • Instruction 型:プレイヤーへの指示(勝つ、負ける、引き分ける)を表します。
  • Result 型:ゲームの結果(正解、不正解)を表します。
  • GameState インターフェース:ゲームの現在の状態を表します。プレイヤーの手、コンピューターの手、指示、結果、スコアが含まれます。

これらの型定義を使用することで、コード内で間違った値を使用するミスを防ぎ、開発効率を向上させることができます。

5. コンポーネントの作成

次に、アプリの主要な部分となるコンポーネントを作成します。src/componentsディレクトリを作成し、その中に以下の2つのファイルを作成してください。

ScoreBoard.tsx

これは、現在のスコアを表示するコンポーネントです。

src/components/ScoreBoard.tsx
import React from 'react';
import { GameState } from '../types';

interface ScoreBoardProps {
  score: GameState['score'];
}

const ScoreBoard: React.FC<ScoreBoardProps> = ({ score }) => {
  return (
    <div className="mt-4 text-xl">
      <p>正解: {score.correct}</p>
      <p>不正解: {score.incorrect}</p>
    </div>
  );
};

export default ScoreBoard;
ScoreBoard コンポーネントの詳細説明

このコンポーネントは、現在の正解数と不正解数を表示します。

  • ScoreBoardProps インターフェース:このコンポーネントが受け取るプロパティの型を定義しています。
  • score プロパティ:GameStatescore オブジェクトを受け取ります。
  • コンポーネントの中身:受け取った score オブジェクトから正解数と不正解数を表示します。

シンプルに、現在のスコアを表示するだけのコンポーネントです。

GameLogic.tsx

これは、ゲームのメインロジックを処理するコンポーネントです。

src/components/GameLogic.tsx
import React, { useState, useCallback, useEffect } from 'react';
import { Hand, Instruction, Result, GameState } from '../types';

const hands: Hand[] = ['rock', 'paper', 'scissors'];
const instructions: Instruction[] = ['win', 'lose', 'draw'];

const getComputerHand = (): Hand => {
  const randomIndex = Math.floor(Math.random() * hands.length);
  return hands[randomIndex];
};

const getInstruction = (): Instruction => {
  const randomIndex = Math.floor(Math.random() * instructions.length);
  return instructions[randomIndex];
};

const getResult = (playerHand: Hand, computerHand: Hand, instruction: Instruction): Result => {
  const actualResult =
    playerHand === computerHand ? 'draw' :
    (playerHand === 'rock' && computerHand === 'scissors') ||
    (playerHand === 'paper' && computerHand === 'rock') ||
    (playerHand === 'scissors' && computerHand === 'paper') ? 'win' : 'lose';

  return actualResult === instruction ? 'correct' : 'incorrect';
};

interface GameLogicProps {
  onStateChange: (newState: GameState) => void;
}

const GameLogic: React.FC<GameLogicProps> = ({ onStateChange }) => {
  const [gameState, setGameState] = useState<GameState>({
    playerHand: null,
    computerHand: getComputerHand(),
    instruction: getInstruction(),
    result: null,
    score: { correct: 0, incorrect: 0 },
  });

  const startNewRound = useCallback(() => {
    setGameState((prevState) => ({
      ...prevState,
      playerHand: null,
      computerHand: getComputerHand(),
      instruction: getInstruction(),
      result: null,
    }));
  }, []);

  const playHand = useCallback((hand: Hand) => {
    if (!gameState.instruction || gameState.result) return;

    const result = getResult(hand, gameState.computerHand!, gameState.instruction);

    setGameState((prevState) => ({
      ...prevState,
      playerHand: hand,
      result,
      score: {
        correct: prevState.score.correct + (result === 'correct' ? 1 : 0),
        incorrect: prevState.score.incorrect + (result === 'incorrect' ? 1 : 0),
      },
    }));

    setTimeout(startNewRound, 2000);
  }, [gameState.instruction, gameState.computerHand, gameState.result, startNewRound]);

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

  useEffect(() => {
    onStateChange(gameState);
  }, [gameState, onStateChange]);

  return (
    <div className="flex justify-center">
      {hands.map((hand) => (
        <button
          key={hand}
          onClick={() => playHand(hand)}
          className="px-4 py-2 m-2 text-2xl bg-blue-500 text-white rounded hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50"
        >
          {hand === 'rock' ? '' : hand === 'paper' ? '' : '✌️'} {hand === 'rock' ? 'グー' : hand === 'paper' ? 'パー' : 'チョキ'}
        </button>
      ))}
    </div>
  );
};

export default GameLogic;
GameLogic コンポーネントの詳細説明

このコンポーネントは、ゲームの核となるロジックを処理します。

  1. ゲームの状態管理:

    • useState フックを使用して、ゲームの状態(gameState)を管理します。
    • 初期状態では、コンピューターの手と指示をランダムに設定します。
  2. 新しいラウンドの開始:

    • startNewRound 関数は、新しいラウンドを開始するためにゲームの状態をリセットします。
    • useCallback フックを使用して、この関数をメモ化し、不必要な再レンダリングを防ぎます。
  3. プレイヤーの手の処理:

    • playHand 関数は、プレイヤーが手を選んだときに呼び出されます。
    • 結果を判定し、スコアを更新し、2秒後に新しいラウンドを開始します。
  4. 副作用の処理:

    • 最初の useEffect フックは、コンポーネントがマウントされたときに最初のラウンドを開始します。
    • 2番目の useEffect フックは、ゲームの状態が変更されるたびに親コンポーネントに通知します。
  5. UIのレンダリング:

    • 3つの手(グー、チョキ、パー)のボタンをレンダリングします。
    • 各ボタンをクリックすると、対応する手が選択されます。

役割としては、ユーザーの入力を受け付け、ゲームの進行を制御する部分を担当しています。

6. App.tsxの更新

最後に、アプリケーションのメインコンポーネントである App.tsx を更新します。このファイルでは、先ほど作成したコンポーネントを組み合わせてアプリケーション全体を構築します。

src/App.tsx ファイルを以下の内容で置き換えてください。

src/App.tsx
import React, { useState } from 'react';
import GameLogic from './components/GameLogic';
import ScoreBoard from './components/ScoreBoard';
import { GameState } from './types';

const App: React.FC = () => {
  const [gameState, setGameState] = useState<GameState>({
    playerHand: null,
    computerHand: null,
    instruction: null,
    result: null,
    score: { correct: 0, incorrect: 0 },
  });

  return (
    <div className="container mx-auto p-4 max-w-md">
      <h1 className="text-3xl font-bold text-center mb-6">じゃんけん脳トレ</h1>
      {gameState.instruction && (
        <p className="text-xl text-center mb-4">
          指示: {gameState.instruction === 'win' ? '勝ってください!' :
                 gameState.instruction === 'lose' ? '負けてください!' :
                 '引き分けてください!'}
        </p>
      )}
      {gameState.computerHand && (
        <p className="text-xl text-center mb-4">
          コンピューター: {gameState.computerHand === 'rock' ? '' : gameState.computerHand === 'paper' ? '' : '✌️'}
        </p>
      )}
      <GameLogic onStateChange={setGameState} />
      <div className="h-16 flex items-center justify-center">
        {gameState.result && (
          <p className="text-2xl font-bold text-center">
            {gameState.result === 'correct' ? '正解!' : '不正解...'}
          </p>
        )}
      </div>
      <ScoreBoard score={gameState.score} />
    </div>
  );
};

export default App;
App コンポーネントの詳細説明

このコンポーネントは、アプリケーション全体の構造を定義します。

  1. 状態管理:

    • useState フックを使用して、ゲームの全体的な状態(gameState)を管理します。
  2. UIのレンダリング:

    • アプリケーションのタイトルを表示します。
    • 現在の指示(勝つ、負ける、引き分ける)を表示します。
    • コンピューターの選んだ手を表示します。
    • GameLogic コンポーネントを配置し、ゲームの状態変更をハンドリングします。
    • 結果(正解または不正解)を表示する固定高さの領域を設けています。これにより、結果表示時にレイアウトが変動するのを防ぎます。
    • ScoreBoard コンポーネントを使用して、現在のスコアを表示します。

全体として、ゲームの流れを視覚的に表現し、プレイヤーがゲームの進行状況を把握しやすくなるようにしています。

7. アプリケーションの実行

さあ、いよいよアプリケーションを起動してみましょう!以下のコマンドを実行してください。

npm run dev

このコマンドを実行すると、開発サーバーが起動します。ターミナルに表示されるURLをブラウザで開いてください(通常は http://localhost:5173 です)。

ブラウザでアプリケーションが表示されたら、以下の操作を試してみてください。

  1. 画面に表示されるコンピューターの手と指示を確認します。
  2. 指示に従って、適切な手のボタンをクリックします。
  3. 結果(正解または不正解)が表示されることを確認します。
  4. スコアが更新されることを確認します。
  5. 2秒後に新しいラウンドが始まることを確認します。

まとめ

いかがでしたでしょうか?

インタラクティブ、かつ脳トレ効果のある「じゃんけん脳トレアプリ」を作成することができました。

もう少し発展させるならば、以下のような機能を追加してみるのも面白いかもしれません。

  • ゲームの難易度を調整する機能
  • ゲームの時間制限を設定する機能
  • プレイヤー名を入力してスコアを保存する機能
8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6