// =========================================================================== // FILE: lib/services/multiplayer_service.dart // =========================================================================== import 'dart:math'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:share_plus/share_plus.dart'; class MultiplayerService { final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FirebaseAuth _auth = FirebaseAuth.instance; CollectionReference get _gamesCollection => _firestore.collection('games'); CollectionReference get _invitesCollection => _firestore.collection('invites'); Future createGameRoom(int boardRadius, String hostName, String shapeName, bool isTimeMode, {bool isPublic = true}) async { String roomCode = _generateRoomCode(); int randomSeed = Random().nextInt(1000000); await _gamesCollection.doc(roomCode).set({ 'status': 'waiting', 'radius': boardRadius, 'createdAt': FieldValue.serverTimestamp(), 'players': ['host'], 'turn': 'host', 'moves': [], 'seed': randomSeed, 'hostName': hostName, 'hostUid': _auth.currentUser?.uid, 'guestName': '', 'shape': shapeName, 'timeMode': isTimeMode, 'isPublic': isPublic, 'p1_reaction': null, 'p2_reaction': null, 'p1_rematch': false, 'p2_rematch': false, }); return roomCode; } Future?> joinGameRoom(String roomCode, String guestName) async { DocumentSnapshot doc = await _gamesCollection.doc(roomCode).get(); if (doc.exists && doc['status'] == 'waiting') { await _gamesCollection.doc(roomCode).update({ 'status': 'playing', 'players': FieldValue.arrayUnion(['guest']), 'guestName': guestName, }); return doc.data() as Map; } return null; } Stream getPublicRooms() { return _gamesCollection .where('status', isEqualTo: 'waiting') .where('isPublic', isEqualTo: true) .snapshots(); } void shareInviteLink(String roomCode) { String message = "Ehi! Giochiamo a TetraQ? 🎮\n\n" "Clicca su questo link per entrare direttamente in stanza:\n" "tetraq://join?code=$roomCode\n\n" "Apri l'app e inserisci manualmente il codice: $roomCode\n" "Se non hai ancora scaricato il gioco lo trovi nei link sotto \n\n" "🍎 iOS: https://apps.apple.com/it/app/tetraq/id6759522394\n" "🤖 Android: https://play.google.com/store/apps/details?id=com.amastra.tetraq"; Share.share(message); } Stream listenToRoom(String roomCode) { return _gamesCollection.doc(roomCode).snapshots(); } String _generateRoomCode() { const chars = 'ACDEFGHJKLMNPQRSTUVWXYZ2345679'; final random = Random(); return String.fromCharCodes(Iterable.generate( 5, (_) => chars.codeUnitAt(random.nextInt(chars.length)), )); } Future sendReaction(String roomCode, bool isHost, String reaction) async { try { String prefix = isHost ? 'p1' : 'p2'; await _gamesCollection.doc(roomCode).update({ '${prefix}_reaction': reaction, '${prefix}_reaction_time': FieldValue.serverTimestamp(), }); } catch (e) { debugPrint("Errore invio reazione: $e"); } } Future requestRematch(String roomCode, bool isHost) async { try { String prefix = isHost ? 'p1' : 'p2'; await _gamesCollection.doc(roomCode).update({ '${prefix}_rematch': true, }); } catch (e) { debugPrint("Errore richiesta rivincita: $e"); } } Future resetMatch(String roomCode, int newRadius, String newShape, int newSeed) async { try { await _gamesCollection.doc(roomCode).update({ 'status': 'playing', 'moves': [], 'seed': newSeed, 'radius': newRadius, 'shape': newShape, 'p1_rematch': false, 'p2_rematch': false, 'p1_reaction': null, 'p2_reaction': null, }); } catch (e) { debugPrint("Errore reset partita: $e"); } } Future sendInvite(String targetUid, String roomCode, String hostName) async { try { await _invitesCollection.add({ 'targetUid': targetUid, 'hostName': hostName, 'roomCode': roomCode, 'timestamp': FieldValue.serverTimestamp(), }); } catch(e) { debugPrint("Errore invio invito: $e"); } } Stream listenForInvites(String myUid) { return _invitesCollection.where('targetUid', isEqualTo: myUid).snapshots(); } Future deleteInvite(String inviteId) async { try { await _invitesCollection.doc(inviteId).delete(); } catch(e) { debugPrint("Errore cancellazione invito: $e"); } } }