import { useEffect, useMemo, useRef, useState } from 'react'; import { Animated, Pressable, StyleSheet, Text, View } from 'react-native'; import { LinearGradient } from 'expo-linear-gradient'; import { WS_URL } from '../api'; import { colors, fonts } from '../theme'; import CasinoButton from '../components/CasinoButton'; import Chip from '../components/Chip'; import DealerArea from '../components/DealerArea'; import Seat from '../components/Seat'; import TableBackground from '../components/TableBackground'; export default function TableScreen({ token, tableId, user, onLeave }) { const [table, setTable] = useState(null); const [balance, setBalance] = useState(user.balance); const [message, setMessage] = useState(''); const [betAmount, setBetAmount] = useState(10); const [turnSeconds, setTurnSeconds] = useState(null); const wsRef = useRef(null); const pulse = useRef(new Animated.Value(0)).current; useEffect(() => { const ws = new WebSocket(WS_URL); wsRef.current = ws; ws.onopen = () => { ws.send(JSON.stringify({ type: 'hello', token })); ws.send(JSON.stringify({ type: 'join', tableId })); }; ws.onmessage = (event) => { const payload = JSON.parse(event.data); if (payload.type === 'table_state') { setTable(payload.table); if (payload.table.minBet) { setBetAmount((prev) => { const clamped = Math.max(payload.table.minBet, Math.min(prev, payload.table.maxBet)); return clamped; }); } } if (payload.type === 'balance') { setBalance(payload.balance); } if (payload.type === 'error') { setMessage(payload.message); setTimeout(() => setMessage(''), 2400); } }; ws.onclose = () => { wsRef.current = null; }; return () => { if (ws.readyState === 1) { ws.send(JSON.stringify({ type: 'leave' })); } ws.close(); }; }, [tableId, token]); const mySeat = useMemo(() => table?.seats?.find((seat) => seat.isYou), [table]); const isMyTurn = table?.phase === 'playing' && table?.currentSeatIndex === mySeat?.index; const bettingLocked = ['playing', 'dealer', 'payout'].includes(table?.phase); useEffect(() => { if (!table?.turnEndsAt || !isMyTurn) { setTurnSeconds(null); return; } const update = () => { const msLeft = Math.max(0, table.turnEndsAt - Date.now()); setTurnSeconds(Math.ceil(msLeft / 1000)); }; update(); const interval = setInterval(update, 250); return () => clearInterval(interval); }, [table?.turnEndsAt, isMyTurn]); useEffect(() => { if (!isMyTurn) { pulse.stopAnimation(); pulse.setValue(0); return; } Animated.loop( Animated.sequence([ Animated.timing(pulse, { toValue: 1, duration: 700, useNativeDriver: true }), Animated.timing(pulse, { toValue: 0, duration: 700, useNativeDriver: true }) ]) ).start(); }, [isMyTurn, pulse]); const pulseStyle = { transform: [ { scale: pulse.interpolate({ inputRange: [0, 1], outputRange: [1, 1.04] }) } ], opacity: pulse.interpolate({ inputRange: [0, 1], outputRange: [0.85, 1] }) }; const send = (payload) => { if (wsRef.current?.readyState === 1) { wsRef.current.send(JSON.stringify(payload)); } }; const adjustBet = (delta) => { if (!table) { return; } setBetAmount((prev) => { const next = prev + delta; return Math.max(table.minBet, Math.min(table.maxBet, next)); }); }; return ( Asztal {tableId} Egyenleg: {balance} Ft {table?.seats?.map((seat) => { const position = seatPositions[seat.index]; if (!position) { return null; } return ( ); })} Tét adjustBet(-10)} style={[styles.betAdjust, bettingLocked && styles.betAdjustDisabled]} disabled={bettingLocked}> - adjustBet(10)} style={[styles.betAdjust, bettingLocked && styles.betAdjustDisabled]} disabled={bettingLocked}> + send({ type: 'bet', amount: betAmount })} variant="gold" disabled={bettingLocked} /> send({ type: 'ready' })} variant="green" disabled={bettingLocked} /> {isMyTurn && turnSeconds !== null ? ( Idő: {turnSeconds} mp ) : null} send({ type: 'action', action: 'hit' })} variant="gold" disabled={!isMyTurn} /> send({ type: 'action', action: 'stand' })} variant="gold" disabled={!isMyTurn} /> send({ type: 'action', action: 'double' })} variant="gold" disabled={!isMyTurn} /> {message ? {message} : null} ); } const seatPositions = { 0: { bottom: 10, left: '38%' }, 1: { bottom: 20, left: '8%' }, 2: { bottom: 20, right: '8%' }, 3: { top: '52%', left: 0 }, 4: { top: '52%', right: 0 }, 5: { top: '22%', left: '6%' }, 6: { top: '22%', right: '6%' } }; const styles = StyleSheet.create({ container: { flex: 1, paddingTop: 40 }, topBar: { paddingHorizontal: 20, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, tableTitle: { color: colors.goldBright, fontSize: 20, fontFamily: fonts.display, letterSpacing: 2 }, balance: { color: colors.muted, marginTop: 4, fontFamily: fonts.mono }, dealerArea: { alignItems: 'center' }, seatsLayer: { flex: 1, position: 'relative' }, seatPosition: { position: 'absolute', width: 140 }, controls: { paddingHorizontal: 20, paddingBottom: 24 }, betRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 8, marginBottom: 12 }, betControls: { flexDirection: 'row', alignItems: 'center', gap: 6 }, betAdjust: { width: 32, height: 32, borderRadius: 16, backgroundColor: 'rgba(255,255,255,0.12)', alignItems: 'center', justifyContent: 'center' }, betAdjustText: { color: colors.text, fontSize: 18, fontWeight: '700' }, betAdjustDisabled: { opacity: 0.4 }, actionRow: { flexDirection: 'row', justifyContent: 'space-between', gap: 10 }, sectionLabel: { color: colors.text, fontSize: 14, fontFamily: fonts.body }, message: { color: colors.goldBright, textAlign: 'center', marginTop: 10, fontFamily: fonts.body }, timer: { color: colors.goldBright, textAlign: 'center', marginBottom: 8, fontFamily: fonts.mono } });