ChatGPTにお願いしてReactとMaterial-UIで野球のピッチングカウンターを作ってみた

こんにちは！今回は、ReactとMaterial UIを使って作成した野球のピッチングカウンターを紹介します。このアプリでは、ストライクやボール、アウト、イニング、失点などのデータを簡単にカウントしながら、ピッチャーのパフォーマンスをリアルタイムで確認できます。

## アプリの概要

このアプリは野球の試合中にピッチャーの成績を管理するために設計されており、以下の機能を備えています。

- ストライク、ボール、アウトのカウント
- イニングの管理
- 失点の追跡
- リセットと元に戻す機能
- ピッチャー名と対戦相手の記入欄
- リアルタイムでのストライク率、アウト率、失点率の表示

アプリのデザインにはMaterial UIを採用しており、シンプルかつ使いやすいインターフェースを実現しました。

アプリのデモはこちらから確認できます: [Pitch Counter](https://baseball-pitch.web.app/)

## アプリの見た目

![スクリーンショット 2024-09-16 10.55.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512829/587686a6-abe8-a157-3ef2-30d76cfc4ece.png)

## 使った技術

- **React**: フロントエンドのコンポーネントベースのUIライブラリ
- **Material-UI**: Reactアプリケーション向けのスタイリングライブラリ
- **JavaScript (ES6)**: アプリの動的な動作を実装

## プロンプト

このアプリを作る際に使用したプロンプトを以下に紹介します。

**プロンプト:**

```
ReactとMaterial UIを使って野球のピッチングカウンターアプリを作成してください。アプリは以下の要素を含みます:
- ストライク、ボール、アウト、イニング、失点のカウント機能
- ピッチャー名と対戦相手名を入力するフィールド
- ストライク率、アウト率、失点率の計算と表示
- ボタンでストライク、ボール、アウト、イニング、失点を追加できる
- 元に戻す機能とリセット機能
- シンプルで使いやすいUIデザイン
```

このプロンプトを使って、以下のReactコードが生成されました。

## コード例

```javascript
import React, { useState } from 'react';
import { Button, TextField, Container, Typography, Grid, Paper } from '@mui/material';

function App() {
  const [pitches, setPitches] = useState(0);
  const [strikes, setStrikes] = useState(0);
  const [balls, setBalls] = useState(0);
  const [outs, setOuts] = useState(0);
  const [innings, setInnings] = useState(1);
  const [runsAllowed, setRunsAllowed] = useState(0);
  const [pitcherName, setPitcherName] = useState('');
  const [opponent, setOpponent] = useState('');
  const [history, setHistory] = useState([]);
  const [pitched, setPitched] = useState(0);

  const today = new Date().toLocaleDateString();

  const handleStrike = () => {
    saveHistory();
    setStrikes(strikes + 1);
    setPitches(pitches + 1);
    setPitched(1);
  };

  const handleBall = () => {
    saveHistory();
    setBalls(balls + 1);
    setPitches(pitches + 1);
    setPitched(1);
  };

  const handleOut = () => {
    saveHistory();
    setOuts(outs + 1);
    if (outs > 0 && outs % 3 === 0) {
      setPitched(0);
    }
  };

  const handleInnings = () => {
    saveHistory();
    setInnings(innings + 1); 
  };

  const handleRun = () => {
    saveHistory();
    setRunsAllowed(runsAllowed + 1);
  };

  const resetCounts = () => {
    setStrikes(0);
    setBalls(0);
  };

  const resetAll = () => {
    saveHistory();
    setPitches(0);
    setStrikes(0);
    setBalls(0);
    setOuts(0);
    setInnings(1);
    setRunsAllowed(0);
    setPitcherName('');
    setOpponent('');
    setPitched(0);
  };

  const undoLastAction = () => {
    if (history.length > 0) {
      const lastState = history[history.length - 1];
      setHistory(history.slice(0, -1));
      setPitches(lastState.pitches);
      setStrikes(lastState.strikes);
      setBalls(lastState.balls);
      setOuts(lastState.outs);
      setInnings(lastState.innings);
      setRunsAllowed(lastState.runsAllowed);
      setPitcherName(lastState.pitcherName);
      setOpponent(lastState.opponent);
    }
  };

  const saveHistory = () => {
    setHistory([...history, { pitches, strikes, balls, outs, innings, runsAllowed, pitcherName, opponent, runsPerInning, pitched }]);
  };

  const strikeRate = pitches > 0 ? ((strikes / pitches) * 100).toFixed(1) : 0;
  const outRate = outs > 0 ? (pitches / outs).toFixed(1) : 0;
  const runsPerInning = (innings > 0) ? (runsAllowed / innings).toFixed(2) : 0;

  return (
    <Container maxWidth="xs">
      <Paper elevation={3} style={{ padding: '20px', marginTop: '20px' }}>
        <Typography variant="h4" gutterBottom>Pitch Counter</Typography>
        <Typography variant="subtitle1" gutterBottom>{today}</Typography>
        <Grid container spacing={2} direction="column" style={{ marginBottom: '20px' }}>
          <Grid item xs={12}>
            <TextField label="Pitcher Name" value={pitcherName} onChange={(e) => setPitcherName(e.target.value)} fullWidth />
          </Grid>
          <Grid item xs={12}>
            <TextField label="Opponent" value={opponent} onChange={(e) => setOpponent(e.target.value)} fullWidth />
          </Grid>
        </Grid>
        <Typography variant="h6">Statistics</Typography>
        <Grid container spacing={2}>
          <Grid item xs={6}>
            <Typography>Strikes: {strikes}</Typography>
            <Typography>Balls: {balls}</Typography>
            <Typography>Outs: {outs}</Typography>
          </Grid>
          <Grid item xs={6}>
            <Typography>Pitches: {pitches}</Typography>
            <Typography>Innings: {innings}</Typography>
            <Typography>Runs Allowed: {runsAllowed}</Typography>
          </Grid>
        </Grid>
        <Grid container spacing={2} style={{ marginTop: '10px' }}>
          <Grid item xs={4}><Button variant="contained" style={{ backgroundColor: 'yellow', color: 'black' }} onClick={handleStrike}>Strike</Button></Grid>
          <Grid item xs={4}><Button variant="contained" style={{ backgroundColor: 'green', color: 'white' }} onClick={handleBall}>Ball</Button></Grid>
          <Grid item xs={4}><Button variant="contained" style={{ backgroundColor: 'red', color: 'white' }} onClick={handleOut}>Out</Button></Grid>
        </Grid>
        <Grid container spacing={2} style={{ marginTop: '10px' }}>
          <Grid item xs={6}><Button variant="outlined" color="error" onClick={handleRun}>Run</Button></Grid>
          <Grid item xs={6}><Button variant="outlined" color="warning" onClick={handleInnings}>Innings</Button></Grid>
        </Grid>
        <Grid container spacing={2} style={{ marginTop: '10px' }}>
          <Grid item xs={6}><Button variant="outlined" onClick={undoLastAction}>Undo</Button></Grid>
          <Grid item xs={6}><Button variant="outlined" onClick={resetAll}>Reset</Button></Grid>
        </Grid>
        <Typography variant="body1" style={{ marginTop: '20px' }}>Strike Rate: {strikeRate}%</Typography>
        <Typography variant="body1">Outs Rate: {outRate} pitches/out</Typography>
        <Typography variant="body1">Runs per Inning: {runsPerInning}</Typography>
      </Paper>
    </Container>
  );
}

export default App;
```

## まとめ

子どもの野球で使っていますが、データを見せてあげるとみんな喜びます。ストライク率が60%以上だとピッチャーの調子がよいようです。それ以下だとフォアボールが多くなって、点を取られやすい状況になりやすい。もうちょっとデータをためていこうと思います。