🦁

「React初心者がゼロからポーカーハンド比較アプリを作り上げるまで:詳細な技術解説」

2024/06/25に公開

はじめに:
本記事では、プログラミング初心者である私が、ReactとMaterial-UIを使用してTexas Hold'emポーカーのハンド比較アプリケーションを開発し、GitHub Pagesにデプロイするまでの全過程を詳細に解説します。各段階での技術的な選択、直面した課題、そしてその解決策を含め、実践的な知識を共有します。

  1. 開発環境のセットアップ:

    a. Node.jsのインストール

    • Node.jsの公式サイト(https://nodejs.org/)からLTS版をダウンロードしインストール
    • ターミナルで node -vnpm -v を実行し、インストールを確認

    b. Reactプロジェクトの作成

    npx create-react-app texas-holdem-comparer
    cd texas-holdem-comparer
    
    • create-react-appを使用する利点:Babel, Webpack, ESLintなどの設定が自動で行われる

    c. 必要なライブラリのインストール

    npm install @mui/material @emotion/react @emotion/styled
    
    • Material-UIを選択した理由:豊富なコンポーネントと柔軟なカスタマイズ性
  2. アプリケーションの構造設計:

    a. コンポーネント構造

    • TexasHoldemComparer (メインコンポーネント)
      • PlayingCard (カードを表示するコンポーネント)
      • CardButton (カード選択用ボタン)

    b. 状態管理

    const [hands, setHands] = useState([[], []]);
    const [board, setBoard] = useState([]);
    const [result, setResult] = useState('');
    const [currentSelection, setCurrentSelection] = useState(0);
    const [selectedRank, setSelectedRank] = useState(null);
    
    • useStateフックを使用して、各状態を管理
  3. UI実装:

    a. カードコンポーネントの実装

    const PlayingCard = ({ rank, suit, onRemove }) => (
      <StyledPlayingCard onClick={onRemove} suit={suit}>
        {rank}{suit === 'h' ? '♥' : suit === 'd' ? '♦' : suit === 'c' ? '♣' : '♠'}
      </StyledPlayingCard>
    );
    
    • Styled Componentsを使用してカスタムスタイルを適用

    b. カード選択UIの実装

    <Box sx={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center' }}>
      {RANKS.map(rank => (
        <StyledCardButton 
          key={rank} 
          onClick={() => setSelectedRank(rank)}
          disabled={selectedRank !== null || SUITS.every(suit => isCardUsed(rank, suit))}
        >
          {rank}
        </StyledCardButton>
      ))}
    </Box>
    
    • mapを使用して動的にボタンを生成
    • disabled属性を使用して、既に選択されたカードを選択不可に
  4. ゲームロジックの実装:

    a. 役判定ロジック

    const getHandRank = (hand, board) => {
      const allCards = [...hand, ...board].sort((a, b) => RANKS.indexOf(b.rank) - RANKS.indexOf(a.rank));
      // ... (詳細なロジック)
      if (flushCards && straightCards && flushCards[0].rank === straightCards[0].rank) 
        return { rank: 8, name: "Straight Flush", value: RANKS.indexOf(straightCards[0].rank), cards: flushCards };
      // ... (他の役の判定)
    };
    
    • 7枚のカードから最強の5枚を見つけるロジック
    • 各役(ストレートフラッシュ、フォーカードなど)の判定ロジック

    b. ハンド比較機能

    const handleCompare = () => {
      const ranks = hands.map(hand => getHandRank(hand, board));
      if (ranks[0].rank > ranks[1].rank) {
        setResult(`Hand 1 wins with ${ranks[0].name}!`);
      } else if (ranks[1].rank > ranks[0].rank) {
        setResult(`Hand 2 wins with ${ranks[1].name}!`);
      } // ... (詳細な比較ロジック)
    };
    
    • 両方のハンドの役を判定し、比較する
  5. 主要な実装上の課題と解決策:

    a. カードの重複選択防止
    課題: 同じカードが複数回選択される可能性
    解決策:

    const isCardUsed = (rank, suit) => allCards.some(card => card.rank === rank && card.suit === suit);
    
    • 選択時にこの関数を使用してチェック

    b. 役判定ロジックの複雑さ
    課題: 7枚から最適な5枚を選ぶロジックの実装
    解決策:

    • 各役ごとに判定関数を実装
    • ソートとフィルタリングを効果的に使用

    c. 非同期処理によるUI更新の遅延
    課題: 状態更新後のUI反映の遅れ
    解決策: useEffect フックを使用して副作用を管理

  6. パフォーマンス最適化:

    a. useMemo フックの使用

    const allCards = useMemo(() => [...hands[0], ...hands[1], ...board], [hands, board]);
    
    • 不要な再計算を防ぎ、パフォーマンスを向上

    b. コンポーネントの最適化

    • Pure Component化とmemo化を検討
  7. デプロイプロセス:

    a. GitHub リポジトリの作成

    • プロジェクトをGitHubにプッシュ

    b. GitHub Pages用の設定

    • package.jsonの修正
      {
        "homepage": "https://ユーザー名.github.io/リポジトリ名",
        "scripts": {
          "predeploy": "npm run build",
          "deploy": "gh-pages -d build"
        }
      }
      

    c. 静的サイトホスティングの課題解決

    • 404.htmlファイルの追加
    • .nojekyllファイルの作成
    • index.htmlにリダイレクトスクリプトを追加

    d. デプロイ

    npm run deploy
    
  8. テストと品質保証:

    a. Jest を使用した単体テストの実装

    test('getHandRank correctly identifies a flush', () => {
      const hand = [{rank: 'A', suit: 'h'}, {rank: 'K', suit: 'h'}];
      const board = [{rank: 'Q', suit: 'h'}, {rank: 'J', suit: 'h'}, {rank: '9', suit: 'h'}];
      expect(getHandRank(hand, board).name).toBe('Flush');
    });
    

    b. React Testing Libraryを使用したコンポーネントテスト

    test('renders card selection buttons', () => {
      render(<TexasHoldemComparer />);
      expect(screen.getByText('A')).toBeInTheDocument();
      expect(screen.getByText('K')).toBeInTheDocument();
      // ... その他のカードボタンのチェック
    });
    
  9. 今後の改善点:

    a. モバイル対応の強化
    b. アニメーションの追加によるUX向上
    c. 多言語対応
    d. オンラインマルチプレイヤー機能の追加

結論:
このプロジェクトを通じて、Reactアプリケーションの開発からデプロイまでの包括的なプロセスを学びました。特に、状態管理、コンポーネント設計、そしてゲームロジックの実装において多くの知識を得ることができました。また、Material-UIを使用したUIデザインやGitHub Pagesを利用したデプロイプロセスなど、実践的なスキルも身につけることができました。

このアプリケーション開発の経験は、単なる技術的なスキルアップだけでなく、プロジェクト管理やデバッグスキルの向上にも大きく貢献しました。今後は、ここで得た知識を基に、さらに複雑で実用的なアプリケーションの開発に挑戦していきたいと思います。

参考文献:

  1. React公式ドキュメント: https://reactjs.org/docs/getting-started.html
  2. Material-UI公式サイト: https://mui.com/
  3. GitHub Pages公式ドキュメント: https://docs.github.com/en/pages
  4. MDN Web Docs (JavaScript): https://developer.mozilla.org/en-US/docs/Web/JavaScript
  5. React Hooks公式ドキュメント: https://reactjs.org/docs/hooks-intro.html

この記事が、Reactを学び始めた方々や、similar projectに取り組む開発者の方々にとって有益な情報源となれば幸いです。プログラミングの世界は常に進化し続けており、学ぶべきことは尽きません。皆さんも、自分のアイデアを形にする過程を楽しんでください!​​​​​​​​​​​​​​​​

Discussion