import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/network/ws_client.dart'; import '../../domain/entities/game_entities.dart'; import '../../domain/repository/game_repository.dart'; import 'game_event.dart'; import 'game_state.dart'; /// بلوکِ realtime بازی: به استریمِ پیام‌ها/وضعیتِ repository گوش می‌دهد و /// اقدام‌های بازیکن را به سرور می‌فرستد. متدهای کمکی برای موتور Flame هم دارد. class GameBloc extends Bloc { final GameRepository repository; late final StreamSubscription _msgSub; late final StreamSubscription _statusSub; Map _joinAction = const {}; bool _joined = false; GameBloc(this.repository) : super(const GameUiState()) { _msgSub = repository.messages.listen((m) => add(GameMessageReceived(m))); _statusSub = repository.status.listen((s) => add(GameStatusChanged(s))); on((event, emit) async { _joinAction = event.joinAction; _joined = false; await repository.connect(); }); on((event, emit) { emit(state.copyWith(connection: event.status)); if (event.status == WsStatus.connected && !_joined) { _joined = true; repository.send(_joinAction); } }); on((event, emit) => _onMessage(event.message, emit)); on( (event, emit) => repository.send({'type': 'choose_trump', 'suit': event.suit})); on( (event, emit) => repository.send({'type': 'play_card', 'card': event.card})); on((event, emit) => repository.send({'type': 'leave'})); on((event, emit) => repository.send({'type': 'start_table'})); on((event, emit) => repository.send({'type': 'leave_table'})); on((event, emit) => emit(state.copyWith(clearNotice: true))); } void _onMessage(Map msg, Emitter emit) { switch (msg['type']) { case 'state': final gs = GameState.fromJson(msg); final clear = gs.phase == 'choose_trump' || gs.phase == 'playing'; emit(state.copyWith(state: gs, clearHandResult: clear)); case 'hand_over': emit(state.copyWith(handResult: HandResult.fromJson(msg))); case 'game_over': emit(state.copyWith(gameOver: GameOver.fromJson(msg))); case 'table_lobby': emit(state.copyWith(lobby: TableLobby.fromJson(msg))); case 'countdown': emit(state.copyWith(countdown: (msg['seconds'] ?? 3) as int)); case 'table_closed': emit(state.copyWith( tableClosed: true, notice: 'میز توسط میزبان بسته شد')); case 'player_disconnected': emit(state.copyWith(notice: 'یک بازیکن قطع شد')); case 'player_reconnected': emit(state.copyWith(notice: 'بازیکن بازگشت')); case 'player_left': emit(state.copyWith(notice: 'یک بازیکن میز را ترک کرد')); case 'error': emit(state.copyWith(notice: (msg['message'] ?? 'خطا').toString())); } } // --- متدهای کمکی برای موتور Flame و صفحه‌ها --- void chooseTrump(String suit) => add(ChooseTrumpEvent(suit)); void playCard(String card) => add(PlayCardEvent(card)); void leave() => add(LeaveGameEvent()); void startTable() => add(StartTableEvent()); void leaveTable() => add(LeaveTableEvent()); void clearNotice() => add(ClearNoticeEvent()); @override Future close() { _msgSub.cancel(); _statusSub.cancel(); repository.disconnect(); return super.close(); } }