ReactでSlotMachineを作る(Step1: 基礎実装)

## はじめに
ReactでSlotMachineを作成します。今回はStep1のため、簡単な実装です。
段々と仕様を難しくしていこうと思います。

## 成果物
![slotmachine.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/5df8d98b-2d73-54e7-5694-bd2b673887b8.gif)

▪️実装Step
[Step1:基本編](https://qiita.com/koji0705/items/be905a51d5b9674f4d46)←いまここ
[Step2:Coin機能実装編](https://qiita.com/koji0705/items/7c4fe495252728e5457c)
[Step3: 仕様追加、リファクタリング実施](https://qiita.com/koji0705/items/bb1708c50978218607ec)
[Step4:ワイルドカード、セブンカードの追加](https://qiita.com/koji0705/items/037a0fd7ea93bf6c63f3)
[Step5:ディレクトリ整理/リファクタリング](https://qiita.com/koji0705/items/a8b12ea24b774129f784)



仕様
・スピンボタンを押すと果物が切り替わる
・3つ揃うと「勝利!」のテキストが表示される

## ソースコード

```shell:ディレクトリ構成
~/develop/react/react_slot_machine$ tree -I node_modules 
.
├── README.md
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.tsx
│   ├── components
│   │   ├── Reel.tsx
│   │   ├── SlotMachine.test.tsx
│   │   └── SlotMachine.tsx
│   ├── index.tsx
│   ├── logo.svg
│   └── setupTests.ts
├── tsconfig.json
└── yarn.lock

4 directories, 17 files
```

```json:package.json
{
  "name": "react_slot_machine",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@emotion/react": "^11.11.3",
    "@emotion/styled": "^11.11.0",
    "@mui/material": "^5.15.2",
    "@mui/styled-engine-sc": "^6.0.0-alpha.10",
    "@testing-library/jest-dom": "^5.14.1",
    "@testing-library/react": "^13.0.0",
    "@testing-library/user-event": "^13.2.1",
    "@types/jest": "^27.0.1",
    "@types/node": "^16.7.13",
    "@types/react": "^18.0.0",
    "@types/react-dom": "^18.0.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "styled-components": "^6.1.6",
    "typescript": "^4.4.2",
    "web-vitals": "^2.1.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
```

```src/App.tsx
import React from "react";
import { SlotMachine } from "./components/SlotMachine";
import { ThemeProvider, createTheme } from "@mui/material";

const theme = createTheme({
  palette: {
    primary: {
      main: "#007bff",
    },
  },
});

function App() {
  return (
    <ThemeProvider theme={theme}>
      <SlotMachine />
    </ThemeProvider>
  );
}

export default App;
```

```tsx:src/components/SlotMachine.tsx
import React, { useState } from "react";
import { Reel } from "./Reel";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";

const symbols = ["🍒", "🍋", "🍊", "🍇", "🍉"];

export const SlotMachine: React.FC = () => {
  const [reels, setReels] = useState(Array(3).fill(symbols[0]));

  const spinReels = () => {
    setReels(
      reels.map(() => symbols[Math.floor(Math.random() * symbols.length)])
    );
  };

  const isWin = new Set(reels).size === 1;

  return (
    <Box sx={{ textAlign: "center", marginTop: 4 }}>
      <Box sx={{ display: "flex", justifyContent: "center", gap: 2 }}>
        {reels.map((symbol, index) => (
          <Reel key={index} symbol={symbol} />
        ))}
      </Box>
      <Button
        variant="contained"
        color="primary"
        style={{ marginTop: "16px" }}
        onClick={spinReels}
      >
        スピン
      </Button>
      {isWin && <Box sx={{ marginTop: 2 }}>勝利！</Box>}
    </Box>
  );
};

```

```tsx:src/components/Reel.tsx
import React from "react";
import Box from "@mui/material/Box";

type ReelProps = {
  symbol: string;
};

export const Reel: React.FC<ReelProps> = ({ symbol }) => {
  return (
    <Box
      sx={{
        fontSize: "2rem",
        padding: "10px",
        border: "1px solid #ccc",
        borderRadius: "4px",
      }}
    >
      {symbol}
    </Box>
  );
};

```

```tsx:src/components/SlotMachine.test.tsx
import React from "react";
import { render, fireEvent, screen } from "@testing-library/react";
import { SlotMachine } from "./SlotMachine";

describe("SlotMachine コンポーネント", () => {
  test("初期レンダリングでスピンボタンが表示される", () => {
    render(<SlotMachine />);
    expect(screen.getByRole("button", { name: "スピン" })).toBeInTheDocument();
  });

  test("スピンボタンをクリックするとリールが回転する", () => {
    render(<SlotMachine />);
    const initialReels = screen
      .getAllByText("🍒")
      .map((reel) => reel.textContent);
    fireEvent.click(screen.getByRole("button", { name: "スピン" }));
    const updatedReels = screen
      .getAllByText(/🍒|🍋|🍊|🍇|🍉/)
      .map((reel) => reel.textContent);
    expect(updatedReels).not.toEqual(initialReels);
  });
});

```