diff --git a/report/TetraQ_29-03-26_18.15.txt b/report/TetraQ_29-03-26_18.15.txt
new file mode 100644
index 0000000..463bb12
--- /dev/null
+++ b/report/TetraQ_29-03-26_18.15.txt
@@ -0,0 +1,11545 @@
+=== FLUTTER PROJECT BACKUP ===
+
+=== PROJECT STRUCTURE (LIB, ASSETS & PUBLIC) ===
+assets/.DS_Store
+assets/audio/.DS_Store
+assets/audio/bgm/8-bit_Prowler.mp3
+assets/audio/bgm/Cyber_Dystopia.mp3
+assets/audio/bgm/Grimorio_Astral.mp3
+assets/audio/bgm/Legno_Canopy.mp3
+assets/audio/bgm/Music_Loop.mp3
+assets/audio/bgm/Quad_Dreams.mp3
+assets/audio/sfx/cyber_box.wav
+assets/audio/sfx/cyber_line.wav
+assets/audio/sfx/doodle_box.wav
+assets/audio/sfx/doodle_line.wav
+assets/audio/sfx/minimal_box.wav
+assets/audio/sfx/minimal_line.wav
+assets/icon/icona_master.png
+assets/images/.DS_Store
+assets/images/arcade.jpg
+assets/images/cyber_bg.jpg
+assets/images/doodle_bg.jpg
+assets/images/doodle_bg_riserva.jpg
+assets/images/egizi_bg.jpg
+assets/images/grimorio copia.jpg
+assets/images/grimorio.jpg
+assets/images/icona_big.jpeg
+assets/images/music_bg.jpg
+assets/images/sfondo_temi.jpg
+lib/.DS_Store
+lib/core/app_colors.dart
+lib/core/constants.dart
+lib/core/theme_manager.dart
+lib/firebase_options.dart
+lib/l10n/app_de.arb
+lib/l10n/app_en.arb
+lib/l10n/app_es.arb
+lib/l10n/app_fr.arb
+lib/l10n/app_it.arb
+lib/l10n/app_localizations.dart
+lib/l10n/app_localizations_de.dart
+lib/l10n/app_localizations_en.dart
+lib/l10n/app_localizations_es.dart
+lib/l10n/app_localizations_fr.dart
+lib/l10n/app_localizations_it.dart
+lib/l10n/app_localizations_pt.dart
+lib/l10n/app_localizations_ru.dart
+lib/l10n/app_localizations_zh.dart
+lib/l10n/app_pt.arb
+lib/l10n/app_ru.arb
+lib/l10n/app_zh.arb
+lib/logic/ai_engine.dart
+lib/logic/game_controller.dart
+lib/main.dart
+lib/models/game_board.dart
+lib/models/player_info.dart
+lib/services/audio_service.dart
+lib/services/firebase_service.dart
+lib/services/multiplayer_service.dart
+lib/services/storage_service.dart
+lib/ui/.DS_Store
+lib/ui/admin/admin_screen.dart
+lib/ui/game/board_painter.dart
+lib/ui/game/game_screen.dart
+lib/ui/game/score_board.dart
+lib/ui/home/dialog.dart
+lib/ui/home/history_screen.dart
+lib/ui/home/home_modals.dart
+lib/ui/home/home_screen.dart
+lib/ui/multiplayer/lobby_screen.dart
+lib/ui/multiplayer/lobby_widgets.dart
+lib/ui/settings/settings_screen.dart
+lib/widgets/custom_button.dart
+lib/widgets/custom_settings_button.dart
+lib/widgets/cyber_border.dart
+lib/widgets/game_over_dialog.dart
+lib/widgets/home_buttons.dart
+lib/widgets/music_theme_widgets.dart
+lib/widgets/painters.dart
+public/404.html
+public/index.html
+public/report.html
+
+=== pubspec.yaml ===
+name: tetraq
+description: A new Flutter project.
+publish_to: 'none'
+version: 1.1.9+4
+environment:
+ sdk: ^3.10.7
+
+dependencies:
+ flutter:
+ sdk: flutter
+ cupertino_icons: ^1.0.8
+
+ # I nostri "Superpoteri"
+ provider: ^6.1.2 # Per gestire lo stato (Temi, Punteggi)
+ shared_preferences: ^2.5.4 # Per salvare opzioni e record sul telefono
+ audioplayers: ^5.2.1 # Per la musica e gli effetti sonori
+ intl: ^0.20.2 # Necessario per le traduzioni
+ flutter_localizations: # Il sistema multilingua ufficiale
+ sdk: flutter
+ firebase_core: ^4.4.0
+ firebase_auth: ^6.1.4 # <--- NUOVO: LA CORAZZA DI SICUREZZA!
+ cloud_firestore: ^6.1.2
+ share_plus: ^12.0.1
+ app_links: ^7.0.0
+ google_fonts: ^8.0.2
+ font_awesome_flutter: ^10.12.0
+ firebase_app_check: ^0.4.1+5
+ package_info_plus: ^9.0.0
+ device_info_plus: ^12.3.0
+
+ # --- NUOVI PACCHETTI PER GLI AGGIORNAMENTI ---
+ upgrader: ^12.5.0
+ in_app_update: ^4.2.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ flutter_lints: ^6.0.0
+ flutter_launcher_icons: ^0.13.1
+ change_app_package_name: ^1.5.0
+
+flutter:
+ uses-material-design: true
+
+ # Abilita la generazione automatica delle traduzioni
+ generate: true
+
+ # Dichiariamo le cartelle dove metteremo immagini e suoni
+ assets:
+ - assets/images/
+ - assets/audio/bgm/
+ - assets/audio/sfx/
+ - assets/audio/
+
+
+flutter_icons:
+ android: "ic_launcher"
+ ios: true
+ macos:
+ generate: true
+ image_path: "assets/icon/icona_master.png"
+ min_sdk_android: 21 # Serve per compatibilità con Android recenti
+=== MAC OS CONFIG ===
+--- Info.plist ---
+
+
+
+
+ LSApplicationCategoryType
+ public.app-category.puzzle-games
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIconFile
+
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSMinimumSystemVersion
+ $(MACOSX_DEPLOYMENT_TARGET)
+ NSHumanReadableCopyright
+ $(PRODUCT_COPYRIGHT)
+ NSMainNibFile
+ MainMenu
+ NSPrincipalClass
+ NSApplication
+
+--- Entitlements ---
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.network.client
+
+ com.apple.security.network.server
+
+ keychain-access-groups
+
+
+
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.network.client
+
+ keychain-access-groups
+
+
+
+--- Podfile ---
+platform :osx, '10.15'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_macos_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+
+ flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
+ target 'RunnerTests' do
+ inherit! :search_paths
+ end
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_macos_build_settings(target)
+ end
+end
+
+=== IOS CONFIG ===
+--- Info.plist ---
+
+
+
+
+ CADisableMinimumFrameDurationOnPhone
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Tetraq
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ tetraq
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLName
+ com.sanza.tetraq
+ CFBundleURLSchemes
+
+ tetraq
+
+
+
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UIApplicationSupportsIndirectInputEvents
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+
+
+--- Podfile ---
+# Uncomment this line to define a global platform for your project
+ platform :ios, '15.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+ target 'RunnerTests' do
+ inherit! :search_paths
+ end
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+ end
+end
+
+=== ANDROID CONFIG ===
+--- AndroidManifest.xml ---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+--- build.gradle / build.gradle.kts ---
+plugins {
+ id("com.android.application")
+ // START: FlutterFire Configuration
+ id("com.google.gms.google-services")
+ // END: FlutterFire Configuration
+ id("kotlin-android")
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id("dev.flutter.flutter-gradle-plugin")
+}
+
+// Aggiungiamo esplicitamente gli import richiesti da Kotlin
+import java.io.FileInputStream
+ import java.util.Properties
+
+// Carichiamo il file con le password
+val keystoreProperties = Properties()
+val keystorePropertiesFile = rootProject.file("key.properties")
+if (keystorePropertiesFile.exists()) {
+ keystoreProperties.load(FileInputStream(keystorePropertiesFile))
+}
+
+android {
+ namespace = "com.amastra.tetraq"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ // Sintassi aggiornata come richiesto dal compilatore Kotlin
+ kotlin {
+ compilerOptions {
+ jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
+ }
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "com.amastra.tetraq"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ minSdk = flutter.minSdkVersion
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ // Aggiunto il blocco per la firma in formato Kotlin DSL
+ signingConfigs {
+ create("release") {
+ keyAlias = keystoreProperties.getProperty("keyAlias")
+ keyPassword = keystoreProperties.getProperty("keyPassword")
+ val storeFileString = keystoreProperties.getProperty("storeFile")
+ if (storeFileString != null) {
+ storeFile = file(storeFileString)
+ }
+ storePassword = keystoreProperties.getProperty("storePassword")
+ }
+ }
+
+ buildTypes {
+ getByName("release") {
+ // TODO: Add your own signing config for the release build.
+ // Ora usiamo la chiave di release appena creata
+ signingConfig = signingConfigs.getByName("release")
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
+=== WEB / FIREBASE (public/) ===
+
+// ===========================================================================
+// FILE: public/404.html
+// ===========================================================================
+
+
+
+
+
+
+ Page Not Found
+
+
+
+
+
+
404
+
Page Not Found
+
The specified file was not found on this website. Please check the URL for mistakes and try again.
+
Why am I seeing this?
+
This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.
+
+
+
+
+// ===========================================================================
+// FILE: public/index.html
+// ===========================================================================
+
+
+
+
+
+
+
+ Gioca a TetraQ!
+
+
+
+
+
+
+
+
+
+ Apertura in corso... 🚀
+
+
+
+// ===========================================================================
+// FILE: public/report.html
+// ===========================================================================
+
+
+
+
+
+
+ Report Giocatori - TetraQ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Ultimo Accesso |
+ Giocatore |
+ Statistiche |
+ Connessione |
+ Dispositivo |
+
+
+
+ | Caricamento dati dal database in corso... |
+
+
+
+
+
+
+
+
+=== SOURCE CODE (lib/) ===
+
+// ===========================================================================
+// FILE: lib/core/app_colors.dart
+// ===========================================================================
+
+// ===========================================================================
+// FILE: lib/core/app_colors.dart
+// ===========================================================================
+
+import 'package:flutter/material.dart';
+import 'package:font_awesome_flutter/font_awesome_flutter.dart';
+
+enum AppThemeType { doodle, cyberpunk, arcade, grimorio, music }
+
+class ThemeColors {
+ final Color background;
+ final Color gridLine;
+ final Color playerRed;
+ final Color playerBlue;
+ final Color text;
+
+ const ThemeColors({
+ required this.background,
+ required this.gridLine,
+ required this.playerRed,
+ required this.playerBlue,
+ required this.text,
+ });
+}
+
+class AppColors {
+ static const ThemeColors doodle = ThemeColors(
+ background: Color(0xFFFFF9E6), gridLine: Color(0xFFB0BEC5),
+ playerRed: Color(0xFFD32F2F), playerBlue: Color(0xFF1565C0), text: Color(0xFF37474F),
+ );
+
+ static const ThemeColors cyberpunk = ThemeColors(
+ background: Color(0xFF0A001A), gridLine: Color(0xFF6200EA),
+ playerRed: Color(0xFFFF007F), playerBlue: Color(0xFF69F0AE), text: Color(0xFFFFFFFF),
+ );
+
+ static const ThemeColors arcade = ThemeColors(
+ background: Color(0xFF111111), gridLine: Color(0xFF00FF00),
+ playerRed: Color(0xFFFF004D), playerBlue: Color(0xFF00E5FF), text: Color(0xFFFFFFFF),
+ );
+
+ static const ThemeColors grimorio = ThemeColors(
+ background: Color(0xFF1E112A), gridLine: Colors.black,
+ playerRed: Color(0xFFE91E63), playerBlue: Color(0xFF4FC3F7), text: Color(0xFFFFF3E0),
+ );
+
+ static const ThemeColors music = ThemeColors(
+ background: Color(0xFF120B29),
+ gridLine: Color(0xFF6A1B9A),
+ playerRed: Color(0xFFFF2A6D),
+ playerBlue: Color(0xFF05D5FF),
+ text: Color(0xFFE0E0E0),
+ );
+
+ static ThemeColors getTheme(AppThemeType type) {
+ switch (type) {
+ case AppThemeType.doodle: return doodle;
+ case AppThemeType.cyberpunk: return cyberpunk;
+ case AppThemeType.arcade: return arcade;
+ case AppThemeType.grimorio: return grimorio;
+ case AppThemeType.music: return music;
+ }
+ }
+}
+
+class ThemeIcons {
+ static IconData gold(AppThemeType type) {
+ switch (type) {
+ case AppThemeType.doodle: return FontAwesomeIcons.star;
+ case AppThemeType.cyberpunk: return FontAwesomeIcons.microchip;
+ case AppThemeType.arcade: return FontAwesomeIcons.coins;
+ case AppThemeType.grimorio: return FontAwesomeIcons.crown;
+ case AppThemeType.music: return FontAwesomeIcons.compactDisc;
+ }
+ }
+
+ static IconData bomb(AppThemeType type) {
+ switch (type) {
+ case AppThemeType.doodle: return FontAwesomeIcons.virus;
+ case AppThemeType.cyberpunk: return FontAwesomeIcons.bug;
+ case AppThemeType.arcade: return FontAwesomeIcons.ghost;
+ case AppThemeType.grimorio: return FontAwesomeIcons.hatWizard;
+ case AppThemeType.music: return FontAwesomeIcons.volumeXmark;
+ }
+ }
+
+ static IconData swap(AppThemeType type) {
+ switch (type) {
+ case AppThemeType.doodle: return FontAwesomeIcons.arrowsRotate;
+ case AppThemeType.cyberpunk: return FontAwesomeIcons.networkWired;
+ case AppThemeType.arcade: return FontAwesomeIcons.shuffle;
+ case AppThemeType.grimorio: return FontAwesomeIcons.hurricane;
+ case AppThemeType.music: return FontAwesomeIcons.sliders;
+ }
+ }
+
+ static IconData joker(AppThemeType type) {
+ switch (type) {
+ case AppThemeType.doodle: return FontAwesomeIcons.faceSmileBeam;
+ case AppThemeType.cyberpunk: return FontAwesomeIcons.robot;
+ case AppThemeType.arcade: return FontAwesomeIcons.gamepad;
+ case AppThemeType.grimorio: return FontAwesomeIcons.masksTheater;
+ case AppThemeType.music: return FontAwesomeIcons.headphones;
+ }
+ }
+
+ static IconData block(AppThemeType type) {
+ switch (type) {
+ case AppThemeType.doodle: return FontAwesomeIcons.squareXmark;
+ case AppThemeType.cyberpunk: return FontAwesomeIcons.shieldHalved;
+ case AppThemeType.arcade: return FontAwesomeIcons.powerOff;
+ case AppThemeType.grimorio: return FontAwesomeIcons.meteor;
+ case AppThemeType.music: return FontAwesomeIcons.pause;
+ }
+ }
+
+ static IconData ice(AppThemeType type) {
+ if (type == AppThemeType.music) return FontAwesomeIcons.music;
+ return FontAwesomeIcons.snowflake;
+ }
+
+ static IconData multiplier(AppThemeType type) {
+ if (type == AppThemeType.music) return FontAwesomeIcons.forwardFast;
+ return FontAwesomeIcons.bolt;
+ }
+}
+// ===========================================================================
+// FILE: lib/core/constants.dart
+// ===========================================================================
+
+class Constants {
+ // Chiavi per salvare i dati sul telefono
+ static const String prefThemeKey = 'selected_theme';
+ static const String prefLanguageKey = 'selected_language';
+ static const String prefBoardSizeKey = 'board_size';
+
+ // Impostazioni della scacchiera a rombo - RAGGI INCREMENTATI
+ static const int minBoardRadius = 2; // Ex Normale, ora è Piccola
+ static const int maxBoardRadius = 5; // Formato MAX, enorme
+ static const int defaultBoardRadius = 3; // Ora il default è più grande
+}
+// ===========================================================================
+// FILE: lib/core/theme_manager.dart
+// ===========================================================================
+
+// ===========================================================================
+// FILE: lib/core/theme_manager.dart
+// ===========================================================================
+
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'app_colors.dart';
+import '../services/storage_service.dart';
+
+// --- ENUM DEI TEMI AGGIORNATO ---
+const Map themeIcons = {
+ AppThemeType.cyberpunk: Icons.electric_bolt,
+ AppThemeType.doodle: Icons.brush,
+ AppThemeType.music: Icons.headset_mic,
+ AppThemeType.arcade: Icons.videogame_asset,
+ AppThemeType.grimorio: Icons.auto_stories,
+};
+
+const Map themeNames = {
+ AppThemeType.cyberpunk: "Cyberpunk",
+ AppThemeType.doodle: "Doodle",
+ AppThemeType.music: "Music",
+ AppThemeType.arcade: "Arcade",
+ AppThemeType.grimorio: "Grimorio",
+};
+
+class ThemeManager with ChangeNotifier {
+ AppThemeType _currentThemeType = AppThemeType.doodle;
+ ThemeColors _currentColors = AppColors.getTheme(AppThemeType.doodle);
+
+ AppThemeType get currentThemeType => _currentThemeType;
+ ThemeColors get currentColors => _currentColors;
+
+ ThemeManager() {
+ _loadTheme();
+ }
+
+ void _loadTheme() async {
+ String themeStr = StorageService.instance.getTheme();
+ AppThemeType loadedType = AppThemeType.values.firstWhere(
+ (e) => e.toString() == themeStr,
+ orElse: () => AppThemeType.doodle
+ );
+ _currentThemeType = loadedType;
+ _currentColors = AppColors.getTheme(loadedType);
+ _updateSystemUI();
+ notifyListeners();
+ }
+
+ void setTheme(AppThemeType type) {
+ _currentThemeType = type;
+ _currentColors = AppColors.getTheme(type);
+ StorageService.instance.saveTheme(type.toString());
+ _updateSystemUI();
+ notifyListeners();
+ }
+
+ void _updateSystemUI() {
+ SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
+ statusBarColor: Colors.transparent,
+ statusBarIconBrightness: _currentThemeType == AppThemeType.doodle ? Brightness.dark : Brightness.light,
+ systemNavigationBarColor: _currentColors.background,
+ systemNavigationBarIconBrightness: _currentThemeType == AppThemeType.doodle ? Brightness.dark : Brightness.light,
+ ));
+ }
+}
+// ===========================================================================
+// FILE: lib/firebase_options.dart
+// ===========================================================================
+
+// File generated by FlutterFire CLI.
+// ignore_for_file: type=lint
+import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
+import 'package:flutter/foundation.dart'
+ show defaultTargetPlatform, kIsWeb, TargetPlatform;
+
+/// Default [FirebaseOptions] for use with your Firebase apps.
+///
+/// Example:
+/// ```dart
+/// import 'firebase_options.dart';
+/// // ...
+/// await Firebase.initializeApp(
+/// options: DefaultFirebaseOptions.currentPlatform,
+/// );
+/// ```
+class DefaultFirebaseOptions {
+ static FirebaseOptions get currentPlatform {
+ if (kIsWeb) {
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions have not been configured for web - '
+ 'you can reconfigure this by running the FlutterFire CLI again.',
+ );
+ }
+ switch (defaultTargetPlatform) {
+ case TargetPlatform.android:
+ return android;
+ case TargetPlatform.iOS:
+ return ios;
+ case TargetPlatform.macOS:
+ return macos;
+ case TargetPlatform.windows:
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions have not been configured for windows - '
+ 'you can reconfigure this by running the FlutterFire CLI again.',
+ );
+ case TargetPlatform.linux:
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions have not been configured for linux - '
+ 'you can reconfigure this by running the FlutterFire CLI again.',
+ );
+ default:
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions are not supported for this platform.',
+ );
+ }
+ }
+
+ static const FirebaseOptions android = FirebaseOptions(
+ apiKey: 'AIzaSyBsXO595xVITDPrRnXrW8HPQLOe7Rz4Gg4',
+ appId: '1:705460445314:android:ceac21bb06b7a9f07b949b',
+ messagingSenderId: '705460445314',
+ projectId: 'tetraq-32a4a',
+ storageBucket: 'tetraq-32a4a.firebasestorage.app',
+ );
+
+ static const FirebaseOptions ios = FirebaseOptions(
+ apiKey: 'AIzaSyB77j18Jgeb9gBAEwp-uyOQvr4m-RJ_rAE',
+ appId: '1:705460445314:ios:54d64cb7592954327b949b',
+ messagingSenderId: '705460445314',
+ projectId: 'tetraq-32a4a',
+ storageBucket: 'tetraq-32a4a.firebasestorage.app',
+ iosBundleId: 'com.amastra.tetraq',
+ );
+
+ static const FirebaseOptions macos = FirebaseOptions(
+ apiKey: 'AIzaSyB77j18Jgeb9gBAEwp-uyOQvr4m-RJ_rAE',
+ appId: '1:705460445314:ios:da11cbca5d1f6bc27b949b',
+ messagingSenderId: '705460445314',
+ projectId: 'tetraq-32a4a',
+ storageBucket: 'tetraq-32a4a.firebasestorage.app',
+ iosBundleId: 'com.sanza.tetraq',
+ );
+
+}
+// ===========================================================================
+// FILE: lib/l10n/app_localizations.dart
+// ===========================================================================
+
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:intl/intl.dart' as intl;
+
+import 'app_localizations_de.dart';
+import 'app_localizations_en.dart';
+import 'app_localizations_es.dart';
+import 'app_localizations_fr.dart';
+import 'app_localizations_it.dart';
+import 'app_localizations_pt.dart';
+import 'app_localizations_ru.dart';
+import 'app_localizations_zh.dart';
+
+// ignore_for_file: type=lint
+
+/// Callers can lookup localized strings with an instance of AppLocalizations
+/// returned by `AppLocalizations.of(context)`.
+///
+/// Applications need to include `AppLocalizations.delegate()` in their app's
+/// `localizationDelegates` list, and the locales they support in the app's
+/// `supportedLocales` list. For example:
+///
+/// ```dart
+/// import 'l10n/app_localizations.dart';
+///
+/// return MaterialApp(
+/// localizationsDelegates: AppLocalizations.localizationsDelegates,
+/// supportedLocales: AppLocalizations.supportedLocales,
+/// home: MyApplicationHome(),
+/// );
+/// ```
+///
+/// ## Update pubspec.yaml
+///
+/// Please make sure to update your pubspec.yaml to include the following
+/// packages:
+///
+/// ```yaml
+/// dependencies:
+/// # Internationalization support.
+/// flutter_localizations:
+/// sdk: flutter
+/// intl: any # Use the pinned version from flutter_localizations
+///
+/// # Rest of dependencies
+/// ```
+///
+/// ## iOS Applications
+///
+/// iOS applications define key application metadata, including supported
+/// locales, in an Info.plist file that is built into the application bundle.
+/// To configure the locales supported by your app, you’ll need to edit this
+/// file.
+///
+/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
+/// Then, in the Project Navigator, open the Info.plist file under the Runner
+/// project’s Runner folder.
+///
+/// Next, select the Information Property List item, select Add Item from the
+/// Editor menu, then select Localizations from the pop-up menu.
+///
+/// Select and expand the newly-created Localizations item then, for each
+/// locale your application supports, add a new item and select the locale
+/// you wish to add from the pop-up menu in the Value field. This list should
+/// be consistent with the languages listed in the AppLocalizations.supportedLocales
+/// property.
+abstract class AppLocalizations {
+ AppLocalizations(String locale)
+ : localeName = intl.Intl.canonicalizedLocale(locale.toString());
+
+ final String localeName;
+
+ static AppLocalizations? of(BuildContext context) {
+ return Localizations.of(context, AppLocalizations);
+ }
+
+ static const LocalizationsDelegate delegate =
+ _AppLocalizationsDelegate();
+
+ /// A list of this localizations delegate along with the default localizations
+ /// delegates.
+ ///
+ /// Returns a list of localizations delegates containing this delegate along with
+ /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
+ /// and GlobalWidgetsLocalizations.delegate.
+ ///
+ /// Additional delegates can be added by appending to this list in
+ /// MaterialApp. This list does not have to be used at all if a custom list
+ /// of delegates is preferred or required.
+ static const List> localizationsDelegates =
+ >[
+ delegate,
+ GlobalMaterialLocalizations.delegate,
+ GlobalCupertinoLocalizations.delegate,
+ GlobalWidgetsLocalizations.delegate,
+ ];
+
+ /// A list of this localizations delegate's supported locales.
+ static const List supportedLocales = [
+ Locale('de'),
+ Locale('en'),
+ Locale('es'),
+ Locale('fr'),
+ Locale('it'),
+ Locale('pt'),
+ Locale('ru'),
+ Locale('zh'),
+ ];
+
+ /// No description provided for @appTitle.
+ ///
+ /// In it, this message translates to:
+ /// **'TetraQ'**
+ String get appTitle;
+
+ /// No description provided for @welcomeTitle.
+ ///
+ /// In it, this message translates to:
+ /// **'BENVENUTO IN TETRAQ!'**
+ String get welcomeTitle;
+
+ /// No description provided for @nameHint.
+ ///
+ /// In it, this message translates to:
+ /// **'NOME'**
+ String get nameHint;
+
+ /// No description provided for @saveAndPlay.
+ ///
+ /// In it, this message translates to:
+ /// **'SALVA E GIOCA'**
+ String get saveAndPlay;
+
+ /// No description provided for @onlineTitle.
+ ///
+ /// In it, this message translates to:
+ /// **'ONLINE'**
+ String get onlineTitle;
+
+ /// No description provided for @onlineSub.
+ ///
+ /// In it, this message translates to:
+ /// **'Sfida il mondo'**
+ String get onlineSub;
+
+ /// No description provided for @cpuTitle.
+ ///
+ /// In it, this message translates to:
+ /// **'VS CPU'**
+ String get cpuTitle;
+
+ /// No description provided for @cpuSub.
+ ///
+ /// In it, this message translates to:
+ /// **'Allenati con l\'IA'**
+ String get cpuSub;
+
+ /// No description provided for @localTitle.
+ ///
+ /// In it, this message translates to:
+ /// **'LOCALE'**
+ String get localTitle;
+
+ /// No description provided for @localSub.
+ ///
+ /// In it, this message translates to:
+ /// **'Stesso schermo'**
+ String get localSub;
+
+ /// No description provided for @leaderboardTitle.
+ ///
+ /// In it, this message translates to:
+ /// **'CLASSIFICA'**
+ String get leaderboardTitle;
+
+ /// No description provided for @questsTitle.
+ ///
+ /// In it, this message translates to:
+ /// **'SFIDE'**
+ String get questsTitle;
+
+ /// No description provided for @themesTitle.
+ ///
+ /// In it, this message translates to:
+ /// **'TEMI'**
+ String get themesTitle;
+
+ /// No description provided for @tutorialTitle.
+ ///
+ /// In it, this message translates to:
+ /// **'TUTORIAL'**
+ String get tutorialTitle;
+
+ /// No description provided for @startGame.
+ ///
+ /// In it, this message translates to:
+ /// **'AVVIA PARTITA'**
+ String get startGame;
+
+ /// No description provided for @createMatch.
+ ///
+ /// In it, this message translates to:
+ /// **'CREA PARTITA'**
+ String get createMatch;
+
+ /// No description provided for @joinMatch.
+ ///
+ /// In it, this message translates to:
+ /// **'UNISCITI'**
+ String get joinMatch;
+
+ /// No description provided for @gameOver.
+ ///
+ /// In it, this message translates to:
+ /// **'FINE PARTITA'**
+ String get gameOver;
+
+ /// No description provided for @mainMenu.
+ ///
+ /// In it, this message translates to:
+ /// **'TORNA AL MENU'**
+ String get mainMenu;
+
+ /// No description provided for @exit.
+ ///
+ /// In it, this message translates to:
+ /// **'ESCI'**
+ String get exit;
+
+ /// No description provided for @roomSettings.
+ ///
+ /// In it, this message translates to:
+ /// **'IMPOSTAZIONI STANZA'**
+ String get roomSettings;
+
+ /// No description provided for @arenaShape.
+ ///
+ /// In it, this message translates to:
+ /// **'FORMA ARENA'**
+ String get arenaShape;
+
+ /// No description provided for @arenaSize.
+ ///
+ /// In it, this message translates to:
+ /// **'GRANDEZZA'**
+ String get arenaSize;
+
+ /// No description provided for @timeAndOptions.
+ ///
+ /// In it, this message translates to:
+ /// **'TEMPO E OPZIONI'**
+ String get timeAndOptions;
+
+ /// No description provided for @timeLabel.
+ ///
+ /// In it, this message translates to:
+ /// **'TEMPO'**
+ String get timeLabel;
+
+ /// No description provided for @btnStart.
+ ///
+ /// In it, this message translates to:
+ /// **'AVVIA'**
+ String get btnStart;
+
+ /// No description provided for @btnCancel.
+ ///
+ /// In it, this message translates to:
+ /// **'ANNULLA'**
+ String get btnCancel;
+
+ /// No description provided for @wordOr.
+ ///
+ /// In it, this message translates to:
+ /// **'OPPURE'**
+ String get wordOr;
+
+ /// No description provided for @codeHint.
+ ///
+ /// In it, this message translates to:
+ /// **'CODICE'**
+ String get codeHint;
+
+ /// No description provided for @publicLobbyTitle.
+ ///
+ /// In it, this message translates to:
+ /// **'LOBBY PUBBLICA'**
+ String get publicLobbyTitle;
+
+ /// No description provided for @emptyLobbyMsg.
+ ///
+ /// In it, this message translates to:
+ /// **'Nessuna stanza pubblica al momento.\nCreane una tu!'**
+ String get emptyLobbyMsg;
+
+ /// No description provided for @roomOf.
+ ///
+ /// In it, this message translates to:
+ /// **'Stanza di'**
+ String get roomOf;
+
+ /// No description provided for @btnEnter.
+ ///
+ /// In it, this message translates to:
+ /// **'ENTRA'**
+ String get btnEnter;
+}
+
+class _AppLocalizationsDelegate
+ extends LocalizationsDelegate {
+ const _AppLocalizationsDelegate();
+
+ @override
+ Future load(Locale locale) {
+ return SynchronousFuture(lookupAppLocalizations(locale));
+ }
+
+ @override
+ bool isSupported(Locale locale) => [
+ 'de',
+ 'en',
+ 'es',
+ 'fr',
+ 'it',
+ 'pt',
+ 'ru',
+ 'zh',
+ ].contains(locale.languageCode);
+
+ @override
+ bool shouldReload(_AppLocalizationsDelegate old) => false;
+}
+
+AppLocalizations lookupAppLocalizations(Locale locale) {
+ // Lookup logic when only language code is specified.
+ switch (locale.languageCode) {
+ case 'de':
+ return AppLocalizationsDe();
+ case 'en':
+ return AppLocalizationsEn();
+ case 'es':
+ return AppLocalizationsEs();
+ case 'fr':
+ return AppLocalizationsFr();
+ case 'it':
+ return AppLocalizationsIt();
+ case 'pt':
+ return AppLocalizationsPt();
+ case 'ru':
+ return AppLocalizationsRu();
+ case 'zh':
+ return AppLocalizationsZh();
+ }
+
+ throw FlutterError(
+ 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
+ 'an issue with the localizations generation tool. Please file an issue '
+ 'on GitHub with a reproducible sample app and the gen-l10n configuration '
+ 'that was used.',
+ );
+}
+
+// ===========================================================================
+// FILE: lib/l10n/app_localizations_de.dart
+// ===========================================================================
+
+// ignore: unused_import
+import 'package:intl/intl.dart' as intl;
+import 'app_localizations.dart';
+
+// ignore_for_file: type=lint
+
+/// The translations for German (`de`).
+class AppLocalizationsDe extends AppLocalizations {
+ AppLocalizationsDe([String locale = 'de']) : super(locale);
+
+ @override
+ String get appTitle => 'TetraQ';
+
+ @override
+ String get welcomeTitle => 'WILLKOMMEN BEI TETRAQ!';
+
+ @override
+ String get nameHint => 'NAME';
+
+ @override
+ String get saveAndPlay => 'SPEICHERN & SPIELEN';
+
+ @override
+ String get onlineTitle => 'ONLINE';
+
+ @override
+ String get onlineSub => 'Fordere die Welt heraus';
+
+ @override
+ String get cpuTitle => 'VS CPU';
+
+ @override
+ String get cpuSub => 'Trainiere mit KI';
+
+ @override
+ String get localTitle => 'LOKAL';
+
+ @override
+ String get localSub => 'Gleicher Bildschirm';
+
+ @override
+ String get leaderboardTitle => 'RANGLISTE';
+
+ @override
+ String get questsTitle => 'MISSIONEN';
+
+ @override
+ String get themesTitle => 'THEMEN';
+
+ @override
+ String get tutorialTitle => 'TUTORIAL';
+
+ @override
+ String get startGame => 'SPIEL STARTEN';
+
+ @override
+ String get createMatch => 'SPIEL ERSTELLEN';
+
+ @override
+ String get joinMatch => 'BEITRETEN';
+
+ @override
+ String get gameOver => 'SPIELENDE';
+
+ @override
+ String get mainMenu => 'ZURÜCK ZUM MENÜ';
+
+ @override
+ String get exit => 'BEENDEN';
+
+ @override
+ String get roomSettings => 'IMPOSTAZIONI STANZA';
+
+ @override
+ String get arenaShape => 'FORMA ARENA';
+
+ @override
+ String get arenaSize => 'GRANDEZZA';
+
+ @override
+ String get timeAndOptions => 'TEMPO E OPZIONI';
+
+ @override
+ String get timeLabel => 'TEMPO';
+
+ @override
+ String get btnStart => 'AVVIA';
+
+ @override
+ String get btnCancel => 'ANNULLA';
+
+ @override
+ String get wordOr => 'OPPURE';
+
+ @override
+ String get codeHint => 'CODICE';
+
+ @override
+ String get publicLobbyTitle => 'LOBBY PUBBLICA';
+
+ @override
+ String get emptyLobbyMsg =>
+ 'Nessuna stanza pubblica al momento.\nCreane una tu!';
+
+ @override
+ String get roomOf => 'Stanza di';
+
+ @override
+ String get btnEnter => 'ENTRA';
+}
+
+// ===========================================================================
+// FILE: lib/l10n/app_localizations_en.dart
+// ===========================================================================
+
+// ignore: unused_import
+import 'package:intl/intl.dart' as intl;
+import 'app_localizations.dart';
+
+// ignore_for_file: type=lint
+
+/// The translations for English (`en`).
+class AppLocalizationsEn extends AppLocalizations {
+ AppLocalizationsEn([String locale = 'en']) : super(locale);
+
+ @override
+ String get appTitle => 'TetraQ';
+
+ @override
+ String get welcomeTitle => 'WELCOME TO TETRAQ!';
+
+ @override
+ String get nameHint => 'NAME';
+
+ @override
+ String get saveAndPlay => 'SAVE & PLAY';
+
+ @override
+ String get onlineTitle => 'ONLINE';
+
+ @override
+ String get onlineSub => 'Challenge the world';
+
+ @override
+ String get cpuTitle => 'VS CPU';
+
+ @override
+ String get cpuSub => 'Train with AI';
+
+ @override
+ String get localTitle => 'LOCAL';
+
+ @override
+ String get localSub => 'Same screen';
+
+ @override
+ String get leaderboardTitle => 'LEADERBOARD';
+
+ @override
+ String get questsTitle => 'QUESTS';
+
+ @override
+ String get themesTitle => 'THEMES';
+
+ @override
+ String get tutorialTitle => 'TUTORIAL';
+
+ @override
+ String get startGame => 'START GAME';
+
+ @override
+ String get createMatch => 'CREATE MATCH';
+
+ @override
+ String get joinMatch => 'JOIN';
+
+ @override
+ String get gameOver => 'GAME OVER';
+
+ @override
+ String get mainMenu => 'BACK TO MENU';
+
+ @override
+ String get exit => 'EXIT';
+
+ @override
+ String get roomSettings => 'ROOM SETTINGS';
+
+ @override
+ String get arenaShape => 'ARENA SHAPE';
+
+ @override
+ String get arenaSize => 'SIZE';
+
+ @override
+ String get timeAndOptions => 'TIME & OPTIONS';
+
+ @override
+ String get timeLabel => 'TIME';
+
+ @override
+ String get btnStart => 'START';
+
+ @override
+ String get btnCancel => 'CANCEL';
+
+ @override
+ String get wordOr => 'OR';
+
+ @override
+ String get codeHint => 'CODE';
+
+ @override
+ String get publicLobbyTitle => 'PUBLIC LOBBY';
+
+ @override
+ String get emptyLobbyMsg => 'No public rooms right now.\nCreate one!';
+
+ @override
+ String get roomOf => 'Room of';
+
+ @override
+ String get btnEnter => 'ENTER';
+}
+
+// ===========================================================================
+// FILE: lib/l10n/app_localizations_es.dart
+// ===========================================================================
+
+// ignore: unused_import
+import 'package:intl/intl.dart' as intl;
+import 'app_localizations.dart';
+
+// ignore_for_file: type=lint
+
+/// The translations for Spanish Castilian (`es`).
+class AppLocalizationsEs extends AppLocalizations {
+ AppLocalizationsEs([String locale = 'es']) : super(locale);
+
+ @override
+ String get appTitle => 'TetraQ';
+
+ @override
+ String get welcomeTitle => '¡BIENVENIDO A TETRAQ!';
+
+ @override
+ String get nameHint => 'NOMBRE';
+
+ @override
+ String get saveAndPlay => 'GUARDAR Y JUGAR';
+
+ @override
+ String get onlineTitle => 'ONLINE';
+
+ @override
+ String get onlineSub => 'Desafía al mundo';
+
+ @override
+ String get cpuTitle => 'VS CPU';
+
+ @override
+ String get cpuSub => 'Entrena con IA';
+
+ @override
+ String get localTitle => 'LOCAL';
+
+ @override
+ String get localSub => 'Misma pantalla';
+
+ @override
+ String get leaderboardTitle => 'RANKING';
+
+ @override
+ String get questsTitle => 'MISIONES';
+
+ @override
+ String get themesTitle => 'TEMAS';
+
+ @override
+ String get tutorialTitle => 'TUTORIAL';
+
+ @override
+ String get startGame => 'INICIAR JUEGO';
+
+ @override
+ String get createMatch => 'CREAR PARTIDA';
+
+ @override
+ String get joinMatch => 'UNIRSE';
+
+ @override
+ String get gameOver => 'FIN DEL JUEGO';
+
+ @override
+ String get mainMenu => 'VOLVER AL MENÚ';
+
+ @override
+ String get exit => 'SALIR';
+
+ @override
+ String get roomSettings => 'IMPOSTAZIONI STANZA';
+
+ @override
+ String get arenaShape => 'FORMA ARENA';
+
+ @override
+ String get arenaSize => 'GRANDEZZA';
+
+ @override
+ String get timeAndOptions => 'TEMPO E OPZIONI';
+
+ @override
+ String get timeLabel => 'TEMPO';
+
+ @override
+ String get btnStart => 'AVVIA';
+
+ @override
+ String get btnCancel => 'ANNULLA';
+
+ @override
+ String get wordOr => 'OPPURE';
+
+ @override
+ String get codeHint => 'CODICE';
+
+ @override
+ String get publicLobbyTitle => 'LOBBY PUBBLICA';
+
+ @override
+ String get emptyLobbyMsg =>
+ 'Nessuna stanza pubblica al momento.\nCreane una tu!';
+
+ @override
+ String get roomOf => 'Stanza di';
+
+ @override
+ String get btnEnter => 'ENTRA';
+}
+
+// ===========================================================================
+// FILE: lib/l10n/app_localizations_fr.dart
+// ===========================================================================
+
+// ignore: unused_import
+import 'package:intl/intl.dart' as intl;
+import 'app_localizations.dart';
+
+// ignore_for_file: type=lint
+
+/// The translations for French (`fr`).
+class AppLocalizationsFr extends AppLocalizations {
+ AppLocalizationsFr([String locale = 'fr']) : super(locale);
+
+ @override
+ String get appTitle => 'TetraQ';
+
+ @override
+ String get welcomeTitle => 'BIENVENUE DANS TETRAQ !';
+
+ @override
+ String get nameHint => 'NOM';
+
+ @override
+ String get saveAndPlay => 'SAUVEGARDER ET JOUER';
+
+ @override
+ String get onlineTitle => 'EN LIGNE';
+
+ @override
+ String get onlineSub => 'Défiez le monde';
+
+ @override
+ String get cpuTitle => 'VS CPU';
+
+ @override
+ String get cpuSub => 'Entraînez avec l\'IA';
+
+ @override
+ String get localTitle => 'LOCAL';
+
+ @override
+ String get localSub => 'Même écran';
+
+ @override
+ String get leaderboardTitle => 'CLASSEMENT';
+
+ @override
+ String get questsTitle => 'QUÊTES';
+
+ @override
+ String get themesTitle => 'THÈMES';
+
+ @override
+ String get tutorialTitle => 'TUTORIEL';
+
+ @override
+ String get startGame => 'JOUER';
+
+ @override
+ String get createMatch => 'CRÉER UN MATCH';
+
+ @override
+ String get joinMatch => 'REJOINDRE';
+
+ @override
+ String get gameOver => 'FIN DE PARTIE';
+
+ @override
+ String get mainMenu => 'RETOUR AU MENU';
+
+ @override
+ String get exit => 'QUITTER';
+
+ @override
+ String get roomSettings => 'IMPOSTAZIONI STANZA';
+
+ @override
+ String get arenaShape => 'FORMA ARENA';
+
+ @override
+ String get arenaSize => 'GRANDEZZA';
+
+ @override
+ String get timeAndOptions => 'TEMPO E OPZIONI';
+
+ @override
+ String get timeLabel => 'TEMPO';
+
+ @override
+ String get btnStart => 'AVVIA';
+
+ @override
+ String get btnCancel => 'ANNULLA';
+
+ @override
+ String get wordOr => 'OPPURE';
+
+ @override
+ String get codeHint => 'CODICE';
+
+ @override
+ String get publicLobbyTitle => 'LOBBY PUBBLICA';
+
+ @override
+ String get emptyLobbyMsg =>
+ 'Nessuna stanza pubblica al momento.\nCreane una tu!';
+
+ @override
+ String get roomOf => 'Stanza di';
+
+ @override
+ String get btnEnter => 'ENTRA';
+}
+
+// ===========================================================================
+// FILE: lib/l10n/app_localizations_it.dart
+// ===========================================================================
+
+// ignore: unused_import
+import 'package:intl/intl.dart' as intl;
+import 'app_localizations.dart';
+
+// ignore_for_file: type=lint
+
+/// The translations for Italian (`it`).
+class AppLocalizationsIt extends AppLocalizations {
+ AppLocalizationsIt([String locale = 'it']) : super(locale);
+
+ @override
+ String get appTitle => 'TetraQ';
+
+ @override
+ String get welcomeTitle => 'BENVENUTO IN TETRAQ!';
+
+ @override
+ String get nameHint => 'NOME';
+
+ @override
+ String get saveAndPlay => 'SALVA E GIOCA';
+
+ @override
+ String get onlineTitle => 'ONLINE';
+
+ @override
+ String get onlineSub => 'Sfida il mondo';
+
+ @override
+ String get cpuTitle => 'VS CPU';
+
+ @override
+ String get cpuSub => 'Allenati con l\'IA';
+
+ @override
+ String get localTitle => 'LOCALE';
+
+ @override
+ String get localSub => 'Stesso schermo';
+
+ @override
+ String get leaderboardTitle => 'CLASSIFICA';
+
+ @override
+ String get questsTitle => 'SFIDE';
+
+ @override
+ String get themesTitle => 'TEMI';
+
+ @override
+ String get tutorialTitle => 'TUTORIAL';
+
+ @override
+ String get startGame => 'AVVIA PARTITA';
+
+ @override
+ String get createMatch => 'CREA PARTITA';
+
+ @override
+ String get joinMatch => 'UNISCITI';
+
+ @override
+ String get gameOver => 'FINE PARTITA';
+
+ @override
+ String get mainMenu => 'TORNA AL MENU';
+
+ @override
+ String get exit => 'ESCI';
+
+ @override
+ String get roomSettings => 'IMPOSTAZIONI STANZA';
+
+ @override
+ String get arenaShape => 'FORMA ARENA';
+
+ @override
+ String get arenaSize => 'GRANDEZZA';
+
+ @override
+ String get timeAndOptions => 'TEMPO E OPZIONI';
+
+ @override
+ String get timeLabel => 'TEMPO';
+
+ @override
+ String get btnStart => 'AVVIA';
+
+ @override
+ String get btnCancel => 'ANNULLA';
+
+ @override
+ String get wordOr => 'OPPURE';
+
+ @override
+ String get codeHint => 'CODICE';
+
+ @override
+ String get publicLobbyTitle => 'LOBBY PUBBLICA';
+
+ @override
+ String get emptyLobbyMsg =>
+ 'Nessuna stanza pubblica al momento.\nCreane una tu!';
+
+ @override
+ String get roomOf => 'Stanza di';
+
+ @override
+ String get btnEnter => 'ENTRA';
+}
+
+// ===========================================================================
+// FILE: lib/l10n/app_localizations_pt.dart
+// ===========================================================================
+
+// ignore: unused_import
+import 'package:intl/intl.dart' as intl;
+import 'app_localizations.dart';
+
+// ignore_for_file: type=lint
+
+/// The translations for Portuguese (`pt`).
+class AppLocalizationsPt extends AppLocalizations {
+ AppLocalizationsPt([String locale = 'pt']) : super(locale);
+
+ @override
+ String get appTitle => 'TetraQ';
+
+ @override
+ String get welcomeTitle => 'BEM-VINDO AO TETRAQ!';
+
+ @override
+ String get nameHint => 'NOME';
+
+ @override
+ String get saveAndPlay => 'SALVAR E JOGAR';
+
+ @override
+ String get onlineTitle => 'ONLINE';
+
+ @override
+ String get onlineSub => 'Desafie o mundo';
+
+ @override
+ String get cpuTitle => 'VS CPU';
+
+ @override
+ String get cpuSub => 'Treine com a IA';
+
+ @override
+ String get localTitle => 'LOCAL';
+
+ @override
+ String get localSub => 'Mesma tela';
+
+ @override
+ String get leaderboardTitle => 'CLASSIFICAÇÃO';
+
+ @override
+ String get questsTitle => 'DESAFIOS';
+
+ @override
+ String get themesTitle => 'TEMAS';
+
+ @override
+ String get tutorialTitle => 'TUTORIAL';
+
+ @override
+ String get startGame => 'INICIAR JOGO';
+
+ @override
+ String get createMatch => 'CRIAR PARTIDA';
+
+ @override
+ String get joinMatch => 'ENTRAR';
+
+ @override
+ String get gameOver => 'FIM DE JOGO';
+
+ @override
+ String get mainMenu => 'VOLTAR AO MENU';
+
+ @override
+ String get exit => 'SAIR';
+
+ @override
+ String get roomSettings => 'IMPOSTAZIONI STANZA';
+
+ @override
+ String get arenaShape => 'FORMA ARENA';
+
+ @override
+ String get arenaSize => 'GRANDEZZA';
+
+ @override
+ String get timeAndOptions => 'TEMPO E OPZIONI';
+
+ @override
+ String get timeLabel => 'TEMPO';
+
+ @override
+ String get btnStart => 'AVVIA';
+
+ @override
+ String get btnCancel => 'ANNULLA';
+
+ @override
+ String get wordOr => 'OPPURE';
+
+ @override
+ String get codeHint => 'CODICE';
+
+ @override
+ String get publicLobbyTitle => 'LOBBY PUBBLICA';
+
+ @override
+ String get emptyLobbyMsg =>
+ 'Nessuna stanza pubblica al momento.\nCreane una tu!';
+
+ @override
+ String get roomOf => 'Stanza di';
+
+ @override
+ String get btnEnter => 'ENTRA';
+}
+
+// ===========================================================================
+// FILE: lib/l10n/app_localizations_ru.dart
+// ===========================================================================
+
+// ignore: unused_import
+import 'package:intl/intl.dart' as intl;
+import 'app_localizations.dart';
+
+// ignore_for_file: type=lint
+
+/// The translations for Russian (`ru`).
+class AppLocalizationsRu extends AppLocalizations {
+ AppLocalizationsRu([String locale = 'ru']) : super(locale);
+
+ @override
+ String get appTitle => 'TetraQ';
+
+ @override
+ String get welcomeTitle => 'ДОБРО ПОЖАЛОВАТЬ В TETRAQ!';
+
+ @override
+ String get nameHint => 'ИМЯ';
+
+ @override
+ String get saveAndPlay => 'СОХРАНИТЬ И ИГРАТЬ';
+
+ @override
+ String get onlineTitle => 'ОНЛАЙН';
+
+ @override
+ String get onlineSub => 'Брось вызов миру';
+
+ @override
+ String get cpuTitle => 'VS ИИ';
+
+ @override
+ String get cpuSub => 'Тренировка с ИИ';
+
+ @override
+ String get localTitle => 'ЛОКАЛЬНО';
+
+ @override
+ String get localSub => 'Один экран';
+
+ @override
+ String get leaderboardTitle => 'РЕЙТИНГ';
+
+ @override
+ String get questsTitle => 'ЗАДАНИЯ';
+
+ @override
+ String get themesTitle => 'ТЕМЫ';
+
+ @override
+ String get tutorialTitle => 'ОБУЧЕНИЕ';
+
+ @override
+ String get startGame => 'НАЧАТЬ ИГРУ';
+
+ @override
+ String get createMatch => 'СОЗДАТЬ ИГРУ';
+
+ @override
+ String get joinMatch => 'ПРИСОЕДИНИТЬСЯ';
+
+ @override
+ String get gameOver => 'ИГРА ОКОНЧЕНА';
+
+ @override
+ String get mainMenu => 'В ГЛАВНОЕ МЕНЮ';
+
+ @override
+ String get exit => 'ВЫХОД';
+
+ @override
+ String get roomSettings => 'IMPOSTAZIONI STANZA';
+
+ @override
+ String get arenaShape => 'FORMA ARENA';
+
+ @override
+ String get arenaSize => 'GRANDEZZA';
+
+ @override
+ String get timeAndOptions => 'TEMPO E OPZIONI';
+
+ @override
+ String get timeLabel => 'TEMPO';
+
+ @override
+ String get btnStart => 'AVVIA';
+
+ @override
+ String get btnCancel => 'ANNULLA';
+
+ @override
+ String get wordOr => 'OPPURE';
+
+ @override
+ String get codeHint => 'CODICE';
+
+ @override
+ String get publicLobbyTitle => 'LOBBY PUBBLICA';
+
+ @override
+ String get emptyLobbyMsg =>
+ 'Nessuna stanza pubblica al momento.\nCreane una tu!';
+
+ @override
+ String get roomOf => 'Stanza di';
+
+ @override
+ String get btnEnter => 'ENTRA';
+}
+
+// ===========================================================================
+// FILE: lib/l10n/app_localizations_zh.dart
+// ===========================================================================
+
+// ignore: unused_import
+import 'package:intl/intl.dart' as intl;
+import 'app_localizations.dart';
+
+// ignore_for_file: type=lint
+
+/// The translations for Chinese (`zh`).
+class AppLocalizationsZh extends AppLocalizations {
+ AppLocalizationsZh([String locale = 'zh']) : super(locale);
+
+ @override
+ String get appTitle => 'TetraQ';
+
+ @override
+ String get welcomeTitle => '欢迎来到 TETRAQ!';
+
+ @override
+ String get nameHint => '名字';
+
+ @override
+ String get saveAndPlay => '保存并开始';
+
+ @override
+ String get onlineTitle => '在线匹配';
+
+ @override
+ String get onlineSub => '挑战世界';
+
+ @override
+ String get cpuTitle => '人机对战';
+
+ @override
+ String get cpuSub => '与AI训练';
+
+ @override
+ String get localTitle => '本地游戏';
+
+ @override
+ String get localSub => '同屏对战';
+
+ @override
+ String get leaderboardTitle => '排行榜';
+
+ @override
+ String get questsTitle => '任务';
+
+ @override
+ String get themesTitle => '主题';
+
+ @override
+ String get tutorialTitle => '教程';
+
+ @override
+ String get startGame => '开始游戏';
+
+ @override
+ String get createMatch => '创建比赛';
+
+ @override
+ String get joinMatch => '加入';
+
+ @override
+ String get gameOver => '游戏结束';
+
+ @override
+ String get mainMenu => '返回主菜单';
+
+ @override
+ String get exit => '退出';
+
+ @override
+ String get roomSettings => 'IMPOSTAZIONI STANZA';
+
+ @override
+ String get arenaShape => 'FORMA ARENA';
+
+ @override
+ String get arenaSize => 'GRANDEZZA';
+
+ @override
+ String get timeAndOptions => 'TEMPO E OPZIONI';
+
+ @override
+ String get timeLabel => 'TEMPO';
+
+ @override
+ String get btnStart => 'AVVIA';
+
+ @override
+ String get btnCancel => 'ANNULLA';
+
+ @override
+ String get wordOr => 'OPPURE';
+
+ @override
+ String get codeHint => 'CODICE';
+
+ @override
+ String get publicLobbyTitle => 'LOBBY PUBBLICA';
+
+ @override
+ String get emptyLobbyMsg =>
+ 'Nessuna stanza pubblica al momento.\nCreane una tu!';
+
+ @override
+ String get roomOf => 'Stanza di';
+
+ @override
+ String get btnEnter => 'ENTRA';
+}
+
+// ===========================================================================
+// FILE: lib/logic/ai_engine.dart
+// ===========================================================================
+
+// ===========================================================================
+// FILE: lib/logic/ai_engine.dart
+// ===========================================================================
+
+import 'dart:math';
+import '../models/game_board.dart';
+
+class _ClosureResult {
+ final bool closesSomething;
+ final int netValue;
+ final bool causesSwap;
+ final bool isIceTrap;
+
+ _ClosureResult(this.closesSomething, this.netValue, this.causesSwap, this.isIceTrap);
+}
+
+class AIEngine {
+ static Line getBestMove(GameBoard board, int level) {
+ List availableLines = board.lines.where((l) => l.owner == Player.none && l.isPlayable).toList();
+ final random = Random();
+
+ if (availableLines.isEmpty) return board.lines.first;
+
+ // Più il livello è alto, più l'IA è "intelligente"
+ double smartChance = 0.50 + ((level - 1) * 0.10);
+ if (smartChance > 1.0) smartChance = 1.0;
+
+ bool beSmart = random.nextDouble() < smartChance;
+
+ int myScore = board.currentPlayer == Player.red ? board.scoreRed : board.scoreBlue;
+ int oppScore = board.currentPlayer == Player.red ? board.scoreBlue : board.scoreRed;
+
+ // --- NUOVA LOGICA: GESTIONE INVERSIONE (TACTICAL FEEDING) ---
+ // Se c'è un numero dispari di caselle Scambio aperte, il gioco è "invertito".
+ // I punti accumulati andranno in regalo all'avversario!
+ int swapCount = board.boxes.where((b) => b.type == BoxType.swap && !b.isClosed()).length;
+ bool isInverted = swapCount % 2 != 0;
+
+ List goodClosingMoves = [];
+ List badClosingMoves = [];
+ List iceTraps = [];
+
+ for (var line in availableLines) {
+ var result = _checkClosure(board, line, isInverted);
+
+ if (result.isIceTrap) {
+ iceTraps.add(line);
+ continue;
+ }
+
+ if (result.closesSomething) {
+ if (result.causesSwap) {
+ if (myScore < oppScore) {
+ goodClosingMoves.add(line); // Se perdiamo, lo scambio è la mossa vincente!
+ } else {
+ badClosingMoves.add(line); // Se vinciamo, NON tocchiamo lo scambio!
+ }
+ } else {
+ if (result.netValue >= 0) {
+ goodClosingMoves.add(line);
+ } else {
+ badClosingMoves.add(line);
+ }
+ }
+ }
+ }
+
+ // --- REGOLA 1: Chiudere i quadrati vantaggiosi ---
+ if (goodClosingMoves.isNotEmpty) {
+ if (beSmart || random.nextDouble() < 0.70) {
+ return goodClosingMoves[random.nextInt(goodClosingMoves.length)];
+ }
+ }
+
+ // --- REGOLA 2: Mosse Sicure ---
+ List safeMoves = [];
+ for (var line in availableLines) {
+ if (!badClosingMoves.contains(line) && !goodClosingMoves.contains(line) && !iceTraps.contains(line) && _isSafeMove(board, line, myScore, oppScore, isInverted)) {
+ safeMoves.add(line);
+ }
+ }
+
+ if (safeMoves.isNotEmpty) {
+ if (beSmart) {
+ return safeMoves[random.nextInt(safeMoves.length)];
+ } else {
+ if (random.nextDouble() < 0.5) {
+ return safeMoves[random.nextInt(safeMoves.length)];
+ }
+ }
+ }
+
+ // --- REGOLA 3: Scegliere il male minore ---
+ if (beSmart) {
+ List riskyButNotTerrible = availableLines.where((l) => !badClosingMoves.contains(l) && !goodClosingMoves.contains(l) && !iceTraps.contains(l)).toList();
+ if (riskyButNotTerrible.isNotEmpty) {
+ return riskyButNotTerrible[random.nextInt(riskyButNotTerrible.length)];
+ }
+ }
+
+ // Ultima spiaggia
+ List nonTerribleMoves = availableLines.where((l) => !badClosingMoves.contains(l) && !iceTraps.contains(l)).toList();
+ if (nonTerribleMoves.isNotEmpty) {
+ return nonTerribleMoves[random.nextInt(nonTerribleMoves.length)];
+ }
+
+ return availableLines[random.nextInt(availableLines.length)];
+ }
+
+ static _ClosureResult _checkClosure(GameBoard board, Line line, bool isInverted) {
+ int netValue = 0;
+ bool closesSomething = false;
+ bool causesSwap = false;
+ bool isIceTrap = false;
+
+ for (var box in board.boxes) {
+ if (box.type == BoxType.invisible) continue;
+
+ if (box.top == line || box.bottom == line || box.left == line || box.right == line) {
+ int linesCount = 0;
+ if (box.top.owner != Player.none || box.top == line) linesCount++;
+ if (box.bottom.owner != Player.none || box.bottom == line) linesCount++;
+ if (box.left.owner != Player.none || box.left == line) linesCount++;
+ if (box.right.owner != Player.none || box.right == line) linesCount++;
+
+ if (linesCount == 4) {
+ if (box.type == BoxType.ice && !line.isIceCracked) {
+ isIceTrap = true;
+ } else {
+ closesSomething = true;
+
+ if (box.type == BoxType.swap) {
+ causesSwap = true;
+ } else {
+ int boxValue = 0;
+ if (box.hiddenJokerOwner == board.currentPlayer) {
+ boxValue = 2;
+ } else {
+ if (box.type == BoxType.gold) boxValue = 2;
+ else if (box.type == BoxType.bomb) boxValue = -1;
+ else if (box.type == BoxType.ice) boxValue = 0;
+ else if (box.type == BoxType.multiplier) boxValue = 1;
+ else boxValue = 1;
+ }
+
+ // LA MAGIA: Se il gioco è invertito, fare punti positivi viene calcolato come MALUS per l'IA!
+ netValue += isInverted ? -boxValue : boxValue;
+ }
+ }
+ }
+ }
+ }
+ return _ClosureResult(closesSomething, netValue, causesSwap, isIceTrap);
+ }
+
+ static bool _isSafeMove(GameBoard board, Line line, int myScore, int oppScore, bool isInverted) {
+ for (var box in board.boxes) {
+ if (box.type == BoxType.invisible) continue;
+
+ if (box.top == line || box.bottom == line || box.left == line || box.right == line) {
+ int currentLinesCount = 0;
+ if (box.top.owner != Player.none) currentLinesCount++;
+ if (box.bottom.owner != Player.none) currentLinesCount++;
+ if (box.left.owner != Player.none) currentLinesCount++;
+ if (box.right.owner != Player.none) currentLinesCount++;
+
+ if (currentLinesCount == 2) {
+ int valueForOpponent = 0;
+
+ if (box.type == BoxType.ice) {
+ valueForOpponent = -5;
+ } else if (box.type == BoxType.swap) {
+ if (myScore < oppScore) {
+ continue; // Sicuro lasciarlo: se lo prende perde i punti.
+ } else {
+ return false; // Pericoloso: se lo prende ci ruba il vantaggio!
+ }
+ } else if (box.hiddenJokerOwner == board.currentPlayer) {
+ valueForOpponent = -1;
+ } else {
+ if (box.type == BoxType.gold) valueForOpponent = 2;
+ else if (box.type == BoxType.bomb) valueForOpponent = -1;
+ else if (box.type == BoxType.multiplier) valueForOpponent = 1;
+ else valueForOpponent = 1;
+ }
+
+ // LA MAGIA 2: Se il tabellone è invertito, regalare un punto all'avversario è un'ottima esca!
+ if (isInverted && box.type != BoxType.swap && box.type != BoxType.ice) {
+ valueForOpponent = -valueForOpponent;
+ }
+
+ if (valueForOpponent < 0) {
+ continue; // Mossa considerata sicura (trappola perfetta)
+ }
+
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
+// ===========================================================================
+// FILE: lib/logic/game_controller.dart
+// ===========================================================================
+
+// ===========================================================================
+// FILE: lib/logic/game_controller.dart
+// ===========================================================================
+
+import 'dart:async';
+import 'dart:math';
+import 'package:cloud_firestore/cloud_firestore.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+import '../models/game_board.dart';
+export '../models/game_board.dart';
+
+import 'ai_engine.dart';
+import '../services/audio_service.dart';
+import '../services/storage_service.dart';
+import '../services/multiplayer_service.dart';
+import '../core/app_colors.dart';
+
+class CpuMatchSetup {
+ final int radius;
+ final ArenaShape shape;
+ CpuMatchSetup(this.radius, this.shape);
+}
+
+class GameController extends ChangeNotifier {
+ late GameBoard board;
+ bool isVsCPU = false;
+ bool isCPUThinking = false;
+
+ bool isOnline = false;
+ String? roomCode;
+ bool isHost = false;
+ StreamSubscription? _onlineSubscription;
+
+ bool opponentLeft = false;
+ bool _hasSavedResult = false;
+
+ Timer? _blitzTimer;
+ int timeLeft = 10;
+ int maxTime = 10;
+ String timeModeSetting = 'fixed'; // 'fixed', 'relax', 'dynamic'
+ bool get isTimeMode => timeModeSetting != 'relax';
+ int consecutiveRematches = 0; // Contatore per la modalità Dinamica
+
+ String effectText = '';
+ Color effectColor = Colors.transparent;
+ Timer? _effectTimer;
+
+ String? myReaction;
+ String? opponentReaction;
+ Timer? _myReactionTimer;
+ Timer? _oppReactionTimer;
+
+ Timestamp? _lastOpponentReactionTime;
+
+ bool rematchRequested = false;
+ bool opponentWantsRematch = false;
+ int lastMatchXP = 0;
+
+ static const Map>> rewardsRoadmap = {
+ 2: [{'title': 'Bomba & Oro', 'desc': 'Appaiono le caselle speciali: Oro (+2) e Bomba (-1)!', 'icon': Icons.stars, 'color': Colors.amber}],
+ 3: [
+ {'title': 'Tema Cyberpunk', 'desc': 'Sbloccato un nuovo tema visivo nelle impostazioni.', 'icon': Icons.palette, 'color': Colors.tealAccent},
+ {'title': 'Arena a Croce', 'desc': 'Sbloccata una nuova forma arena più complessa.', 'icon': Icons.add_box, 'color': Colors.blueAccent}
+ ],
+ 5: [{'title': 'Scambio', 'desc': 'Nuova casella! Inverte istantaneamente i punteggi.', 'icon': Icons.swap_horiz, 'color': Colors.purpleAccent}],
+ 7: [
+ {'title': 'Tema 8-Bit', 'desc': 'Sbloccato il nostalgico tema sala giochi.', 'icon': Icons.videogame_asset, 'color': Colors.greenAccent},
+ {'title': 'Arene Caos', 'desc': 'Generazione procedurale sbloccata. Nessuna partita sarà uguale!', 'icon': Icons.all_inclusive, 'color': Colors.redAccent}
+ ],
+ 10: [
+ {'title': 'Tema Grimorio', 'desc': 'Sbloccato il tema della magia antica.', 'icon': Icons.auto_stories, 'color': Colors.deepPurpleAccent},
+ {'title': 'Blocco di Ghiaccio', 'desc': 'Nuova meccanica! Il ghiaccio richiede due colpi per rompersi.', 'icon': Icons.ac_unit, 'color': Colors.cyanAccent}
+ ],
+ 15: [
+ {'title': 'Tema Musica', 'desc': 'Sbloccato il tema a tempo di beat.', 'icon': Icons.headphones, 'color': Colors.pinkAccent},
+ {'title': 'Moltiplicatore x2', 'desc': 'Nuova casella! Raddoppia i punti della tua prossima conquista.', 'icon': Icons.bolt, 'color': Colors.yellowAccent}
+ ],
+ };
+
+ bool hasLeveledUp = false;
+ int newlyReachedLevel = 1;
+ List