// موجودیت‌های وضعیت بازی (نگاشت از پیام‌های WebSocket سرور). class GamePlayer { final int seat; final String name; final bool bot; final bool connected; final int coins; final String rankTier; // bronze..king (خالی برای بات) final String avatar; // seed آواتارِ انتخابیِ کاربر (خالی برای بات) final String frame; // قابِ آواتارِ انتخابی (کازمتیک؛ خالی ⇒ قابِ رتبه) GamePlayer.fromJson(Map j) : seat = (j['seat'] ?? 0) as int, name = (j['name'] ?? '') as String, bot = (j['bot'] ?? false) as bool, connected = (j['connected'] ?? false) as bool, coins = (j['coins'] ?? 0) as int, rankTier = (j['rank_tier'] ?? '') as String, avatar = (j['avatar'] ?? '') as String, frame = (j['frame'] ?? '') as String; /// seedِ آواتار برای نمایش: آواتارِ انتخابی، وگرنه نام (fallback برای بات). String get avatarSeed => avatar.isNotEmpty ? avatar : name; } class TrickCard { final int seat; final String card; TrickCard(this.seat, this.card); factory TrickCard.fromJson(Map j) => TrickCard((j['seat'] ?? 0) as int, (j['card'] ?? '') as String); } /// نمای وضعیت بازی برای بازیکن جاری (پیام type=state). class GameState { final String room; final String phase; // choose_trump | playing | hand_over | game_over final int yourSeat; final int hakem; final int turn; final String? trump; final bool trickDone; final List yourHand; final List handCounts; final List trick; final String? leadSuit; final List tricksWon; final List scores; final int targetScore; final int turnTimeoutMs; // مهلتِ نوبتِ انسان (برای شمارنده‌ی نوبت) final List players; GameState({ required this.room, required this.phase, required this.yourSeat, required this.hakem, required this.turn, required this.trump, required this.trickDone, required this.yourHand, required this.handCounts, required this.trick, required this.leadSuit, required this.tricksWon, required this.scores, required this.targetScore, required this.turnTimeoutMs, required this.players, }); factory GameState.fromJson(Map j) { List ints(dynamic v) => ((v as List?) ?? []).map((e) => (e as num).toInt()).toList(); return GameState( room: (j['room'] ?? '') as String, phase: (j['phase'] ?? '') as String, yourSeat: (j['your_seat'] ?? 0) as int, hakem: (j['hakem'] ?? 0) as int, turn: (j['turn'] ?? 0) as int, trump: j['trump'] as String?, trickDone: (j['trick_done'] ?? false) as bool, yourHand: ((j['your_hand'] as List?) ?? []).map((e) => e as String).toList(), handCounts: ints(j['hand_counts']), trick: ((j['trick'] as List?) ?? []) .map((e) => TrickCard.fromJson(Map.from(e as Map))) .toList(), leadSuit: j['lead_suit'] as String?, tricksWon: ints(j['tricks_won']), scores: ints(j['scores']), targetScore: (j['target_score'] ?? 7) as int, turnTimeoutMs: (j['turn_timeout_ms'] ?? 0) as int, players: ((j['players'] as List?) ?? []) .map((e) => GamePlayer.fromJson(Map.from(e as Map))) .toList(), ); } bool get isMyTurn => turn == yourSeat; bool get amHakem => hakem == yourSeat; GamePlayer? playerAt(int seat) => players.where((p) => p.seat == seat).cast().firstOrNull; } /// نتیجه‌ی یک هَند (پیام type=hand_over). class HandResult { final int winnerTeam; final bool kot; final bool hakemKot; final int points; final List scores; HandResult.fromJson(Map j) : winnerTeam = (j['winner_team'] ?? 0) as int, kot = (j['kot'] ?? false) as bool, hakemKot = (j['hakem_kot'] ?? false) as bool, points = (j['points'] ?? 0) as int, scores = ((j['scores'] as List?) ?? []).map((e) => (e as num).toInt()).toList(); } /// نتیجه‌ی پایان بازی (پیام type=game_over). class GameOver { final int winnerTeam; final List scores; GameOver.fromJson(Map j) : winnerTeam = (j['winner_team'] ?? 0) as int, scores = ((j['scores'] as List?) ?? []).map((e) => (e as num).toInt()).toList(); } extension FirstOrNullExt on Iterable { E? get firstOrNull { final it = iterator; return it.moveNext() ? it.current : null; } }