commit a8088fd1e7283873ac5d4f2eb7cd57945d7f0389 Author: savinmax Date: Sun Sep 21 15:41:07 2025 +0200 Initialize Match Three project with basic structure diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79c113f --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..eff4eb4 --- /dev/null +++ b/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "edada7c56edf4a183c1735310e123c7f923584f1" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: edada7c56edf4a183c1735310e123c7f923584f1 + base_revision: edada7c56edf4a183c1735310e123c7f923584f1 + - platform: macos + create_revision: edada7c56edf4a183c1735310e123c7f923584f1 + base_revision: edada7c56edf4a183c1735310e123c7f923584f1 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a6a86b --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# Match 3 Game - Flutter/Flame + +A cross-platform Match 3 puzzle game built with Flutter and Flame game engine. + +## Technology Stack +- **Framework**: Flutter +- **Game Engine**: Flame +- **State Management**: Bloc/Cubit +- **Platforms**: iOS, Android, Web, Desktop + +## Features +- Classic match-3 gameplay (8x8 grid) +- Smooth animations and particle effects +- Multiple game modes (Classic, Timed, Moves-limited) +- Power-ups and special gems +- Progressive difficulty levels +- Score tracking and achievements +- Cross-platform compatibility + +## Project Structure +``` +lib/ +├── main.dart +├── game/ +│ ├── match_three_game.dart # Main game class +│ ├── components/ # Game components +│ │ ├── grid_component.dart +│ │ ├── gem_component.dart +│ │ └── ui_overlay.dart +│ ├── systems/ # Game systems +│ │ ├── match_detector.dart +│ │ ├── gravity_system.dart +│ │ └── score_system.dart +│ └── models/ # Data models +│ ├── gem.dart +│ ├── grid.dart +│ └── game_state.dart +├── screens/ # UI screens +│ ├── menu_screen.dart +│ ├── game_screen.dart +│ └── settings_screen.dart +├── bloc/ # State management +│ ├── game_bloc.dart +│ └── menu_bloc.dart +└── utils/ + ├── constants.dart + └── helpers.dart +``` + +## Getting Started +1. Install Flutter SDK +2. Add Flame dependency +3. Run `flutter pub get` +4. Launch with `flutter run` diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/lib/bloc/game_bloc.dart b/lib/bloc/game_bloc.dart new file mode 100644 index 0000000..fee36ae --- /dev/null +++ b/lib/bloc/game_bloc.dart @@ -0,0 +1,319 @@ +import 'dart:async'; + +import 'package:flame/game.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; +import '../game/models/grid.dart'; +import '../game/models/gem.dart'; +import '../game/systems/match_detector.dart'; +import '../game/systems/gravity_system.dart'; +import '../utils/constants.dart'; + +// Events +abstract class GameEvent extends Equatable { + @override + List get props => []; +} + +class StartGame extends GameEvent {} + +class SwapGems extends GameEvent { + final int row1, col1, row2, col2; + SwapGems(this.row1, this.col1, this.row2, this.col2); + @override + List get props => [row1, col1, row2, col2]; +} + +class ProcessMatches extends GameEvent { + final int combo; + ProcessMatches({this.combo = 0}); + @override + List get props => [combo]; +} + +// States +typedef DoneCallback = void Function([FutureOr?]); + +abstract class GameState extends Equatable { + @override + List get props => []; +} + +// Internal match state event +class _GamePlayingMatch extends GameState { + final GameGrid grid; + final int score; + final int moves; + final List matches; + + _GamePlayingMatch({ + required this.grid, + this.score = 0, + this.moves = 0, + this.matches = const [], + }); + + @override + List get props => [grid, score, moves, matches]; +} + +class GameInitial extends GameState {} + +class GamePlaying extends GameState { + final GameGrid grid; + final DoneCallback done; + final int score; + final int moves; + + GamePlaying({ + required this.grid, + this.done = _defaultDoneCallback, + this.score = 0, + this.moves = 0, + }); + + static void _defaultDoneCallback([FutureOr? _]) {} + + @override + List get props => [grid, score, moves]; +} + +class GamePlayingStart extends GamePlaying { + GamePlayingStart({ + required super.grid, + super.done, + super.score, + super.moves, + }); +} + +class GamePlayingIdle extends GamePlaying { + GamePlayingIdle({ + required super.grid, + super.done, + super.score, + super.moves, + }); +} + +class GamePlayingDrop extends GamePlaying { + GamePlayingDrop({ + required super.grid, + super.done, + super.score, + super.moves, + }); +} + +class GamePlayingSwap extends GamePlaying { + final Gem gem1, gem2; + + GamePlayingSwap( + {required super.grid, + super.score, + super.moves, + super.done, + required this.gem1, + required this.gem2}); + + @override + List get props => [grid, score, moves, gem1, gem2]; +} + +class GamePlayingNewGems extends GamePlaying { + final List gems; + + GamePlayingNewGems( + {required super.grid, + super.score, + super.moves, + super.done, + required this.gems}); + + @override + List get props => [grid, score, moves, gems]; +} + +class GamePlayingMatch extends GamePlaying { + final List matches; + final int combo; + + GamePlayingMatch({ + required super.grid, + super.score, + super.moves, + super.done, + this.combo = 0, + this.matches = const [], + }); + + @override + List get props => [grid, score, moves, matches]; +} + +// Bloc +class GameBloc extends Bloc { + GameBloc() : super(GameInitial()) { + on(_onStartGame); + on(_onSwapGems); + on(_onProcessMatches); + } + + void _onStartGame(StartGame event, Emitter emit) async { + final grid = GameGrid(); + + final done = Completer(); + emit(GamePlayingStart(grid: grid, done: done.complete)); + await done.future; + + final matches = MatchDetector.findMatches(grid); + emit(_GamePlayingMatch(grid: grid, matches: matches)); + add(ProcessMatches()); + } + + void _onSwapGems(SwapGems event, Emitter emit) async { + if (state is GamePlaying) { + final currentState = state as GamePlaying; + + if (!MatchDetector.isValidSwap( + currentState.grid, event.row1, event.col1, event.row2, event.col2)) { + return; + } + final newGrid = currentState.grid.clone(); + + // Perform swap + final gem1 = newGrid.getGem(event.row1, event.col1); + final gem2 = newGrid.getGem(event.row2, event.col2); + + if (gem1 != null && gem2 != null) { + newGrid.setGem(event.row1, event.col1, + gem2.copyWith(row: event.row1, col: event.col1)); + newGrid.setGem(event.row2, event.col2, + gem1.copyWith(row: event.row2, col: event.col2)); + + // Emit state for swap animation + var done = Completer(); + emit(GamePlayingSwap( + grid: newGrid, + score: currentState.score, + moves: currentState.moves, + gem1: gem1, + gem2: gem2, + done: done.complete, + )); + + // Wait for swap animation + await done.future; + + // Check for matches + final matches = MatchDetector.findMatches(newGrid); + if (matches.isNotEmpty) { + emit(_GamePlayingMatch( + grid: newGrid, + score: currentState.score, + moves: currentState.moves + 1, + matches: matches, + )); + add(ProcessMatches()); + } else { + // Revert swap if no matches + newGrid.setGem(event.row1, event.col1, gem1); + newGrid.setGem(event.row2, event.col2, gem2); + var done = Completer(); + + emit(GamePlayingSwap( + grid: newGrid, + score: currentState.score, + moves: currentState.moves, + gem1: gem2, + gem2: gem1, + done: done.complete, + )); + } + // Wait for swap animation + await done.future; + } + } + } + + void _onProcessMatches(ProcessMatches event, Emitter emit) async { + if (state is _GamePlayingMatch) { + final currentState = state as _GamePlayingMatch; + + // Calculate score + int newScore = currentState.score + + currentState.matches.length * GameConstants.baseScore; + + if (event.combo > 0) { + newScore += GameConstants.comboMultiplier * event.combo; + } + var newGrid = currentState.grid.clone(); + + // Mark matched gems and emit state for animation + for (final gem in currentState.matches) { + newGrid.setGem(gem.row, gem.col, null); + } + + var done = Completer(); + emit(GamePlayingMatch( + grid: newGrid, + score: newScore, + moves: currentState.moves, + matches: currentState.matches, + combo: event.combo, + done: done.complete, + )); + + // Wait for match animations to complete + await done.future; + newGrid = newGrid.clone(); + + // Apply gravity + GravitySystem.applyGravity(newGrid); + + // Emit state for drop columns + done = Completer(); + emit(GamePlayingDrop( + grid: newGrid, + score: newScore, + moves: currentState.moves, + done: done.complete, + )); + await done.future; + newGrid = newGrid.clone(); + + // Apply gravity + final newGems = GravitySystem.generateGems(newGrid); + + // Emit state for drop columns + done = Completer(); + emit(GamePlayingNewGems( + grid: newGrid, + score: newScore, + moves: currentState.moves, + gems: newGems, + done: done.complete, + )); + // Wait for fall animations + await done.future; + + newGrid = newGrid.clone(); + + // Check for new matches + final newMatches = MatchDetector.findMatches(newGrid); + + if (newMatches.isNotEmpty) { + emit(_GamePlayingMatch( + grid: newGrid, + score: newScore, + moves: currentState.moves, + matches: newMatches, + )); + add(ProcessMatches(combo: event.combo + 1)); + } else { + emit(GamePlayingIdle( + grid: newGrid, score: newScore, moves: currentState.moves)); + } + } + } +} diff --git a/lib/game/components/background_component.dart b/lib/game/components/background_component.dart new file mode 100644 index 0000000..bb988e0 --- /dev/null +++ b/lib/game/components/background_component.dart @@ -0,0 +1,23 @@ +import 'package:flame/components.dart'; +import 'package:flutter/material.dart'; + +class BackgroundComponent extends RectangleComponent { + BackgroundComponent() : super( + paint: Paint() + ..shader = const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFF1a1a2e), + Color(0xFF16213e), + Color(0xFF0f3460), + ], + ).createShader(const Rect.fromLTWH(0, 0, 800, 600)), + ); + + @override + void onGameResize(Vector2 size) { + super.onGameResize(size); + this.size = size; + } +} diff --git a/lib/game/components/gem_component.dart b/lib/game/components/gem_component.dart new file mode 100644 index 0000000..c5237b6 --- /dev/null +++ b/lib/game/components/gem_component.dart @@ -0,0 +1,141 @@ +import 'package:flame/components.dart'; +import 'package:flame/effects.dart'; +import 'package:flame/events.dart'; +import 'package:flutter/material.dart'; +import '../models/gem.dart'; +import '../systems/physics_system.dart'; +import 'grid_component.dart'; +import 'particle_system.dart'; +import '../../utils/constants.dart'; + +class GemComponent extends RectangleComponent with TapCallbacks { + final Gem gem; + final GridComponent gridComponent; + bool isSelected = false; + + static const List gemColors = [ + Colors.red, + Colors.blue, + Colors.green, + Colors.yellow, + Colors.purple, + Colors.orange, + ]; + + GemComponent({required this.gem, required this.gridComponent}) + : super( + size: Vector2.all(GameConstants.gemSize - 4), + paint: Paint()..color = gemColors[gem.type % gemColors.length], + ); + + @override + bool onTapDown(TapDownEvent event) { + if (!gridComponent.canInterract) return false; + gridComponent.onGemTapped(gem); + _animateSelection(); + PhysicsSystem.animatePop(this); + return true; + } + + void _animateSelection() { + isSelected = !isSelected; + final scaleEffect = ScaleEffect.to( + Vector2.all(isSelected ? 1.1 : 1.0), + EffectController(duration: 0.2), + ); + add(scaleEffect); + } + + animateMatch() async { + // Add particle explosion + final explosion = ParticleSystem.createMatchExplosion( + position + size / 2, + gemColors[gem.type % gemColors.length], + ); + parent?.add(explosion); + + // Animate gem destruction + PhysicsSystem.animateMatch(this); + + // Remove after animation + await Future.delayed( + Duration(milliseconds: (GameConstants.matchDuration * 1000).round()), + () { + removeFromParent(); + }); + } + + animateFall(Vector2 targetPosition) async { + await PhysicsSystem.animateFall(this, targetPosition); + } + + animateCombo() { + final comboEffect = ParticleSystem.createComboEffect(position + size / 2); + parent?.add(comboEffect); + } + + @override + void render(Canvas canvas) { + super.render(canvas); + + // Add gradient effect + final gradient = RadialGradient( + colors: [ + gemColors[gem.type % gemColors.length].withOpacity(0.9), + gemColors[gem.type % gemColors.length], + ], + ); + + final gradientPaint = Paint() + ..shader = gradient.createShader(size.toRect()); + + canvas.drawRRect( + RRect.fromRectAndRadius( + size.toRect(), + const Radius.circular(8), + ), + gradientPaint, + ); + + // Add border and glow effect + final borderColor = isSelected ? Colors.white : Colors.black26; + final borderWidth = isSelected ? 3.0 : 1.0; + + canvas.drawRRect( + RRect.fromRectAndRadius( + size.toRect(), + const Radius.circular(8), + ), + Paint() + ..color = borderColor + ..style = PaintingStyle.stroke + ..strokeWidth = borderWidth, + ); + + if (isSelected) { + canvas.drawRRect( + RRect.fromRectAndRadius( + size.toRect().inflate(2), + const Radius.circular(10), + ), + Paint() + ..color = Colors.white.withOpacity(0.3) + ..style = PaintingStyle.stroke + ..strokeWidth = 2, + ); + } + + // Add shine effect + final shinePaint = Paint() + ..color = Colors.white.withOpacity(0.3) + ..style = PaintingStyle.fill; + + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromLTWH(size.x * 0.2, size.y * 0.1, size.x * 0.3, size.y * 0.2), + const Radius.circular(4), + ), + shinePaint, + ); + } +} diff --git a/lib/game/components/grid_component.dart b/lib/game/components/grid_component.dart new file mode 100644 index 0000000..7928935 --- /dev/null +++ b/lib/game/components/grid_component.dart @@ -0,0 +1,185 @@ +import 'package:flame/components.dart'; +import 'package:match_three/bloc/game_bloc.dart'; +import 'package:match_three/game/systems/physics_system.dart'; +import '../models/grid.dart'; +import '../models/gem.dart'; +import '../match_three_game.dart'; +import 'gem_component.dart'; +import '../../utils/constants.dart'; + +class GridComponent extends PositionComponent + with HasGameReference { + late GameGrid gameGrid; + final List> gemComponents = []; + final MatchThreeGame game; + bool canInterract = false; + + GridComponent(this.game) : super(); + + @override + Future onLoad() async { + game.gameBloc.add(StartGame()); + } + + void _createGemComponents() { + // Clear existing components + for (final row in gemComponents) { + for (final gemComponent in row) { + gemComponent?.removeFromParent(); + } + row.clear(); + } + gemComponents.clear(); + for (final child in children) { + if (child is GemComponent) { + child.removeFromParent(); + } + } + + for (int row = 0; row < GameConstants.gridHeight; row++) { + gemComponents.add([]); + for (int col = 0; col < GameConstants.gridWidth; col++) { + final gem = gameGrid.getGem(row, col); + if (gem != null) { + final gemComponent = GemComponent(gem: gem, gridComponent: this); + gemComponent.position = Vector2( + col * GameConstants.gemSize + GameConstants.gridPadding, + row * GameConstants.gemSize + GameConstants.gridPadding, + ); + gemComponents[row].add(gemComponent); + add(gemComponent); + } else { + gemComponents[row].add(null); + } + } + } + } + + void updateGrid(GameState state) async { + // Work only with relevant events + if (state is! GamePlaying) { + return; + } + canInterract = false; + print("Update event with state ${state.runtimeType.toString()}"); + if (state is GamePlayingSwap) { + await _swapGems( + state.grid, + state.gem1, + state.gem2, + ); + } + if (state is GamePlayingMatch) { + await _animateMatch(state.grid, state.matches); + } + if (state is GamePlayingDrop) { + await _animateDrop(state.grid); + } + if (state is GamePlayingNewGems) { + await _animateNewGemsFall(state.grid, state.gems); + } + if (state is GamePlayingIdle) { + canInterract = true; + } + gameGrid = state.grid; + gameGrid.printGrid(); + _createGemComponents(); + await Future.delayed(const Duration(milliseconds: 100)); + print("Updated state ${state.runtimeType.toString()}"); + state.done(); + } + + GemComponent? _findGemComponent(Gem gem) { + return gemComponents[gem.row][gem.col]; + } + + _swapGems(GameGrid newGrid, Gem gem1, Gem gem2) async { + final gemComp1 = _findGemComponent(gem1); + final gemComp2 = _findGemComponent(gem2); + + if (gemComp1 == null || gemComp2 == null) { + print("!! Gem not found"); + return; + } + + await PhysicsSystem.animateSwap(gemComp1, gemComp2); + } + + _animateMatch(GameGrid newGrid, List matches, [int combo = 0]) async { + final effects = []; + final toRemove = []; + + // Animate explosion + for (final gem in matches) { + final gemComponent = _findGemComponent(gem); + if (gemComponent != null) { + toRemove.add(gemComponent); + effects.add(gemComponent.animateMatch()); + if (combo > 0) { + gemComponent.animateCombo(); + } + } + } + await Future.wait(effects); + for (final gemComponent in toRemove) { + gemComponent.removeFromParent(); + } + } + + _animateDrop(GameGrid newGrid) async { + final effects = []; + + for (int col = GameConstants.gridWidth - 1; col >= 0; col--) { + for (int row = GameConstants.gridHeight - 1; row >= 0; row--) { + final gem = gameGrid.getGem(row, col); + + // if gem is removed - animate fall effect from top + if (gem == null) { + int fallToRow = row; + while (row >= 0) { + final gemAbove = gameGrid.getGem(row, col); + final gemComponent = gemComponents[row][col]; + if (gemAbove != null) { + final targetPosition = Vector2( + col * GameConstants.gemSize + GameConstants.gridPadding, + fallToRow * GameConstants.gemSize + GameConstants.gridPadding, + ); + print( + "@@ Fall $row, $col -> $fallToRow, $col ${gemComponent == null ? "NULL" : "OK"}"); + effects.add(gemComponent?.animateFall(targetPosition)); + fallToRow--; + } + row--; + } + } + } + } + await Future.wait(effects); + } + + _animateNewGemsFall(GameGrid newGrid, List newGems) async { + final effects = []; + + for (final gem in newGems) { + // Create gem component from gem and animate falling effect to proper location + final gemComponent = GemComponent(gem: gem, gridComponent: this); + final targetPosition = Vector2( + gem.col * GameConstants.gemSize + GameConstants.gridPadding, + gem.row * GameConstants.gemSize + GameConstants.gridPadding, + ); + + gemComponent.position.x = targetPosition.x; + gemComponent.position.y = -GameConstants.gemSize; + add(gemComponent); + effects.add(gemComponent.animateFall(targetPosition)); + } + + await Future.wait(effects); + } + + void onGemTapped(Gem gem) { + if (canInterract) { + game.selectGem(gem); + } + } +} diff --git a/lib/game/components/particle_system.dart b/lib/game/components/particle_system.dart new file mode 100644 index 0000000..a715940 --- /dev/null +++ b/lib/game/components/particle_system.dart @@ -0,0 +1,64 @@ +import 'dart:math'; +import 'package:flame/components.dart'; +import 'package:flame/particles.dart'; +import 'package:flutter/material.dart'; + +class ParticleSystem { + static ParticleSystemComponent createMatchExplosion( + Vector2 position, Color color) { + return ParticleSystemComponent( + priority: 1, + particle: Particle.generate( + count: 15, + lifespan: 1.0, + generator: (i) => AcceleratedParticle( + acceleration: Vector2(0, 200), + speed: Vector2.random(Random()) * 100, + position: Vector2.zero(), + child: CircleParticle( + radius: Random().nextDouble() * 3 + 2, + paint: Paint()..color = color.withOpacity(0.8), + ), + ), + ), + position: position, + ); + } + + static ParticleSystemComponent createComboEffect(Vector2 position) { + return ParticleSystemComponent( + particle: Particle.generate( + count: 25, + lifespan: 1.5, + generator: (i) => AcceleratedParticle( + acceleration: Vector2(0, -50), + speed: Vector2.random(Random()) * 150, + position: Vector2.zero(), + child: CircleParticle( + radius: Random().nextDouble() * 4 + 3, + paint: Paint()..color = Colors.yellow.withOpacity(0.9), + ), + ), + ), + position: position, + ); + } + + static ParticleSystemComponent createSwapTrail(Vector2 start, Vector2 end) { + return ParticleSystemComponent( + particle: Particle.generate( + count: 8, + lifespan: 0.5, + generator: (i) => MovingParticle( + from: Vector2.zero(), + to: end - start, + child: CircleParticle( + radius: 2, + paint: Paint()..color = Colors.white.withOpacity(0.6), + ), + ), + ), + position: start, + ); + } +} diff --git a/lib/game/match_three_game.dart b/lib/game/match_three_game.dart new file mode 100644 index 0000000..7d39898 --- /dev/null +++ b/lib/game/match_three_game.dart @@ -0,0 +1,68 @@ +import 'dart:ui'; + +import 'package:flame/game.dart'; +import 'package:match_three/game/systems/match_detector.dart'; +import 'components/grid_component.dart'; +import 'components/background_component.dart'; +import 'models/gem.dart'; +import '../bloc/game_bloc.dart'; + +const gridSize = 8 * 64.0 + 32.0; // 8 gems * 64px + padding + +class MatchThreeGame extends FlameGame { + GridComponent? gridComponent; + late BackgroundComponent backgroundComponent; + late GameBloc gameBloc; + + Gem? selectedGem; + + @override + Future onLoad() async { + backgroundComponent = BackgroundComponent(); + add(backgroundComponent); + + gridComponent = GridComponent(this); + add(gridComponent!); + } + + @override + void onGameResize(Vector2 size) { + super.onGameResize(size); + // Center the grid on screen + if (gridComponent != null && hasLayout) { + gridComponent!.position = Vector2( + (size.x - gridSize) / 2, + (size.y - gridSize) / 2, + ); + } + } + + void selectGem(Gem gem) { + print(">>> Selecting gem ${gem.name}(${gem.type}) ${gem.row}, ${gem.col}"); + if (selectedGem == null) { + selectedGem = gem; + return; + } + if (MatchDetector.isValidSwap(gridComponent!.gameGrid, selectedGem!.row, + selectedGem!.col, gem.row, gem.col)) { + // Attempt swap + print( + ")) Valid swapping ${selectedGem!.row}, ${selectedGem!.col} with ${gem.row}, ${gem.col}"); + + gameBloc.add(SwapGems( + selectedGem!.row, + selectedGem!.col, + gem.row, + gem.col, + )); + } else { + print( + "(( Invalid swapping ${selectedGem!.row}, ${selectedGem!.col} with ${gem.row}, ${gem.col}"); + } + selectedGem = null; + } + + void setGameBloc(GameBloc bloc) { + gameBloc = bloc; + } +} diff --git a/lib/game/models/gem.dart b/lib/game/models/gem.dart new file mode 100644 index 0000000..8bdc3c1 --- /dev/null +++ b/lib/game/models/gem.dart @@ -0,0 +1,77 @@ +import 'package:equatable/equatable.dart'; + +class Gem extends Equatable { + final int type; + final int row; + final int col; + final bool isMatched; + final bool isSpecial; + + const Gem({ + required this.type, + required this.row, + required this.col, + this.isMatched = false, + this.isSpecial = false, + }); + + Gem copyWith({ + int? type, + int? row, + int? col, + bool? isMatched, + bool? isSpecial, + }) { + return Gem( + type: type ?? this.type, + row: row ?? this.row, + col: col ?? this.col, + isMatched: isMatched ?? this.isMatched, + isSpecial: isSpecial ?? this.isSpecial, + ); + } + + get name { + switch (type) { + case 0: + return 'red'; + case 1: + return 'blue'; + case 2: + return 'green'; + case 3: + return 'yellow'; + case 4: + return 'purple'; + case 5: + return 'orange'; + default: + return 'unknown'; + } + } + + @override + List get props => [type, row, col, isMatched, isSpecial]; + + // equals + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Gem && + other.type == type && + other.row == row && + other.col == col && + other.isMatched == isMatched && + other.isSpecial == isSpecial; + } + + @override + int get hashCode { + return type.hashCode ^ + row.hashCode ^ + col.hashCode ^ + isMatched.hashCode ^ + isSpecial.hashCode; + } +} diff --git a/lib/game/models/grid.dart b/lib/game/models/grid.dart new file mode 100644 index 0000000..e32e333 --- /dev/null +++ b/lib/game/models/grid.dart @@ -0,0 +1,89 @@ +import 'dart:math'; +import 'gem.dart'; +import '../../utils/constants.dart'; + +class GameGrid { + late List> _grid; + final Random _random = Random(); + + GameGrid() { + _initializeGrid(); + } + + void _initializeGrid() { + _grid = List.generate( + GameConstants.gridHeight, + (row) => List.generate( + GameConstants.gridWidth, + (col) => Gem( + type: _random.nextInt(GameConstants.gemTypes.length), + row: row, + col: col, + ), + ), + ); + } + + Gem? getGem(int row, int col) { + if (row < 0 || + row >= GameConstants.gridHeight || + col < 0 || + col >= GameConstants.gridWidth) { + return null; + } + return _grid[row][col]; + } + + void setGem(int row, int col, Gem? gem) { + if (row >= 0 && + row < GameConstants.gridHeight && + col >= 0 && + col < GameConstants.gridWidth) { + _grid[row][col] = gem; + } + } + + List> get grid => _grid; + + bool isValidPosition(int row, int col) { + return row >= 0 && + row < GameConstants.gridHeight && + col >= 0 && + col < GameConstants.gridWidth; + } + + clone() { + final clonedGrid = GameGrid(); + for (int row = 0; row < _grid.length; row++) { + for (int col = 0; col < _grid[row].length; col++) { + final gem = getGem(row, col); + + clonedGrid.setGem(row, col, gem?.copyWith()); + } + } + return clonedGrid; + } + + void printGrid() { + final cols = _grid[0].length; + var colHeader = " |"; + for (int col = 0; col < cols; col++) { + colHeader += " ${col.toString().padRight(3, " ")} |"; + } + colHeader += "\n--- |"; + for (int col = 0; col < cols; col++) { + colHeader += " --- |"; + } + print(colHeader); + for (int row = 0; row < _grid.length; row++) { + var rowStr = ""; + for (int col = 0; col < cols; col++) { + final gem = getGem(row, col); + if (col == 0) rowStr += "${row.toString().padLeft(3, " ")} |"; + rowStr += " ${(gem?.type.toString() ?? "-").padLeft(3, " ")} |"; + } + print(rowStr); + } + print(""); + } +} diff --git a/lib/game/systems/gravity_system.dart b/lib/game/systems/gravity_system.dart new file mode 100644 index 0000000..6c497c6 --- /dev/null +++ b/lib/game/systems/gravity_system.dart @@ -0,0 +1,55 @@ +import 'dart:math'; +import '../models/gem.dart'; +import '../models/grid.dart'; +import '../../utils/constants.dart'; + +class GravitySystem { + static void applyGravity(GameGrid grid) { + for (int col = 0; col < GameConstants.gridWidth; col++) { + _dropColumn(grid, col); + } + } + + static List generateGems(GameGrid grid) { + // Fill empty spaces with new gems + final random = Random(); + final List newGems = []; + for (int row = 0; row < GameConstants.gridHeight; row++) { + for (int col = 0; col < GameConstants.gridWidth; col++) { + if (grid.getGem(row, col) == null) { + final gem = Gem( + row: row, + col: col, + type: random.nextInt(GameConstants.gemTypes.length), + ); + newGems.add(gem); + grid.setGem(row, col, gem); + } + } + } + return newGems; + } + + static void _dropColumn(GameGrid grid, int col) { + final gems = []; + + // Collect non-null gems from bottom to top + for (int row = GameConstants.gridHeight - 1; row >= 0; row--) { + final gem = grid.getGem(row, col); + if (gem != null) { + gems.add(gem); + } + } + + // Clear column + for (int row = 0; row < GameConstants.gridHeight; row++) { + grid.setGem(row, col, null); + } + + // Place gems at bottom + for (int i = 0; i < gems.length; i++) { + final newRow = GameConstants.gridHeight - 1 - i; + grid.setGem(newRow, col, gems[i].copyWith(row: newRow, col: col)); + } + } +} diff --git a/lib/game/systems/match_detector.dart b/lib/game/systems/match_detector.dart new file mode 100644 index 0000000..2397cb6 --- /dev/null +++ b/lib/game/systems/match_detector.dart @@ -0,0 +1,65 @@ +import '../models/gem.dart'; +import '../models/grid.dart'; +import '../../utils/constants.dart'; + +class MatchDetector { + static List findMatches(GameGrid grid) { + final matches = []; + + // Check matches + List currentMatch = []; + for (int row = 0; row < GameConstants.gridHeight; row++) { + for (int col = 0; col < GameConstants.gridWidth; col++) { + final gem = grid.getGem(row, col); + if (gem == null || matches.contains(gem)) { + continue; + } + currentMatch.add(gem); + for (int i = row + 1; i < GameConstants.gridHeight; i++) { + final nextGem = grid.getGem(i, col); + if (nextGem == null || nextGem.type != gem.type) { + break; + } + currentMatch.add(nextGem); + } + if (currentMatch.length >= GameConstants.minMatchLength) { + matches.addAll(currentMatch); + } + currentMatch.clear(); + currentMatch.add(gem); + for (int i = col + 1; i < GameConstants.gridWidth; i++) { + final nextGem = grid.getGem(row, i); + if (nextGem == null || nextGem.type != gem.type) { + break; + } + currentMatch.add(nextGem); + } + if (currentMatch.length >= GameConstants.minMatchLength) { + currentMatch.add(gem); + matches.addAll(currentMatch); + } + currentMatch.clear(); + } + } + + print("Matches found: ${matches.length}"); + matches.forEach((gem) { + print(" - Match found at (${gem.row}, ${gem.col}) [${gem.type}]"); + }); + + return matches.toSet().toList(growable: false); + } + + static bool isValidSwap( + GameGrid grid, int row1, int col1, int row2, int col2) { + if (!grid.isValidPosition(row1, col1) || + !grid.isValidPosition(row2, col2)) { + return false; + } + + // Check if positions are adjacent + final rowDiff = (row1 - row2).abs(); + final colDiff = (col1 - col2).abs(); + return (rowDiff == 1 && colDiff == 0) || (rowDiff == 0 && colDiff == 1); + } +} diff --git a/lib/game/systems/physics_system.dart b/lib/game/systems/physics_system.dart new file mode 100644 index 0000000..ad76f1b --- /dev/null +++ b/lib/game/systems/physics_system.dart @@ -0,0 +1,64 @@ +import 'package:flame/components.dart'; +import 'package:flame/effects.dart'; +import 'package:flutter/material.dart'; +import '../components/gem_component.dart'; +import '../../utils/constants.dart'; + +class PhysicsSystem { + static animateSwap(GemComponent gem1, GemComponent gem2) async { + final pos1 = gem1.position.clone(); + final pos2 = gem2.position.clone(); + + gem1.add(MoveEffect.to( + pos2, + EffectController(duration: GameConstants.swapDuration), + )); + + gem2.add(MoveEffect.to( + pos1, + EffectController(duration: GameConstants.swapDuration), + )); + await Future.delayed( + Duration(milliseconds: (GameConstants.swapDuration * 1000).toInt())); + } + + static Future animateFall( + GemComponent gem, Vector2 targetPosition) async { + final fallDistance = targetPosition.y - gem.position.y; + final fallTime = + (fallDistance / 400).clamp(0.2, GameConstants.fallDuration); + + gem.add(MoveEffect.to( + targetPosition, + EffectController( + duration: fallTime, + curve: Curves.easeIn, + ), + )); + await Future.delayed(Duration(milliseconds: (fallTime * 1000).toInt())); + } + + static void animateMatch(GemComponent gem) { + // Scale down and fade out + gem.add(ScaleEffect.to( + Vector2.zero(), + EffectController(duration: GameConstants.matchDuration), + )); + + gem.add(OpacityEffect.to( + 0.0, + EffectController(duration: GameConstants.matchDuration), + )); + } + + static void animatePop(GemComponent gem) { + // Quick scale up then down + gem.add(ScaleEffect.by( + Vector2.all(1.3), + EffectController( + duration: 0.1, + reverseDuration: 0.1, + ), + )); + } +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..944646a --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'screens/menu_screen.dart'; +import 'bloc/game_bloc.dart'; + +void main() { + runApp(const MatchThreeApp()); +} + +class MatchThreeApp extends StatelessWidget { + const MatchThreeApp({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => GameBloc(), + child: MaterialApp( + title: 'Match Three', + theme: ThemeData( + primarySwatch: Colors.purple, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + home: const MenuScreen(), + debugShowCheckedModeBanner: false, + ), + ); + } +} diff --git a/lib/screens/game_screen.dart b/lib/screens/game_screen.dart new file mode 100644 index 0000000..633956b --- /dev/null +++ b/lib/screens/game_screen.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flame/game.dart'; +import '../game/match_three_game.dart'; +import '../bloc/game_bloc.dart'; + +class GameScreen extends StatefulWidget { + const GameScreen({super.key}); + + @override + State createState() => _GameScreenState(); +} + +class _GameScreenState extends State { + late MatchThreeGame game; + + @override + void initState() { + super.initState(); + game = MatchThreeGame(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: BlocListener( + listener: (context, state) { + if (state is GamePlaying && game.gridComponent != null) { + game.gridComponent!.updateGrid(state); + } + }, + child: BlocBuilder( + builder: (context, state) { + // Set game bloc reference + game.setGameBloc(context.read()); + + return Stack( + children: [ + GameWidget.controlled( + gameFactory: () => game, + ), + if (state is GamePlaying) + Positioned( + top: 50, + left: 20, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Score: ${state.score}', + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Moves: ${state.moves}', + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + Text( + 'Combo x ${state is GamePlayingMatch ? (state.combo + 1) : "-"}', + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + Text( + 'Last state: ${state.runtimeType}', + style: const TextStyle( + color: Colors.blue, + fontSize: 16, + ), + ), + ], + ), + ), + ), + Positioned( + top: 50, + right: 20, + child: IconButton( + onPressed: () => Navigator.pop(context), + icon: + const Icon(Icons.close, color: Colors.white, size: 30), + ), + ), + ], + ); + }, + ), + ), + ); + } +} diff --git a/lib/screens/menu_screen.dart b/lib/screens/menu_screen.dart new file mode 100644 index 0000000..074bd6f --- /dev/null +++ b/lib/screens/menu_screen.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../bloc/game_bloc.dart'; +import 'game_screen.dart'; + +class MenuScreen extends StatelessWidget { + const MenuScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.purple, Colors.deepPurple], + ), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Match Three', + style: TextStyle( + fontSize: 48, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 60), + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const GameScreen()), + ); + }, + style: ElevatedButton.styleFrom( + padding: + const EdgeInsets.symmetric(horizontal: 40, vertical: 16), + ), + child: const Text('Start Game', style: TextStyle(fontSize: 20)), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart new file mode 100644 index 0000000..9858e88 --- /dev/null +++ b/lib/utils/constants.dart @@ -0,0 +1,19 @@ +class GameConstants { + static const int gridWidth = 9; + static const int gridHeight = 8; + static const double gemSize = 64.0; + static const double gridPadding = 16.0; + static const int minMatchLength = 3; + + // Gem types + static const List gemTypes = [0, 1, 2, 3, 4, 5]; + + // Animation durations + static const double swapDuration = 0.3; + static const double fallDuration = 0.5; + static const double matchDuration = 0.2; + + // Scoring + static const int baseScore = 100; + static const int comboMultiplier = 50; +} diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..724bb2a --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import shared_preferences_foundation + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) +} diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 0000000..29c8eb3 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.14' + +# 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 diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 0000000..1999362 --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,23 @@ +PODS: + - FlutterMacOS (1.0.0) + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + +SPEC CHECKSUMS: + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + +PODFILE CHECKSUM: 7eb978b976557c8c1cd717d8185ec483fd090a82 + +COCOAPODS: 1.16.2 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..fa96b66 --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,801 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 2AF7720158F86299FD7D9188 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FAFB60DE9BEAA4A2B92DB64 /* Pods_Runner.framework */; }; + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + DF20E6CF73D3B8C6716C74B1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B8F32569755F2F39AE36C8F8 /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 202BC8D1AED7C69A2CEEDEDE /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* match_three.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = match_three.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 560D369BEDBED6F73B8E458B /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 5FAFB60DE9BEAA4A2B92DB64 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 629C2F0D776A0BCBC6D11F69 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 6A7F4708666B030EB8A7C21A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 93DD8D27E966F2F011B75C39 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A0E3C9A23718DA4272324493 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + B8F32569755F2F39AE36C8F8 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DF20E6CF73D3B8C6716C74B1 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2AF7720158F86299FD7D9188 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 03614EB98E04C521355C525D /* Pods */ = { + isa = PBXGroup; + children = ( + 6A7F4708666B030EB8A7C21A /* Pods-Runner.debug.xcconfig */, + 93DD8D27E966F2F011B75C39 /* Pods-Runner.release.xcconfig */, + A0E3C9A23718DA4272324493 /* Pods-Runner.profile.xcconfig */, + 629C2F0D776A0BCBC6D11F69 /* Pods-RunnerTests.debug.xcconfig */, + 202BC8D1AED7C69A2CEEDEDE /* Pods-RunnerTests.release.xcconfig */, + 560D369BEDBED6F73B8E458B /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 03614EB98E04C521355C525D /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* match_three.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5FAFB60DE9BEAA4A2B92DB64 /* Pods_Runner.framework */, + B8F32569755F2F39AE36C8F8 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 37D9CD9B09AF4925F0D10B04 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 16778579D57299E0F269617D /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 3138F69F1E53B0795608209E /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* match_three.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 16778579D57299E0F269617D /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3138F69F1E53B0795608209E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 37D9CD9B09AF4925F0D10B04 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 629C2F0D776A0BCBC6D11F69 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.matchThree.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/match_three.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/match_three"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 202BC8D1AED7C69A2CEEDEDE /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.matchThree.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/match_three.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/match_three"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 560D369BEDBED6F73B8E458B /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.matchThree.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/match_three.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/match_three"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..14dd18d --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..2875a72 --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = match_three + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.matchThree + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + 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 + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..f22fbd8 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,394 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + flame: + dependency: "direct main" + description: + name: flame + sha256: "86f63943349ef4d891fd1988ccd9fa3d27952eb8d9eb17b774a64e78cd62aaa6" + url: "https://pub.dev" + source: hosted + version: "1.32.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a + url: "https://pub.dev" + source: hosted + version: "8.1.6" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + ordered_set: + dependency: transitive + description: + name: ordered_set + sha256: d6c1d053a533e84931a388cbf03f1ad21a0543bf06c7a281859d3ffacd8e15f2 + url: "https://pub.dev" + source: hosted + version: "8.0.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + provider: + dependency: transitive + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74 + url: "https://pub.dev" + source: hosted + version: "2.4.12" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..0546115 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,26 @@ +name: match_three +description: A cross-platform Match 3 puzzle game built with Flutter and Flame. +version: 1.0.0+1 + +environment: + sdk: '>=3.0.0 <4.0.0' + flutter: ">=3.10.0" + +dependencies: + flutter: + sdk: flutter + flame: ^1.10.1 + flutter_bloc: ^8.1.3 + equatable: ^2.0.5 + shared_preferences: ^2.2.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 + +flutter: + uses-material-design: true + assets: + - assets/images/ + - assets/audio/ diff --git a/test/game_test.dart b/test/game_test.dart new file mode 100644 index 0000000..ae2813e --- /dev/null +++ b/test/game_test.dart @@ -0,0 +1,40 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:match_three/game/models/gem.dart'; +import 'package:match_three/game/models/grid.dart'; +import 'package:match_three/game/systems/match_detector.dart'; + +void main() { + group('Match Three Game Tests', () { + test('Grid initialization creates 8x8 grid', () { + final grid = GameGrid(); + + for (int row = 0; row < 8; row++) { + for (int col = 0; col < 8; col++) { + expect(grid.getGem(row, col), isNotNull); + } + } + }); + + test('Match detector validates adjacent positions', () { + final grid = GameGrid(); + + // Test adjacent positions (should be valid) + expect(MatchDetector.isValidSwap(grid, 0, 0, 0, 1), isTrue); + expect(MatchDetector.isValidSwap(grid, 0, 0, 1, 0), isTrue); + + // Test non-adjacent positions (should be invalid) + expect(MatchDetector.isValidSwap(grid, 0, 0, 0, 2), isFalse); + expect(MatchDetector.isValidSwap(grid, 0, 0, 2, 0), isFalse); + }); + + test('Gem creation with correct properties', () { + final gem = Gem(type: 1, row: 2, col: 3); + + expect(gem.type, equals(1)); + expect(gem.row, equals(2)); + expect(gem.col, equals(3)); + expect(gem.isMatched, isFalse); + expect(gem.isSpecial, isFalse); + }); + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..6e85eaa --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:match_three/main.dart'; + +void main() { + testWidgets('Match Three app smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MatchThreeApp()); + + // Verify that the menu screen shows up + expect(find.text('Match Three'), findsOneWidget); + expect(find.text('Start Game'), findsOneWidget); + }); +} diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..05573bf --- /dev/null +++ b/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + match_three + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..f8c5e47 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "match_three", + "short_name": "match_three", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +}