はじめに
ReactでSlotMachineを作成します。今回はStep1のため、簡単な実装です。
段々と仕様を難しくしていこうと思います。
成果物
▪️実装Step
Step1:基本編←いまここ
Step2:Coin機能実装編
Step3: 仕様追加、リファクタリング実施
Step4:ワイルドカード、セブンカードの追加
Step5:ディレクトリ整理/リファクタリング
仕様
・スピンボタンを押すと果物が切り替わる
・3つ揃うと「勝利!」のテキストが表示される
ソースコード
ディレクトリ構成
~/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
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;
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>
);
};
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>
);
};
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);
});
});