From f4c1e7697cd8ca0db0b9b3c49cda47012d425ddf Mon Sep 17 00:00:00 2001 From: alexvasl Date: Fri, 27 Sep 2024 16:40:14 +0300 Subject: [PATCH] Added screens: pause, game over. Added modes: game by levels, game by time limit. Added sounds. Added database for storing points. --- assets/images/game_over_score.png | Bin 0 -> 76357 bytes assets/svg/arrow-left.svg | 4 + assets/svg/check-circle.svg | 4 + assets/svg/check.svg | 3 + assets/svg/chevron-down.svg | 3 + assets/svg/chevron-right.svg | 3 + assets/svg/clipboard.svg | 4 + assets/svg/music.svg | 5 + assets/svg/pause-circle.svg | 5 + assets/svg/star.svg | 3 + assets/svg/ticket.svg | 6 + assets/svg/volume.svg | 4 + lib/db/main_db.dart | 103 +++ lib/game/board.dart | 759 +++++++++--------- lib/game/game_mode_manager.dart | 89 ++ lib/game/match_magic_game.dart | 245 ------ lib/game/sprite_loader.dart | 24 +- lib/game/swap_notifier.dart | 54 -- lib/game/tile.dart | 58 +- lib/main.dart | 33 +- lib/models/app_state_manager.dart | 16 + lib/models/isar/high_score.dart | 13 + lib/models/isar/high_score.g.dart | 355 ++++++++ lib/models/isar/settings.dart | 14 + lib/models/isar/settings.g.dart | 361 +++++++++ lib/screens/game_over_screen.dart | 153 ++++ lib/screens/game_screen.dart | 88 ++ lib/screens/main_menu.dart | 70 -- lib/screens/main_menu_screen.dart | 301 +++++++ lib/screens/options_screen.dart | 266 ++++++ lib/screens/pause_screen.dart | 206 +++++ lib/styles/styles.dart | 43 + lib/utilities/assets.dart | 28 + lib/widgets/gradient_button.dart | 39 + lib/widgets/icon_widgets/arrow_left_icon.dart | 27 + .../icon_widgets/check_circle_icon.dart | 25 + lib/widgets/icon_widgets/check_icon.dart | 25 + .../icon_widgets/chevron_down_icon.dart | 25 + .../icon_widgets/chevron_right_icon.dart | 25 + lib/widgets/icon_widgets/clipboard_icon.dart | 27 + lib/widgets/icon_widgets/music_icon.dart | 25 + .../icon_widgets/pause_circle_icon.dart | 27 + lib/widgets/icon_widgets/star_icon.dart | 25 + lib/widgets/icon_widgets/ticket_icon.dart | 25 + lib/widgets/icon_widgets/volume_icon.dart | 25 + .../overlays/game_overlay/hint_button.dart | 26 + .../overlays/game_overlay/pause_button.dart | 32 + .../overlays/game_overlay/restart_button.dart | 26 + lib/widgets/radio_button.dart | 32 + lib/widgets/toggle_switch.dart | 26 + linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 6 + pubspec.lock | 482 ++++++++++- pubspec.yaml | 22 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 57 files changed, 3505 insertions(+), 799 deletions(-) create mode 100644 assets/images/game_over_score.png create mode 100644 assets/svg/arrow-left.svg create mode 100644 assets/svg/check-circle.svg create mode 100644 assets/svg/check.svg create mode 100644 assets/svg/chevron-down.svg create mode 100644 assets/svg/chevron-right.svg create mode 100644 assets/svg/clipboard.svg create mode 100644 assets/svg/music.svg create mode 100644 assets/svg/pause-circle.svg create mode 100644 assets/svg/star.svg create mode 100644 assets/svg/ticket.svg create mode 100644 assets/svg/volume.svg create mode 100644 lib/db/main_db.dart create mode 100644 lib/game/game_mode_manager.dart delete mode 100644 lib/game/match_magic_game.dart delete mode 100644 lib/game/swap_notifier.dart create mode 100644 lib/models/app_state_manager.dart create mode 100644 lib/models/isar/high_score.dart create mode 100644 lib/models/isar/high_score.g.dart create mode 100644 lib/models/isar/settings.dart create mode 100644 lib/models/isar/settings.g.dart create mode 100644 lib/screens/game_over_screen.dart create mode 100644 lib/screens/game_screen.dart delete mode 100644 lib/screens/main_menu.dart create mode 100644 lib/screens/main_menu_screen.dart create mode 100644 lib/screens/options_screen.dart create mode 100644 lib/screens/pause_screen.dart create mode 100644 lib/styles/styles.dart create mode 100644 lib/utilities/assets.dart create mode 100644 lib/widgets/gradient_button.dart create mode 100644 lib/widgets/icon_widgets/arrow_left_icon.dart create mode 100644 lib/widgets/icon_widgets/check_circle_icon.dart create mode 100644 lib/widgets/icon_widgets/check_icon.dart create mode 100644 lib/widgets/icon_widgets/chevron_down_icon.dart create mode 100644 lib/widgets/icon_widgets/chevron_right_icon.dart create mode 100644 lib/widgets/icon_widgets/clipboard_icon.dart create mode 100644 lib/widgets/icon_widgets/music_icon.dart create mode 100644 lib/widgets/icon_widgets/pause_circle_icon.dart create mode 100644 lib/widgets/icon_widgets/star_icon.dart create mode 100644 lib/widgets/icon_widgets/ticket_icon.dart create mode 100644 lib/widgets/icon_widgets/volume_icon.dart create mode 100644 lib/widgets/overlays/game_overlay/hint_button.dart create mode 100644 lib/widgets/overlays/game_overlay/pause_button.dart create mode 100644 lib/widgets/overlays/game_overlay/restart_button.dart create mode 100644 lib/widgets/radio_button.dart create mode 100644 lib/widgets/toggle_switch.dart diff --git a/assets/images/game_over_score.png b/assets/images/game_over_score.png new file mode 100644 index 0000000000000000000000000000000000000000..e2cd7e5701fdfdc935afc8c117f32ee03dcbd0db GIT binary patch literal 76357 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 literal 0 HcmV?d00001 diff --git a/assets/svg/arrow-left.svg b/assets/svg/arrow-left.svg new file mode 100644 index 0000000..339776e --- /dev/null +++ b/assets/svg/arrow-left.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/check-circle.svg b/assets/svg/check-circle.svg new file mode 100644 index 0000000..19abddf --- /dev/null +++ b/assets/svg/check-circle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/check.svg b/assets/svg/check.svg new file mode 100644 index 0000000..5406b7e --- /dev/null +++ b/assets/svg/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/chevron-down.svg b/assets/svg/chevron-down.svg new file mode 100644 index 0000000..86bb99c --- /dev/null +++ b/assets/svg/chevron-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/chevron-right.svg b/assets/svg/chevron-right.svg new file mode 100644 index 0000000..e7ef342 --- /dev/null +++ b/assets/svg/chevron-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/clipboard.svg b/assets/svg/clipboard.svg new file mode 100644 index 0000000..bab777f --- /dev/null +++ b/assets/svg/clipboard.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/music.svg b/assets/svg/music.svg new file mode 100644 index 0000000..1fb1c17 --- /dev/null +++ b/assets/svg/music.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/svg/pause-circle.svg b/assets/svg/pause-circle.svg new file mode 100644 index 0000000..196453f --- /dev/null +++ b/assets/svg/pause-circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/svg/star.svg b/assets/svg/star.svg new file mode 100644 index 0000000..6558583 --- /dev/null +++ b/assets/svg/star.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ticket.svg b/assets/svg/ticket.svg new file mode 100644 index 0000000..06726cb --- /dev/null +++ b/assets/svg/ticket.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/svg/volume.svg b/assets/svg/volume.svg new file mode 100644 index 0000000..e8616ea --- /dev/null +++ b/assets/svg/volume.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/db/main_db.dart b/lib/db/main_db.dart new file mode 100644 index 0000000..db1e449 --- /dev/null +++ b/lib/db/main_db.dart @@ -0,0 +1,103 @@ +import 'package:isar/isar.dart'; +import 'package:match_magic/models/isar/high_score.dart'; +import 'package:match_magic/models/isar/settings.dart'; +import 'package:path_provider/path_provider.dart'; + +class MainDB { + MainDB._(); + static MainDB? _instance; + static MainDB get instance => _instance ??= MainDB._(); + + Isar? _isar; + + Isar get isar => _isar!; + + Future initMainDB({Isar? mock}) async { + final dir = await getApplicationDocumentsDirectory(); + if (mock != null) { + _isar = mock; + return true; + } + if (_isar != null && isar.isOpen) return false; + + _isar = await Isar.open( + [SettingsSchema, HighScoreSchema], + directory: dir.path, + inspector: false, + ); + return true; + } + + Future saveSoundEnabled(bool isEnabled) async { + print('saveSoundEnabled $isEnabled'); + final settings = await isar.settings.where().findFirst(); + await isar.writeTxn(() async { + if (settings != null) { + settings.isSoundEnabled = isEnabled; + await isar.settings.put(settings); + } else { + final newSettings = Settings( + isSoundEnabled: isEnabled, + isMusicEnabled: true, + ); + await isar.settings.put(newSettings); + } + }); + } + + Future saveMusicEnabled(bool isEnabled) async { + final settings = await isar.settings.where().findFirst(); + await isar.writeTxn(() async { + if (settings != null) { + settings.isMusicEnabled = isEnabled; + await isar.settings.put(settings); + } else { + final newSettings = Settings( + isSoundEnabled: true, + isMusicEnabled: isEnabled, + ); + await isar.settings.put(newSettings); + } + }); + } + + Future getSoundEnabled() async { + final settings = await isar.settings.where().findFirst(); + return settings?.isSoundEnabled ?? true; + } + + Future getMusicEnabled() async { + final settings = await isar.settings.where().findFirst(); + return settings?.isMusicEnabled ?? true; + } + + Future addHighScore(int score, Function()? callback) async { + final highScore = HighScore( + highScoreValue: score, + ); + + await isar.writeTxn(() async { + final existingHighScore = await isar.highScores.where().findFirst(); + + if (existingHighScore != null) { + if (score > existingHighScore.highScoreValue) { + existingHighScore.highScoreValue = score; + await isar.highScores.put(existingHighScore); + } else { + await isar.highScores.put(existingHighScore); + } + } else { + await isar.highScores.put(highScore); + } + }); + if (callback != null) { + callback(); + } + } + + Future getHighScoreValue() async { + final highScore = await isar.highScores.where().findFirst(); + + return highScore?.highScoreValue; + } +} diff --git a/lib/game/board.dart b/lib/game/board.dart index 0f499be..c6ca576 100644 --- a/lib/game/board.dart +++ b/lib/game/board.dart @@ -3,40 +3,65 @@ import 'package:flame/components.dart'; import 'package:flame/effects.dart'; import 'package:flame/game.dart'; import 'package:flutter/material.dart'; +import 'package:match_magic/db/main_db.dart'; +import 'package:match_magic/game/game_mode_manager.dart'; +import 'package:match_magic/game/sprite_loader.dart'; +import 'package:match_magic/screens/game_over_screen.dart'; import 'package:match_magic/utilities/audio_manager.dart'; +import 'package:match_magic/widgets/overlays/game_overlay/hint_button.dart'; +import 'package:match_magic/widgets/overlays/game_overlay/pause_button.dart'; +import 'package:match_magic/widgets/overlays/game_overlay/restart_button.dart'; import 'tile.dart'; -import 'package:flame/sprite.dart'; -import 'swap_notifier.dart'; class Board extends FlameGame { - final List sprites; - final SwapNotifier swapNotifier; - final Sprite magicCubeSprite; + BuildContext context; + final bool gameMode; + late GameModeManager gameModeManager; + static const int rows = 8; static const int cols = 8; late double tileSize; + Tile? selectedTile; List> tiles = []; int? selectedRow; int? selectedCol; bool animating = false; + bool isGridInitialized = false; Tile? lastMovedTile; Tile? lastMovedByGravity; + static int score = 0; + late TextComponent _playerScore; + late TextComponent _playerMoves; + late TextComponent _remainingTime; bool isFirstLaunch = true; - Board({ - required this.sprites, - required this.swapNotifier, - required this.magicCubeSprite, - }); + bool isGameOver = false; + static bool isSoundPlaying = true; + + Board( + this.context, { + required this.gameMode, + }) { + gameModeManager = GameModeManager( + currentMode: gameMode, + onGameOver: _onGameOver, + ); + } @override Future onLoad() async { super.onLoad(); - _resetGame(); + await loadImages(); + gameModeManager.initializeMode(); + + newGame(); } - void _resetGame() { + void newGame() { + isFirstLaunch = true; + resetGame(); + gameModeManager.initializeMode(); tiles.clear(); selectedRow = null; selectedCol = null; @@ -44,16 +69,82 @@ class Board extends FlameGame { tileSize = size.x / cols; _initializeGrid(isFirstLaunch); _removeInitialMatches(); + isFirstLaunch = false; + _playerScore = TextComponent( + text: 'Score: ', + position: Vector2(60, 70), + anchor: Anchor.centerLeft, + ); + add(_playerScore); + if (gameModeManager.currentMode) { + _playerMoves = TextComponent( + text: 'Moves: ', + position: Vector2(200, 70), + anchor: Anchor.centerLeft, + ); + add(_playerMoves); + } else { + _remainingTime = TextComponent( + text: 'Time: ', + position: Vector2(200, 70), + anchor: Anchor.centerLeft, + ); + add(_remainingTime); + } } - void restartGame() { - isFirstLaunch = true; - swapNotifier.resetScore(); - _resetGame(); + void resetGame() { + isGameOver = false; + this.overlays.remove(GameOverMenu.id); + + score = 0; + gameModeManager.resetMode(); + selectedTile = null; + final List tilesToRemove = []; + for (final component in children) { + if (component is Tile) { + tilesToRemove.add(component); + } + } + for (final tile in tilesToRemove) { + remove(tile); + } + } + + @override + void render(Canvas canvas) { + super.render(canvas); + + // Progress bar renderer in level mode + if (gameModeManager.currentMode == GameMode.levelProgression) { + final progressBarWidth = size.x * (score / gameModeManager.targetScore); + final progressBarHeight = 20.0; + final rect = Rect.fromLTWH(10, size.y - progressBarHeight - 10, + progressBarWidth, progressBarHeight); + final paint = Paint()..color = Colors.green; + canvas.drawRect(rect, paint); + } + } + + @override + void update(double dt) { + _playerScore.text = 'Score: $score'; + if (gameModeManager.currentMode) { + _playerMoves.text = 'Moves: ${GameModeManager.movesLeft}'; + } else { + _remainingTime.text = 'Time: ${gameModeManager.remainingTime}'; + } + if (!isGameOver && gameModeManager.isGameOverCondition()) { + _onGameOver(); + } + + super.update(dt); } void _initializeGrid(bool animate) { + int totalTiles = rows * cols; + int completedTiles = 0; for (int row = 0; row < rows; row++) { List rowTiles = []; for (int col = 0; col < cols; col++) { @@ -64,7 +155,7 @@ class Board extends FlameGame { : Vector2(col * tileSize, row * tileSize); var tile = Tile( - sprite: sprites[spriteIndex], + sprite: Tile.crystals[spriteIndex], spriteIndex: spriteIndex, size: Vector2.all(tileSize), position: initialPosition, @@ -80,22 +171,31 @@ class Board extends FlameGame { tile.add( MoveEffect.to( - Vector2(col * tileSize, row * tileSize), + Vector2(col * tileSize, row * tileSize + 200), EffectController( duration: 0.5, startDelay: delay, curve: Curves.bounceOut, ), + onComplete: () { + completedTiles++; + if (completedTiles == totalTiles) { + isGridInitialized = true; + } + }, ), ); } } tiles.add(rowTiles); } + if (!animate) { + isGridInitialized = true; + } } int _randomElement() { - return Random().nextInt(sprites.length); + return Random().nextInt(Tile.crystals.length); } void _removeInitialMatches() { @@ -106,7 +206,7 @@ class Board extends FlameGame { for (int col = 0; col < cols; col++) { if (_hasMatch(row, col)) { int spriteIndex = _randomElement(); - tiles[row][col]!.sprite = sprites[spriteIndex]; + tiles[row][col]!.sprite = Tile.crystals[spriteIndex]; tiles[row][col]!.spriteIndex = spriteIndex; hasMatches = true; } @@ -115,53 +215,53 @@ class Board extends FlameGame { } while (hasMatches); } - // Future handleTileSwipe(Tile tile, Vector2 delta) async { - // if (animating) return; + int _calculateScore(int matchLength) { + if (matchLength == 3) { + return 50; + } else if (matchLength == 4) { + return 400; + } else if (matchLength == 5) { + return 200; + } + return 0; + } - // int row = tile.row; - // int col = tile.col; - // Tile? targetTile; + // Moving elements - // if (delta.x.abs() > delta.y.abs()) { - // if (delta.x > 0 && col < cols - 1) { - // targetTile = tiles[row][col + 1]; - // } else if (delta.x < 0 && col > 0) { - // targetTile = tiles[row][col - 1]; - // } - // } else { - // if (delta.y > 0 && row < rows - 1) { - // targetTile = tiles[row + 1][col]; - // } else if (delta.y < 0 && row > 0) { - // targetTile = tiles[row - 1][col]; - // } - // } + void selectTile(Tile tile) { + if (selectedTile == null) { + selectedTile = tile; + tile.select(); + } else { + if (_isNeighbor(selectedTile!, tile) || selectedTile!.isMagicCube) { + } else { + selectedTile?.deselect(); + selectedTile = tile; + tile.select(); + } + } + } - // if (targetTile != null) { - // animating = true; - // lastMovedTile = tile; - // swapTiles(tile, targetTile, true); - - // await Future.delayed(const Duration(milliseconds: 300)); - - // if (!checkMatches()) { - // swapTiles(tile, targetTile, true); - // } else { - // swapNotifier.incrementMoveCount(); - // } - // selectedRow = null; - // selectedCol = null; - // animating = false; - // } - // } + bool _isNeighbor(Tile tile1, Tile tile2) { + return (tile1.row == tile2.row && (tile1.col - tile2.col).abs() == 1) || + (tile1.col == tile2.col && (tile1.row - tile2.row).abs() == 1); + } Future handleTileSwipe(Tile tile, Vector2 delta) async { - if (animating) return; + if (!isGridInitialized || animating) return; Tile? targetTile; if (tile.isMagicCube) { targetTile = _getTileBySwipeDirection(tile, delta); if (targetTile != null) { _removeAllOfType(targetTile.spriteIndex); + _animateRemoveTile(tile); + isSoundPlaying = await MainDB.instance.getSoundEnabled(); + if (isSoundPlaying) { + AudioManager.playExplosionSound(); + } + + tiles[tile.row][tile.col] = null; } return; } @@ -174,11 +274,11 @@ class Board extends FlameGame { swapTiles(tile, targetTile, true); await Future.delayed(const Duration(milliseconds: 300)); - - if (!checkMatches()) { + bool matchesFound = await checkMatches(); + if (!matchesFound) { swapTiles(tile, targetTile, true); } else { - swapNotifier.incrementMoveCount(); + GameModeManager.movesLeft--; } selectedRow = null; selectedCol = null; @@ -207,28 +307,7 @@ class Board extends FlameGame { return targetTile; } - void _removeAllOfType(int spriteIndex) { - for (int row = 0; row < rows; row++) { - for (int col = 0; col < cols; col++) { - if (tiles[row][col]?.spriteIndex == spriteIndex) { - _animateRemoveTile(tiles[row][col]!); - tiles[row][col] = null; - } - } - } - - _applyGravity(); - _fillEmptySpaces(); - } - - bool _isAdjacent(int row1, int col1, int row2, int col2) { - return (row1 == row2 && (col1 - col2).abs() == 1) || - (col1 == col2 && (row1 - row2).abs() == 1); - } - void swapTiles(Tile tile1, Tile tile2, bool animate) { - // final tempPosition1 = tile1.position.clone(); - // final tempPosition2 = tile2.position.clone(); final tempRow1 = tile1.row; final tempCol1 = tile1.col; final tempRow2 = tile2.row; @@ -252,7 +331,49 @@ class Board extends FlameGame { tile1.deselect(); } - bool checkMatches({bool simulate = false}) { + // Hint button + + Future findHint() async { + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + Tile? tile = tiles[row][col]; + + if (col < cols - 1 && await _canSwap(row, col, row, col + 1)) { + return tile; + } + if (row < rows - 1 && await _canSwap(row, col, row + 1, col)) { + return tile; + } + } + } + return null; + } + + Future _canSwap(int row1, int col1, int row2, int col2) async { + Tile tempTile1 = tiles[row1][col1]!; + Tile tempTile2 = tiles[row2][col2]!; + + tiles[row1][col1] = tempTile2; + tiles[row2][col2] = tempTile1; + + bool matchFound = await checkMatches(simulate: true); + + tiles[row1][col1] = tempTile1; + tiles[row2][col2] = tempTile2; + + return matchFound; + } + + void showHint() async { + Tile? hintTile = await findHint(); + if (hintTile != null) { + hintTile.select(); + } + } + + // Match checks + + Future checkMatches({bool simulate = false}) async { if (simulate) { for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { @@ -279,7 +400,10 @@ class Board extends FlameGame { for (final match in matches) { points += _removeMatchedElements(match[0], match[1]); } - AudioManager.playSelectSound(); + isSoundPlaying = await MainDB.instance.getSoundEnabled(); + if (isSoundPlaying) { + AudioManager.playSelectSound(); + } Future.delayed(const Duration(milliseconds: 300), () { _applyGravity(); Future.delayed(const Duration(milliseconds: 300), () { @@ -290,7 +414,7 @@ class Board extends FlameGame { }); }); }); - swapNotifier.incrementScore(points); + score += points; return true; } @@ -302,46 +426,81 @@ class Board extends FlameGame { final value = tiles[row][col]?.spriteIndex; int count = 1; - for (int i = col + 1; i < cols && tiles[row][i]?.spriteIndex == value; i++) + for (int i = col + 1; + i < cols && tiles[row][i]?.spriteIndex == value; + i++) { count++; - for (int i = col - 1; i >= 0 && tiles[row][i]?.spriteIndex == value; i--) + } + for (int i = col - 1; i >= 0 && tiles[row][i]?.spriteIndex == value; i--) { count++; + } if (count >= 3) return true; count = 1; - for (int i = row + 1; i < rows && tiles[i][col]?.spriteIndex == value; i++) + for (int i = row + 1; + i < rows && tiles[i][col]?.spriteIndex == value; + i++) { count++; - for (int i = row - 1; i >= 0 && tiles[i][col]?.spriteIndex == value; i--) + } + for (int i = row - 1; i >= 0 && tiles[i][col]?.spriteIndex == value; i--) { count++; + } return count >= 3; } + // Removing items + + void _removeAllOfType(int spriteIndex) { + int removedCount = 0; + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + if (tiles[row][col]?.spriteIndex == spriteIndex) { + _animateRemoveTile(tiles[row][col]!); + tiles[row][col] = null; + removedCount++; + } + } + } + score += removedCount * 100; + Future.delayed(const Duration(milliseconds: 300), () { + _applyGravity(); + Future.delayed(const Duration(milliseconds: 300), () { + _fillEmptySpaces(); + }); + }); + } + int _removeMatchedElements(int row, int col) { int score = 0; final int? value = tiles[row][col]?.spriteIndex; - bool bombTriggered = false; - Tile? tileToTransformIntoBomb = null; + bool specialTriggered = false; + Tile? tileToTransformIntoSpecial; int left = col; - while (left > 0 && tiles[row][left - 1]?.spriteIndex == value) left--; + while (left > 0 && tiles[row][left - 1]?.spriteIndex == value) { + left--; + } int right = col; - while (right < cols - 1 && tiles[row][right + 1]?.spriteIndex == value) + while (right < cols - 1 && tiles[row][right + 1]?.spriteIndex == value) { right++; + } if (right - left + 1 >= 3) { score += _calculateScore(right - left + 1); - if (right - left + 1 >= 4) { - tileToTransformIntoBomb = lastMovedTile ?? lastMovedByGravity; + if (right - left + 1 == 4) { + tileToTransformIntoSpecial = lastMovedTile ?? lastMovedByGravity; + } else if (right - left + 1 >= 5) { + tileToTransformIntoSpecial = lastMovedTile ?? lastMovedByGravity; } for (int i = left; i <= right; i++) { if (tiles[row][i] != null) { if (tiles[row][i]!.isBomb) { - bombTriggered = true; + specialTriggered = true; _triggerBomb(row, i); } - if (tiles[row][i] != tileToTransformIntoBomb) { + if (tiles[row][i] != tileToTransformIntoSpecial) { _animateRemoveTile(tiles[row][i]!); tiles[row][i] = null; } @@ -350,25 +509,30 @@ class Board extends FlameGame { } int top = row; - while (top > 0 && tiles[top - 1][col]?.spriteIndex == value) top--; + while (top > 0 && tiles[top - 1][col]?.spriteIndex == value) { + top--; + } int bottom = row; - while (bottom < rows - 1 && tiles[bottom + 1][col]?.spriteIndex == value) + while (bottom < rows - 1 && tiles[bottom + 1][col]?.spriteIndex == value) { bottom++; + } if (bottom - top + 1 >= 3) { score += _calculateScore(bottom - top + 1); - if (bottom - top + 1 >= 4) { - tileToTransformIntoBomb = lastMovedTile ?? lastMovedByGravity; + if (bottom - top + 1 == 4) { + tileToTransformIntoSpecial = lastMovedTile ?? lastMovedByGravity; + } else if (bottom - top + 1 >= 5) { + tileToTransformIntoSpecial = lastMovedTile ?? lastMovedByGravity; } for (int i = top; i <= bottom; i++) { if (tiles[i][col] != null) { if (tiles[i][col]!.isBomb) { - bombTriggered = true; + specialTriggered = true; _triggerBomb(i, col); } - if (tiles[i][col] != tileToTransformIntoBomb) { + if (tiles[i][col] != tileToTransformIntoSpecial) { _animateRemoveTile(tiles[i][col]!); tiles[i][col] = null; } @@ -376,19 +540,82 @@ class Board extends FlameGame { } } - if (bombTriggered) { - _triggerBomb(row, col); + if (tileToTransformIntoSpecial != null) { + if ((right - left + 1 >= 5) || (bottom - top + 1 >= 5)) { + _createMagicCube( + tileToTransformIntoSpecial.row, tileToTransformIntoSpecial.col); + } else { + _createBomb( + tileToTransformIntoSpecial.row, tileToTransformIntoSpecial.col); + } } - if (tileToTransformIntoBomb != null) { - _createBomb(tileToTransformIntoBomb.row, tileToTransformIntoBomb.col); - AudioManager.playFourElementsSound(); + if (specialTriggered) { + _triggerBomb(row, col); } return score; } - void _triggerBomb(int row, int col) { + void _animateRemoveTile(Tile tile) { + tile.add(ScaleEffect.to( + Vector2.zero(), + EffectController( + duration: 0.2, + curve: Curves.easeInBack, + ), + onComplete: () => tile.removeFromParent(), + )); + } + + // The emergence of new elements + + void _applyGravity() { + for (int col = 0; col < cols; col++) { + for (int row = rows - 1; row >= 0; row--) { + if (tiles[row][col] == null) { + for (int k = row - 1; k >= 0; k--) { + if (tiles[k][col] != null) { + tiles[row][col] = tiles[k][col]!; + tiles[k][col] = null; + tiles[row][col]!.row = row; + tiles[row][col]!.animateMoveTo( + Vector2(col * tileSize, row * tileSize + 200), () {}); + lastMovedByGravity = tiles[row][col]; + break; + } + } + } + } + } + } + + void _fillEmptySpaces() { + for (int col = 0; col < cols; col++) { + for (int row = rows - 1; row >= 0; row--) { + if (tiles[row][col] == null) { + int spriteIndex = _randomElement(); + var tile = Tile( + sprite: Tile.crystals[spriteIndex], + spriteIndex: spriteIndex, + size: Vector2.all(tileSize), + position: Vector2(col * tileSize, -tileSize), + row: row, + col: col, + onSwipe: handleTileSwipe, + ); + tiles[row][col] = tile; + add(tile); + tile.animateMoveTo( + Vector2(col * tileSize, row * tileSize + 200), () {}); + } + } + } + } + + // Bomb + + void _triggerBomb(int row, int col) async { final tile = tiles[row][col]; if (tile == null || !tile.isBomb) return; @@ -412,7 +639,10 @@ class Board extends FlameGame { _animateRemoveTile(tile); tiles[row][col] = null; - AudioManager.playExplosionSound(); + isSoundPlaying = await MainDB.instance.getSoundEnabled(); + if (isSoundPlaying) { + AudioManager.playExplosionSound(); + } } void _animateBombExplosion(Vector2 position) { @@ -433,36 +663,11 @@ class Board extends FlameGame { ); } - int _calculateScore(int matchLength) { - if (matchLength == 3) { - return 50; - } else if (matchLength == 4) { - return 100; - } else if (matchLength == 5) { - return 200; + void _createBomb(int row, int col) async { + isSoundPlaying = await MainDB.instance.getSoundEnabled(); + if (isSoundPlaying) { + AudioManager.playFourElementsSound(); } - return 0; - } - - // void _animateRemoveTile(Tile tile) { - // tile.add(RemoveEffect( - // delay: 0.5, - // onComplete: () => remove(tile), - // )); - // } - - void _animateRemoveTile(Tile tile) { - tile.add(ScaleEffect.to( - Vector2.zero(), - EffectController( - duration: 0.2, - curve: Curves.easeInBack, - ), - onComplete: () => tile.removeFromParent(), - )); - } - - void _createBomb(int row, int col) { final tile = tiles[row][col]; if (tile != null) { tile.isBomb = true; @@ -493,12 +698,6 @@ class Board extends FlameGame { } } - void _createMagicCube(int row, int col) { - var tile = tiles[row][col]; - tile?.sprite = magicCubeSprite; - tile?.isMagicCube = true; - } - void explodeBomb(Tile bombTile) { final bombPosition = bombTile.position.clone(); final bombRow = bombTile.row; @@ -521,259 +720,39 @@ class Board extends FlameGame { tiles[bombRow][bombCol] = null; } - void _applyGravity() { - for (int col = 0; col < cols; col++) { - for (int row = rows - 1; row >= 0; row--) { - if (tiles[row][col] == null) { - for (int k = row - 1; k >= 0; k--) { - if (tiles[k][col] != null) { - tiles[row][col] = tiles[k][col]!; - tiles[k][col] = null; - tiles[row][col]!.row = row; - tiles[row][col]!.animateMoveTo( - Vector2(col * tileSize, row * tileSize), () {}); - lastMovedByGravity = tiles[row][col]; - break; - } - } - } - } + // Magic cube + + void _createMagicCube(int row, int col) async { + isSoundPlaying = await MainDB.instance.getSoundEnabled(); + if (isSoundPlaying) { + AudioManager.playFourElementsSound(); } + var tile = tiles[row][col]; + tile?.sprite = Tile.magicCubeSprite; + tile?.isMagicCube = true; } - void _fillEmptySpaces() { - for (int col = 0; col < cols; col++) { - for (int row = rows - 1; row >= 0; row--) { - if (tiles[row][col] == null) { - int spriteIndex = _randomElement(); - var tile = Tile( - sprite: sprites[spriteIndex], - spriteIndex: spriteIndex, - size: Vector2.all(tileSize), - position: Vector2(col * tileSize, -tileSize), - row: row, - col: col, - // onTileTap: handleTileTap, - onSwipe: handleTileSwipe, - ); - tiles[row][col] = tile; - add(tile); - tile.animateMoveTo(Vector2(col * tileSize, row * tileSize), () {}); - } - } - } + // Game over + + void _onGameOver() { + print("Game Over! Score: ${score}"); + showGameOverScreen(); } - Tile? findHint() { - for (int row = 0; row < rows; row++) { - for (int col = 0; col < cols; col++) { - Tile? tile = tiles[row][col]; - - if (col < cols - 1 && _canSwap(row, col, row, col + 1)) { - return tile; - } - if (row < rows - 1 && _canSwap(row, col, row + 1, col)) { - return tile; - } - } + void showGameOverScreen() { + isGameOver = true; + remove(_playerScore); + if (gameModeManager.currentMode) { + remove(_playerMoves); + } else { + remove(_remainingTime); } - return null; - } - bool _canSwap(int row1, int col1, int row2, int col2) { - Tile tempTile1 = tiles[row1][col1]!; - Tile tempTile2 = tiles[row2][col2]!; - - tiles[row1][col1] = tempTile2; - tiles[row2][col2] = tempTile1; - - bool matchFound = checkMatches(simulate: true); - - tiles[row1][col1] = tempTile1; - tiles[row2][col2] = tempTile2; - - return matchFound; - } - - void showHint() { - Tile? hintTile = findHint(); - if (hintTile != null) { - hintTile.select(); - } + this.overlays.remove(PauseButton.id); + this.overlays.remove(RestartButton.id); + this.overlays.remove(HintButton.id); + MainDB.instance.addHighScore(score, () { + this.overlays.add(GameOverMenu.id); + }); } } - - - - // void handleTileTap(Tile tappedTile) { - // if (animating) return; - - // int row = tappedTile.row; - // int col = tappedTile.col; - - // if (selectedRow == null || selectedCol == null) { - // tappedTile.select(); - // selectedRow = row; - // selectedCol = col; - // } else { - // tiles[selectedRow!][selectedCol!]?.deselect(); - // if (_isAdjacent(selectedRow!, selectedCol!, row, col)) { - // lastMovedTile = tiles[selectedRow!][selectedCol!]; - // swapTiles(tiles[selectedRow!][selectedCol!]!, tiles[row][col]!, true); - // Future.delayed(const Duration(milliseconds: 300), () { - // if (!checkMatches()) { - // swapTiles( - // tiles[row][col]!, tiles[selectedRow!][selectedCol!]!, true); - // } - // selectedRow = null; - // selectedCol = null; - // }); - // } else { - // tiles[selectedRow!][selectedCol!]?.deselect(); - // tappedTile.select(); - // selectedRow = row; - // selectedCol = col; - // } - // } - // } - - - // int _removeMatchedElements(int row, int col) { - // int score = 0; - // final int? value = tiles[row][col]?.spriteIndex; - // bool bombTriggered = false; - // Tile? tileToTransformIntoBomb = null; - - // int left = col; - // while (left > 0 && tiles[row][left - 1]?.spriteIndex == value) left--; - // int right = col; - // while (right < cols - 1 && tiles[row][right + 1]?.spriteIndex == value) - // right++; - - // if (right - left + 1 >= 3) { - // score += _calculateScore(right - left + 1); - - // if (right - left + 1 >= 4 && - // lastMovedTile != null && - // lastMovedTile!.row == row && - // lastMovedTile!.col >= left && - // lastMovedTile!.col <= right) { - // tileToTransformIntoBomb = lastMovedTile; - // } - - // for (int i = left; i <= right; i++) { - // if (tiles[row][i] != null) { - // if (tiles[row][i]!.isBomb) { - // bombTriggered = true; - // _triggerBomb(row, i); - // } - // if (tiles[row][i] != tileToTransformIntoBomb) { - // _animateRemoveTile(tiles[row][i]!); - // tiles[row][i] = null; - // } - // } - // } - // } - - // int top = row; - // while (top > 0 && tiles[top - 1][col]?.spriteIndex == value) top--; - // int bottom = row; - // while (bottom < rows - 1 && tiles[bottom + 1][col]?.spriteIndex == value) - // bottom++; - - // if (bottom - top + 1 >= 3) { - // score += _calculateScore(bottom - top + 1); - - // if (bottom - top + 1 >= 4 && - // lastMovedTile != null && - // lastMovedTile!.col == col && - // lastMovedTile!.row >= top && - // lastMovedTile!.row <= bottom) { - // tileToTransformIntoBomb = lastMovedTile; - // } - - // for (int i = top; i <= bottom; i++) { - // if (tiles[i][col] != null) { - // if (tiles[i][col]!.isBomb) { - // bombTriggered = true; - // _triggerBomb(i, col); - // } - // if (tiles[i][col] != tileToTransformIntoBomb) { - // _animateRemoveTile(tiles[i][col]!); - // tiles[i][col] = null; - // } - // } - // } - // } - - // if (bombTriggered) { - // _triggerBomb(row, col); - // } - - // if (tileToTransformIntoBomb != null) { - // _createBomb(tileToTransformIntoBomb.row, tileToTransformIntoBomb.col); - // } - - // return score; - // } - - - - // void explodeBomb(Tile bombTile) { - // final bombPosition = bombTile.position; - // final bombRow = bombTile.row; - // final bombCol = bombTile.col; - - // for (int rowOffset = -1; rowOffset <= 1; rowOffset++) { - // for (int colOffset = -1; colOffset <= 1; colOffset++) { - // final row = bombRow + rowOffset; - // final col = bombCol + colOffset; - - // if (row >= 0 && row < rows && col >= 0 && col < cols) { - // final tile = tiles[row][col]; - // if (tile != null && tile != bombTile) { - // _animateRemoveTile(tile); - // tiles[row][col] = null; - // } - // } - // } - // } - - // bombTile.add(RemoveEffect( - // delay: 0.5, - // onComplete: () => remove(bombTile), - // )); - - // final explosion = CircleComponent( - // radius: tileSize / 2, - // paint: Paint()..color = Colors.orange.withOpacity(0.7), - // position: bombPosition, - // ); - // add(explosion); - - // explosion.add(ScaleEffect.to( - // Vector2.all(2), - // EffectController(duration: 0.5), - // onComplete: () => explosion.removeFromParent(), - // )); - // } - - - // void _applyGravity() { - // for (int col = 0; col < cols; col++) { - // for (int row = rows - 1; row >= 0; row--) { - // if (tiles[row][col] == null) { - // for (int k = row - 1; k >= 0; k--) { - // if (tiles[k][col] != null) { - // tiles[row][col] = tiles[k][col]!; - // tiles[k][col] = null; - // tiles[row][col]!.row = row; - // tiles[row][col]!.animateMoveTo( - // Vector2(col * tileSize, row * tileSize), () {}); - // break; - // } - // } - // } - // } - // } - // } \ No newline at end of file diff --git a/lib/game/game_mode_manager.dart b/lib/game/game_mode_manager.dart new file mode 100644 index 0000000..de37931 --- /dev/null +++ b/lib/game/game_mode_manager.dart @@ -0,0 +1,89 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:match_magic/game/board.dart'; + +enum GameMode { + timeAttack, + levelProgression, +} + +class GameModeManager { + final bool currentMode; + final VoidCallback onGameOver; + + int remainingTime = 20; + int targetScore = 5000; + int currentLevel = 1; + static int movesLeft = 20; + + Timer? _timer; + + GameModeManager({ + required this.currentMode, + required this.onGameOver, + }); + + void initializeMode() { + if (currentMode) { + _startLevelProgressionMode(); + } else if (!currentMode) { + _startTimeAttackMode(); + } + } + + // Reset modes + void resetMode() { + _timer?.cancel(); + remainingTime = 20; + currentLevel = 1; + movesLeft = 20; + } + + // Time mode + void _startTimeAttackMode() { + _timer = Timer.periodic(Duration(seconds: 1), (timer) { + if (remainingTime > 0) { + remainingTime--; + } else { + onGameOver(); + _timer?.cancel(); + } + }); + } + + // Levels mode + void _startLevelProgressionMode() { + movesLeft = 20; // Number of moves per level + } + + // Time update + void updateTime(double dt) {} + + // Game Completion Logic + bool isGameOverCondition() { + if (!currentMode) { + return remainingTime <= 0; + } else if (currentMode) { + return movesLeft <= 0; + } + return false; + } + + // Moving to the next level + void nextLevel() { + if (Board.score >= targetScore) { + currentLevel++; + targetScore += 100; + movesLeft = 20; + } + } + + // Updating the number of remaining moves + void updateMoves(int movesUsed) { + movesLeft -= movesUsed; + if (movesLeft <= 0 && Board.score < targetScore) { + onGameOver(); + } + } +} diff --git a/lib/game/match_magic_game.dart b/lib/game/match_magic_game.dart deleted file mode 100644 index 27fdfc9..0000000 --- a/lib/game/match_magic_game.dart +++ /dev/null @@ -1,245 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flame/game.dart'; -import 'package:flutter/material.dart'; -import 'package:match_magic/game/sprite_loader.dart'; -import 'package:match_magic/utilities/audio_manager.dart'; -import 'package:provider/provider.dart'; -import 'board.dart'; -import 'swap_notifier.dart'; - -class MatchMagicGameScreen extends StatefulWidget { - const MatchMagicGameScreen({Key? key}) : super(key: key); - - @override - _MatchMagicGameScreenState createState() => _MatchMagicGameScreenState(); -} - -class _MatchMagicGameScreenState extends State { - late Future> _crystalsFuture; - late Future _magicCubeFuture; - Board? board; - - @override - void initState() { - super.initState(); - _crystalsFuture = SpriteLoader.loadCrystalSprites(); - _magicCubeFuture = SpriteLoader.loadMagicCubeSprite(); - AudioManager.load(); - } - - void _restartGame() { - context.read().resetScore(); - setState(() { - board = null; - _crystalsFuture = SpriteLoader.loadCrystalSprites(); - _magicCubeFuture = SpriteLoader.loadMagicCubeSprite(); - }); - } - - void _showSettingsDialog() { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Settings'), - content: const Text('The settings have not yet been implemented.'), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - _showExitConfirmationDialog(); - }, - child: const Text('Exit to Main Menu'), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text('Close'), - ), - ], - ); - }, - ); - } - - void _showExitConfirmationDialog() { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Warning'), - content: const Text( - 'You will lose all game progress. Are you sure you want to exit?'), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - Navigator.of(context).popUntil((route) => route.isFirst); - context.read().resetScore(); - }, - child: const Text('Yes'), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text('No'), - ), - ], - ); - }, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - body: FutureBuilder>( - future: _crystalsFuture, - builder: (context, crystalSnapshot) { - if (crystalSnapshot.connectionState == ConnectionState.done) { - if (crystalSnapshot.hasError) { - return Center(child: Text('Error: ${crystalSnapshot.error}')); - } else if (!crystalSnapshot.hasData || - crystalSnapshot.data == null) { - return const Center(child: Text('No crystal sprites found')); - } else { - final crystals = crystalSnapshot.data!; - - return FutureBuilder( - future: _magicCubeFuture, - builder: (context, cubeSnapshot) { - if (cubeSnapshot.connectionState == ConnectionState.done) { - if (cubeSnapshot.hasError) { - return Center( - child: Text('Error: ${cubeSnapshot.error}')); - } else if (!cubeSnapshot.hasData || - cubeSnapshot.data == null) { - return const Center( - child: Text('No magic cube sprite found')); - } else { - final magicCube = cubeSnapshot.data!; - - board ??= Board( - sprites: crystals, - magicCubeSprite: magicCube, - swapNotifier: context.read(), - ); - - return Stack( - children: [ - Column( - children: [ - const SizedBox(height: 50), - const ScoreDisplay(), - Expanded( - child: Center( - child: AspectRatio( - aspectRatio: 1, - child: GameWidget(game: board!), - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: _restartGame, - child: const Text( - 'Restart', - style: TextStyle(color: Colors.black), - ), - ), - const SizedBox(width: 20), - ElevatedButton( - onPressed: () { - board?.showHint(); - }, - child: const Text( - 'Hint', - style: TextStyle(color: Colors.black), - ), - ), - ], - ), - const SizedBox(height: 50), - ], - ), - Positioned( - top: 16, - left: 16, - child: IconButton( - icon: const Icon(Icons.settings, - color: Colors.white), - onPressed: _showSettingsDialog, - ), - ), - ], - ); - } - } else { - return const Center(child: CircularProgressIndicator()); - } - }, - ); - } - } else { - return const Center(child: CircularProgressIndicator()); - } - }, - ), - ); - } -} - -class ScoreDisplay extends StatelessWidget { - const ScoreDisplay({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Consumer( - builder: (context, notifier, child) { - return Card( - color: Colors.blueGrey[900], - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'Score: ${notifier.score}', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.white), - ), - ), - ); - }, - ), - Consumer( - builder: (context, notifier, child) { - return Card( - color: Colors.blueGrey[900], - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'Moves: ${notifier.moveCount}', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.white), - ), - ), - ); - }, - ), - ], - ), - ); - } -} diff --git a/lib/game/sprite_loader.dart b/lib/game/sprite_loader.dart index d1cb4ac..22568ca 100644 --- a/lib/game/sprite_loader.dart +++ b/lib/game/sprite_loader.dart @@ -1,15 +1,13 @@ -import 'package:flame/components.dart'; +// import 'package:flame/components.dart'; +import 'package:flame/flame.dart'; -class SpriteLoader { - static Future> loadCrystalSprites() async { - List sprites = []; - for (int i = 1; i <= 7; i++) { - sprites.add(await Sprite.load('crystal$i.png')); - } - return sprites; - } - - static Future loadMagicCubeSprite() async { - return Sprite.load('magic_cube.png'); - } +Future loadImages() async { + await Flame.images.load('crystal1.png'); + await Flame.images.load('crystal2.png'); + await Flame.images.load('crystal3.png'); + await Flame.images.load('crystal4.png'); + await Flame.images.load('crystal5.png'); + await Flame.images.load('crystal6.png'); + await Flame.images.load('crystal7.png'); + await Flame.images.load('magic_cube.png'); } diff --git a/lib/game/swap_notifier.dart b/lib/game/swap_notifier.dart deleted file mode 100644 index ad9c754..0000000 --- a/lib/game/swap_notifier.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/material.dart'; -import 'tile.dart'; - -class SwapNotifier extends ChangeNotifier { - Tile? selectedTile; - int _score = 0; - int _moveCount = 0; - - int get score => _score; - int get moveCount => _moveCount; - - void resetScore() { - _score = 0; - _moveCount = 0; - selectedTile = null; - notifyListeners(); - } - - void incrementScore(int value) { - _score += value; - notifyListeners(); - } - - void incrementMoveCount() { - _moveCount += 1; - notifyListeners(); - } - - void selectTile(Tile tile) { - if (selectedTile == null) { - selectedTile = tile; - tile.select(); - } else { - if (_isNeighbor(selectedTile!, tile) || selectedTile!.isMagicCube) { - notifyListeners(); - } else { - selectedTile?.deselect(); - selectedTile = tile; - tile.select(); - notifyListeners(); - } - } - } - - bool _isNeighbor(Tile tile1, Tile tile2) { - return (tile1.row == tile2.row && (tile1.col - tile2.col).abs() == 1) || - (tile1.col == tile2.col && (tile1.row - tile2.row).abs() == 1); - } - - void clearSelectedTile() { - selectedTile = null; - notifyListeners(); - } -} diff --git a/lib/game/tile.dart b/lib/game/tile.dart index e607029..194d226 100644 --- a/lib/game/tile.dart +++ b/lib/game/tile.dart @@ -1,6 +1,9 @@ +import 'dart:math'; + import 'package:flame/components.dart'; import 'package:flame/effects.dart'; import 'package:flame/events.dart'; +import 'package:flame/flame.dart'; import 'package:flutter/material.dart'; import 'board.dart'; @@ -26,8 +29,17 @@ class Tile extends SpriteComponent with TapCallbacks, DragCallbacks { required this.col, // required this.onTileTap, required this.onSwipe, - }) : super(sprite: sprite, size: size, position: position); + }) : super( + sprite: sprite, + size: size, + position: position, + ) { + // final randomSpriteIndex = _random.nextInt(crystals.length); + // tile = isMagicCube ? crystals[randomSpriteIndex] : magicCube(); + } + // static final Random _random = Random(); + late Sprite tile; // @override // bool onTapDown(TapDownEvent event) { // if (isAnimating || (parent is Board && (parent as Board).animating)) { @@ -37,6 +49,18 @@ class Tile extends SpriteComponent with TapCallbacks, DragCallbacks { // return true; // } + static List crystals = [ + crystal1(), + crystal2(), + crystal3(), + crystal4(), + crystal5(), + crystal6(), + crystal7(), + ]; + + static Sprite magicCubeSprite = magicCube(); + @override bool onDragStart(DragStartEvent event) { if (isAnimating || (parent as Board).animating) { @@ -123,3 +147,35 @@ class Tile extends SpriteComponent with TapCallbacks, DragCallbacks { // )); // } } + +Sprite crystal1() { + return Sprite(Flame.images.fromCache('crystal1.png')); +} + +Sprite crystal2() { + return Sprite(Flame.images.fromCache('crystal2.png')); +} + +Sprite crystal3() { + return Sprite(Flame.images.fromCache('crystal3.png')); +} + +Sprite crystal4() { + return Sprite(Flame.images.fromCache('crystal4.png')); +} + +Sprite crystal5() { + return Sprite(Flame.images.fromCache('crystal5.png')); +} + +Sprite crystal6() { + return Sprite(Flame.images.fromCache('crystal6.png')); +} + +Sprite crystal7() { + return Sprite(Flame.images.fromCache('crystal7.png')); +} + +Sprite magicCube() { + return Sprite(Flame.images.fromCache('magic_cube.png')); +} diff --git a/lib/main.dart b/lib/main.dart index ce89a71..ed044b0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,20 +1,25 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:match_magic/screens/main_menu.dart'; -import 'package:provider/provider.dart'; -import 'game/match_magic_game.dart'; -import 'game/swap_notifier.dart'; +import 'package:match_magic/db/main_db.dart'; +import 'package:match_magic/screens/main_menu_screen.dart'; -void main() { - runApp( - MultiProvider( - providers: [ - ChangeNotifierProvider(create: (_) => SwapNotifier()), - ], - child: const MyApp(), +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await MainDB.instance.initMainDB(); + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ]).then( + (value) => runApp( + // MultiProvider( + // providers: [ + // ChangeNotifierProvider(create: (_) => SwapNotifier()), + // ], + // child: const + MyApp(), ), + // ) ); - SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); } class MyApp extends StatelessWidget { @@ -22,8 +27,8 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: MainMenu(), + return MaterialApp( + home: MainMenuScreen(), ); } } diff --git a/lib/models/app_state_manager.dart b/lib/models/app_state_manager.dart new file mode 100644 index 0000000..6e8d5a7 --- /dev/null +++ b/lib/models/app_state_manager.dart @@ -0,0 +1,16 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class AppStateManager { + static const String _modeKey = 'mode'; + + // Installing the bubbles or balloons mod + static Future getMode() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + return prefs.getBool(_modeKey) ?? true; + } + + static Future setMode(bool mode) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setBool(_modeKey, mode); + } +} diff --git a/lib/models/isar/high_score.dart b/lib/models/isar/high_score.dart new file mode 100644 index 0000000..f8d548b --- /dev/null +++ b/lib/models/isar/high_score.dart @@ -0,0 +1,13 @@ +import 'package:isar/isar.dart'; + +part 'high_score.g.dart'; + +@Collection() +class HighScore { + Id id = Isar.autoIncrement; + + @enumerated + late int highScoreValue; + + HighScore({required this.highScoreValue}); +} diff --git a/lib/models/isar/high_score.g.dart b/lib/models/isar/high_score.g.dart new file mode 100644 index 0000000..609f76f --- /dev/null +++ b/lib/models/isar/high_score.g.dart @@ -0,0 +1,355 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'high_score.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetHighScoreCollection on Isar { + IsarCollection get highScores => this.collection(); +} + +const HighScoreSchema = CollectionSchema( + name: r'HighScore', + id: 6847972554114346288, + properties: { + r'highScoreValue': PropertySchema( + id: 0, + name: r'highScoreValue', + type: IsarType.long, + ) + }, + estimateSize: _highScoreEstimateSize, + serialize: _highScoreSerialize, + deserialize: _highScoreDeserialize, + deserializeProp: _highScoreDeserializeProp, + idName: r'id', + indexes: {}, + links: {}, + embeddedSchemas: {}, + getId: _highScoreGetId, + getLinks: _highScoreGetLinks, + attach: _highScoreAttach, + version: '3.1.0+1', +); + +int _highScoreEstimateSize( + HighScore object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + return bytesCount; +} + +void _highScoreSerialize( + HighScore object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeLong(offsets[0], object.highScoreValue); +} + +HighScore _highScoreDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = HighScore( + highScoreValue: reader.readLong(offsets[0]), + ); + object.id = id; + return object; +} + +P _highScoreDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readLong(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _highScoreGetId(HighScore object) { + return object.id; +} + +List> _highScoreGetLinks(HighScore object) { + return []; +} + +void _highScoreAttach(IsarCollection col, Id id, HighScore object) { + object.id = id; +} + +extension HighScoreQueryWhereSort + on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension HighScoreQueryWhere + on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } +} + +extension HighScoreQueryFilter + on QueryBuilder { + QueryBuilder + highScoreValueEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'highScoreValue', + value: value, + )); + }); + } + + QueryBuilder + highScoreValueGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'highScoreValue', + value: value, + )); + }); + } + + QueryBuilder + highScoreValueLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'highScoreValue', + value: value, + )); + }); + } + + QueryBuilder + highScoreValueBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'highScoreValue', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder idEqualTo( + Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } +} + +extension HighScoreQueryObject + on QueryBuilder {} + +extension HighScoreQueryLinks + on QueryBuilder {} + +extension HighScoreQuerySortBy on QueryBuilder { + QueryBuilder sortByHighScoreValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'highScoreValue', Sort.asc); + }); + } + + QueryBuilder sortByHighScoreValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'highScoreValue', Sort.desc); + }); + } +} + +extension HighScoreQuerySortThenBy + on QueryBuilder { + QueryBuilder thenByHighScoreValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'highScoreValue', Sort.asc); + }); + } + + QueryBuilder thenByHighScoreValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'highScoreValue', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } +} + +extension HighScoreQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByHighScoreValue() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'highScoreValue'); + }); + } +} + +extension HighScoreQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder highScoreValueProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'highScoreValue'); + }); + } +} diff --git a/lib/models/isar/settings.dart b/lib/models/isar/settings.dart new file mode 100644 index 0000000..02739f8 --- /dev/null +++ b/lib/models/isar/settings.dart @@ -0,0 +1,14 @@ +import 'package:isar/isar.dart'; + +part 'settings.g.dart'; + +@Collection() +class Settings { + Settings({ + required this.isSoundEnabled, + required this.isMusicEnabled, + }); + Id id = 1; + late bool isSoundEnabled; + late bool isMusicEnabled; +} diff --git a/lib/models/isar/settings.g.dart b/lib/models/isar/settings.g.dart new file mode 100644 index 0000000..d2777c3 --- /dev/null +++ b/lib/models/isar/settings.g.dart @@ -0,0 +1,361 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'settings.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetSettingsCollection on Isar { + IsarCollection get settings => this.collection(); +} + +const SettingsSchema = CollectionSchema( + name: r'Settings', + id: -8656046621518759136, + properties: { + r'isMusicEnabled': PropertySchema( + id: 0, + name: r'isMusicEnabled', + type: IsarType.bool, + ), + r'isSoundEnabled': PropertySchema( + id: 1, + name: r'isSoundEnabled', + type: IsarType.bool, + ) + }, + estimateSize: _settingsEstimateSize, + serialize: _settingsSerialize, + deserialize: _settingsDeserialize, + deserializeProp: _settingsDeserializeProp, + idName: r'id', + indexes: {}, + links: {}, + embeddedSchemas: {}, + getId: _settingsGetId, + getLinks: _settingsGetLinks, + attach: _settingsAttach, + version: '3.1.0+1', +); + +int _settingsEstimateSize( + Settings object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + return bytesCount; +} + +void _settingsSerialize( + Settings object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeBool(offsets[0], object.isMusicEnabled); + writer.writeBool(offsets[1], object.isSoundEnabled); +} + +Settings _settingsDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = Settings( + isMusicEnabled: reader.readBool(offsets[0]), + isSoundEnabled: reader.readBool(offsets[1]), + ); + object.id = id; + return object; +} + +P _settingsDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readBool(offset)) as P; + case 1: + return (reader.readBool(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _settingsGetId(Settings object) { + return object.id; +} + +List> _settingsGetLinks(Settings object) { + return []; +} + +void _settingsAttach(IsarCollection col, Id id, Settings object) { + object.id = id; +} + +extension SettingsQueryWhereSort on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension SettingsQueryWhere on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } +} + +extension SettingsQueryFilter + on QueryBuilder { + QueryBuilder idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder isMusicEnabledEqualTo( + bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isMusicEnabled', + value: value, + )); + }); + } + + QueryBuilder isSoundEnabledEqualTo( + bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isSoundEnabled', + value: value, + )); + }); + } +} + +extension SettingsQueryObject + on QueryBuilder {} + +extension SettingsQueryLinks + on QueryBuilder {} + +extension SettingsQuerySortBy on QueryBuilder { + QueryBuilder sortByIsMusicEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMusicEnabled', Sort.asc); + }); + } + + QueryBuilder sortByIsMusicEnabledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMusicEnabled', Sort.desc); + }); + } + + QueryBuilder sortByIsSoundEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isSoundEnabled', Sort.asc); + }); + } + + QueryBuilder sortByIsSoundEnabledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isSoundEnabled', Sort.desc); + }); + } +} + +extension SettingsQuerySortThenBy + on QueryBuilder { + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByIsMusicEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMusicEnabled', Sort.asc); + }); + } + + QueryBuilder thenByIsMusicEnabledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMusicEnabled', Sort.desc); + }); + } + + QueryBuilder thenByIsSoundEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isSoundEnabled', Sort.asc); + }); + } + + QueryBuilder thenByIsSoundEnabledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isSoundEnabled', Sort.desc); + }); + } +} + +extension SettingsQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByIsMusicEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isMusicEnabled'); + }); + } + + QueryBuilder distinctByIsSoundEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isSoundEnabled'); + }); + } +} + +extension SettingsQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder isMusicEnabledProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isMusicEnabled'); + }); + } + + QueryBuilder isSoundEnabledProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isSoundEnabled'); + }); + } +} diff --git a/lib/screens/game_over_screen.dart b/lib/screens/game_over_screen.dart new file mode 100644 index 0000000..b4b6543 --- /dev/null +++ b/lib/screens/game_over_screen.dart @@ -0,0 +1,153 @@ +import 'package:flame_audio/flame_audio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:match_magic/db/main_db.dart'; +import 'package:match_magic/game/board.dart'; +import 'package:match_magic/screens/main_menu_screen.dart'; +import 'package:match_magic/widgets/gradient_button.dart'; +import 'package:match_magic/widgets/overlays/game_overlay/hint_button.dart'; +import 'package:match_magic/widgets/overlays/game_overlay/pause_button.dart'; +import 'package:match_magic/widgets/overlays/game_overlay/restart_button.dart'; + +class GameOverMenu extends StatelessWidget { + static const String id = 'GameOverMenu'; + final Board gameRef; + + GameOverMenu({ + super.key, + required this.gameRef, + }); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFFEBEBEB).withOpacity(0.6), + Color(0xFFBFBEC0).withOpacity(0.6), + ], + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric(vertical: 70, horizontal: 16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Game over title. + const Padding( + padding: EdgeInsets.symmetric(vertical: 50.0), + child: Text( + 'Game Over', + style: TextStyle( + fontSize: 32.0, + fontWeight: FontWeight.w800, + color: Colors.black, + shadows: [ + Shadow( + blurRadius: 20.0, + color: Colors.white, + offset: Offset(0, 0), + ) + ], + ), + ), + ), + Stack( + children: [ + Container( + margin: EdgeInsets.symmetric(horizontal: 64), + child: Image.asset('assets/images/game_over_score.png')), + Positioned.fill( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: 20), + Text( + 'Score: ${Board.score}', + style: TextStyle( + fontSize: 24, + color: Colors.white, + ), + ), + SizedBox(height: 8), + FutureBuilder( + future: MainDB.instance.getHighScoreValue(), + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return CircularProgressIndicator(); // + } else if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } else if (snapshot.hasData) { + return Text( + 'High score: ${snapshot.data}', + style: TextStyle( + fontSize: 16, color: Colors.white), + ); + } else { + return Text('No data'); + } + }, + ), + ], + ), + ), + ], + ), + Expanded(child: SizedBox()), + Row( + children: [ + Expanded( + child: GradientButton( + onPressed: () async { + // AdManager.showInterstitialAd(context, () {}); + // Pause + gameRef.overlays.remove(GameOverMenu.id); + gameRef.overlays.add(PauseButton.id); + gameRef.overlays.add(RestartButton.id); + gameRef.overlays.add(HintButton.id); + + gameRef.newGame(); + gameRef.resumeEngine(); + }, + buttonText: 'Play again'), + ), + ], + ), + SizedBox( + height: 12, + ), + // Exit button. + Row( + children: [ + Expanded( + child: GradientButton( + onPressed: () { + FlameAudio.bgm.stop(); + gameRef.overlays.remove(GameOverMenu.id); + gameRef.removed; + gameRef.resumeEngine(); + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => MainMenuScreen(), + ), + ); + }, + buttonText: 'Exit'), + ) + ], + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/screens/game_screen.dart b/lib/screens/game_screen.dart new file mode 100644 index 0000000..57aa181 --- /dev/null +++ b/lib/screens/game_screen.dart @@ -0,0 +1,88 @@ +import 'package:flame/game.dart'; + +import 'package:flutter/material.dart'; +import 'package:match_magic/game/board.dart'; + +import 'package:match_magic/screens/game_over_screen.dart'; +import 'package:match_magic/screens/pause_screen.dart'; +import 'package:match_magic/utilities/audio_manager.dart'; +import 'package:match_magic/widgets/overlays/game_overlay/hint_button.dart'; +import 'package:match_magic/widgets/overlays/game_overlay/pause_button.dart'; +import 'package:match_magic/widgets/overlays/game_overlay/restart_button.dart'; + +class GameScreen extends StatefulWidget { + GameScreen(this.gameMode); + + final bool gameMode; + + @override + _GameScreenState createState() => _GameScreenState(); +} + +class _GameScreenState extends State { + Board? board; + + @override + void initState() { + super.initState(); + AudioManager.load(); + // AdManager.loadBannerAd(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + GameWidget( + initialActiveOverlays: [ + PauseButton.id, + HintButton.id, + RestartButton.id + ], + overlayBuilderMap: { + PauseButton.id: (BuildContext context, Board gameRef) => + PauseButton( + gameRef: gameRef, + ), + HintButton.id: (BuildContext context, Board gameRef) => + HintButton( + gameRef: gameRef, + ), + RestartButton.id: (BuildContext context, Board gameRef) => + RestartButton( + gameRef: gameRef, + ), + PauseMenu.id: (BuildContext context, Board gameRef) => + PauseMenu( + gameRef: gameRef, + ), + GameOverMenu.id: (BuildContext context, Board gameRef) => + GameOverMenu( + gameRef: gameRef, + ) + }, + game: Board( + context, + gameMode: widget.gameMode, + )), + // Display Ads + // if (AdManager.bannerAd != null) + // Align( + // alignment: Alignment.bottomCenter, + // child: Container( + // width: AdManager.bannerAd!.size.width.toDouble(), + // height: AdManager.bannerAd!.size.height.toDouble(), + // child: AdWidget(ad: AdManager.bannerAd!), + // ), + // ), + ], + ), + ); + } +} diff --git a/lib/screens/main_menu.dart b/lib/screens/main_menu.dart deleted file mode 100644 index 36bae61..0000000 --- a/lib/screens/main_menu.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:match_magic/game/match_magic_game.dart'; - -class MainMenu extends StatelessWidget { - const MainMenu({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - leading: IconButton( - icon: const Icon(Icons.settings, color: Colors.white), - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Settings'), - content: - const Text('The settings have not yet been implemented.'), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text('Ok'), - ), - ], - ), - ); - }, - ), - backgroundColor: Colors.black, - ), - backgroundColor: Colors.black, - body: Center( - child: Column( - children: [ - const SizedBox( - height: 100, - ), - const Text( - 'Match Magic', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: Colors.white), - ), - const SizedBox( - height: 300, - ), - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const MatchMagicGameScreen()), - ); - }, - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 60.0, vertical: 16.0), - ), - child: const Text('Play', style: TextStyle(fontSize: 16)), - ), - ], - ), - ), - ); - } -} diff --git a/lib/screens/main_menu_screen.dart b/lib/screens/main_menu_screen.dart new file mode 100644 index 0000000..c9ef983 --- /dev/null +++ b/lib/screens/main_menu_screen.dart @@ -0,0 +1,301 @@ +// import 'package:flutter/material.dart'; +// import 'package:match_magic/game/match_magic_game.dart'; + +// class MainMenu extends StatelessWidget { +// const MainMenu({Key? key}) : super(key: key); + +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// appBar: AppBar( +// leading: IconButton( +// icon: const Icon(Icons.settings, color: Colors.white), +// onPressed: () { +// showDialog( +// context: context, +// builder: (context) => AlertDialog( +// title: const Text('Settings'), +// content: +// const Text('The settings have not yet been implemented.'), +// actions: [ +// TextButton( +// onPressed: () { +// Navigator.of(context).pop(); +// }, +// child: const Text('Ok'), +// ), +// ], +// ), +// ); +// }, +// ), +// backgroundColor: Colors.black, +// ), +// backgroundColor: Colors.black, +// body: Center( +// child: Column( +// children: [ +// const SizedBox( +// height: 100, +// ), +// const Text( +// 'Match Magic', +// style: TextStyle( +// fontSize: 28, +// fontWeight: FontWeight.bold, +// color: Colors.white), +// ), +// const SizedBox( +// height: 300, +// ), +// ElevatedButton( +// onPressed: () { +// Navigator.push( +// context, +// MaterialPageRoute( +// builder: (context) => const MatchMagicGameScreen()), +// ); +// }, +// style: ElevatedButton.styleFrom( +// padding: const EdgeInsets.symmetric( +// horizontal: 60.0, vertical: 16.0), +// ), +// child: const Text('Play', style: TextStyle(fontSize: 16)), +// ), +// ], +// ), +// ), +// ); +// } +// } + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:match_magic/models/app_state_manager.dart'; +import 'package:match_magic/screens/game_screen.dart'; +import 'package:match_magic/screens/options_screen.dart'; +import 'package:match_magic/styles/styles.dart'; +import 'package:match_magic/utilities/assets.dart'; + +class MainMenuScreen extends StatefulWidget { + @override + State createState() => _MainMenuScreenState(); +} + +class _MainMenuScreenState extends State { + bool islevelProgression = true; + + @override + void initState() { + super.initState(); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); + // _loadBackground(); + // _checkPremiumStatus(); + _loadMode(); + } + + Future _loadMode() async { + final mode = await AppStateManager.getMode(); + setState(() { + islevelProgression = mode; + }); + } + + // Future _loadBackground() async { + // final background = await MainDB.instance.getBackground(); + // setState(() { + // backgroundImage = background; + // }); + // } + + // void _updateBackground(Background newBackground) { + // setState(() { + // backgroundImage = newBackground; + // }); + // } + + // // Premium check + // Future _checkPremiumStatus() async { + // bool isPremium = await AppStateManager.isPremium(); + // setState(() { + // _isPremium = isPremium; + // }); + // } + + Future _refreshMainScreen() async { + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + // Container( + // decoration: BoxDecoration( + // image: DecorationImage( + // image: BackgroundImageProvider.getBackgroundImage( + // backgroundImage ?? Background.sky), + // fit: BoxFit.cover, + // ), + // ), + // ), + Container( + padding: + const EdgeInsets.only(left: 16, top: 32, right: 16, bottom: 70), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + TextButton( + onPressed: () async { + bool? refreshScreen = await showDialog( + context: context, + builder: (BuildContext context) => OptionsScreen( + // onUpdateBackground: (background) { + // setState(() { + // backgroundImage = background; + // }); + // }, + ), + ); + if (refreshScreen != null && refreshScreen == true) { + await _refreshMainScreen(); + } + }, + child: Text( + 'Options', + style: AppStyles.subtitleTextStyle + .copyWith(color: AppStyles.accentColorTopBar), + )), + ], + ), + Expanded( + child: Container(), + ), + Container( + height: 150, + child: Text( + 'Match Magic', + style: TextStyle( + color: AppStyles.accentColorTopBar, fontSize: 30), + ), + ), + const SizedBox(height: 50), + Container( + height: 200, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Levels mode + Expanded( + child: GestureDetector( + onTap: () async { + setState(() { + islevelProgression = true; + }); + await AppStateManager.setMode(true); + }, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: + const BorderRadius.all(Radius.circular(20)), + border: islevelProgression + ? Border.all( + color: AppStyles.accentColor, + width: 2, + ) + : null, + ), + child: const Align( + alignment: Alignment.bottomCenter, + child: Text( + 'Levels', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + const SizedBox( + width: 16, + ), + // Limited time mode + Expanded( + child: GestureDetector( + onTap: () async { + setState(() { + islevelProgression = false; + }); + await AppStateManager.setMode(false); + }, + child: Stack( + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: const BorderRadius.all( + Radius.circular(20)), + border: islevelProgression + ? null + : Border.all( + color: AppStyles.accentColor, + width: 2, + ), + ), + child: const Align( + alignment: Alignment.bottomCenter, + child: Text( + 'Limited time', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + const SizedBox(height: 50), + + // Play game + + Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => + GameScreen(islevelProgression)), + (route) => false); + }, + child: Text('Play game'), + ), + ), + ], + ), + const SizedBox(height: 12), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/options_screen.dart b/lib/screens/options_screen.dart new file mode 100644 index 0000000..9185126 --- /dev/null +++ b/lib/screens/options_screen.dart @@ -0,0 +1,266 @@ +import 'package:flutter/material.dart'; +import 'package:match_magic/db/main_db.dart'; +import 'package:match_magic/game/board.dart'; +import 'package:match_magic/styles/styles.dart'; +import 'package:match_magic/widgets/icon_widgets/arrow_left_icon.dart'; +import 'package:match_magic/widgets/icon_widgets/music_icon.dart'; +import 'package:match_magic/widgets/icon_widgets/star_icon.dart'; +import 'package:match_magic/widgets/icon_widgets/volume_icon.dart'; +import 'package:match_magic/widgets/toggle_switch.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class OptionsScreen extends StatefulWidget { + // final Function(Background) onUpdateBackground; + + OptionsScreen(); + @override + State createState() => _OptionsScreenState(); +} + +class _OptionsScreenState extends State { + bool isSound = true; + bool isMusic = true; + // Background? backgroundImage; + + @override + void initState() { + super.initState(); + fetchSoundAndMusicState(); + // _loadBackground(); + getVersionNumber(); + // _checkPremiumStatus(); + } + + Future getVersionNumber() async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + return packageInfo.version; + } + + // Future _loadBackground() async { + // final background = await MainDB.instance.getBackground(); + // setState(() { + // backgroundImage = background; + // }); + // } + + // Future _checkPremiumStatus() async { + // bool isPremium = await AppStateManager.isPremium(); + // setState(() { + // _isPremium = isPremium; + // }); + // } + + // Future refreshOptionsScreen() async { + // await _checkPremiumStatus(); + + // setState(() {}); + // } + + Future fetchSoundAndMusicState() async { + bool soundEnabled = await MainDB.instance.getSoundEnabled(); + bool musicEnabled = await MainDB.instance.getMusicEnabled(); + + setState(() { + isSound = soundEnabled; + isMusic = musicEnabled; + }); + } + + // void updateBackground(Background newBackground) { + // setState(() { + // backgroundImage = newBackground; + // }); + // widget.onUpdateBackground(newBackground); + // } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + // Container( + // decoration: BoxDecoration( + // image: DecorationImage( + // image: BackgroundImageProvider.getBackgroundImage( + // backgroundImage ?? Background.sky), + // fit: BoxFit.cover, + // ), + // ), + // ), + Container( + padding: + const EdgeInsets.only(top: 38, left: 16, right: 16, bottom: 16), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + IconButton( + onPressed: () { + Navigator.pop(context, true); + }, + icon: ArrowLeftIcon()), + Text( + 'Options', + style: AppStyles.subtitleTextStyle + .copyWith(color: AppStyles.accentColorTopBar), + ), + ], + ), + const SizedBox( + height: 28, + ), + Expanded( + child: ListView( + children: [ + // Sound settings + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: AppStyles.mainBackground, + ), + padding: EdgeInsets.only( + top: 12, bottom: 12, right: 12, left: 18), + child: Column( + children: [ + const SizedBox( + height: 10, + ), + const Row( + children: [ + Text( + 'Sound settings', + style: AppStyles.subtitleTextStyle, + ), + ], + ), + const SizedBox( + height: 10, + ), + Row( + children: [ + VolumeIcon(), + const SizedBox( + width: 6, + ), + const Text( + 'Sound Effects', + style: TextStyle(fontSize: 16), + ), + const Expanded(child: SizedBox()), + ToggleSwitch( + value: isSound, + onChanged: (value) async { + setState(() { + isSound = value; + }); + + await MainDB.instance + .saveSoundEnabled(value); + }) + ], + ), + const SizedBox( + height: 10, + ), + Row( + children: [ + MusicIcon(), + const SizedBox( + width: 6, + ), + const Text( + 'Music', + style: TextStyle(fontSize: 16), + ), + const Expanded(child: SizedBox()), + ToggleSwitch( + value: isMusic, + onChanged: (value) async { + setState(() { + isMusic = value; + }); + await MainDB.instance + .saveMusicEnabled(value); + }) + ], + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + + // High score + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: AppStyles.mainBackground, + ), + child: ExpansionTile( + backgroundColor: AppStyles.mainBackground, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + title: Row( + children: [ + StarIcon(), + SizedBox( + width: 6, + ), + Text('High score'), + ], + ), + children: [ + ListTile( + title: FutureBuilder( + future: MainDB.instance.getHighScoreValue(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Text( + '${snapshot.data}', + style: TextStyle(fontSize: 16), + ); + } else { + return Text('HighScore: 0'); + } + }, + ), + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + ], + ), + ), + // About + Container( + margin: EdgeInsets.only(top: 16), + child: Column( + children: [ + Text('MatchMagic. Copyright 2024 Cypher Stack, LLC'), + FutureBuilder( + future: getVersionNumber(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Text('Version: ${snapshot.data}'); + } else { + return Text('Version: loading...'); + } + }, + ) + ], + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/pause_screen.dart b/lib/screens/pause_screen.dart new file mode 100644 index 0000000..945e40c --- /dev/null +++ b/lib/screens/pause_screen.dart @@ -0,0 +1,206 @@ +import 'package:flame_audio/flame_audio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:match_magic/db/main_db.dart'; +import 'package:match_magic/game/board.dart'; +import 'package:match_magic/screens/main_menu_screen.dart'; +import 'package:match_magic/styles/styles.dart'; + +import 'package:match_magic/widgets/icon_widgets/volume_icon.dart'; + +import 'package:match_magic/widgets/overlays/game_overlay/hint_button.dart'; +import 'package:match_magic/widgets/overlays/game_overlay/pause_button.dart'; +import 'package:match_magic/widgets/overlays/game_overlay/restart_button.dart'; +import 'package:match_magic/widgets/toggle_switch.dart'; + +class PauseMenu extends StatefulWidget { + static const String id = 'PauseMenu'; + final Board gameRef; + + const PauseMenu({super.key, required this.gameRef}); + + @override + State createState() => _PauseMenuState(); +} + +class _PauseMenuState extends State { + bool isMusic = true; + bool isSound = Board.isSoundPlaying; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 70, horizontal: 16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFFEBEBEB).withOpacity(0.6), + Color(0xFFBFBEC0).withOpacity(0.6), + ], + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox( + height: 122, + ), + // Pause menu title. + Padding( + padding: const EdgeInsets.symmetric(vertical: 50.0), + child: Text('Game Paused', + style: AppStyles.titleTextStyle.copyWith( + fontSize: 32, color: AppStyles.accentColorTopBar)), + ), + // Sound settings + Card( + child: Container( + height: 56, + padding: const EdgeInsets.all(12), + child: Row( + children: [ + const VolumeIcon(), + const SizedBox( + width: 6, + ), + const Text( + 'Sound settings', + style: TextStyle(), + ), + const Expanded(child: SizedBox()), + ToggleSwitch( + value: isSound, + onChanged: (value) async { + setState(() { + isSound = value; + Board.isSoundPlaying = !Board.isSoundPlaying; + }); + await MainDB.instance.saveSoundEnabled(value); + }) + ], + ), + ), + ), + const SizedBox( + height: 6, + ), + // Music + // Card( + // child: Container( + // height: 56, + // padding: const EdgeInsets.all(12), + // child: Row( + // children: [ + // const MusicIcon(), + // const SizedBox( + // width: 6, + // ), + // const Text( + // 'Music', + // style: TextStyle(), + // ), + // const Expanded(child: SizedBox()), + // ToggleSwitch( + // value: isMusic, + // onChanged: (value) async { + // setState(() { + // isMusic = value; + // if (Board.isMusicPlaying) { + // FlameAudio.bgm.stop(); + // } else { + // FlameAudio.bgm.play('.ogg'); + // } + // Board.isMusicPlaying = !Board.isMusicPlaying; + // }); + // await MainDB.instance.saveMusicEnabled(value); + // }) + // ], + // ), + // ), + // ), + const Expanded(child: SizedBox()), + Row( + children: [ + // Exit button + Expanded( + child: ElevatedButton( + onPressed: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AlertDialog( + backgroundColor: AppStyles.mainBackground, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + title: const Text( + 'Quit', + style: AppStyles.titleTextStyle, + ), + content: const Text( + 'Are you sure you want to leave the game? You progress will be lost.', + style: AppStyles.subtitleTextStyle, + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + 'CANCEL', + style: AppStyles.subtitleTextStyle.copyWith( + fontSize: 14, color: AppStyles.accentColor), + ), + ), + TextButton( + onPressed: () { + FlameAudio.bgm.stop(); + widget.gameRef.overlays.remove(PauseMenu.id); + widget.gameRef.removed; + widget.gameRef.resumeEngine(); + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => MainMenuScreen(), + ), + ); + }, + child: Text( + 'EXIT', + style: AppStyles.subtitleTextStyle.copyWith( + fontSize: 14, color: AppStyles.accentColor), + ), + ), + ], + ), + ); + }, + child: const Text('Exit'), + ), + ), + const SizedBox( + width: 16, + ), + // Continue button + Expanded( + child: ElevatedButton( + onPressed: () async { + widget.gameRef.overlays.remove(PauseMenu.id); + + widget.gameRef.overlays.add(PauseButton.id); + widget.gameRef.overlays.add(RestartButton.id); + widget.gameRef.overlays.add(HintButton.id); + widget.gameRef.resumeEngine(); + print('${Board.isSoundPlaying}'); + }, + child: const Text('Continue'), + )), + ], + ), + ], + ), + ); + } +} diff --git a/lib/styles/styles.dart b/lib/styles/styles.dart new file mode 100644 index 0000000..855657d --- /dev/null +++ b/lib/styles/styles.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class AppStyles { +// Fonts + static const TextStyle titleTextStyle = TextStyle( + fontFamily: 'MPLUSRounded1c-ExtraBold', + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppStyles.textColor, + ); + + static const TextStyle subtitleTextStyle = TextStyle( + fontFamily: 'MPLUSRounded1c-Medium', + fontSize: 16, + color: AppStyles.textColor, + ); + + // AppColor + static const Color primaryColor = Color(0xFF4124DF); + static const Color accentColor = Color(0xFFFF4FA3); + static const Color accentColorTopBar = Color(0xFF6014DC); + static const Color textColor = Colors.black; + static const Color linkColor = Color(0xFF3753E8); + + static const Color mainBackground = Color(0xFFFFFFFF); + + // Button + static const buttonGradient = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xBFFF4FA3), Color(0xBF3E24E0)], + ); + static const buttonPrimaryDefaultText = Color(0xFFFBF8FF); + + // ToggleSwitch + static const toggleSwitchBg = Color(0xFFFFC9ED); + static const toggleSwitchTextInactive = Color(0xFFFFFFFF); + static const toggleSwitchActiveBg = Color(0xFFFF4FA3); + static const toggleSwitchTextActive = Color(0xFFFFDCF3); + + // TextField + static const textField = Color(0xFFFEF1FF); +} diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart new file mode 100644 index 0000000..e053497 --- /dev/null +++ b/lib/utilities/assets.dart @@ -0,0 +1,28 @@ +abstract class Assets { + static const svg = _SVG(); + static const png = _PNG(); +} + +class _SVG { + const _SVG(); + String get arrowLeft => "assets/svg/arrow-left.svg"; + String get check => "assets/svg/check.svg"; + String get checkCircle => "assets/svg/check-circle.svg"; + String get chevronDown => "assets/svg/chevron-down.svg"; + String get chevronRight => "assets/svg/chevron-right.svg"; + String get clipboard => "assets/svg/clipboard.svg"; + String get music => "assets/svg/music.svg"; + String get pauseCircle => "assets/svg/pause-circle.svg"; + String get star => "assets/svg/star.svg"; + String get ticket => "assets/svg/ticket.svg"; + String get volume => "assets/svg/volume.svg"; +} + +class _PNG { + const _PNG(); + + // String get logo => "assets/images/teeny_pop_logo.png"; + + // Background + // String get backgroundSky => "assets/images/background_sky.png"; +} diff --git a/lib/widgets/gradient_button.dart b/lib/widgets/gradient_button.dart new file mode 100644 index 0000000..368f206 --- /dev/null +++ b/lib/widgets/gradient_button.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:match_magic/styles/styles.dart'; + +class GradientButton extends StatelessWidget { + final VoidCallback onPressed; + final String buttonText; + + const GradientButton({ + Key? key, + required this.onPressed, + required this.buttonText, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: 40, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(100)), + gradient: AppStyles.buttonGradient), + child: ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + ), + ), + child: Text( + buttonText.toUpperCase(), + style: const TextStyle( + color: AppStyles.buttonPrimaryDefaultText, + fontWeight: FontWeight.w700), + ), + ), + ); + } +} diff --git a/lib/widgets/icon_widgets/arrow_left_icon.dart b/lib/widgets/icon_widgets/arrow_left_icon.dart new file mode 100644 index 0000000..f3bd98c --- /dev/null +++ b/lib/widgets/icon_widgets/arrow_left_icon.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:match_magic/styles/styles.dart'; +import 'package:match_magic/utilities/assets.dart'; + +class ArrowLeftIcon extends StatelessWidget { + const ArrowLeftIcon({ + Key? key, + this.width = 24, + this.height = 24, + this.color, + }) : super(key: key); + + final double width; + final double height; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.arrowLeft, + width: width, + height: height, + color: AppStyles.accentColorTopBar, + ); + } +} diff --git a/lib/widgets/icon_widgets/check_circle_icon.dart b/lib/widgets/icon_widgets/check_circle_icon.dart new file mode 100644 index 0000000..c672cc6 --- /dev/null +++ b/lib/widgets/icon_widgets/check_circle_icon.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:match_magic/utilities/assets.dart'; + +class CheckCircleIcon extends StatelessWidget { + const CheckCircleIcon({ + Key? key, + this.width = 24, + this.height = 24, + this.color, + }) : super(key: key); + + final double width; + final double height; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.checkCircle, + width: width, + height: height, + ); + } +} diff --git a/lib/widgets/icon_widgets/check_icon.dart b/lib/widgets/icon_widgets/check_icon.dart new file mode 100644 index 0000000..c484080 --- /dev/null +++ b/lib/widgets/icon_widgets/check_icon.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:match_magic/utilities/assets.dart'; + +class CheckIcon extends StatelessWidget { + const CheckIcon({ + Key? key, + this.width = 18, + this.height = 18, + this.color, + }) : super(key: key); + + final double width; + final double height; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.check, + width: width, + height: height, + ); + } +} diff --git a/lib/widgets/icon_widgets/chevron_down_icon.dart b/lib/widgets/icon_widgets/chevron_down_icon.dart new file mode 100644 index 0000000..51919e2 --- /dev/null +++ b/lib/widgets/icon_widgets/chevron_down_icon.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:match_magic/utilities/assets.dart'; + +class ChevronDownIcon extends StatelessWidget { + const ChevronDownIcon({ + Key? key, + this.width = 24, + this.height = 24, + this.color, + }) : super(key: key); + + final double width; + final double height; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.chevronDown, + width: width, + height: height, + ); + } +} diff --git a/lib/widgets/icon_widgets/chevron_right_icon.dart b/lib/widgets/icon_widgets/chevron_right_icon.dart new file mode 100644 index 0000000..3f948f7 --- /dev/null +++ b/lib/widgets/icon_widgets/chevron_right_icon.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:match_magic/utilities/assets.dart'; + +class ChevronRightIcon extends StatelessWidget { + const ChevronRightIcon({ + Key? key, + this.width = 24, + this.height = 24, + this.color, + }) : super(key: key); + + final double width; + final double height; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.chevronRight, + width: width, + height: height, + ); + } +} diff --git a/lib/widgets/icon_widgets/clipboard_icon.dart b/lib/widgets/icon_widgets/clipboard_icon.dart new file mode 100644 index 0000000..edec2a5 --- /dev/null +++ b/lib/widgets/icon_widgets/clipboard_icon.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:match_magic/styles/styles.dart'; +import 'package:match_magic/utilities/assets.dart'; + +class ClipboardIcon extends StatelessWidget { + const ClipboardIcon({ + Key? key, + this.width = 18, + this.height = 18, + this.color, + }) : super(key: key); + + final double width; + final double height; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.clipboard, + width: width, + height: height, + color: AppStyles.accentColor, + ); + } +} diff --git a/lib/widgets/icon_widgets/music_icon.dart b/lib/widgets/icon_widgets/music_icon.dart new file mode 100644 index 0000000..46ef162 --- /dev/null +++ b/lib/widgets/icon_widgets/music_icon.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:match_magic/utilities/assets.dart'; + +class MusicIcon extends StatelessWidget { + const MusicIcon({ + Key? key, + this.width = 18, + this.height = 18, + this.color, + }) : super(key: key); + + final double width; + final double height; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.music, + width: width, + height: height, + ); + } +} diff --git a/lib/widgets/icon_widgets/pause_circle_icon.dart b/lib/widgets/icon_widgets/pause_circle_icon.dart new file mode 100644 index 0000000..e14d24a --- /dev/null +++ b/lib/widgets/icon_widgets/pause_circle_icon.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:match_magic/styles/styles.dart'; +import 'package:match_magic/utilities/assets.dart'; + +class PauseCircleIcon extends StatelessWidget { + const PauseCircleIcon({ + Key? key, + this.width = 34, + this.height = 34, + this.color, + }) : super(key: key); + + final double width; + final double height; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.pauseCircle, + width: width, + height: height, + color: AppStyles.accentColorTopBar, + ); + } +} diff --git a/lib/widgets/icon_widgets/star_icon.dart b/lib/widgets/icon_widgets/star_icon.dart new file mode 100644 index 0000000..a55c064 --- /dev/null +++ b/lib/widgets/icon_widgets/star_icon.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:match_magic/utilities/assets.dart'; + +class StarIcon extends StatelessWidget { + const StarIcon({ + Key? key, + this.width = 18, + this.height = 18, + this.color, + }) : super(key: key); + + final double width; + final double height; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.star, + width: width, + height: height, + ); + } +} diff --git a/lib/widgets/icon_widgets/ticket_icon.dart b/lib/widgets/icon_widgets/ticket_icon.dart new file mode 100644 index 0000000..ecd133f --- /dev/null +++ b/lib/widgets/icon_widgets/ticket_icon.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:match_magic/utilities/assets.dart'; + +class TicketIcon extends StatelessWidget { + const TicketIcon({ + Key? key, + this.width = 18, + this.height = 18, + this.color, + }) : super(key: key); + + final double width; + final double height; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.ticket, + width: width, + height: height, + ); + } +} diff --git a/lib/widgets/icon_widgets/volume_icon.dart b/lib/widgets/icon_widgets/volume_icon.dart new file mode 100644 index 0000000..970c70b --- /dev/null +++ b/lib/widgets/icon_widgets/volume_icon.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:match_magic/utilities/assets.dart'; + +class VolumeIcon extends StatelessWidget { + const VolumeIcon({ + Key? key, + this.width = 18, + this.height = 18, + this.color, + }) : super(key: key); + + final double width; + final double height; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.volume, + width: width, + height: height, + ); + } +} diff --git a/lib/widgets/overlays/game_overlay/hint_button.dart b/lib/widgets/overlays/game_overlay/hint_button.dart new file mode 100644 index 0000000..20da7e4 --- /dev/null +++ b/lib/widgets/overlays/game_overlay/hint_button.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:match_magic/game/board.dart'; + +class HintButton extends StatelessWidget { + final Board gameRef; + static const String id = 'HintButton'; + const HintButton({Key? key, required this.gameRef}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Positioned( + bottom: 46, + left: 220, + child: ElevatedButton( + onPressed: () { + gameRef.showHint(); + }, + child: const Text( + 'Hint', + style: TextStyle(color: Colors.black), + ), + ), + ); + } +} diff --git a/lib/widgets/overlays/game_overlay/pause_button.dart b/lib/widgets/overlays/game_overlay/pause_button.dart new file mode 100644 index 0000000..71f5428 --- /dev/null +++ b/lib/widgets/overlays/game_overlay/pause_button.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:match_magic/game/board.dart'; +import 'package:match_magic/screens/pause_screen.dart'; +import 'package:match_magic/widgets/icon_widgets/pause_circle_icon.dart'; +import 'package:match_magic/widgets/overlays/game_overlay/hint_button.dart'; +import 'package:match_magic/widgets/overlays/game_overlay/restart_button.dart'; + +class PauseButton extends StatelessWidget { + final Board gameRef; + static const String id = 'PauseButton'; + const PauseButton({Key? key, required this.gameRef}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Positioned( + top: 46, + left: 10, + child: IconButton( + onPressed: () { + // AdManager.loadInterstitialAd(); + gameRef.pauseEngine(); + gameRef.overlays.add(PauseMenu.id); + gameRef.overlays.remove(PauseButton.id); + gameRef.overlays.remove(RestartButton.id); + gameRef.overlays.remove(HintButton.id); + }, + icon: PauseCircleIcon(), + ), + ); + } +} diff --git a/lib/widgets/overlays/game_overlay/restart_button.dart b/lib/widgets/overlays/game_overlay/restart_button.dart new file mode 100644 index 0000000..76d9ca2 --- /dev/null +++ b/lib/widgets/overlays/game_overlay/restart_button.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:match_magic/game/board.dart'; + +class RestartButton extends StatelessWidget { + final Board gameRef; + static const String id = 'RestartButton'; + const RestartButton({Key? key, required this.gameRef}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Positioned( + bottom: 46, + left: 80, + child: ElevatedButton( + onPressed: () { + gameRef.newGame(); + }, + child: const Text( + 'Restart', + style: TextStyle(color: Colors.black), + ), + ), + ); + } +} diff --git a/lib/widgets/radio_button.dart b/lib/widgets/radio_button.dart new file mode 100644 index 0000000..9b79ee6 --- /dev/null +++ b/lib/widgets/radio_button.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:match_magic/styles/styles.dart'; + +class RadioButton extends StatelessWidget { + final T value; + final T? groupValue; + final ValueChanged? onChanged; + + const RadioButton({ + required this.value, + required this.groupValue, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Radio( + value: value, + groupValue: groupValue, + onChanged: onChanged, + activeColor: AppStyles.accentColor, + visualDensity: const VisualDensity( + horizontal: VisualDensity.minimumDensity, + vertical: VisualDensity.minimumDensity, + ), + ), + ], + ); + } +} diff --git a/lib/widgets/toggle_switch.dart b/lib/widgets/toggle_switch.dart new file mode 100644 index 0000000..17547b2 --- /dev/null +++ b/lib/widgets/toggle_switch.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:match_magic/styles/styles.dart'; + +class ToggleSwitch extends StatelessWidget { + final ValueChanged? onChanged; + final bool value; + + const ToggleSwitch({ + Key? key, + required this.onChanged, + required this.value, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Switch( + value: value, + onChanged: onChanged, + activeColor: AppStyles.toggleSwitchTextActive, + activeTrackColor: AppStyles.toggleSwitchActiveBg, + inactiveThumbColor: AppStyles.toggleSwitchTextInactive, + inactiveTrackColor: AppStyles.toggleSwitchBg, + trackOutlineColor: MaterialStateProperty.all(Colors.white.withOpacity(0)), + ); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 1830e5c..7faaafd 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); + g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin"); + isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index e9abb91..98e76e5 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_linux + isar_flutter_libs ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index a9f2f23..c272181 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,9 +6,15 @@ import FlutterMacOS import Foundation import audioplayers_darwin +import isar_flutter_libs +import package_info_plus import path_provider_foundation +import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) + IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 284de48..f0f3a3d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + url: "https://pub.dev" + source: hosted + version: "61.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + url: "https://pub.dev" + source: hosted + version: "5.13.0" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" async: dependency: transitive description: @@ -73,6 +97,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" + url: "https://pub.dev" + source: hosted + version: "2.4.11" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe + url: "https://pub.dev" + source: hosted + version: "7.3.1" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" characters: dependency: transitive description: @@ -81,6 +169,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" clock: dependency: transitive description: @@ -89,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" collection: dependency: transitive description: @@ -97,6 +201,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" crypto: dependency: transitive description: @@ -113,6 +225,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + dartx: + dependency: transitive + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" fake_async: dependency: transitive description: @@ -174,6 +302,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + url: "https://pub.dev" + source: hosted + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter @@ -184,6 +320,30 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" http: dependency: transitive description: @@ -192,6 +352,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" http_parser: dependency: transitive description: @@ -200,6 +368,54 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + isar: + dependency: "direct main" + description: + name: isar + sha256: "99165dadb2cf2329d3140198363a7e7bff9bbd441871898a87e26914d25cf1ea" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + isar_flutter_libs: + dependency: "direct main" + description: + name: isar_flutter_libs + sha256: bc6768cc4b9c61aabff77152e7f33b4b17d2fc93134f7af1c3dd51500fe8d5e8 + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + isar_generator: + dependency: "direct dev" + description: + name: isar_generator + sha256: "76c121e1295a30423604f2f819bc255bc79f852f3bc8743a24017df6068ad133" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -232,6 +448,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" matcher: dependency: transitive description: @@ -256,6 +480,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.0" + mime: + dependency: transitive + description: + name: mime + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + url: "https://pub.dev" + source: hosted + version: "1.0.6" nested: dependency: transitive description: @@ -272,6 +504,30 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.1" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 + url: "https://pub.dev" + source: hosted + version: "8.0.2" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + url: "https://pub.dev" + source: hosted + version: "3.0.1" path: dependency: transitive description: @@ -280,8 +536,16 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" - path_provider: + path_parsing: dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" + path_provider: + dependency: "direct main" description: name: path_provider sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 @@ -328,6 +592,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" platform: dependency: transitive description: @@ -344,6 +616,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" provider: dependency: "direct main" description: @@ -352,11 +632,107 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + url: "https://pub.dev" + source: hosted + version: "2.5.2" + 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: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + url: "https://pub.dev" + source: hosted + version: "2.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" source_span: dependency: transitive description: @@ -389,6 +765,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -421,6 +805,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + time: + dependency: transitive + description: + name: time + sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221 + url: "https://pub.dev" + source: hosted + version: "2.1.4" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" typed_data: dependency: transitive description: @@ -437,6 +837,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.0" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -453,6 +877,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.1" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" web: dependency: transitive description: @@ -461,6 +893,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + url: "https://pub.dev" + source: hosted + version: "5.5.4" xdg_directories: dependency: transitive description: @@ -469,6 +925,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" + xxh3: + dependency: transitive + description: + name: xxh3 + sha256: a92b30944a9aeb4e3d4f3c3d4ddb3c7816ca73475cd603682c4f8149690f56d7 + url: "https://pub.dev" + source: hosted + version: "1.0.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" sdks: dart: ">=3.4.3 <4.0.0" flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index c992ecf..050314a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,7 +38,13 @@ dependencies: cupertino_icons: ^1.0.6 flame: ^1.18.0 audioplayers: ^6.1.0 + flutter_svg: ^2.0.9 flame_audio: ^2.10.3 + isar: ^3.1.0+1 + isar_flutter_libs: ^3.1.0+1 + package_info_plus: ^8.0.2 + path_provider: ^2.1.4 + shared_preferences: ^2.3.2 dev_dependencies: flutter_test: @@ -50,6 +56,8 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^3.0.0 + isar_generator: ^3.1.0+1 + build_runner: ^2.4.11 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -64,17 +72,9 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - assets/images/crystal1.png - - assets/images/crystal2.png - - assets/images/crystal3.png - - assets/images/crystal4.png - - assets/images/crystal5.png - - assets/images/crystal6.png - - assets/images/crystal7.png - - assets/images/magic_cube.png - - assets/audio/explosion.ogg - - assets/audio/four_elements.ogg - - assets/audio/select.ogg + - assets/images/ + - assets/svg/ + - assets/audio/ # An image asset can refer to one or more resolution-specific "variants", see diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 09e8e2c..ba7f138 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { AudioplayersWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); + IsarFlutterLibsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 375535c..f310bcc 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows + isar_flutter_libs ) list(APPEND FLUTTER_FFI_PLUGIN_LIST