import { useEffect, useMemo, useRef, useState } from 'react'; import { Animated, Pressable, StyleSheet, Text, useWindowDimensions, View } from 'react-native'; import { LinearGradient } from 'expo-linear-gradient'; import { BlurView } from 'expo-blur'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; 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'; import TableMarkings from '../components/TableMarkings'; 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; const insets = useSafeAreaInsets(); const { width, height } = useWindowDimensions(); const isPortrait = height >= width; 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)); }); }; const seats = table?.seats || []; if (isPortrait) { return ( Fordítsd el a telefont A blackjack asztal fekvő nézetben működik jól. ); } return ( Asztal {tableId} Egyenleg: {balance} Ft {seats.map((seat) => ( ))} 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 styles = StyleSheet.create({ container: { flex: 1, paddingHorizontal: 12 }, topBar: { paddingHorizontal: 6, 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 }, tableWrap: { flex: 1, justifyContent: 'flex-start', alignItems: 'center', marginTop: 8, marginBottom: 8, width: '100%' }, tableSurface: { flex: 1, width: '100%', padding: 0 }, tableInner: { flex: 1, position: 'relative' }, dealerArea: { alignItems: 'center', paddingTop: 8 }, tableContent: { flex: 1, justifyContent: 'space-between', paddingBottom: 8 }, seatLayer: { flex: 1, position: 'relative' }, seatSpot: { position: 'absolute', width: 96, transform: [{ translateX: -48 }] }, controlsOverlay: { position: 'absolute', left: 12, right: 12, bottom: 14, gap: 10 }, blurPanel: { borderRadius: 18, paddingVertical: 10, paddingHorizontal: 12, backgroundColor: 'rgba(10, 18, 14, 0.45)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.12)', overflow: 'hidden' }, betRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 8 }, 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: 8, fontFamily: fonts.body }, timer: { color: colors.goldBright, textAlign: 'center', marginBottom: 6, fontFamily: fonts.mono }, rotateWrap: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 24 }, rotateTitle: { color: colors.goldBright, fontFamily: fonts.display, fontSize: 24, letterSpacing: 2, textAlign: 'center' }, rotateSubtitle: { color: colors.muted, fontFamily: fonts.body, fontSize: 14, marginTop: 12, textAlign: 'center' } }); const seatPositions = { 0: { left: '8%', top: '62%' }, 1: { left: '22%', top: '55%' }, 2: { left: '36%', top: '50%' }, 3: { left: '50%', top: '48%' }, 4: { left: '64%', top: '50%' }, 5: { left: '78%', top: '55%' }, 6: { left: '92%', top: '62%' } };