2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

記事投稿キャンペーン 「2024年!初アウトプットをしよう」

ReactでSlotMachineを作る(Step4: ワイルドカード、セブンカードの追加)

Last updated at Posted at 2024-01-01

はじめに

前回までの記事の続きです。(シリーズ化予定)

▪️実装Step
Step1:基本編
Step2:Coin機能実装編
Step3: 仕様追加、リファクタリング実施
Step4:ワイルドカード、セブンカードの追加 ←いまここ
Step5:ディレクトリ整理/リファクタリング

成果物

step4_seven.gif

▪️追加仕様
・ワイルドカードを追加。ワイルドカードは何でも合わせられる
・セブンカードを追加。セブンカードが3つ揃った場合、JackPotに移動する。
・セブンカードが3つ揃った場合、JackPotに移動する。ワイルドカードでも代替可能だが、少なくとも1つはセブンカードを含む必要がある。
・JackPotに移動した場合、gif動画が流れる。

ソースコード

ディレクトリ
~/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
│   └── shiba_motivate.gif
├── src
│   ├── App.tsx
│   ├── components
│   │   ├── Dialog.tsx
│   │   ├── Reel.tsx
│   │   ├── SlotMachine.test.tsx
│   │   ├── SlotMachine.tsx
│   │   ├── const.ts
│   │   ├── func.test.ts
│   │   ├── func.ts
│   │   └── type.ts
│   ├── index.tsx
│   ├── logo.svg
│   └── setupTests.ts
├── tsconfig.json
└── yarn.lock

4 directories, 23 files
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";
import {BET_COINS, INITIAL_COINS, SYMBOLS} from "./const"; // 定数のインポート
import {SymbolTile} from "./type";
import {spinReels} from "./func";
import {JackpotDialog} from "./Dialog";

export const SlotMachine: React.FC = () => {
  const [coins, setCoins] = useState(INITIAL_COINS);
  const [reels, setReels] = useState<SymbolTile[]>(Array(3).fill(SYMBOLS[0])); // SymbolTile型の配列
  const [isJackpot, setIsJackpot] = useState(false); // ジャックポット状態の追加

  const handleSpin = () => {
    const {newReels, win, jackpot, points} = spinReels(SYMBOLS, BET_COINS);
    setReels(newReels);
    setIsJackpot(jackpot);
    setCoins((prev) => prev - BET_COINS + (win ? points : 0));
  };

  const isWin = new Set(reels.map((tile) => tile.id)).size === 1;

  return (
    <Box sx={{textAlign: "center", marginTop: 4}}>
      <Box>Coinの枚数:{coins}</Box>
      <Box sx={{display: "flex", justifyContent: "center", gap: 2}}>
        {reels.map((tile, index) => (
          <Reel key={index} symbol={tile.label} />
        ))}
      </Box>
      <Button
        variant="contained"
        color="primary"
        style={{marginTop: "16px"}}
        onClick={handleSpin}
        disabled={coins < BET_COINS}
      >
        スピン
      </Button>
      {isJackpot && <JackpotDialog />}
      {isWin && <Box sx={{marginTop: 2}}>勝利!</Box>}
      {isJackpot && <Box sx={{marginTop: 2}}>ジャックポット!</Box>}
    </Box>
  );
};
src/components/const.ts
import {SymbolTile} from "./type";

export const INITIAL_COINS = 100;
export const BET_COINS = 3;

export const CHERRY_ID = "cherry";
export const LEMON_ID = "lemon";
export const ORANGE_ID = "orange";
export const GRAPE_ID = "grape";
export const WATERMELON_ID = "watermelon";
export const WILD_CARD_ID = "wild_card";
export const SEVEN_CARD_ID = "seven";

export const CHERRY_SYMBOL = "🍒";
export const LEMON_SYMBOL = "🍋";
export const ORANGE_SYMBOL = "🍊";
export const GRAPE_SYMBOL = "🍇";
export const WATERMELON_SYMBOL = "🍉";
export const WILD_CARD_SYMBOL = "⚙️";
export const SEVEN_SYMBOL = "💰";

