import { motion } from 'framer-motion' import { Card as Card_Type, Rank, get_rank_symbol } from '../game/types' import { Hand } from './Hand' import { Card } from './Card' import { use_is_mobile } from '../hooks/use_is_mobile' interface Player_Play { cards: Card_Type[] is_pass: boolean } interface Game_Props { hand: Card_Type[] level: Rank selected_ids: Set on_card_click: (id: number) => void on_select_same_rank: (rank: number) => void on_play: () => void on_pass: () => void table_cards: Card_Type[] combo_type: string current_turn: number my_seat: number can_pass: boolean player_card_counts: number[] team_levels: [number, number] players_map: Record last_play_seat: number | null player_plays: Record leading_seat: number | null } export function Game({ hand, level, selected_ids, on_card_click, on_select_same_rank, on_play, on_pass, combo_type, current_turn, my_seat, can_pass, player_card_counts, team_levels, players_map, player_plays, leading_seat, }: Game_Props) { const is_my_turn = current_turn === my_seat const relative_positions = get_relative_positions(my_seat) const is_mobile = use_is_mobile() return (
{/* Info bar */}
Lvl: {get_rank_symbol(level)}
T1: {get_rank_symbol(team_levels[0] as Rank)} T2: {get_rank_symbol(team_levels[1] as Rank)}
{/* Game area - relative container with absolute positioned elements */}
{/* Top player badge + cards */} {/* Left player badge + cards */} {/* Right player badge + cards */} {/* My played cards - at bottom center of game area */}
{/* My area at bottom */}
{/* Turn indicator - desktop only (mobile shows in Hand button row) */} {!is_mobile && is_my_turn && hand.length > 0 && ( Your turn! )} {hand.length === 0 && ( You finished! )}
) } interface Player_Badge_Props { seat: number name?: string count: number is_turn: boolean is_leading: boolean position: 'top' | 'left' | 'right' is_mobile: boolean } function Player_Badge({ seat, name, count, is_turn, is_leading, position, is_mobile }: Player_Badge_Props) { const get_position_style = (): React.CSSProperties => { const base: React.CSSProperties = { position: 'absolute', zIndex: 10, } if (position === 'top') { return { ...base, top: is_mobile ? 2 : 8, left: '50%', transform: 'translateX(-50%)', } } if (position === 'left') { return { ...base, left: is_mobile ? 2 : 8, top: '40%', transform: 'translateY(-50%)', } } // right return { ...base, right: is_mobile ? 2 : 8, top: '40%', transform: 'translateY(-50%)', } } const get_border_color = () => { if (is_leading) return '#4caf50' if (is_turn) return '#ffc107' return 'rgba(255,255,255,0.2)' } const get_bg_color = () => { if (is_leading) return 'rgba(76, 175, 80, 0.3)' if (is_turn) return 'rgba(255, 193, 7, 0.3)' return 'rgba(0, 0, 0, 0.6)' } // Mobile: minimal floating badge with no border/background if (is_mobile) { return (
{name || `P${seat + 1}`}
{count}
) } // Desktop: boxed badge return (
{name || `P${seat + 1}`}
{count}
) } interface Played_Cards_Props { play?: Player_Play is_leading: boolean combo_type: string level: Rank position: 'top' | 'left' | 'right' is_mobile: boolean } function Played_Cards({ play, is_leading, combo_type, level, position, is_mobile }: Played_Cards_Props) { if (!play) return null const get_position_style = (): React.CSSProperties => { const base: React.CSSProperties = { position: 'absolute', zIndex: 5, display: 'flex', flexDirection: 'row', alignItems: 'center', } // Cards appear toward the center from the badge if (position === 'top') { return { ...base, top: is_mobile ? 28 : 70, left: '50%', transform: 'translateX(-50%)', } } if (position === 'left') { return { ...base, left: is_mobile ? 45 : 80, top: '40%', transform: 'translateY(-50%)', } } // right return { ...base, right: is_mobile ? 45 : 80, top: '40%', transform: 'translateY(-50%)', } } if (play.is_pass) { return (
Pass
) } // Less overlap for played cards so all cards are visible const card_overlap = is_mobile ? -20 : -28 return (
{play.cards.map((card, idx) => ( 0 ? card_overlap : 0 }} > {}} size="small" /> ))}
{is_leading && combo_type && (
{combo_type}
)}
) } interface My_Played_Cards_Props { play?: Player_Play is_leading: boolean combo_type: string level: Rank is_mobile: boolean } function My_Played_Cards({ play, is_leading, combo_type, level, is_mobile }: My_Played_Cards_Props) { if (!play) return null const base_style: React.CSSProperties = { position: 'absolute', zIndex: 5, display: 'flex', flexDirection: 'row', alignItems: 'center', bottom: is_mobile ? 8 : 16, left: '50%', transform: 'translateX(-50%)', } if (play.is_pass) { return (
Pass
) } // Less overlap for played cards so all cards are visible const card_overlap = is_mobile ? -20 : -28 return (
{play.cards.map((card, idx) => ( 0 ? card_overlap : 0 }} > {}} size="small" /> ))}
{is_leading && combo_type && (
{combo_type}
)}
) } function get_relative_positions(my_seat: number) { return { top: (my_seat + 2) % 4, left: (my_seat + 1) % 4, right: (my_seat + 3) % 4, } } const styles: Record = { container: { display: 'flex', flexDirection: 'column', height: '100vh', backgroundColor: '#0f3460', overflow: 'hidden', }, info_bar: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '4px 12px', backgroundColor: '#16213e', flexShrink: 0, }, level_badge: { padding: '3px 8px', backgroundColor: '#ffc107', color: '#000', borderRadius: 6, fontWeight: 'bold', fontSize: 12, }, team_scores: { color: '#fff', fontSize: 12, }, game_area: { flex: 1, position: 'relative', minHeight: 0, overflow: 'hidden', }, my_area: { display: 'flex', flexDirection: 'column', alignItems: 'center', paddingTop: 4, paddingBottom: 8, borderTop: '1px solid rgba(255,255,255,0.1)', flexShrink: 0, backgroundColor: 'rgba(0,0,0,0.2)', }, turn_indicator: { marginTop: 4, padding: '4px 10px', backgroundColor: '#ffc107', color: '#000', borderRadius: 6, fontWeight: 'bold', fontSize: 11, }, } const mobile_styles: Record = { container: { display: 'flex', flexDirection: 'column', height: '100dvh', backgroundColor: '#0f3460', overflow: 'hidden', }, info_bar: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '3px 8px', backgroundColor: '#16213e', flexShrink: 0, }, level_badge: { padding: '2px 6px', backgroundColor: '#ffc107', color: '#000', borderRadius: 6, fontWeight: 'bold', fontSize: 10, }, team_scores: { color: '#fff', fontSize: 10, }, game_area: { flex: 1, position: 'relative', minHeight: 0, overflow: 'hidden', }, my_area: { display: 'flex', flexDirection: 'column', alignItems: 'center', paddingTop: 2, paddingBottom: 4, flexShrink: 0, }, turn_indicator: { marginTop: 2, padding: '2px 6px', backgroundColor: '#28a745', color: '#fff', borderRadius: 4, fontWeight: 'bold', fontSize: 9, }, }