From 9e0f663881dd0ea17f0ea4468a412153706e028d Mon Sep 17 00:00:00 2001 From: Strelia Illia Date: Sat, 4 Oct 2025 22:34:36 +0300 Subject: [PATCH 01/10] fix: refactor Center component --- .../_components/Actions/ActionsSection.tsx | 118 +++++ .../_components/Actions/BuyFieldAction.tsx | 58 +++ .../game/_components/Actions/CoinAction.tsx | 32 ++ .../_components/Actions/PayRentAction.tsx | 32 ++ .../_components/Actions/RollDiceAction.tsx | 38 ++ .../_components/Actions/SecretPayAction.tsx | 51 ++ .../game/_components/Actions/VDNHAction.tsx | 32 ++ src/app/(game)/game/_components/Center.tsx | 450 ++---------------- .../(game)/game/_components/WinnerDisplay.tsx | 55 +++ .../game/_hooks/useGameCenterFunctionality.ts | 186 ++++++++ src/app/(game)/game/_utils/constants.ts | 8 + src/app/layout.tsx | 2 +- src/types/game.ts | 11 + 13 files changed, 649 insertions(+), 424 deletions(-) create mode 100644 src/app/(game)/game/_components/Actions/ActionsSection.tsx create mode 100644 src/app/(game)/game/_components/Actions/BuyFieldAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/CoinAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/PayRentAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/RollDiceAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/SecretPayAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/VDNHAction.tsx create mode 100644 src/app/(game)/game/_components/WinnerDisplay.tsx create mode 100644 src/app/(game)/game/_hooks/useGameCenterFunctionality.ts create mode 100644 src/app/(game)/game/_utils/constants.ts diff --git a/src/app/(game)/game/_components/Actions/ActionsSection.tsx b/src/app/(game)/game/_components/Actions/ActionsSection.tsx new file mode 100644 index 0000000..42f7b15 --- /dev/null +++ b/src/app/(game)/game/_components/Actions/ActionsSection.tsx @@ -0,0 +1,118 @@ +import type { FC } from 'react'; +import { Player } from '@/types/player'; +import RollDiceAction from './RollDiceAction'; +import BuyFieldAction from './BuyFieldAction'; +import PayRentAction from './PayRentAction'; +import VDNHAction from './VDNHAction'; +import CoinAction from './CoinAction'; +import SecretPayAction from './SecretPayAction'; +import { Action } from '@/types'; +import { Field } from '@/types/field'; +import { useAppSelector } from '@/hooks/store'; +import { ACTION_TITLES } from '../../_utils/constants'; +import { api } from '@/api/build/api'; + +interface Props { + action: Action; + currentField: Field; + secretInfo: { + users: string[]; + amounts: number[]; + }; + amountToPay: number; +} + +const ActionsSection: FC = ({ + action, + currentField, + secretInfo, + amountToPay, +}) => { + let renderJsx = null; + + const game = useAppSelector(state => state.game.game); + const { data: user } = useAppSelector(state => state.user); + + const payForSecret = () => { + if (secretInfo.users.length > 2 && secretInfo.amounts[0] === null) { + api.payToUserForSecret(); + } else { + api.payToBankForSecret(); + } + }; + + switch (action) { + case 'rollDice': + renderJsx = ; + + case 'buy': + if (!currentField.ownedBy) { + renderJsx = ( + + ); + } + if (currentField.ownedBy && currentField.ownedBy !== user?.id) { + renderJsx = ( + + ); + } + renderJsx = null; + break; + + case 'payForField': + renderJsx = ( + + ); + break; + + case 'VDNH': + renderJsx = ( + + ); + break; + + case 'COIN': + renderJsx = ( + + ); + break; + + case 'secretPay': + renderJsx = ( + + ); + break; + } + + return ( +
+
+ {ACTION_TITLES[action as keyof typeof ACTION_TITLES]} +
+ {renderJsx} +
+ ); +}; + +export default ActionsSection; diff --git a/src/app/(game)/game/_components/Actions/BuyFieldAction.tsx b/src/app/(game)/game/_components/Actions/BuyFieldAction.tsx new file mode 100644 index 0000000..2d8496c --- /dev/null +++ b/src/app/(game)/game/_components/Actions/BuyFieldAction.tsx @@ -0,0 +1,58 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; +import HintBulb from '../HintBulb'; + +interface BuyFieldActionProps { + fieldPrice: number; + onBuyField: () => void; + onPutUpForAuction: () => void; +} + +const BuyFieldAction = ({ + fieldPrice, + onBuyField, + onPutUpForAuction, +}: BuyFieldActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+
+
+ +
+
+

+ If you cancel the purchase, the field will be put up for auction. +

+
+
+ +
+ +
+
+ + ); +}; + +export default BuyFieldAction; diff --git a/src/app/(game)/game/_components/Actions/CoinAction.tsx b/src/app/(game)/game/_components/Actions/CoinAction.tsx new file mode 100644 index 0000000..3a51e52 --- /dev/null +++ b/src/app/(game)/game/_components/Actions/CoinAction.tsx @@ -0,0 +1,32 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; + +interface CoinActionProps { + amountToPay: number; + onPay: () => void; +} + +const CoinAction = ({ amountToPay, onPay }: CoinActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+

+ The state has decided that you live too luxuriously! Pay + your taxes and maintain the balance in the game. +

+
+ + + ); +}; + +export default CoinAction; diff --git a/src/app/(game)/game/_components/Actions/PayRentAction.tsx b/src/app/(game)/game/_components/Actions/PayRentAction.tsx new file mode 100644 index 0000000..e0afc6a --- /dev/null +++ b/src/app/(game)/game/_components/Actions/PayRentAction.tsx @@ -0,0 +1,32 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; + +interface PayRentActionProps { + rentAmount: number; + onPayRent: () => void; +} + +const PayRentAction = ({ rentAmount, onPayRent }: PayRentActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+

+ When you enter someone else's field, you are obliged to + pay the owner a rent according to the value of this field. +

+
+ + + ); +}; + +export default PayRentAction; diff --git a/src/app/(game)/game/_components/Actions/RollDiceAction.tsx b/src/app/(game)/game/_components/Actions/RollDiceAction.tsx new file mode 100644 index 0000000..544fefe --- /dev/null +++ b/src/app/(game)/game/_components/Actions/RollDiceAction.tsx @@ -0,0 +1,38 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; +import HintBulb from '../HintBulb'; + +interface RollDiceActionProps { + onRollDice: () => void; +} + +const RollDiceAction = ({ onRollDice }: RollDiceActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+
+
+ +
+

Tip

+
+

+ Remember: sometimes it's better to save money than to + spend it on everything that happens. +

+
+ + + ); +}; + +export default RollDiceAction; diff --git a/src/app/(game)/game/_components/Actions/SecretPayAction.tsx b/src/app/(game)/game/_components/Actions/SecretPayAction.tsx new file mode 100644 index 0000000..9521efe --- /dev/null +++ b/src/app/(game)/game/_components/Actions/SecretPayAction.tsx @@ -0,0 +1,51 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; +import { Player } from '@/types/player'; + +interface SecretPayActionProps { + secretInfo: { + users: string[]; + amounts: number[]; + }; + amountToPay: number; + gamePlayers: Player[]; + onPay: () => void; +} + +const SecretPayAction = ({ + secretInfo, + amountToPay, + gamePlayers, + onPay, +}: SecretPayActionProps) => { + const screenSize = useScreenSize(); + + const getSecretMessage = () => { + if (secretInfo.users.length === 1) { + return `The unknown demands sacrifices! You have entered a secret field and must make a payment.`; + } else { + const player = gamePlayers.find( + player => player.userId === secretInfo?.users[0], + ); + return `Player ${player?.user.nickname} activated a secret field, and this led to an event that affected you.`; + } + }; + + return ( + <> +
+

{getSecretMessage()}

+
+ + + ); +}; + +export default SecretPayAction; diff --git a/src/app/(game)/game/_components/Actions/VDNHAction.tsx b/src/app/(game)/game/_components/Actions/VDNHAction.tsx new file mode 100644 index 0000000..e75aa72 --- /dev/null +++ b/src/app/(game)/game/_components/Actions/VDNHAction.tsx @@ -0,0 +1,32 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; + +interface VDNHActionProps { + amountToPay: number; + onPay: () => void; +} + +const VDNHAction = ({ amountToPay, onPay }: VDNHActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+

+ You've come to the festival at VDNH! Fairs, attractions + and delicious food - unforgettable impressions guaranteed! +

+
+ + + ); +}; + +export default VDNHAction; diff --git a/src/app/(game)/game/_components/Center.tsx b/src/app/(game)/game/_components/Center.tsx index dd87821..c977c9a 100644 --- a/src/app/(game)/game/_components/Center.tsx +++ b/src/app/(game)/game/_components/Center.tsx @@ -1,214 +1,29 @@ -import { Button } from '@/components/ui/button'; -import useScreenSize from '@/hooks/screenSize'; -import { useAppDispatch, useAppSelector } from '@/hooks/store'; -import { setGame } from '@/store/slices/game'; -import { DataWithGame } from '@/types'; -import { useEffect, useRef, useState } from 'react'; +import { useGameCenterFunctionality } from '../_hooks/useGameCenterFunctionality'; +import ActionsSection from './Actions/ActionsSection'; +import Auction from './Auction/Auction'; import Chat from './Chat/Chat'; -import HintBulb from './HintBulb'; import DicesContainer from './Dice/DicesContainer'; -import Auction from './Auction/Auction'; -import Image from 'next/image'; -import { Avatar } from '@nextui-org/react'; -import { Player } from '@/types/player'; -import Link from 'next/link'; -import { AuctionType } from '@/types/auction'; -import TradeOffer from './Trade/TradeOffer'; import TradeAcceptence from './Trade/TradeAcceptence'; -import { TradeData } from '@/types/trade'; -import { api } from '@/api/build/api'; -type Action = - | 'rollDice' - | 'auction' - | 'buy' - | 'payForField' - | 'secretPay' - | 'toPay' - | 'VDNH' - | 'COIN' - | ''; +import TradeOffer from './Trade/TradeOffer'; +import WinnerDisplay from './WinnerDisplay'; const Center = () => { - const screenSize = useScreenSize(); - const dispatch = useAppDispatch(); - const fields = useAppSelector(state => state.fields.fields); - const game = useAppSelector(state => state.game.game); - const { data: user } = useAppSelector(state => state.user); - const [action, setAction] = useState( - game.turnOfUserId === user?.id && !game.dices ? 'rollDice' : '', - ); + const { + tradeAcceptance, + setTradeAcceptance, + trade, + turnOfUser, + currentField, + currentPlayer, + chipTransition, + action, + game, + secretInfo, + amountToPay, + auction, + playerWon, + } = useGameCenterFunctionality(); - const { data: chipTransition } = useAppSelector( - state => state.chipTransition, - ); - const { data: trade } = useAppSelector(state => state.trade); - const [tradeAcceptance, setTradeAcceptance] = useState( - null, - ); - const [playerWon, setPlayerWon] = useState(undefined); - const [playerWithTurn] = game.players.filter( - player => player.userId === game.turnOfUserId, - ); - const [currentField] = fields.filter( - field => field.index === playerWithTurn?.currentFieldIndex, - ); - const [secretInfo, setSecretInfo] = useState<{ - users: string[]; - amounts: number[]; - }>({ - users: [], - amounts: [], - }); - const [amountToPay, setAmountToPay] = useState(0); - const rolledDice = useRef(false); - const [auction, setAuction] = useState(null); - - useEffect(() => { - if (rolledDice.current) { - if (!currentField.ownedBy && !currentField.specialField) { - setAction('buy'); - } - if (currentField.ownedBy && currentField.ownedBy !== user?.id) { - setAction('payForField'); - } - if (currentField.toPay && currentField.name === 'ВДНХ') { - setAction('VDNH'); - } - if (currentField.toPay && currentField.name === 'COIN') { - setAction('COIN'); - } - - if (currentField.secret && secretInfo) { - if (secretInfo.users.length === 1) { - if (secretInfo.amounts[0] < 0) { - setAction('secretPay'); - setAmountToPay(secretInfo.amounts[0]); - } - } else if (secretInfo.users.length === 2) { - const index = secretInfo.users.findIndex( - userId => userId === user?.id, - ); - if (secretInfo.amounts[index] < 0) { - setAction('secretPay'); - setAmountToPay(secretInfo.amounts[index]); - } - } else if ( - secretInfo.users.length > 2 && - user?.id !== secretInfo.users[0] - ) { - if (secretInfo.amounts.length === 2) { - if (secretInfo.amounts[1] < 0) { - setAction('secretPay'); - setAmountToPay(secretInfo.amounts[1]); - } - } - - if (secretInfo.amounts.length === 1) { - if (secretInfo.amounts[0] < 0) { - setAction('secretPay'); - setAmountToPay(secretInfo.amounts[0]); - } - } - } - } - rolledDice.current = false; - } - }, [game]); - useEffect(() => { - const handleRolledDice = (data: any) => { - rolledDice.current = true; - setAction(''); - }; - const handleHasPutUpForAuction = (data: any) => { - setAction('auction'); - setAuction(data.auction); - }; - const handleChangeAuction = (data: any) => { - setAuction(data.auction); - }; - const handleSecret = (data: any) => { - setSecretInfo(data); - }; - const handleUpdatePlayers = (data: any) => { - setSecretInfo(data.secretInfo); - if (!data.secretInfo.users?.includes(user?.id || 'notIncluded')) - setAction(''); - dispatch(setGame(data.game)); - }; - const handlePassTurnToNext = (data: DataWithGame) => { - if (data.game.turnOfUserId === user?.id) { - setAction('rollDice'); - } else { - setAction(''); - } - }; - const onPlayerWon = (data: any) => { - const playerWon = data.game.players.find( - (player: Player) => !player.lost, - ) as Player | undefined; - - if (playerWon) { - setPlayerWon(playerWon); - } - }; - const handleGameData = (data: any) => { - if (data.auction) { - setAction('auction'); - setAuction(data.auction); - } - if (data.secretInfo) { - setSecretInfo(data); - } - }; - const hadleTradeOffered = (data: any) => { - setTradeAcceptance(data.trade); - }; - api.on.tradeOffered(hadleTradeOffered); - api.on.gameData(handleGameData); - api.on.playerWon(onPlayerWon); - api.on.raisedPrice(handleChangeAuction); - api.on.refusedFromAuction(handleChangeAuction); - api.on.rolledDice(handleRolledDice); - api.on.hasPutUpForAuction(handleHasPutUpForAuction); - api.on.passTurnToNext(handlePassTurnToNext); - api.on.secret(handleSecret); - api.on.updatePlayers(handleUpdatePlayers); - return () => { - api.off.rolledDice(handleRolledDice); - api.off.refusedFromAuction(handleChangeAuction); - api.off.raisedPrice(handleChangeAuction); - api.off.passTurnToNext(handlePassTurnToNext); - api.off.playerWon(onPlayerWon); - api.off.secret(handleSecret); - api.off.updatePlayers(handleUpdatePlayers); - api.off.gameData(handleGameData); - api.off.tradeOffered(hadleTradeOffered); - }; - }, [user]); - const rollDice = () => { - api.rollDice(); - }; - const buyField = () => { - api.buyField(); - }; - const payForField = () => { - api.payForField(); - }; - const payToBankForSpecialField = () => { - api.payToBankForSpecialField(); - }; - const payForSecret = () => { - if (secretInfo.users.length > 2 && secretInfo.amounts[0] === null) { - api.payToUserForSecret(); - } else { - api.payToBankForSecret(); - } - }; - const turnOfUser = game.turnOfUserId === user?.id; - const currentPlayer = game.players.find(player => player.userId === user?.id); - const handlePutUpForAuction = () => { - api.putUpForAuction(); - }; return (
{tradeAcceptance && ( @@ -225,183 +40,12 @@ const Center = () => { action && action !== 'auction' && (currentField.ownedBy !== game.turnOfUserId || !game.dices) && ( -
-
- {action === 'rollDice' - ? 'Your turn' - : action === 'buy' - ? 'Buy field?' - : action === 'payForField' - ? 'Pay for rent' - : action === 'secretPay' - ? 'Unexpected expenses' - : action === 'COIN' - ? 'Luxury tax' - : action === 'VDNH' - ? 'VDNH is a time for fun!' - : ''} -
- {action === 'rollDice' && ( - <> -
-
-
- -
-

Tip

-
-

- Remember: sometimes it's better to save money than to - spend it on everything that happens. -

-
- - - )} - {action === 'buy' && !currentField.ownedBy && ( - <> -
-
-
- -
-
-

- If you cancel the purchase, the field will be put up for - auction. -

-
-
- -
- -
-
- - )} - {action === 'payForField' && ( - <> -
-

- When you enter someone else's field, you are obliged to - pay the owner a rent according to the value of this field. -

-
- - - )} - {action === 'VDNH' && ( - <> -
-

- You've come to the festival at VDNH! Fairs, attractions - and delicious food - unforgettable impressions guaranteed! -

-
- - - )} - {action === 'COIN' && ( - <> -
-

- The state has decided that you live too luxuriously! Pay - your taxes and maintain the balance in the game. -

-
- - - )} - {action === 'secretPay' && ( - <> -
-

- {secretInfo && - (secretInfo.users.length === 1 - ? `The unknown demands sacrifices! You have entered a secret field and must make a payment.` - : `Player ${game.players.find(player => player.userId === secretInfo?.users[0])?.user.nickname} activated a secret field, and this led to an event that affected you.`)} -

-
- - - )} - {action === 'buy' && - currentField.ownedBy && - currentField.ownedBy !== user?.id && ( - <> -
-

- When you enter someone else's field, you are obliged to - pay the owner a rent according to the value of this - field. -

-
- - - )} -
+ )}
{ - {playerWon && !chipTransition && ( -
-

- Winner -

- -
-
- back-button - -
-

- {playerWon?.user.nickname} -

-
- - -
- back-button -

- Exit -

-
- -
- )} + {playerWon && !chipTransition && } ); }; diff --git a/src/app/(game)/game/_components/WinnerDisplay.tsx b/src/app/(game)/game/_components/WinnerDisplay.tsx new file mode 100644 index 0000000..7388415 --- /dev/null +++ b/src/app/(game)/game/_components/WinnerDisplay.tsx @@ -0,0 +1,55 @@ +import { Player } from '@/types/player'; +import { Avatar } from '@nextui-org/react'; +import Image from 'next/image'; +import Link from 'next/link'; +import type { FC } from 'react'; + +interface Props { + playerWon: Player; +} + +const WinnerDisplay: FC = ({ playerWon }) => { + return ( +
+

+ Winner +

+ +
+
+ back-button + +
+

+ {playerWon?.user.nickname} +

+
+ + +
+ back-button +

+ Exit +

+
+ +
+ ); +}; + +export default WinnerDisplay; diff --git a/src/app/(game)/game/_hooks/useGameCenterFunctionality.ts b/src/app/(game)/game/_hooks/useGameCenterFunctionality.ts new file mode 100644 index 0000000..7bfe77b --- /dev/null +++ b/src/app/(game)/game/_hooks/useGameCenterFunctionality.ts @@ -0,0 +1,186 @@ +import { api } from '@/api/build/api'; +import { useAppDispatch, useAppSelector } from '@/hooks/store'; +import { setGame } from '@/store/slices/game'; +import { Action, DataWithGame } from '@/types'; +import { AuctionType } from '@/types/auction'; +import { Player } from '@/types/player'; +import { TradeData } from '@/types/trade'; +import { useEffect, useRef, useState } from 'react'; + +export const useGameCenterFunctionality = () => { + const dispatch = useAppDispatch(); + const fields = useAppSelector(state => state.fields.fields); + const game = useAppSelector(state => state.game.game); + const { data: user } = useAppSelector(state => state.user); + const [action, setAction] = useState( + game.turnOfUserId === user?.id && !game.dices ? 'rollDice' : '', + ); + + const { data: chipTransition } = useAppSelector( + state => state.chipTransition, + ); + const { data: trade } = useAppSelector(state => state.trade); + const [tradeAcceptance, setTradeAcceptance] = useState( + null, + ); + const [playerWon, setPlayerWon] = useState(undefined); + const [playerWithTurn] = game.players.filter( + player => player.userId === game.turnOfUserId, + ); + const [currentField] = fields.filter( + field => field.index === playerWithTurn?.currentFieldIndex, + ); + const [secretInfo, setSecretInfo] = useState<{ + users: string[]; + amounts: number[]; + }>({ + users: [], + amounts: [], + }); + const [amountToPay, setAmountToPay] = useState(0); + const rolledDice = useRef(false); + const [auction, setAuction] = useState(null); + + useEffect(() => { + if (rolledDice.current) { + if (!currentField.ownedBy && !currentField.specialField) { + setAction('buy'); + } + if (currentField.ownedBy && currentField.ownedBy !== user?.id) { + setAction('payForField'); + } + if (currentField.toPay && currentField.name === 'ВДНХ') { + setAction('VDNH'); + } + if (currentField.toPay && currentField.name === 'COIN') { + setAction('COIN'); + } + + if (currentField.secret && secretInfo) { + if (secretInfo.users.length === 1) { + if (secretInfo.amounts[0] < 0) { + setAction('secretPay'); + setAmountToPay(secretInfo.amounts[0]); + } + } else if (secretInfo.users.length === 2) { + const index = secretInfo.users.findIndex( + userId => userId === user?.id, + ); + if (secretInfo.amounts[index] < 0) { + setAction('secretPay'); + setAmountToPay(secretInfo.amounts[index]); + } + } else if ( + secretInfo.users.length > 2 && + user?.id !== secretInfo.users[0] + ) { + if (secretInfo.amounts.length === 2) { + if (secretInfo.amounts[1] < 0) { + setAction('secretPay'); + setAmountToPay(secretInfo.amounts[1]); + } + } + + if (secretInfo.amounts.length === 1) { + if (secretInfo.amounts[0] < 0) { + setAction('secretPay'); + setAmountToPay(secretInfo.amounts[0]); + } + } + } + } + rolledDice.current = false; + } + }, [game]); + useEffect(() => { + const handleRolledDice = (data: any) => { + rolledDice.current = true; + setAction(''); + }; + const handleHasPutUpForAuction = (data: any) => { + setAction('auction'); + setAuction(data.auction); + }; + const handleChangeAuction = (data: any) => { + setAuction(data.auction); + }; + const handleSecret = (data: any) => { + setSecretInfo(data); + }; + const handleUpdatePlayers = (data: any) => { + setSecretInfo(data.secretInfo); + if (!data.secretInfo.users?.includes(user?.id || 'notIncluded')) + setAction(''); + dispatch(setGame(data.game)); + }; + const handlePassTurnToNext = (data: DataWithGame) => { + if (data.game.turnOfUserId === user?.id) { + setAction('rollDice'); + } else { + setAction(''); + } + }; + const onPlayerWon = (data: any) => { + const playerWon = data.game.players.find( + (player: Player) => !player.lost, + ) as Player | undefined; + + if (playerWon) { + setPlayerWon(playerWon); + } + }; + const handleGameData = (data: any) => { + if (data.auction) { + setAction('auction'); + setAuction(data.auction); + } + if (data.secretInfo) { + setSecretInfo(data); + } + }; + const handleTradeOffered = (data: any) => { + setTradeAcceptance(data.trade); + }; + api.on.tradeOffered(handleTradeOffered); + api.on.gameData(handleGameData); + api.on.playerWon(onPlayerWon); + api.on.raisedPrice(handleChangeAuction); + api.on.refusedFromAuction(handleChangeAuction); + api.on.rolledDice(handleRolledDice); + api.on.hasPutUpForAuction(handleHasPutUpForAuction); + api.on.passTurnToNext(handlePassTurnToNext); + api.on.secret(handleSecret); + api.on.updatePlayers(handleUpdatePlayers); + return () => { + api.off.rolledDice(handleRolledDice); + api.off.refusedFromAuction(handleChangeAuction); + api.off.raisedPrice(handleChangeAuction); + api.off.passTurnToNext(handlePassTurnToNext); + api.off.playerWon(onPlayerWon); + api.off.secret(handleSecret); + api.off.updatePlayers(handleUpdatePlayers); + api.off.gameData(handleGameData); + api.off.tradeOffered(handleTradeOffered); + }; + }, [user]); + + const turnOfUser = game.turnOfUserId === user?.id; + const currentPlayer = game.players.find(player => player.userId === user?.id); + + return { + tradeAcceptance, + setTradeAcceptance, + trade, + action, + currentField, + secretInfo, + amountToPay, + playerWon, + game, + user, + chipTransition, + turnOfUser, + auction, + currentPlayer, + }; +}; diff --git a/src/app/(game)/game/_utils/constants.ts b/src/app/(game)/game/_utils/constants.ts new file mode 100644 index 0000000..919f23f --- /dev/null +++ b/src/app/(game)/game/_utils/constants.ts @@ -0,0 +1,8 @@ +export const ACTION_TITLES = { + rollDice: 'Your turn', + buy: 'Buy field?', + payForField: 'Pay for rent', + secretPay: 'Unexpected expenses', + COIN: 'Luxury tax', + VDNH: 'VDNH is a time for fun!', +} as const; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4ee6f35..425f852 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,7 +13,7 @@ export const metadata: Metadata = { export const viewport: Viewport = { themeColor: [{ media: '(prefers-color-scheme: light)', color: 'white' }], -}; +}; export default function RootLayout({ children, diff --git a/src/types/game.ts b/src/types/game.ts index 01f2fdf..cf01720 100644 --- a/src/types/game.ts +++ b/src/types/game.ts @@ -20,6 +20,17 @@ export interface Game { hotelsQty: number; } +export type Action = + | 'rollDice' + | 'auction' + | 'buy' + | 'payForField' + | 'secretPay' + | 'toPay' + | 'VDNH' + | 'COIN' + | ''; + export interface DataWithGame { game: Game; fields?: Field[]; From 817edf840e24494cf85e53bcba64c9389b1d4786 Mon Sep 17 00:00:00 2001 From: Strelia Illia Date: Sat, 4 Oct 2025 22:34:36 +0300 Subject: [PATCH 02/10] fix: refactor Center component --- .../_components/Actions/ActionsSection.tsx | 118 +++++ .../_components/Actions/BuyFieldAction.tsx | 58 +++ .../game/_components/Actions/CoinAction.tsx | 32 ++ .../_components/Actions/PayRentAction.tsx | 32 ++ .../_components/Actions/RollDiceAction.tsx | 38 ++ .../_components/Actions/SecretPayAction.tsx | 51 ++ .../game/_components/Actions/VDNHAction.tsx | 32 ++ src/app/(game)/game/_components/Center.tsx | 450 ++---------------- .../(game)/game/_components/WinnerDisplay.tsx | 55 +++ .../game/_hooks/useGameCenterFunctionality.ts | 186 ++++++++ src/app/(game)/game/_utils/constants.ts | 8 + src/app/layout.tsx | 2 +- src/types/game.ts | 11 + 13 files changed, 649 insertions(+), 424 deletions(-) create mode 100644 src/app/(game)/game/_components/Actions/ActionsSection.tsx create mode 100644 src/app/(game)/game/_components/Actions/BuyFieldAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/CoinAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/PayRentAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/RollDiceAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/SecretPayAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/VDNHAction.tsx create mode 100644 src/app/(game)/game/_components/WinnerDisplay.tsx create mode 100644 src/app/(game)/game/_hooks/useGameCenterFunctionality.ts create mode 100644 src/app/(game)/game/_utils/constants.ts diff --git a/src/app/(game)/game/_components/Actions/ActionsSection.tsx b/src/app/(game)/game/_components/Actions/ActionsSection.tsx new file mode 100644 index 0000000..42f7b15 --- /dev/null +++ b/src/app/(game)/game/_components/Actions/ActionsSection.tsx @@ -0,0 +1,118 @@ +import type { FC } from 'react'; +import { Player } from '@/types/player'; +import RollDiceAction from './RollDiceAction'; +import BuyFieldAction from './BuyFieldAction'; +import PayRentAction from './PayRentAction'; +import VDNHAction from './VDNHAction'; +import CoinAction from './CoinAction'; +import SecretPayAction from './SecretPayAction'; +import { Action } from '@/types'; +import { Field } from '@/types/field'; +import { useAppSelector } from '@/hooks/store'; +import { ACTION_TITLES } from '../../_utils/constants'; +import { api } from '@/api/build/api'; + +interface Props { + action: Action; + currentField: Field; + secretInfo: { + users: string[]; + amounts: number[]; + }; + amountToPay: number; +} + +const ActionsSection: FC = ({ + action, + currentField, + secretInfo, + amountToPay, +}) => { + let renderJsx = null; + + const game = useAppSelector(state => state.game.game); + const { data: user } = useAppSelector(state => state.user); + + const payForSecret = () => { + if (secretInfo.users.length > 2 && secretInfo.amounts[0] === null) { + api.payToUserForSecret(); + } else { + api.payToBankForSecret(); + } + }; + + switch (action) { + case 'rollDice': + renderJsx = ; + + case 'buy': + if (!currentField.ownedBy) { + renderJsx = ( + + ); + } + if (currentField.ownedBy && currentField.ownedBy !== user?.id) { + renderJsx = ( + + ); + } + renderJsx = null; + break; + + case 'payForField': + renderJsx = ( + + ); + break; + + case 'VDNH': + renderJsx = ( + + ); + break; + + case 'COIN': + renderJsx = ( + + ); + break; + + case 'secretPay': + renderJsx = ( + + ); + break; + } + + return ( +
+
+ {ACTION_TITLES[action as keyof typeof ACTION_TITLES]} +
+ {renderJsx} +
+ ); +}; + +export default ActionsSection; diff --git a/src/app/(game)/game/_components/Actions/BuyFieldAction.tsx b/src/app/(game)/game/_components/Actions/BuyFieldAction.tsx new file mode 100644 index 0000000..2d8496c --- /dev/null +++ b/src/app/(game)/game/_components/Actions/BuyFieldAction.tsx @@ -0,0 +1,58 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; +import HintBulb from '../HintBulb'; + +interface BuyFieldActionProps { + fieldPrice: number; + onBuyField: () => void; + onPutUpForAuction: () => void; +} + +const BuyFieldAction = ({ + fieldPrice, + onBuyField, + onPutUpForAuction, +}: BuyFieldActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+
+
+ +
+
+

+ If you cancel the purchase, the field will be put up for auction. +

+
+
+ +
+ +
+
+ + ); +}; + +export default BuyFieldAction; diff --git a/src/app/(game)/game/_components/Actions/CoinAction.tsx b/src/app/(game)/game/_components/Actions/CoinAction.tsx new file mode 100644 index 0000000..3a51e52 --- /dev/null +++ b/src/app/(game)/game/_components/Actions/CoinAction.tsx @@ -0,0 +1,32 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; + +interface CoinActionProps { + amountToPay: number; + onPay: () => void; +} + +const CoinAction = ({ amountToPay, onPay }: CoinActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+

+ The state has decided that you live too luxuriously! Pay + your taxes and maintain the balance in the game. +

+
+ + + ); +}; + +export default CoinAction; diff --git a/src/app/(game)/game/_components/Actions/PayRentAction.tsx b/src/app/(game)/game/_components/Actions/PayRentAction.tsx new file mode 100644 index 0000000..e0afc6a --- /dev/null +++ b/src/app/(game)/game/_components/Actions/PayRentAction.tsx @@ -0,0 +1,32 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; + +interface PayRentActionProps { + rentAmount: number; + onPayRent: () => void; +} + +const PayRentAction = ({ rentAmount, onPayRent }: PayRentActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+

+ When you enter someone else's field, you are obliged to + pay the owner a rent according to the value of this field. +

+
+ + + ); +}; + +export default PayRentAction; diff --git a/src/app/(game)/game/_components/Actions/RollDiceAction.tsx b/src/app/(game)/game/_components/Actions/RollDiceAction.tsx new file mode 100644 index 0000000..544fefe --- /dev/null +++ b/src/app/(game)/game/_components/Actions/RollDiceAction.tsx @@ -0,0 +1,38 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; +import HintBulb from '../HintBulb'; + +interface RollDiceActionProps { + onRollDice: () => void; +} + +const RollDiceAction = ({ onRollDice }: RollDiceActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+
+
+ +
+

Tip

+
+

+ Remember: sometimes it's better to save money than to + spend it on everything that happens. +

+
+ + + ); +}; + +export default RollDiceAction; diff --git a/src/app/(game)/game/_components/Actions/SecretPayAction.tsx b/src/app/(game)/game/_components/Actions/SecretPayAction.tsx new file mode 100644 index 0000000..9521efe --- /dev/null +++ b/src/app/(game)/game/_components/Actions/SecretPayAction.tsx @@ -0,0 +1,51 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; +import { Player } from '@/types/player'; + +interface SecretPayActionProps { + secretInfo: { + users: string[]; + amounts: number[]; + }; + amountToPay: number; + gamePlayers: Player[]; + onPay: () => void; +} + +const SecretPayAction = ({ + secretInfo, + amountToPay, + gamePlayers, + onPay, +}: SecretPayActionProps) => { + const screenSize = useScreenSize(); + + const getSecretMessage = () => { + if (secretInfo.users.length === 1) { + return `The unknown demands sacrifices! You have entered a secret field and must make a payment.`; + } else { + const player = gamePlayers.find( + player => player.userId === secretInfo?.users[0], + ); + return `Player ${player?.user.nickname} activated a secret field, and this led to an event that affected you.`; + } + }; + + return ( + <> +
+

{getSecretMessage()}

+
+ + + ); +}; + +export default SecretPayAction; diff --git a/src/app/(game)/game/_components/Actions/VDNHAction.tsx b/src/app/(game)/game/_components/Actions/VDNHAction.tsx new file mode 100644 index 0000000..e75aa72 --- /dev/null +++ b/src/app/(game)/game/_components/Actions/VDNHAction.tsx @@ -0,0 +1,32 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; + +interface VDNHActionProps { + amountToPay: number; + onPay: () => void; +} + +const VDNHAction = ({ amountToPay, onPay }: VDNHActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+

+ You've come to the festival at VDNH! Fairs, attractions + and delicious food - unforgettable impressions guaranteed! +

+
+ + + ); +}; + +export default VDNHAction; diff --git a/src/app/(game)/game/_components/Center.tsx b/src/app/(game)/game/_components/Center.tsx index 7aeed74..c977c9a 100644 --- a/src/app/(game)/game/_components/Center.tsx +++ b/src/app/(game)/game/_components/Center.tsx @@ -1,214 +1,29 @@ -import { Button } from '@/components/ui/button'; -import useScreenSize from '@/hooks/screenSize'; -import { useAppDispatch, useAppSelector } from '@/hooks/store'; -import { setGame } from '@/store/slices/game'; -import { DataWithGame } from '@/types'; -import { useEffect, useRef, useState } from 'react'; +import { useGameCenterFunctionality } from '../_hooks/useGameCenterFunctionality'; +import ActionsSection from './Actions/ActionsSection'; +import Auction from './Auction/Auction'; import Chat from './Chat/Chat'; -import HintBulb from './HintBulb'; import DicesContainer from './Dice/DicesContainer'; -import Auction from './Auction/Auction'; -import Image from 'next/image'; -import { Avatar } from '@nextui-org/react'; -import { Player } from '@/types/player'; -import Link from 'next/link'; -import { AuctionType } from '@/types/auction'; -import TradeOffer from './Trade/TradeOffer'; import TradeAcceptence from './Trade/TradeAcceptence'; -import { TradeData } from '@/types/trade'; -import { api } from '@/api/build/api'; -type Action = - | 'rollDice' - | 'auction' - | 'buy' - | 'payForPrivateField' - | 'secretPay' - | 'toPay' - | 'VDNH' - | 'COIN' - | ''; +import TradeOffer from './Trade/TradeOffer'; +import WinnerDisplay from './WinnerDisplay'; const Center = () => { - const screenSize = useScreenSize(); - const dispatch = useAppDispatch(); - const fields = useAppSelector(state => state.fields.fields); - const game = useAppSelector(state => state.game.game); - const { data: user } = useAppSelector(state => state.user); - const [action, setAction] = useState( - game.turnOfUserId === user?.id && !game.dices ? 'rollDice' : '', - ); + const { + tradeAcceptance, + setTradeAcceptance, + trade, + turnOfUser, + currentField, + currentPlayer, + chipTransition, + action, + game, + secretInfo, + amountToPay, + auction, + playerWon, + } = useGameCenterFunctionality(); - const { data: chipTransition } = useAppSelector( - state => state.chipTransition, - ); - const { data: trade } = useAppSelector(state => state.trade); - const [tradeAcceptance, setTradeAcceptance] = useState( - null, - ); - const [playerWon, setPlayerWon] = useState(undefined); - const [playerWithTurn] = game.players.filter( - player => player.userId === game.turnOfUserId, - ); - const [currentField] = fields.filter( - field => field.index === playerWithTurn?.currentFieldIndex, - ); - const [secretInfo, setSecretInfo] = useState<{ - users: string[]; - amounts: number[]; - }>({ - users: [], - amounts: [], - }); - const [amountToPay, setAmountToPay] = useState(0); - const rolledDice = useRef(false); - const [auction, setAuction] = useState(null); - - useEffect(() => { - if (rolledDice.current) { - if (!currentField.ownedBy && !currentField.specialField) { - setAction('buy'); - } - if (currentField.ownedBy && currentField.ownedBy !== user?.id) { - setAction('payForPrivateField'); - } - if (currentField.toPay && currentField.name === 'ВДНХ') { - setAction('VDNH'); - } - if (currentField.toPay && currentField.name === 'COIN') { - setAction('COIN'); - } - - if (currentField.secret && secretInfo) { - if (secretInfo.users.length === 1) { - if (secretInfo.amounts[0] < 0) { - setAction('secretPay'); - setAmountToPay(secretInfo.amounts[0]); - } - } else if (secretInfo.users.length === 2) { - const index = secretInfo.users.findIndex( - userId => userId === user?.id, - ); - if (secretInfo.amounts[index] < 0) { - setAction('secretPay'); - setAmountToPay(secretInfo.amounts[index]); - } - } else if ( - secretInfo.users.length > 2 && - user?.id !== secretInfo.users[0] - ) { - if (secretInfo.amounts.length === 2) { - if (secretInfo.amounts[1] < 0) { - setAction('secretPay'); - setAmountToPay(secretInfo.amounts[1]); - } - } - - if (secretInfo.amounts.length === 1) { - if (secretInfo.amounts[0] < 0) { - setAction('secretPay'); - setAmountToPay(secretInfo.amounts[0]); - } - } - } - } - rolledDice.current = false; - } - }, [game]); - useEffect(() => { - const handleRolledDice = (data: any) => { - rolledDice.current = true; - setAction(''); - }; - const handleHasPutUpForAuction = (data: any) => { - setAction('auction'); - setAuction(data.auction); - }; - const handleChangeAuction = (data: any) => { - setAuction(data.auction); - }; - const handleSecret = (data: any) => { - setSecretInfo(data); - }; - const handleUpdatePlayers = (data: any) => { - setSecretInfo(data.secretInfo); - if (!data.secretInfo.users?.includes(user?.id || 'notIncluded')) - setAction(''); - dispatch(setGame(data.game)); - }; - const handlePassTurnToNext = (data: DataWithGame) => { - if (data.game.turnOfUserId === user?.id) { - setAction('rollDice'); - } else { - setAction(''); - } - }; - const onPlayerWon = (data: any) => { - const playerWon = data.game.players.find( - (player: Player) => !player.lost, - ) as Player | undefined; - - if (playerWon) { - setPlayerWon(playerWon); - } - }; - const handleGameData = (data: any) => { - if (data.auction) { - setAction('auction'); - setAuction(data.auction); - } - if (data.secretInfo) { - setSecretInfo(data); - } - }; - const hadleTradeOffered = (data: any) => { - setTradeAcceptance(data.trade); - }; - api.on.tradeOffered(hadleTradeOffered); - api.on.gameData(handleGameData); - api.on.playerWon(onPlayerWon); - api.on.raisedPrice(handleChangeAuction); - api.on.refusedFromAuction(handleChangeAuction); - api.on.rolledDice(handleRolledDice); - api.on.hasPutUpForAuction(handleHasPutUpForAuction); - api.on.passTurnToNext(handlePassTurnToNext); - api.on.secret(handleSecret); - api.on.updatePlayers(handleUpdatePlayers); - return () => { - api.off.rolledDice(handleRolledDice); - api.off.refusedFromAuction(handleChangeAuction); - api.off.raisedPrice(handleChangeAuction); - api.off.passTurnToNext(handlePassTurnToNext); - api.off.playerWon(onPlayerWon); - api.off.secret(handleSecret); - api.off.updatePlayers(handleUpdatePlayers); - api.off.gameData(handleGameData); - api.off.tradeOffered(hadleTradeOffered); - }; - }, [user]); - const rollDice = () => { - api.rollDice(); - }; - const buyField = () => { - api.buyField(); - }; - const payForPrivateField = () => { - api.payForPrivateField(); - }; - const payToBankForSpecialField = () => { - api.payToBankForSpecialField(); - }; - const payForSecret = () => { - if (secretInfo.users.length > 2 && secretInfo.amounts[0] === null) { - api.payToUserForSecret(); - } else { - api.payToBankForSecret(); - } - }; - const turnOfUser = game.turnOfUserId === user?.id; - const currentPlayer = game.players.find(player => player.userId === user?.id); - const handlePutUpForAuction = () => { - api.putUpForAuction(); - }; return (
{tradeAcceptance && ( @@ -225,183 +40,12 @@ const Center = () => { action && action !== 'auction' && (currentField.ownedBy !== game.turnOfUserId || !game.dices) && ( -
-
- {action === 'rollDice' - ? 'Your turn' - : action === 'buy' - ? 'Buy field?' - : action === 'payForPrivateField' - ? 'Pay for rent' - : action === 'secretPay' - ? 'Unexpected expenses' - : action === 'COIN' - ? 'Luxury tax' - : action === 'VDNH' - ? 'VDNH is a time for fun!' - : ''} -
- {action === 'rollDice' && ( - <> -
-
-
- -
-

Tip

-
-

- Remember: sometimes it's better to save money than to - spend it on everything that happens. -

-
- - - )} - {action === 'buy' && !currentField.ownedBy && ( - <> -
-
-
- -
-
-

- If you cancel the purchase, the field will be put up for - auction. -

-
-
- -
- -
-
- - )} - {action === 'payForPrivateField' && ( - <> -
-

- When you enter someone else's field, you are obliged to - pay the owner a rent according to the value of this field. -

-
- - - )} - {action === 'VDNH' && ( - <> -
-

- You've come to the festival at VDNH! Fairs, attractions - and delicious food - unforgettable impressions guaranteed! -

-
- - - )} - {action === 'COIN' && ( - <> -
-

- The state has decided that you live too luxuriously! Pay - your taxes and maintain the balance in the game. -

-
- - - )} - {action === 'secretPay' && ( - <> -
-

- {secretInfo && - (secretInfo.users.length === 1 - ? `The unknown demands sacrifices! You have entered a secret field and must make a payment.` - : `Player ${game.players.find(player => player.userId === secretInfo?.users[0])?.user.nickname} activated a secret field, and this led to an event that affected you.`)} -

-
- - - )} - {action === 'buy' && - currentField.ownedBy && - currentField.ownedBy !== user?.id && ( - <> -
-

- When you enter someone else's field, you are obliged to - pay the owner a rent according to the value of this - field. -

-
- - - )} -
+ )}
{ - {playerWon && !chipTransition && ( -
-

- Winner -

- -
-
- back-button - -
-

- {playerWon?.user.nickname} -

-
- - -
- back-button -

- Exit -

-
- -
- )} + {playerWon && !chipTransition && } ); }; diff --git a/src/app/(game)/game/_components/WinnerDisplay.tsx b/src/app/(game)/game/_components/WinnerDisplay.tsx new file mode 100644 index 0000000..7388415 --- /dev/null +++ b/src/app/(game)/game/_components/WinnerDisplay.tsx @@ -0,0 +1,55 @@ +import { Player } from '@/types/player'; +import { Avatar } from '@nextui-org/react'; +import Image from 'next/image'; +import Link from 'next/link'; +import type { FC } from 'react'; + +interface Props { + playerWon: Player; +} + +const WinnerDisplay: FC = ({ playerWon }) => { + return ( +
+

+ Winner +

+ +
+
+ back-button + +
+

+ {playerWon?.user.nickname} +

+
+ + +
+ back-button +

+ Exit +

+
+ +
+ ); +}; + +export default WinnerDisplay; diff --git a/src/app/(game)/game/_hooks/useGameCenterFunctionality.ts b/src/app/(game)/game/_hooks/useGameCenterFunctionality.ts new file mode 100644 index 0000000..7bfe77b --- /dev/null +++ b/src/app/(game)/game/_hooks/useGameCenterFunctionality.ts @@ -0,0 +1,186 @@ +import { api } from '@/api/build/api'; +import { useAppDispatch, useAppSelector } from '@/hooks/store'; +import { setGame } from '@/store/slices/game'; +import { Action, DataWithGame } from '@/types'; +import { AuctionType } from '@/types/auction'; +import { Player } from '@/types/player'; +import { TradeData } from '@/types/trade'; +import { useEffect, useRef, useState } from 'react'; + +export const useGameCenterFunctionality = () => { + const dispatch = useAppDispatch(); + const fields = useAppSelector(state => state.fields.fields); + const game = useAppSelector(state => state.game.game); + const { data: user } = useAppSelector(state => state.user); + const [action, setAction] = useState( + game.turnOfUserId === user?.id && !game.dices ? 'rollDice' : '', + ); + + const { data: chipTransition } = useAppSelector( + state => state.chipTransition, + ); + const { data: trade } = useAppSelector(state => state.trade); + const [tradeAcceptance, setTradeAcceptance] = useState( + null, + ); + const [playerWon, setPlayerWon] = useState(undefined); + const [playerWithTurn] = game.players.filter( + player => player.userId === game.turnOfUserId, + ); + const [currentField] = fields.filter( + field => field.index === playerWithTurn?.currentFieldIndex, + ); + const [secretInfo, setSecretInfo] = useState<{ + users: string[]; + amounts: number[]; + }>({ + users: [], + amounts: [], + }); + const [amountToPay, setAmountToPay] = useState(0); + const rolledDice = useRef(false); + const [auction, setAuction] = useState(null); + + useEffect(() => { + if (rolledDice.current) { + if (!currentField.ownedBy && !currentField.specialField) { + setAction('buy'); + } + if (currentField.ownedBy && currentField.ownedBy !== user?.id) { + setAction('payForField'); + } + if (currentField.toPay && currentField.name === 'ВДНХ') { + setAction('VDNH'); + } + if (currentField.toPay && currentField.name === 'COIN') { + setAction('COIN'); + } + + if (currentField.secret && secretInfo) { + if (secretInfo.users.length === 1) { + if (secretInfo.amounts[0] < 0) { + setAction('secretPay'); + setAmountToPay(secretInfo.amounts[0]); + } + } else if (secretInfo.users.length === 2) { + const index = secretInfo.users.findIndex( + userId => userId === user?.id, + ); + if (secretInfo.amounts[index] < 0) { + setAction('secretPay'); + setAmountToPay(secretInfo.amounts[index]); + } + } else if ( + secretInfo.users.length > 2 && + user?.id !== secretInfo.users[0] + ) { + if (secretInfo.amounts.length === 2) { + if (secretInfo.amounts[1] < 0) { + setAction('secretPay'); + setAmountToPay(secretInfo.amounts[1]); + } + } + + if (secretInfo.amounts.length === 1) { + if (secretInfo.amounts[0] < 0) { + setAction('secretPay'); + setAmountToPay(secretInfo.amounts[0]); + } + } + } + } + rolledDice.current = false; + } + }, [game]); + useEffect(() => { + const handleRolledDice = (data: any) => { + rolledDice.current = true; + setAction(''); + }; + const handleHasPutUpForAuction = (data: any) => { + setAction('auction'); + setAuction(data.auction); + }; + const handleChangeAuction = (data: any) => { + setAuction(data.auction); + }; + const handleSecret = (data: any) => { + setSecretInfo(data); + }; + const handleUpdatePlayers = (data: any) => { + setSecretInfo(data.secretInfo); + if (!data.secretInfo.users?.includes(user?.id || 'notIncluded')) + setAction(''); + dispatch(setGame(data.game)); + }; + const handlePassTurnToNext = (data: DataWithGame) => { + if (data.game.turnOfUserId === user?.id) { + setAction('rollDice'); + } else { + setAction(''); + } + }; + const onPlayerWon = (data: any) => { + const playerWon = data.game.players.find( + (player: Player) => !player.lost, + ) as Player | undefined; + + if (playerWon) { + setPlayerWon(playerWon); + } + }; + const handleGameData = (data: any) => { + if (data.auction) { + setAction('auction'); + setAuction(data.auction); + } + if (data.secretInfo) { + setSecretInfo(data); + } + }; + const handleTradeOffered = (data: any) => { + setTradeAcceptance(data.trade); + }; + api.on.tradeOffered(handleTradeOffered); + api.on.gameData(handleGameData); + api.on.playerWon(onPlayerWon); + api.on.raisedPrice(handleChangeAuction); + api.on.refusedFromAuction(handleChangeAuction); + api.on.rolledDice(handleRolledDice); + api.on.hasPutUpForAuction(handleHasPutUpForAuction); + api.on.passTurnToNext(handlePassTurnToNext); + api.on.secret(handleSecret); + api.on.updatePlayers(handleUpdatePlayers); + return () => { + api.off.rolledDice(handleRolledDice); + api.off.refusedFromAuction(handleChangeAuction); + api.off.raisedPrice(handleChangeAuction); + api.off.passTurnToNext(handlePassTurnToNext); + api.off.playerWon(onPlayerWon); + api.off.secret(handleSecret); + api.off.updatePlayers(handleUpdatePlayers); + api.off.gameData(handleGameData); + api.off.tradeOffered(handleTradeOffered); + }; + }, [user]); + + const turnOfUser = game.turnOfUserId === user?.id; + const currentPlayer = game.players.find(player => player.userId === user?.id); + + return { + tradeAcceptance, + setTradeAcceptance, + trade, + action, + currentField, + secretInfo, + amountToPay, + playerWon, + game, + user, + chipTransition, + turnOfUser, + auction, + currentPlayer, + }; +}; diff --git a/src/app/(game)/game/_utils/constants.ts b/src/app/(game)/game/_utils/constants.ts new file mode 100644 index 0000000..919f23f --- /dev/null +++ b/src/app/(game)/game/_utils/constants.ts @@ -0,0 +1,8 @@ +export const ACTION_TITLES = { + rollDice: 'Your turn', + buy: 'Buy field?', + payForField: 'Pay for rent', + secretPay: 'Unexpected expenses', + COIN: 'Luxury tax', + VDNH: 'VDNH is a time for fun!', +} as const; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4ee6f35..425f852 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,7 +13,7 @@ export const metadata: Metadata = { export const viewport: Viewport = { themeColor: [{ media: '(prefers-color-scheme: light)', color: 'white' }], -}; +}; export default function RootLayout({ children, diff --git a/src/types/game.ts b/src/types/game.ts index 01f2fdf..cf01720 100644 --- a/src/types/game.ts +++ b/src/types/game.ts @@ -20,6 +20,17 @@ export interface Game { hotelsQty: number; } +export type Action = + | 'rollDice' + | 'auction' + | 'buy' + | 'payForField' + | 'secretPay' + | 'toPay' + | 'VDNH' + | 'COIN' + | ''; + export interface DataWithGame { game: Game; fields?: Field[]; From 402f56b9feda3c6b6741edd6f642e16d9a054691 Mon Sep 17 00:00:00 2001 From: Taras Yurchenko Date: Thu, 9 Oct 2025 19:39:40 +0300 Subject: [PATCH 03/10] feat: changed api method to right name --- .../(game)/game/_components/Actions/ActionsSection.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/app/(game)/game/_components/Actions/ActionsSection.tsx b/src/app/(game)/game/_components/Actions/ActionsSection.tsx index 42f7b15..f1c576e 100644 --- a/src/app/(game)/game/_components/Actions/ActionsSection.tsx +++ b/src/app/(game)/game/_components/Actions/ActionsSection.tsx @@ -40,10 +40,11 @@ const ActionsSection: FC = ({ api.payToBankForSecret(); } }; - + console.log('action', action); switch (action) { case 'rollDice': renderJsx = ; + break; case 'buy': if (!currentField.ownedBy) { @@ -59,7 +60,7 @@ const ActionsSection: FC = ({ renderJsx = ( ); } @@ -70,7 +71,7 @@ const ActionsSection: FC = ({ renderJsx = ( ); break; @@ -104,7 +105,7 @@ const ActionsSection: FC = ({ ); break; } - + console.log('renderJsx', renderJsx); return (
From 875453eb26aaebbf9bf8669e6c24a7b9672ccd43 Mon Sep 17 00:00:00 2001 From: Strelia Illia Date: Sun, 19 Oct 2025 14:01:24 +0300 Subject: [PATCH 04/10] Fix center component --- src/app/(game)/game/_components/Actions/ActionsSection.tsx | 6 +++--- src/app/(game)/game/_components/Center.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/(game)/game/_components/Actions/ActionsSection.tsx b/src/app/(game)/game/_components/Actions/ActionsSection.tsx index 42f7b15..937a4fd 100644 --- a/src/app/(game)/game/_components/Actions/ActionsSection.tsx +++ b/src/app/(game)/game/_components/Actions/ActionsSection.tsx @@ -44,6 +44,7 @@ const ActionsSection: FC = ({ switch (action) { case 'rollDice': renderJsx = ; + break; case 'buy': if (!currentField.ownedBy) { @@ -59,18 +60,17 @@ const ActionsSection: FC = ({ renderJsx = ( ); } - renderJsx = null; break; case 'payForField': renderJsx = ( ); break; diff --git a/src/app/(game)/game/_components/Center.tsx b/src/app/(game)/game/_components/Center.tsx index c977c9a..fccb24f 100644 --- a/src/app/(game)/game/_components/Center.tsx +++ b/src/app/(game)/game/_components/Center.tsx @@ -34,7 +34,7 @@ const Center = () => { )} {trade && }
- {(turnOfUser || action === 'secretPay') && + {turnOfUser && !currentPlayer?.lost && !chipTransition && action && From 55b7705b825788bd18c07ba3fae43af3d21ef21e Mon Sep 17 00:00:00 2001 From: Strelia Illia Date: Sun, 19 Oct 2025 15:01:31 +0300 Subject: [PATCH 05/10] Fix event calls --- .../game/_components/Actions/ActionsSection.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/(game)/game/_components/Actions/ActionsSection.tsx b/src/app/(game)/game/_components/Actions/ActionsSection.tsx index 937a4fd..6426246 100644 --- a/src/app/(game)/game/_components/Actions/ActionsSection.tsx +++ b/src/app/(game)/game/_components/Actions/ActionsSection.tsx @@ -43,7 +43,7 @@ const ActionsSection: FC = ({ switch (action) { case 'rollDice': - renderJsx = ; + renderJsx = api.rollDice()} />; break; case 'buy': @@ -51,8 +51,8 @@ const ActionsSection: FC = ({ renderJsx = ( api.buyField()} + onPutUpForAuction={() => api.putUpForAuction()} /> ); } @@ -60,7 +60,7 @@ const ActionsSection: FC = ({ renderJsx = ( api.payForPrivateField()} /> ); } @@ -70,7 +70,7 @@ const ActionsSection: FC = ({ renderJsx = ( api.payForPrivateField()} /> ); break; @@ -79,7 +79,7 @@ const ActionsSection: FC = ({ renderJsx = ( api.payToBankForSpecialField()} /> ); break; @@ -88,7 +88,7 @@ const ActionsSection: FC = ({ renderJsx = ( api.payToBankForSpecialField()} /> ); break; @@ -99,7 +99,7 @@ const ActionsSection: FC = ({ secretInfo={secretInfo} amountToPay={amountToPay} gamePlayers={game.players} - onPay={payForSecret} + onPay={() => payForSecret()} /> ); break; From 6b7e590f5e3be7243dd4e2ee2800582cfc4158da Mon Sep 17 00:00:00 2001 From: Strelia Illia Date: Sat, 4 Oct 2025 22:34:36 +0300 Subject: [PATCH 06/10] fix: refactor Center component --- .../_components/Actions/ActionsSection.tsx | 118 +++++ .../_components/Actions/BuyFieldAction.tsx | 58 +++ .../game/_components/Actions/CoinAction.tsx | 32 ++ .../_components/Actions/PayRentAction.tsx | 32 ++ .../_components/Actions/RollDiceAction.tsx | 38 ++ .../_components/Actions/SecretPayAction.tsx | 51 ++ .../game/_components/Actions/VDNHAction.tsx | 32 ++ src/app/(game)/game/_components/Center.tsx | 450 ++---------------- .../(game)/game/_components/WinnerDisplay.tsx | 55 +++ .../game/_hooks/useGameCenterFunctionality.ts | 186 ++++++++ src/app/(game)/game/_utils/constants.ts | 8 + src/app/layout.tsx | 2 +- src/types/game.ts | 11 + 13 files changed, 649 insertions(+), 424 deletions(-) create mode 100644 src/app/(game)/game/_components/Actions/ActionsSection.tsx create mode 100644 src/app/(game)/game/_components/Actions/BuyFieldAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/CoinAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/PayRentAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/RollDiceAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/SecretPayAction.tsx create mode 100644 src/app/(game)/game/_components/Actions/VDNHAction.tsx create mode 100644 src/app/(game)/game/_components/WinnerDisplay.tsx create mode 100644 src/app/(game)/game/_hooks/useGameCenterFunctionality.ts create mode 100644 src/app/(game)/game/_utils/constants.ts diff --git a/src/app/(game)/game/_components/Actions/ActionsSection.tsx b/src/app/(game)/game/_components/Actions/ActionsSection.tsx new file mode 100644 index 0000000..42f7b15 --- /dev/null +++ b/src/app/(game)/game/_components/Actions/ActionsSection.tsx @@ -0,0 +1,118 @@ +import type { FC } from 'react'; +import { Player } from '@/types/player'; +import RollDiceAction from './RollDiceAction'; +import BuyFieldAction from './BuyFieldAction'; +import PayRentAction from './PayRentAction'; +import VDNHAction from './VDNHAction'; +import CoinAction from './CoinAction'; +import SecretPayAction from './SecretPayAction'; +import { Action } from '@/types'; +import { Field } from '@/types/field'; +import { useAppSelector } from '@/hooks/store'; +import { ACTION_TITLES } from '../../_utils/constants'; +import { api } from '@/api/build/api'; + +interface Props { + action: Action; + currentField: Field; + secretInfo: { + users: string[]; + amounts: number[]; + }; + amountToPay: number; +} + +const ActionsSection: FC = ({ + action, + currentField, + secretInfo, + amountToPay, +}) => { + let renderJsx = null; + + const game = useAppSelector(state => state.game.game); + const { data: user } = useAppSelector(state => state.user); + + const payForSecret = () => { + if (secretInfo.users.length > 2 && secretInfo.amounts[0] === null) { + api.payToUserForSecret(); + } else { + api.payToBankForSecret(); + } + }; + + switch (action) { + case 'rollDice': + renderJsx = ; + + case 'buy': + if (!currentField.ownedBy) { + renderJsx = ( + + ); + } + if (currentField.ownedBy && currentField.ownedBy !== user?.id) { + renderJsx = ( + + ); + } + renderJsx = null; + break; + + case 'payForField': + renderJsx = ( + + ); + break; + + case 'VDNH': + renderJsx = ( + + ); + break; + + case 'COIN': + renderJsx = ( + + ); + break; + + case 'secretPay': + renderJsx = ( + + ); + break; + } + + return ( +
+
+ {ACTION_TITLES[action as keyof typeof ACTION_TITLES]} +
+ {renderJsx} +
+ ); +}; + +export default ActionsSection; diff --git a/src/app/(game)/game/_components/Actions/BuyFieldAction.tsx b/src/app/(game)/game/_components/Actions/BuyFieldAction.tsx new file mode 100644 index 0000000..2d8496c --- /dev/null +++ b/src/app/(game)/game/_components/Actions/BuyFieldAction.tsx @@ -0,0 +1,58 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; +import HintBulb from '../HintBulb'; + +interface BuyFieldActionProps { + fieldPrice: number; + onBuyField: () => void; + onPutUpForAuction: () => void; +} + +const BuyFieldAction = ({ + fieldPrice, + onBuyField, + onPutUpForAuction, +}: BuyFieldActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+
+
+ +
+
+

+ If you cancel the purchase, the field will be put up for auction. +

+
+
+ +
+ +
+
+ + ); +}; + +export default BuyFieldAction; diff --git a/src/app/(game)/game/_components/Actions/CoinAction.tsx b/src/app/(game)/game/_components/Actions/CoinAction.tsx new file mode 100644 index 0000000..3a51e52 --- /dev/null +++ b/src/app/(game)/game/_components/Actions/CoinAction.tsx @@ -0,0 +1,32 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; + +interface CoinActionProps { + amountToPay: number; + onPay: () => void; +} + +const CoinAction = ({ amountToPay, onPay }: CoinActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+

+ The state has decided that you live too luxuriously! Pay + your taxes and maintain the balance in the game. +

+
+ + + ); +}; + +export default CoinAction; diff --git a/src/app/(game)/game/_components/Actions/PayRentAction.tsx b/src/app/(game)/game/_components/Actions/PayRentAction.tsx new file mode 100644 index 0000000..e0afc6a --- /dev/null +++ b/src/app/(game)/game/_components/Actions/PayRentAction.tsx @@ -0,0 +1,32 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; + +interface PayRentActionProps { + rentAmount: number; + onPayRent: () => void; +} + +const PayRentAction = ({ rentAmount, onPayRent }: PayRentActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+

+ When you enter someone else's field, you are obliged to + pay the owner a rent according to the value of this field. +

+
+ + + ); +}; + +export default PayRentAction; diff --git a/src/app/(game)/game/_components/Actions/RollDiceAction.tsx b/src/app/(game)/game/_components/Actions/RollDiceAction.tsx new file mode 100644 index 0000000..544fefe --- /dev/null +++ b/src/app/(game)/game/_components/Actions/RollDiceAction.tsx @@ -0,0 +1,38 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; +import HintBulb from '../HintBulb'; + +interface RollDiceActionProps { + onRollDice: () => void; +} + +const RollDiceAction = ({ onRollDice }: RollDiceActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+
+
+ +
+

Tip

+
+

+ Remember: sometimes it's better to save money than to + spend it on everything that happens. +

+
+ + + ); +}; + +export default RollDiceAction; diff --git a/src/app/(game)/game/_components/Actions/SecretPayAction.tsx b/src/app/(game)/game/_components/Actions/SecretPayAction.tsx new file mode 100644 index 0000000..9521efe --- /dev/null +++ b/src/app/(game)/game/_components/Actions/SecretPayAction.tsx @@ -0,0 +1,51 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; +import { Player } from '@/types/player'; + +interface SecretPayActionProps { + secretInfo: { + users: string[]; + amounts: number[]; + }; + amountToPay: number; + gamePlayers: Player[]; + onPay: () => void; +} + +const SecretPayAction = ({ + secretInfo, + amountToPay, + gamePlayers, + onPay, +}: SecretPayActionProps) => { + const screenSize = useScreenSize(); + + const getSecretMessage = () => { + if (secretInfo.users.length === 1) { + return `The unknown demands sacrifices! You have entered a secret field and must make a payment.`; + } else { + const player = gamePlayers.find( + player => player.userId === secretInfo?.users[0], + ); + return `Player ${player?.user.nickname} activated a secret field, and this led to an event that affected you.`; + } + }; + + return ( + <> +
+

{getSecretMessage()}

+
+ + + ); +}; + +export default SecretPayAction; diff --git a/src/app/(game)/game/_components/Actions/VDNHAction.tsx b/src/app/(game)/game/_components/Actions/VDNHAction.tsx new file mode 100644 index 0000000..e75aa72 --- /dev/null +++ b/src/app/(game)/game/_components/Actions/VDNHAction.tsx @@ -0,0 +1,32 @@ +import { Button } from '@/components/ui/button'; +import useScreenSize from '@/hooks/screenSize'; + +interface VDNHActionProps { + amountToPay: number; + onPay: () => void; +} + +const VDNHAction = ({ amountToPay, onPay }: VDNHActionProps) => { + const screenSize = useScreenSize(); + + return ( + <> +
+

+ You've come to the festival at VDNH! Fairs, attractions + and delicious food - unforgettable impressions guaranteed! +

+
+ + + ); +}; + +export default VDNHAction; diff --git a/src/app/(game)/game/_components/Center.tsx b/src/app/(game)/game/_components/Center.tsx index 297989d..c977c9a 100644 --- a/src/app/(game)/game/_components/Center.tsx +++ b/src/app/(game)/game/_components/Center.tsx @@ -1,214 +1,29 @@ -import { Button } from '@/components/ui/button'; -import useScreenSize from '@/hooks/screenSize'; -import { useAppDispatch, useAppSelector } from '@/hooks/store'; -import { setGame } from '@/store/slices/game'; -import { DataWithGame } from '@/types'; -import { useEffect, useRef, useState } from 'react'; +import { useGameCenterFunctionality } from '../_hooks/useGameCenterFunctionality'; +import ActionsSection from './Actions/ActionsSection'; +import Auction from './Auction/Auction'; import Chat from './Chat/Chat'; -import HintBulb from './HintBulb'; import DicesContainer from './Dice/DicesContainer'; -import Auction from './Auction/Auction'; -import Image from 'next/image'; -import { Avatar } from '@nextui-org/react'; -import { Player } from '@/types/player'; -import Link from 'next/link'; -import { AuctionType } from '@/types/auction'; -import TradeOffer from './Trade/TradeOffer'; import TradeAcceptence from './Trade/TradeAcceptence'; -import { TradeData } from '@/types/trade'; -import { api } from '@/api/build/api'; -type Action = - | 'rollDice' - | 'auction' - | 'buy' - | 'payForPrivateField' - | 'secretPay' - | 'toPay' - | 'VDNH' - | 'COIN' - | ''; +import TradeOffer from './Trade/TradeOffer'; +import WinnerDisplay from './WinnerDisplay'; const Center = () => { - const screenSize = useScreenSize(); - const dispatch = useAppDispatch(); - const fields = useAppSelector(state => state.fields.fields); - const game = useAppSelector(state => state.game.game); - const { data: user } = useAppSelector(state => state.user); - const [action, setAction] = useState( - game.turnOfUserId === user?.id && !game.dices ? 'rollDice' : '', - ); + const { + tradeAcceptance, + setTradeAcceptance, + trade, + turnOfUser, + currentField, + currentPlayer, + chipTransition, + action, + game, + secretInfo, + amountToPay, + auction, + playerWon, + } = useGameCenterFunctionality(); - const { data: chipTransition } = useAppSelector( - state => state.chipTransition, - ); - const { data: trade } = useAppSelector(state => state.trade); - const [tradeAcceptance, setTradeAcceptance] = useState( - null, - ); - const [playerWon, setPlayerWon] = useState(undefined); - const [playerWithTurn] = game.players.filter( - player => player.userId === game.turnOfUserId, - ); - const [currentField] = fields.filter( - field => field.index === playerWithTurn?.currentFieldIndex, - ); - const [secretInfo, setSecretInfo] = useState<{ - users: string[]; - amounts: number[]; - }>({ - users: [], - amounts: [], - }); - const [amountToPay, setAmountToPay] = useState(0); - const rolledDice = useRef(false); - const [auction, setAuction] = useState(null); - - useEffect(() => { - if (rolledDice.current) { - if (!currentField.ownedBy && !currentField.specialField) { - setAction('buy'); - } - if (currentField.ownedBy && currentField.ownedBy !== user?.id) { - setAction('payForPrivateField'); - } - if (currentField.toPay && currentField.name === 'ВДНХ') { - setAction('VDNH'); - } - if (currentField.toPay && currentField.name === 'COIN') { - setAction('COIN'); - } - - if (currentField.secret && secretInfo) { - if (secretInfo.users.length === 1) { - if (secretInfo.amounts[0] < 0) { - setAction('secretPay'); - setAmountToPay(secretInfo.amounts[0]); - } - } else if (secretInfo.users.length === 2) { - const index = secretInfo.users.findIndex( - userId => userId === user?.id, - ); - if (secretInfo.amounts[index] < 0) { - setAction('secretPay'); - setAmountToPay(secretInfo.amounts[index]); - } - } else if ( - secretInfo.users.length > 2 && - user?.id !== secretInfo.users[0] - ) { - if (secretInfo.amounts.length === 2) { - if (secretInfo.amounts[1] < 0) { - setAction('secretPay'); - setAmountToPay(secretInfo.amounts[1]); - } - } - - if (secretInfo.amounts.length === 1) { - if (secretInfo.amounts[0] < 0) { - setAction('secretPay'); - setAmountToPay(secretInfo.amounts[0]); - } - } - } - } - rolledDice.current = false; - } - }, [game]); - useEffect(() => { - const handleRolledDice = (data: any) => { - rolledDice.current = true; - setAction(''); - }; - const handleHasPutUpForAuction = (data: any) => { - setAction('auction'); - setAuction(data.auction); - }; - const handleChangeAuction = (data: any) => { - setAuction(data.auction); - }; - const handleSecret = (data: any) => { - setSecretInfo(data); - }; - const handleUpdatePlayers = (data: any) => { - setSecretInfo(data.secretInfo); - if (!data.secretInfo?.users?.includes(user?.id || 'notIncluded')) - setAction(''); - dispatch(setGame(data.game)); - }; - const handlePassTurnToNext = (data: DataWithGame) => { - if (data.game.turnOfUserId === user?.id) { - setAction('rollDice'); - } else { - setAction(''); - } - }; - const onPlayerWon = (data: any) => { - const playerWon = data.game.players.find( - (player: Player) => !player.lost, - ) as Player | undefined; - - if (playerWon) { - setPlayerWon(playerWon); - } - }; - const handleGameData = (data: any) => { - if (data.auction) { - setAction('auction'); - setAuction(data.auction); - } - if (data.secretInfo) { - setSecretInfo(data); - } - }; - const hadleTradeOffered = (data: any) => { - setTradeAcceptance(data.trade); - }; - api.on.tradeOffered(hadleTradeOffered); - api.on.gameData(handleGameData); - api.on.playerWon(onPlayerWon); - api.on.raisedPrice(handleChangeAuction); - api.on.refusedFromAuction(handleChangeAuction); - api.on.rolledDice(handleRolledDice); - api.on.hasPutUpForAuction(handleHasPutUpForAuction); - api.on.passTurnToNext(handlePassTurnToNext); - api.on.secret(handleSecret); - api.on.updatePlayers(handleUpdatePlayers); - return () => { - api.off.rolledDice(handleRolledDice); - api.off.refusedFromAuction(handleChangeAuction); - api.off.raisedPrice(handleChangeAuction); - api.off.passTurnToNext(handlePassTurnToNext); - api.off.playerWon(onPlayerWon); - api.off.secret(handleSecret); - api.off.updatePlayers(handleUpdatePlayers); - api.off.gameData(handleGameData); - api.off.tradeOffered(hadleTradeOffered); - }; - }, [user]); - const rollDice = () => { - api.rollDice(); - }; - const buyField = () => { - api.buyField(); - }; - const payForPrivateField = () => { - api.payForPrivateField(); - }; - const payToBankForSpecialField = () => { - api.payToBankForSpecialField(); - }; - const payForSecret = () => { - if (secretInfo.users.length > 2 && secretInfo.amounts[0] === null) { - api.payToUserForSecret(); - } else { - api.payToBankForSecret(); - } - }; - const turnOfUser = game.turnOfUserId === user?.id; - const currentPlayer = game.players.find(player => player.userId === user?.id); - const handlePutUpForAuction = () => { - api.putUpForAuction(); - }; return (
{tradeAcceptance && ( @@ -225,183 +40,12 @@ const Center = () => { action && action !== 'auction' && (currentField.ownedBy !== game.turnOfUserId || !game.dices) && ( -
-
- {action === 'rollDice' - ? 'Your turn' - : action === 'buy' - ? 'Buy field?' - : action === 'payForPrivateField' - ? 'Pay for rent' - : action === 'secretPay' - ? 'Unexpected expenses' - : action === 'COIN' - ? 'Luxury tax' - : action === 'VDNH' - ? 'VDNH is a time for fun!' - : ''} -
- {action === 'rollDice' && ( - <> -
-
-
- -
-

Tip

-
-

- Remember: sometimes it's better to save money than to - spend it on everything that happens. -

-
- - - )} - {action === 'buy' && !currentField.ownedBy && ( - <> -
-
-
- -
-
-

- If you cancel the purchase, the field will be put up for - auction. -

-
-
- -
- -
-
- - )} - {action === 'payForPrivateField' && ( - <> -
-

- When you enter someone else's field, you are obliged to - pay the owner a rent according to the value of this field. -

-
- - - )} - {action === 'VDNH' && ( - <> -
-

- You've come to the festival at VDNH! Fairs, attractions - and delicious food - unforgettable impressions guaranteed! -

-
- - - )} - {action === 'COIN' && ( - <> -
-

- The state has decided that you live too luxuriously! Pay - your taxes and maintain the balance in the game. -

-
- - - )} - {action === 'secretPay' && ( - <> -
-

- {secretInfo && - (secretInfo.users.length === 1 - ? `The unknown demands sacrifices! You have entered a secret field and must make a payment.` - : `Player ${game.players.find(player => player.userId === secretInfo?.users[0])?.user.nickname} activated a secret field, and this led to an event that affected you.`)} -

-
- - - )} - {action === 'buy' && - currentField.ownedBy && - currentField.ownedBy !== user?.id && ( - <> -
-

- When you enter someone else's field, you are obliged to - pay the owner a rent according to the value of this - field. -

-
- - - )} -
+ )}
{
- {playerWon && !chipTransition && ( -
-

- Winner -

- -
-
- back-button - -
-

- {playerWon?.user.nickname} -

-
- - -
- back-button -

- Exit -

-
- -
- )} + {playerWon && !chipTransition && }
); }; diff --git a/src/app/(game)/game/_components/WinnerDisplay.tsx b/src/app/(game)/game/_components/WinnerDisplay.tsx new file mode 100644 index 0000000..7388415 --- /dev/null +++ b/src/app/(game)/game/_components/WinnerDisplay.tsx @@ -0,0 +1,55 @@ +import { Player } from '@/types/player'; +import { Avatar } from '@nextui-org/react'; +import Image from 'next/image'; +import Link from 'next/link'; +import type { FC } from 'react'; + +interface Props { + playerWon: Player; +} + +const WinnerDisplay: FC = ({ playerWon }) => { + return ( +
+

+ Winner +

+ +
+
+ back-button + +
+

+ {playerWon?.user.nickname} +

+
+ + +
+ back-button +

+ Exit +

+
+ +
+ ); +}; + +export default WinnerDisplay; diff --git a/src/app/(game)/game/_hooks/useGameCenterFunctionality.ts b/src/app/(game)/game/_hooks/useGameCenterFunctionality.ts new file mode 100644 index 0000000..7bfe77b --- /dev/null +++ b/src/app/(game)/game/_hooks/useGameCenterFunctionality.ts @@ -0,0 +1,186 @@ +import { api } from '@/api/build/api'; +import { useAppDispatch, useAppSelector } from '@/hooks/store'; +import { setGame } from '@/store/slices/game'; +import { Action, DataWithGame } from '@/types'; +import { AuctionType } from '@/types/auction'; +import { Player } from '@/types/player'; +import { TradeData } from '@/types/trade'; +import { useEffect, useRef, useState } from 'react'; + +export const useGameCenterFunctionality = () => { + const dispatch = useAppDispatch(); + const fields = useAppSelector(state => state.fields.fields); + const game = useAppSelector(state => state.game.game); + const { data: user } = useAppSelector(state => state.user); + const [action, setAction] = useState( + game.turnOfUserId === user?.id && !game.dices ? 'rollDice' : '', + ); + + const { data: chipTransition } = useAppSelector( + state => state.chipTransition, + ); + const { data: trade } = useAppSelector(state => state.trade); + const [tradeAcceptance, setTradeAcceptance] = useState( + null, + ); + const [playerWon, setPlayerWon] = useState(undefined); + const [playerWithTurn] = game.players.filter( + player => player.userId === game.turnOfUserId, + ); + const [currentField] = fields.filter( + field => field.index === playerWithTurn?.currentFieldIndex, + ); + const [secretInfo, setSecretInfo] = useState<{ + users: string[]; + amounts: number[]; + }>({ + users: [], + amounts: [], + }); + const [amountToPay, setAmountToPay] = useState(0); + const rolledDice = useRef(false); + const [auction, setAuction] = useState(null); + + useEffect(() => { + if (rolledDice.current) { + if (!currentField.ownedBy && !currentField.specialField) { + setAction('buy'); + } + if (currentField.ownedBy && currentField.ownedBy !== user?.id) { + setAction('payForField'); + } + if (currentField.toPay && currentField.name === 'ВДНХ') { + setAction('VDNH'); + } + if (currentField.toPay && currentField.name === 'COIN') { + setAction('COIN'); + } + + if (currentField.secret && secretInfo) { + if (secretInfo.users.length === 1) { + if (secretInfo.amounts[0] < 0) { + setAction('secretPay'); + setAmountToPay(secretInfo.amounts[0]); + } + } else if (secretInfo.users.length === 2) { + const index = secretInfo.users.findIndex( + userId => userId === user?.id, + ); + if (secretInfo.amounts[index] < 0) { + setAction('secretPay'); + setAmountToPay(secretInfo.amounts[index]); + } + } else if ( + secretInfo.users.length > 2 && + user?.id !== secretInfo.users[0] + ) { + if (secretInfo.amounts.length === 2) { + if (secretInfo.amounts[1] < 0) { + setAction('secretPay'); + setAmountToPay(secretInfo.amounts[1]); + } + } + + if (secretInfo.amounts.length === 1) { + if (secretInfo.amounts[0] < 0) { + setAction('secretPay'); + setAmountToPay(secretInfo.amounts[0]); + } + } + } + } + rolledDice.current = false; + } + }, [game]); + useEffect(() => { + const handleRolledDice = (data: any) => { + rolledDice.current = true; + setAction(''); + }; + const handleHasPutUpForAuction = (data: any) => { + setAction('auction'); + setAuction(data.auction); + }; + const handleChangeAuction = (data: any) => { + setAuction(data.auction); + }; + const handleSecret = (data: any) => { + setSecretInfo(data); + }; + const handleUpdatePlayers = (data: any) => { + setSecretInfo(data.secretInfo); + if (!data.secretInfo.users?.includes(user?.id || 'notIncluded')) + setAction(''); + dispatch(setGame(data.game)); + }; + const handlePassTurnToNext = (data: DataWithGame) => { + if (data.game.turnOfUserId === user?.id) { + setAction('rollDice'); + } else { + setAction(''); + } + }; + const onPlayerWon = (data: any) => { + const playerWon = data.game.players.find( + (player: Player) => !player.lost, + ) as Player | undefined; + + if (playerWon) { + setPlayerWon(playerWon); + } + }; + const handleGameData = (data: any) => { + if (data.auction) { + setAction('auction'); + setAuction(data.auction); + } + if (data.secretInfo) { + setSecretInfo(data); + } + }; + const handleTradeOffered = (data: any) => { + setTradeAcceptance(data.trade); + }; + api.on.tradeOffered(handleTradeOffered); + api.on.gameData(handleGameData); + api.on.playerWon(onPlayerWon); + api.on.raisedPrice(handleChangeAuction); + api.on.refusedFromAuction(handleChangeAuction); + api.on.rolledDice(handleRolledDice); + api.on.hasPutUpForAuction(handleHasPutUpForAuction); + api.on.passTurnToNext(handlePassTurnToNext); + api.on.secret(handleSecret); + api.on.updatePlayers(handleUpdatePlayers); + return () => { + api.off.rolledDice(handleRolledDice); + api.off.refusedFromAuction(handleChangeAuction); + api.off.raisedPrice(handleChangeAuction); + api.off.passTurnToNext(handlePassTurnToNext); + api.off.playerWon(onPlayerWon); + api.off.secret(handleSecret); + api.off.updatePlayers(handleUpdatePlayers); + api.off.gameData(handleGameData); + api.off.tradeOffered(handleTradeOffered); + }; + }, [user]); + + const turnOfUser = game.turnOfUserId === user?.id; + const currentPlayer = game.players.find(player => player.userId === user?.id); + + return { + tradeAcceptance, + setTradeAcceptance, + trade, + action, + currentField, + secretInfo, + amountToPay, + playerWon, + game, + user, + chipTransition, + turnOfUser, + auction, + currentPlayer, + }; +}; diff --git a/src/app/(game)/game/_utils/constants.ts b/src/app/(game)/game/_utils/constants.ts new file mode 100644 index 0000000..919f23f --- /dev/null +++ b/src/app/(game)/game/_utils/constants.ts @@ -0,0 +1,8 @@ +export const ACTION_TITLES = { + rollDice: 'Your turn', + buy: 'Buy field?', + payForField: 'Pay for rent', + secretPay: 'Unexpected expenses', + COIN: 'Luxury tax', + VDNH: 'VDNH is a time for fun!', +} as const; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4ee6f35..425f852 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,7 +13,7 @@ export const metadata: Metadata = { export const viewport: Viewport = { themeColor: [{ media: '(prefers-color-scheme: light)', color: 'white' }], -}; +}; export default function RootLayout({ children, diff --git a/src/types/game.ts b/src/types/game.ts index 01f2fdf..cf01720 100644 --- a/src/types/game.ts +++ b/src/types/game.ts @@ -20,6 +20,17 @@ export interface Game { hotelsQty: number; } +export type Action = + | 'rollDice' + | 'auction' + | 'buy' + | 'payForField' + | 'secretPay' + | 'toPay' + | 'VDNH' + | 'COIN' + | ''; + export interface DataWithGame { game: Game; fields?: Field[]; From 1b6f3e112d96257637b623283ec723057a94d943 Mon Sep 17 00:00:00 2001 From: Taras Yurchenko Date: Thu, 9 Oct 2025 19:39:40 +0300 Subject: [PATCH 07/10] feat: changed api method to right name --- .../(game)/game/_components/Actions/ActionsSection.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/app/(game)/game/_components/Actions/ActionsSection.tsx b/src/app/(game)/game/_components/Actions/ActionsSection.tsx index 42f7b15..f1c576e 100644 --- a/src/app/(game)/game/_components/Actions/ActionsSection.tsx +++ b/src/app/(game)/game/_components/Actions/ActionsSection.tsx @@ -40,10 +40,11 @@ const ActionsSection: FC = ({ api.payToBankForSecret(); } }; - + console.log('action', action); switch (action) { case 'rollDice': renderJsx = ; + break; case 'buy': if (!currentField.ownedBy) { @@ -59,7 +60,7 @@ const ActionsSection: FC = ({ renderJsx = ( ); } @@ -70,7 +71,7 @@ const ActionsSection: FC = ({ renderJsx = ( ); break; @@ -104,7 +105,7 @@ const ActionsSection: FC = ({ ); break; } - + console.log('renderJsx', renderJsx); return (
From 3ae3c7c1697dccee170342d1e8182a14d29832fb Mon Sep 17 00:00:00 2001 From: Strelia Illia Date: Sun, 19 Oct 2025 14:01:24 +0300 Subject: [PATCH 08/10] Fix center component --- src/app/(game)/game/_components/Actions/ActionsSection.tsx | 1 - src/app/(game)/game/_components/Center.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/(game)/game/_components/Actions/ActionsSection.tsx b/src/app/(game)/game/_components/Actions/ActionsSection.tsx index f1c576e..2af8ccc 100644 --- a/src/app/(game)/game/_components/Actions/ActionsSection.tsx +++ b/src/app/(game)/game/_components/Actions/ActionsSection.tsx @@ -64,7 +64,6 @@ const ActionsSection: FC = ({ /> ); } - renderJsx = null; break; case 'payForField': diff --git a/src/app/(game)/game/_components/Center.tsx b/src/app/(game)/game/_components/Center.tsx index c977c9a..fccb24f 100644 --- a/src/app/(game)/game/_components/Center.tsx +++ b/src/app/(game)/game/_components/Center.tsx @@ -34,7 +34,7 @@ const Center = () => { )} {trade && }
- {(turnOfUser || action === 'secretPay') && + {turnOfUser && !currentPlayer?.lost && !chipTransition && action && From 36c7863e0fd536577b08ff2282dcb6534b75b066 Mon Sep 17 00:00:00 2001 From: Strelia Illia Date: Sun, 19 Oct 2025 15:01:31 +0300 Subject: [PATCH 09/10] Fix event calls --- .../_components/Actions/ActionsSection.tsx | 18 +++++++++--------- .../game/_hooks/useGameCenterFunctionality.ts | 4 ++-- src/app/(game)/game/_utils/constants.ts | 2 +- src/types/game.ts | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/app/(game)/game/_components/Actions/ActionsSection.tsx b/src/app/(game)/game/_components/Actions/ActionsSection.tsx index 2af8ccc..347ef07 100644 --- a/src/app/(game)/game/_components/Actions/ActionsSection.tsx +++ b/src/app/(game)/game/_components/Actions/ActionsSection.tsx @@ -43,7 +43,7 @@ const ActionsSection: FC = ({ console.log('action', action); switch (action) { case 'rollDice': - renderJsx = ; + renderJsx = api.rollDice()} />; break; case 'buy': @@ -51,8 +51,8 @@ const ActionsSection: FC = ({ renderJsx = ( api.buyField()} + onPutUpForAuction={() => api.putUpForAuction()} /> ); } @@ -60,17 +60,17 @@ const ActionsSection: FC = ({ renderJsx = ( api.payForPrivateField()} /> ); } break; - case 'payForField': + case 'payForPrivateField': renderJsx = ( api.payForPrivateField()} /> ); break; @@ -79,7 +79,7 @@ const ActionsSection: FC = ({ renderJsx = ( api.payToBankForSpecialField()} /> ); break; @@ -88,7 +88,7 @@ const ActionsSection: FC = ({ renderJsx = ( api.payToBankForSpecialField()} /> ); break; @@ -99,7 +99,7 @@ const ActionsSection: FC = ({ secretInfo={secretInfo} amountToPay={amountToPay} gamePlayers={game.players} - onPay={payForSecret} + onPay={() => payForSecret()} /> ); break; diff --git a/src/app/(game)/game/_hooks/useGameCenterFunctionality.ts b/src/app/(game)/game/_hooks/useGameCenterFunctionality.ts index 7bfe77b..c978108 100644 --- a/src/app/(game)/game/_hooks/useGameCenterFunctionality.ts +++ b/src/app/(game)/game/_hooks/useGameCenterFunctionality.ts @@ -47,7 +47,7 @@ export const useGameCenterFunctionality = () => { setAction('buy'); } if (currentField.ownedBy && currentField.ownedBy !== user?.id) { - setAction('payForField'); + setAction('payForPrivateField'); } if (currentField.toPay && currentField.name === 'ВДНХ') { setAction('VDNH'); @@ -109,7 +109,7 @@ export const useGameCenterFunctionality = () => { }; const handleUpdatePlayers = (data: any) => { setSecretInfo(data.secretInfo); - if (!data.secretInfo.users?.includes(user?.id || 'notIncluded')) + if (!data?.secretInfo?.users?.includes(user?.id || 'notIncluded')) setAction(''); dispatch(setGame(data.game)); }; diff --git a/src/app/(game)/game/_utils/constants.ts b/src/app/(game)/game/_utils/constants.ts index 919f23f..be88296 100644 --- a/src/app/(game)/game/_utils/constants.ts +++ b/src/app/(game)/game/_utils/constants.ts @@ -1,7 +1,7 @@ export const ACTION_TITLES = { rollDice: 'Your turn', buy: 'Buy field?', - payForField: 'Pay for rent', + payForPrivateField: 'Pay for rent', secretPay: 'Unexpected expenses', COIN: 'Luxury tax', VDNH: 'VDNH is a time for fun!', diff --git a/src/types/game.ts b/src/types/game.ts index cf01720..c9fefb7 100644 --- a/src/types/game.ts +++ b/src/types/game.ts @@ -24,7 +24,7 @@ export type Action = | 'rollDice' | 'auction' | 'buy' - | 'payForField' + | 'payForPrivateField' | 'secretPay' | 'toPay' | 'VDNH' From 77883c34a07ab5e0f06ccb1a726b478433383b62 Mon Sep 17 00:00:00 2001 From: Strelia Illia Date: Sun, 19 Oct 2025 20:41:47 +0300 Subject: [PATCH 10/10] fix secretPay action not rendering --- .../_components/Actions/ActionsSection.tsx | 118 ++++++++++++++++++ src/app/(game)/game/_components/Center.tsx | 2 +- src/app/(game)/game/_utils/constants.ts | 4 - 3 files changed, 119 insertions(+), 5 deletions(-) diff --git a/src/app/(game)/game/_components/Actions/ActionsSection.tsx b/src/app/(game)/game/_components/Actions/ActionsSection.tsx index e69de29..0b94153 100644 --- a/src/app/(game)/game/_components/Actions/ActionsSection.tsx +++ b/src/app/(game)/game/_components/Actions/ActionsSection.tsx @@ -0,0 +1,118 @@ +import type { FC } from 'react'; +import { Player } from '@/types/player'; +import RollDiceAction from './RollDiceAction'; +import BuyFieldAction from './BuyFieldAction'; +import PayRentAction from './PayRentAction'; +import VDNHAction from './VDNHAction'; +import CoinAction from './CoinAction'; +import SecretPayAction from './SecretPayAction'; +import { Action } from '@/types'; +import { Field } from '@/types/field'; +import { useAppSelector } from '@/hooks/store'; +import { ACTION_TITLES } from '../../_utils/constants'; +import { api } from '@/api/build/api'; + +interface Props { + action: Action; + currentField: Field; + secretInfo: { + users: string[]; + amounts: number[]; + }; + amountToPay: number; +} + +const ActionsSection: FC = ({ + action, + currentField, + secretInfo, + amountToPay, +}) => { + let renderJsx = null; + + const game = useAppSelector(state => state.game.game); + const { data: user } = useAppSelector(state => state.user); + + const payForSecret = () => { + if (secretInfo.users.length > 2 && secretInfo.amounts[0] === null) { + api.payToUserForSecret(); + } else { + api.payToBankForSecret(); + } + }; + + switch (action) { + case 'rollDice': + renderJsx = api.rollDice()} />; + break; + + case 'buy': + if (!currentField.ownedBy) { + renderJsx = ( + api.buyField()} + onPutUpForAuction={() => api.putUpForAuction()} + /> + ); + } + if (currentField.ownedBy && currentField.ownedBy !== user?.id) { + renderJsx = ( + api.payForPrivateField()} + /> + ); + } + break; + + case 'payForPrivateField': + renderJsx = ( + api.payForPrivateField()} + /> + ); + break; + + case 'VDNH': + renderJsx = ( + api.payToBankForSpecialField()} + /> + ); + break; + + case 'COIN': + renderJsx = ( + api.payToBankForSpecialField()} + /> + ); + break; + + case 'secretPay': + renderJsx = ( + payForSecret()} + /> + ); + break; + } + console.log('renderJsx', renderJsx); + return ( +
+
+ {ACTION_TITLES[action as keyof typeof ACTION_TITLES]} +
+ {renderJsx} +
+ ); +}; + +export default ActionsSection; diff --git a/src/app/(game)/game/_components/Center.tsx b/src/app/(game)/game/_components/Center.tsx index fccb24f..c977c9a 100644 --- a/src/app/(game)/game/_components/Center.tsx +++ b/src/app/(game)/game/_components/Center.tsx @@ -34,7 +34,7 @@ const Center = () => { )} {trade && }
- {turnOfUser && + {(turnOfUser || action === 'secretPay') && !currentPlayer?.lost && !chipTransition && action && diff --git a/src/app/(game)/game/_utils/constants.ts b/src/app/(game)/game/_utils/constants.ts index 354e8ae..be88296 100644 --- a/src/app/(game)/game/_utils/constants.ts +++ b/src/app/(game)/game/_utils/constants.ts @@ -1,11 +1,7 @@ export const ACTION_TITLES = { rollDice: 'Your turn', buy: 'Buy field?', -<<<<<<< HEAD - payForField: 'Pay for rent', -======= payForPrivateField: 'Pay for rent', ->>>>>>> 36c7863e0fd536577b08ff2282dcb6534b75b066 secretPay: 'Unexpected expenses', COIN: 'Luxury tax', VDNH: 'VDNH is a time for fun!',