103 lines
4.1 KiB
Dart
103 lines
4.1 KiB
Dart
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<GameEvent, GameUiState> {
|
|
final GameRepository repository;
|
|
late final StreamSubscription _msgSub;
|
|
late final StreamSubscription _statusSub;
|
|
|
|
Map<String, dynamic> _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<ConnectGameEvent>((event, emit) async {
|
|
_joinAction = event.joinAction;
|
|
_joined = false;
|
|
await repository.connect();
|
|
});
|
|
|
|
on<GameStatusChanged>((event, emit) {
|
|
emit(state.copyWith(connection: event.status));
|
|
if (event.status == WsStatus.connected && !_joined) {
|
|
_joined = true;
|
|
repository.send(_joinAction);
|
|
}
|
|
});
|
|
|
|
on<GameMessageReceived>((event, emit) => _onMessage(event.message, emit));
|
|
|
|
on<ChooseTrumpEvent>(
|
|
(event, emit) => repository.send({'type': 'choose_trump', 'suit': event.suit}));
|
|
on<PlayCardEvent>(
|
|
(event, emit) => repository.send({'type': 'play_card', 'card': event.card}));
|
|
on<ReshuffleEvent>((event, emit) => repository.send({'type': 'reshuffle'}));
|
|
on<LeaveGameEvent>((event, emit) => repository.send({'type': 'leave'}));
|
|
on<StartTableEvent>((event, emit) =>
|
|
repository.send({'type': 'start_table', 'hands': event.hands}));
|
|
on<RotateTableEvent>((event, emit) =>
|
|
repository.send({'type': 'rotate_table', 'dir': event.dir}));
|
|
on<LeaveTableEvent>((event, emit) => repository.send({'type': 'leave_table'}));
|
|
on<ClearNoticeEvent>((event, emit) => emit(state.copyWith(clearNotice: true)));
|
|
}
|
|
|
|
void _onMessage(Map<String, dynamic> msg, Emitter<GameUiState> 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 reshuffle() => add(ReshuffleEvent());
|
|
void playCard(String card) => add(PlayCardEvent(card));
|
|
void leave() => add(LeaveGameEvent());
|
|
void startTable(int hands) => add(StartTableEvent(hands));
|
|
void rotateTable(int dir) => add(RotateTableEvent(dir));
|
|
void leaveTable() => add(LeaveTableEvent());
|
|
void clearNotice() => add(ClearNoticeEvent());
|
|
|
|
@override
|
|
Future<void> close() {
|
|
_msgSub.cancel();
|
|
_statusSub.cancel();
|
|
repository.disconnect();
|
|
return super.close();
|
|
}
|
|
}
|