|
@ -37,4 +37,4 @@ export const AppNav = observer(() => {
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
103
frontend/casino/src/app/components/App/Slot/Reel.tsx
Normal 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;
|
|
@ -0,0 +1,7 @@
|
|||
export const RPSPlayButton = () => {
|
||||
return (
|
||||
<button className="bg-rose-700 rounded-[100px] shadow text-white text-2xl font-bold py-5 px-10 hover:bg-rose-600 inline-block max-w-[200px]">
|
||||
Играть
|
||||
</button >
|
||||
)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export const SlotPlayButton = () => {
|
||||
return (
|
||||
<button className="bg-rose-700 rounded-[100px] shadow text-white text-2xl font-bold py-5 px-10 hover:bg-rose-600">
|
||||
Крутить
|
||||
</button>
|
||||
)
|
||||
}
|
|
@ -18,15 +18,7 @@ export const MainPage = () => {
|
|||
<div className="absolute inset-0 bg-gradient-to-r from-[#171A21] via-[rgba(22, 25, 32, 0.10)] to-[#171A21] via-[rgba(22,22,22,0)]" />
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-[#171A21] via-[rgba(22, 25, 32, 0.10)] to-[#171A21] via-[rgba(22,22,22,0)]" />
|
||||
</div>
|
||||
<div className="text-white text-[46px] font-bold mb-[300px] relative z-10 mt-[100px]">
|
||||
<span>
|
||||
GET UP TO </span>
|
||||
<span className="text-amber-400">$1500</span>
|
||||
<span> BONUS
|
||||
</span>
|
||||
<div className="text-gray-400 text-[25px] font-bold">REGISTER AND GET YOUR BONUS</div>
|
||||
</div>
|
||||
<div className="w-full h-max-content bg-gray-800 rounded-[25px] p-[30px] text-white flex flex-col gap-[30px] relative z-10">
|
||||
<div className="w-full h-max-content bg-gray-800 rounded-[25px] p-[30px] text-white flex flex-col gap-[30px] relative z-10 mt-[400px]">
|
||||
<div className="flex flex-row justify-between">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<GamepadIcon />
|
||||
|
@ -46,7 +38,7 @@ export const MainPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-evenly">
|
||||
<div className="flex flex-row justify-between">
|
||||
{/* dices */}
|
||||
<div className="w-[374px] h-[187.35px] bg-[#323846] rounded-[23px] border-2 border-gray-700 flex flex-row items-center justify-between px-4 py-6 gap-3">
|
||||
<div className="rounded-full bg-neutral-800 inline-block">
|
||||
|
@ -57,7 +49,7 @@ export const MainPage = () => {
|
|||
/>
|
||||
</div>
|
||||
<div className="flex flex-col h-full justify-between">
|
||||
<div className="flex flex-row justify-between min-w-[200px]">
|
||||
<div className="flex flex-row justify-between min-w-[200px] my-auto items-center">
|
||||
<span className="text-xl font-bold">DICE</span>
|
||||
<Link to="dice" className="min-w-[100px] max-h-[45px] bg-blue-500 rounded-[10px] border border-blue-400 font-bold px-3 items-center flex justify-center">Play now</Link>
|
||||
</div>
|
||||
|
@ -73,14 +65,13 @@ export const MainPage = () => {
|
|||
/>
|
||||
</div>
|
||||
<div className="flex flex-col h-full justify-between">
|
||||
<div className="flex flex-row justify-between min-w-[200px]">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xl font-bold uppercase">Rock paper scissors</span>
|
||||
</div>
|
||||
<button className="bg-blue-500 min-w-[103px] max-h-[45px] rounded-[10px] border border-blue-400 font-bold px-3 inline-block">Play now</button>
|
||||
<div className="flex flex-row justify-between min-w-[200px] my-auto">
|
||||
<span className="text-xl font-bold uppercase">Rock paper scissors</span>
|
||||
<a href="/rock-paper-scissors" className="min-w-[100px]max-h-[45px] bg-blue-500 rounded-[10px] border border-blue-400 font-bold px-3 items-center flex justify-center py-2">Play now</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* slot */}
|
||||
<div className="w-[374px] h-[187.35px] bg-[#323846] rounded-[23px] border-2 border-gray-700 flex flex-row items-center justify-between px-4 py-6 gap-3">
|
||||
<div className="rounded-full bg-neutral-800 inline-block">
|
||||
<img
|
||||
|
@ -90,11 +81,9 @@ export const MainPage = () => {
|
|||
/>
|
||||
</div>
|
||||
<div className="flex flex-col h-full justify-between">
|
||||
<div className="flex flex-row justify-between min-w-[200px]">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xl font-bold uppercase">Slot machine</span>
|
||||
</div>
|
||||
<button className="bg-blue-500 min-w-[103px] max-h-[45px] rounded-[10px] border border-blue-400 font-bold px-3 inline-block">Play now</button>
|
||||
<div className="flex flex-row justify-between min-w-[200px] my-auto">
|
||||
<span className="text-xl font-bold uppercase">Slot machine</span>
|
||||
<a href="/slot" className="min-w-[100px]max-h-[45px] bg-blue-500 rounded-[10px] border border-blue-400 font-bold px-3 items-center flex justify-center py-2">Play now</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import React, { useState } from 'react';
|
||||
import ContainerLayout from "../../utils/ContainerLayout"
|
||||
import { RPSPlayButton } from "../../components/web3/RPSPlayButton"
|
||||
|
||||
|
||||
export const RockPaperScissorsGamePage = () => {
|
||||
const [userChoice, setUserChoice] = useState('src/assets/img/RPS-rock.png');
|
||||
|
||||
const handleImageClick = () => {
|
||||
// cycle
|
||||
if (userChoice === 'src/assets/img/RPS-rock.png') {
|
||||
setUserChoice('src/assets/img/RPS-paper.png');
|
||||
} else if (userChoice === 'src/assets/img/RPS-paper.png') {
|
||||
setUserChoice('src/assets/img/RPS-scissors.png');
|
||||
} else {
|
||||
setUserChoice('src/assets/img/RPS-rock.png');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ContainerLayout>
|
||||
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="rounded">
|
||||
<p className="text-[46px] font-bold uppercase bg-slate-400 rounded inline-block p-4 m-2">
|
||||
ты
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded">
|
||||
<p className="text-[46px] font-bold uppercase bg-slate-400 rounded inline-block p-4 m-2">
|
||||
не ты
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="w-[500px] h-[500px]">
|
||||
<img src={userChoice} alt="your_hand" onClick={handleImageClick} />
|
||||
</div>
|
||||
<div className="w-[500px] h-[500px]">
|
||||
<img src="src/assets/img/RPS-rock.png" alt="casino_hand" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 items-center">
|
||||
<img src="src/assets/img/RPS-rules.png" alt="rules" />
|
||||
<div className="max-h-[100px] flex items-center justify-center">
|
||||
<RPSPlayButton />
|
||||
</div>
|
||||
<div className="bg-green-700 shadow text-white text-2xl font-bold py-4 px-6">
|
||||
<p>won: 5 times</p>
|
||||
<p>lost: 2 times</p>
|
||||
</div>
|
||||
</div>
|
||||
</ContainerLayout>
|
||||
)
|
||||
}
|
67
frontend/casino/src/app/pages/SlotGamePage/SlotGamePage.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import ContainerLayout from "../../utils/ContainerLayout"
|
||||
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-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="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
|
||||
};
|
82
frontend/casino/src/app/pages/SlotGamePage/symbols2.ts
Normal 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
|
|
@ -4,6 +4,8 @@ import { AppLayout } from '../components/App'
|
|||
import { MainPage } from "./MainPage/MainPage.tsx";
|
||||
import { NotMainPage } from "./NotMainPage/NotMainPage.tsx";
|
||||
import { DiceGamePage } from './DiceGamePage/DiceGamePage.tsx';
|
||||
import { RockPaperScissorsGamePage } from './RockPaperScissorsGamePage/RockPaperScissorsGamePage.tsx';
|
||||
import { SlotGamePage } from './SlotGamePage/SlotGamePage.tsx';
|
||||
|
||||
const routes: RouteObject[] = [
|
||||
{
|
||||
|
@ -18,6 +20,14 @@ const routes: RouteObject[] = [
|
|||
path: '/dice',
|
||||
element: <DiceGamePage />,
|
||||
},
|
||||
{
|
||||
path: '/rock-paper-scissors',
|
||||
element: <RockPaperScissorsGamePage />,
|
||||
},
|
||||
{
|
||||
path: '/slot',
|
||||
element: <SlotGamePage />,
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
element: <Navigate replace to={'/'} />,
|
||||
|
|
BIN
frontend/casino/src/assets/img/RPS-paper.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
frontend/casino/src/assets/img/RPS-rock.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
frontend/casino/src/assets/img/RPS-rules.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
frontend/casino/src/assets/img/RPS-scissors.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
frontend/casino/src/assets/img/slot/apple-512.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
frontend/casino/src/assets/img/slot/bar-512.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
frontend/casino/src/assets/img/slot/bell-512.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
frontend/casino/src/assets/img/slot/cherries-512.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
frontend/casino/src/assets/img/slot/clover-512.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
frontend/casino/src/assets/img/slot/coin-512.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
frontend/casino/src/assets/img/slot/crown-512.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
frontend/casino/src/assets/img/slot/diamond-512.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
frontend/casino/src/assets/img/slot/grapes-512.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
frontend/casino/src/assets/img/slot/horseshoe-512.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
frontend/casino/src/assets/img/slot/orange-512.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
frontend/casino/src/assets/img/slot/seven-512.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
frontend/casino/src/assets/img/slot/star-512.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
frontend/casino/src/assets/img/slot/watermelon-512.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
frontend/casino/src/assets/img/slot/win-512.png
Normal file
After Width: | Height: | Size: 16 KiB |
|
@ -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;
|
||||
}
|
||||
|
|