export const SYMBOLS: SymbolTile[] = [
  {
    id: CHERRY_ID,
    label: CHERRY_SYMBOL,
    point: 10,
  },
  //   {
  //     id: LEMON_ID,
  //     label: LEMON_SYMBOL,
  //     point: 10,
  //   },
  //   {
  //     id: ORANGE_ID,
  //     label: ORANGE_SYMBOL,
  //     point: 10,
  //   },
  //   {
  //     id: GRAPE_ID,
  //     label: GRAPE_SYMBOL,
  //     point: 20,
  //   },
  //   {
  //     id: WATERMELON_ID,
  //     label: WATERMELON_SYMBOL,
  //     point: 30,
  //   },
  {
    id: SEVEN_CARD_ID,
    label: SEVEN_SYMBOL,
    point: 30,
    isJackpot: true,
  },
  {
    id: WILD_CARD_ID,
    label: WILD_CARD_SYMBOL,
    point: 30,
    isWildCard: true,
  },
];

src/components/type.ts
import {
  CHERRY_ID,
  CHERRY_SYMBOL,
  GRAPE_ID,
  GRAPE_SYMBOL,
  LEMON_ID,
  LEMON_SYMBOL,
  ORANGE_ID,
  ORANGE_SYMBOL,
  SEVEN_CARD_ID,
  SEVEN_SYMBOL,
  WATERMELON_ID,
  WATERMELON_SYMBOL,
  WILD_CARD_ID,
  WILD_CARD_SYMBOL,
} from "./const";

export type SymbolTile = {
  id: SymbolId;
  label: SymbolLabel;
  point: number;
  isWildCard?: boolean;
  isJackpot?: boolean;
};

export type SymbolId =
  | typeof CHERRY_ID
  | typeof LEMON_ID
  | typeof ORANGE_ID
  | typeof GRAPE_ID
  | typeof WATERMELON_ID
  | typeof WILD_CARD_ID
  | typeof SEVEN_CARD_ID;

export type SymbolLabel =
  | typeof CHERRY_SYMBOL
  | typeof LEMON_SYMBOL
  | typeof ORANGE_SYMBOL
  | typeof GRAPE_SYMBOL
  | typeof WATERMELON_SYMBOL
  | typeof WILD_CARD_SYMBOL
  | typeof SEVEN_SYMBOL;

src/components/Dialog.tsx
import React, {useState} from "react";
import Dialog from "@mui/material/Dialog";
import {Typography} from "@mui/material";

export const JackpotDialog: React.FC = () => {
  const [open, setOpen] = useState(true); // ダイアログの開閉状態

  // ダイアログを閉じる関数
  const handleClose = () => {
    setOpen(false);
  };

  return (
    <Dialog
      open={open}
      onClose={handleClose}
      maxWidth="md" // または 'lg', 'xl' など、必要に応じてサイズを調整
    >
      <img
        src="/shiba_motivate.gif"
        alt="Motivated Shiba Inu"
        style={{width: "200px"}}
      />
      <Typography>JackPot始まるよ!!!</Typography>
    </Dialog>
  );
};
src/components/func.ts
import {WILD_CARD_ID, SEVEN_CARD_ID} from "./const";
import {SymbolTile} from "./type";

export const spinReels = (
  symbols: SymbolTile[],
  betCoins: number
): {newReels: SymbolTile[]; win: boolean; jackpot: boolean; points: number} => {
  const newReels = Array.from(
    {length: 3},
    () => symbols[Math.floor(Math.random() * symbols.length)]
  );

  const hasWildCard = newReels.some((tile) => tile.id === WILD_CARD_ID);
  const isJackpot =
    newReels.every(
      (tile) => tile.id === SEVEN_CARD_ID || tile.id === WILD_CARD_ID
    ) && newReels.some((tile) => tile.id === SEVEN_CARD_ID);

  let win = false;
  let points = 0;

  if (isJackpot) {
    // ジャックポットの場合
    win = true;
    points = 100; // ジャックポット時のポイント
  } else if (
    new Set(
      newReels.map((tile) =>
        hasWildCard && tile.id !== WILD_CARD_ID ? "wild" : tile.id
      )
    ).size === 1
  ) {
    // ワイルドカードを含む勝利
    win = true;
    points = newReels[0].point;
  }

  return {newReels, win, jackpot: isJackpot, points};
};

いい感じでコードが汚くなってきました!!!
次回はディレクトリ整理とリファクタリングを実施していこうと思います。

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?