feat: add slot machine

This commit is contained in:
Mikhail Saveliev 2024-01-09 05:10:08 +05:00
parent ede7dda619
commit 09b5e7581f
19 changed files with 279 additions and 22 deletions

View file

@ -0,0 +1,103 @@
import { useState, useEffect, useRef, useLayoutEffect } from "react";
import PropTypes from "prop-types";
import symbols2 from "../../../pages/SlotGamePage/symbols2";
function Reel({ isHorizontal, rng, rngReverse, cellCount }) {
const carousel = useRef(null);
const refs = useRef([]);
const cells = refs.current.map((value) => value);
const [selectedIndex, setSelectedIndex] = useState(null);
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
let rotateFn = isHorizontal ? "rotateY" : "rotateX";
let radius;
let theta;
let cellAngle;
function randomNumberInRange(min, max) {
// 👇️ get number between min (inclusive) and max (inclusive)
return Math.floor(Math.random() * (max - min + 1)) + min;
}
useEffect(() => {
return cellCount < 7
? setSelectedIndex(selectedIndex + randomNumberInRange(3, 6))
: setSelectedIndex(selectedIndex + randomNumberInRange(6, 14));
}, [rng]);
useEffect(() => {
return cellCount < 7
? setSelectedIndex(selectedIndex + randomNumberInRange(-3, -6))
: setSelectedIndex(selectedIndex + randomNumberInRange(-6, -14));
}, [rngReverse]);
useLayoutEffect(() => {
setWidth(carousel.current.offsetWidth);
setHeight(carousel.current.offsetHeight);
setSelectedIndex(0);
}, []);
useEffect(() => {
theta = 360 / cellCount;
rotateFn = isHorizontal ? "rotateY" : "rotateX";
const angle = theta * selectedIndex * -1;
carousel.current.style.transform = `translateZ(${-radius}px) ${rotateFn}(${angle}deg)`;
}, [selectedIndex]);
function rotateCarousel() {
const angles = theta * selectedIndex * -1;
carousel.current.style.transform = `translateZ(${-radius}px) ${rotateFn}(${angles}deg)`;
}
function changeCarousel() {
theta = 360 / cellCount;
const cellSize = isHorizontal ? width : height;
radius = Math.round(cellSize / 2 / Math.tan(Math.PI / cellCount));
// eslint-disable-next-line no-plusplus
for (let i = 0; i < cells.length; i++) {
const cell = cells[i];
if (i < cellCount) {
cell.style.opacity = 1;
cellAngle = theta * i;
cell.style.transform = `${rotateFn}(${cellAngle}deg) translateZ(${radius}px)`;
rotateCarousel();
} else {
cell.style.opacity = 0;
cell.style.transform = "none";
}
}
}
function onOrientationChange() {
rotateFn = isHorizontal ? "rotateY" : "rotateX";
changeCarousel();
}
// set initials
onOrientationChange();
return (
<div className="scene">
<div className="carousel" ref={carousel}>
{symbols2.map((c, index) => (
<div
key={c.id}
className={`carousel__cell flex bg-white ${c.transform}`}
ref={(element) => {
refs.current[index] = element;
}}
>
<img src={c.src} alt="..." className="h-24 w-24 m-auto" />
</div>
))}
</div>
</div>
);
}
Reel.propTypes = {
isHorizontal: PropTypes.bool.isRequired,
cellCount: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
.isRequired,
rng: PropTypes.bool.isRequired,
rngReverse: PropTypes.bool.isRequired,
};
export default Reel;

View file

@ -1,31 +1,67 @@
import ContainerLayout from "../../utils/ContainerLayout"
import { RPSPlayButton } from "../../components/web3/RPSPlayButton"
import PropTypes from 'prop-types';
import Reel from '../../components/App/Slot/Reel';
import { useEffect, useState } from 'react';
import { RPSPlayButton } from '../../components/web3/RPSPlayButton';
export const SlotGamePage = () => {
const [isHorizontal, setIsHorizontal] = useState(false)
const [cellCount, setCellCount] = useState(7)
const [rng, setRng] = useState(false)
const [rngReverse, setRngReverse] = useState(false)
const mql = window.matchMedia('(orientation: portrait)')
mql.onchange = (e) => {
if (e.matches) {
setIsHorizontal(true)
} else {
setIsHorizontal(false)
}
}
const handleRng = () => {
setRng(!rng)
}
const handleRngReverse = () => {
setRngReverse(!rngReverse)
}
useEffect(() => {
if (mql.matches) {
setIsHorizontal(true)
} else {
setIsHorizontal(false)
}
}, [])
return (
<ContainerLayout>
<div className="flex flex-col items-center text-white">
<p className="text-[46px] font-bold uppercase">
брось кубики онлайн!
</p>
<div className="justify-center flex pointer-events-none">
<div className="absolute w-[710px] h-[456px] bg-lime-800 rounded-[187px] blur-[300px]" />
</div>
<div className="flex flex-row min-w-full justify-center gap-10 relative mt-12">
<img src="src/assets/img/dice-img.png" alt="Dice" />
<img src="src/assets/img/dice-img.png" alt="Dice" />
</div>
{/* Кнопка для бека */}
<RPSPlayButton />
<div className="flex flex-row justify-around w-full font-semibold">
<div className="bg-gray-500 rounded-[30px] py-5 px-10 text-2xl">
Вы загадали: 5
<div className="flex flex-row gap-4">
<div className="max-h-[180px] bg-green-700 flex flex-col justify-between shadow text-white text-2xl font-bold py-4 px-6 rounded-xl">
<div className="flex flex-col">
<p>выигрыш:</p><span className="text-yellow-400">5000</span>
</div>
<div className="bg-gray-800 rounded-[30px] py-5 px-10 text-2xl">
Выпало число: 5
<div className="flex flex-col">
<p>ставка:</p><span className="text-yellow-400">10 000</span>
</div>
</div>
<div className="min-h-[800px] w-full bg-fuchsia-600 flex items-center justify-center">
<div className="flex flex-row portrait:flex-col justify-center items-center my-auto">
<Reel rng={rng} rngReverse={rngReverse} cellCount={cellCount} isHorizontal={isHorizontal} />
<Reel rng={rng} rngReverse={rngReverse} cellCount={cellCount} isHorizontal={isHorizontal} />
<Reel rng={rng} rngReverse={rngReverse} cellCount={cellCount} isHorizontal={isHorizontal} />
</div>
</div>
<div className="max-w-[100px]">
<RPSPlayButton />
</div>
</div>
</ContainerLayout>
)
}
);
}
SlotGamePage.propTypes = {
cellCount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired
};

View file

@ -0,0 +1,82 @@
import bar from '../../../assets/img/slot/bar-512.png'
import bell from '../../../assets/img/slot/bell-512.png'
import cherries from '../../../assets/img/slot/cherries-512.png'
import crown from '../../../assets/img/slot/crown-512.png'
import diamond from '../../../assets/img/slot/diamond-512.png'
import horseshoe from '../../../assets/img/slot/horseshoe-512.png'
import seven from '../../../assets/img/slot/seven-512.png'
import watermelon from '../../../assets/img/slot/watermelon-512.png'
const symbols2 = [
{
transform: 'rotateY(0deg) translateZ(288px)',
src: seven,
id: 1,
},
{
transform: 'rotateY(40deg) translateZ(288px)',
src: bar,
id: 2,
},
{
transform: 'rotateY(80deg) translateZ(288px)',
src: bell,
id: 3,
},
{
transform: 'rotateY(120deg) translateZ(288px)',
src: cherries,
id: 4,
},
{
transform: 'rotateY(160deg) translateZ(288px)',
src: horseshoe,
id: 5,
},
{
transform: 'rotateY(200deg) translateZ(288px)',
src: bar,
id: 6,
},
{
transform: 'rotateY(240deg) translateZ(288px)',
src: crown,
id: 7,
},
{
transform: 'rotateY(280deg) translateZ(288px)',
src: diamond,
id: 8,
},
{
transform: 'rotateY(320deg) translateZ(288px)',
src: bar,
id: 9,
},
{
src: horseshoe,
id: 10,
},
{
src: bar,
id: 11,
},
{
src: seven,
id: 12,
},
{
src: bar,
id: 13,
},
{
src: watermelon,
id: 14,
},
{
src: cherries,
id: 15,
},
]
export default symbols2

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,3 +1,39 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind utilities;
.scene {
border: 1px solid #ccc;
position: relative;
width: 210px;
height: 140px;
perspective: 1500px;
}
.carousel {
width: 100%;
height: 100%;
position: absolute;
transform: translateZ(-288px);
transform-style: preserve-3d;
transition: transform 1.4s;
transition-timing-function: ease-in-out;
}
.carousel__cell {
position: absolute;
width: 190px;
height: 120px;
left: 10px;
top: 10px;
border: 2px solid black;
line-height: 116px;
font-size: 80px;
font-weight: bold;
color: white;
text-align: center;
transition:
transform 1s,
opacity 1s;
backface-visibility: hidden;
}