From 910b2ce2daec191a9c2adca33f809094e179f524 Mon Sep 17 00:00:00 2001 From: Allison Ryan Date: Fri, 6 May 2022 17:30:32 -0500 Subject: [PATCH] feat: connect game ball to theme selection --- .../behaviors/ball_spawning_behavior.dart | 9 +- lib/game/behaviors/ball_theming_behavior.dart | 29 +++++++ lib/game/behaviors/behaviors.dart | 1 + .../flutter_forest_bonus_behavior.dart | 5 +- .../components/game_bloc_status_listener.dart | 5 +- lib/game/pinball_game.dart | 26 ++++-- lib/game/view/pinball_game_page.dart | 7 +- .../ball_spawning_behavior_test.dart | 5 +- .../behaviors/ball_theming_behavior_test.dart | 86 +++++++++++++++++++ .../flutter_forest_bonus_behavior_test.dart | 5 +- .../game_bloc_status_listener_test.dart | 6 +- test/game/pinball_game_test.dart | 23 ++++- test/game/view/pinball_game_page_test.dart | 3 +- 13 files changed, 177 insertions(+), 33 deletions(-) create mode 100644 lib/game/behaviors/ball_theming_behavior.dart rename test/game/{components/android_acres => }/behaviors/ball_spawning_behavior_test.dart (96%) create mode 100644 test/game/behaviors/ball_theming_behavior_test.dart diff --git a/lib/game/behaviors/ball_spawning_behavior.dart b/lib/game/behaviors/ball_spawning_behavior.dart index c074fe52..bb7ea0a6 100644 --- a/lib/game/behaviors/ball_spawning_behavior.dart +++ b/lib/game/behaviors/ball_spawning_behavior.dart @@ -1,9 +1,9 @@ import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; /// Spawns a new [Ball] into the game when all balls are lost and still /// [GameStatus.playing]. @@ -23,9 +23,10 @@ class BallSpawningBehavior extends Component void onNewState(GameState state) { final plunger = gameRef.descendants().whereType().single; final canvas = gameRef.descendants().whereType().single; - final characterTheme = readProvider(); - final ball = ControlledBall.launch(characterTheme: characterTheme) - ..initialPosition = Vector2( + final characterThemeBloc = readProvider(); + final ball = ControlledBall.launch( + characterTheme: characterThemeBloc.state.characterTheme, + )..initialPosition = Vector2( plunger.body.position.x, plunger.body.position.y - Ball.size.y, ); diff --git a/lib/game/behaviors/ball_theming_behavior.dart b/lib/game/behaviors/ball_theming_behavior.dart new file mode 100644 index 00000000..53789262 --- /dev/null +++ b/lib/game/behaviors/ball_theming_behavior.dart @@ -0,0 +1,29 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// Updates the launch [Ball] to reflect character selections. +class BallThemingBehavior extends Component + with + FlameBlocListenable, + HasGameRef { + @override + void onNewState(CharacterThemeState state) { + final ballsInGame = gameRef.descendants().whereType(); + if (ballsInGame.isNotEmpty) { + gameRef.removeAll(ballsInGame); + } + final plunger = gameRef.descendants().whereType().single; + final canvas = gameRef.descendants().whereType().single; + final ball = ControlledBall.launch(characterTheme: state.characterTheme) + ..initialPosition = Vector2( + plunger.body.position.x, + plunger.body.position.y - Ball.size.y + 1.1, + ); + + canvas.add(ball); + } +} diff --git a/lib/game/behaviors/behaviors.dart b/lib/game/behaviors/behaviors.dart index 44cce1df..4e4aa52a 100644 --- a/lib/game/behaviors/behaviors.dart +++ b/lib/game/behaviors/behaviors.dart @@ -1,4 +1,5 @@ export 'ball_spawning_behavior.dart'; +export 'ball_theming_behavior.dart'; export 'bumper_noise_behavior.dart'; export 'camera_focusing_behavior.dart'; export 'scoring_behavior.dart'; diff --git a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart index a4931f90..2eb41694 100644 --- a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart +++ b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart @@ -1,9 +1,9 @@ import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; /// Bonus obtained at the [FlutterForest]. /// @@ -43,7 +43,8 @@ class FlutterForestBonusBehavior extends Component bloc.add(const BonusActivated(GameBonus.dashNest)); canvas.add( ControlledBall.bonus( - characterTheme: readProvider(), + characterTheme: + readProvider().state.characterTheme, )..initialPosition = Vector2(29.2, -24.5), ); animatronic.playing = true; diff --git a/lib/game/components/game_bloc_status_listener.dart b/lib/game/components/game_bloc_status_listener.dart index 6e11f3d6..986078e7 100644 --- a/lib/game/components/game_bloc_status_listener.dart +++ b/lib/game/components/game_bloc_status_listener.dart @@ -1,9 +1,9 @@ import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; /// Listens to the [GameBloc] and updates the game accordingly. class GameBlocStatusListener extends Component @@ -26,7 +26,8 @@ class GameBlocStatusListener extends Component readProvider().play(PinballAudio.gameOverVoiceOver); gameRef.descendants().whereType().first.requestInitials( score: state.displayScore, - character: readProvider(), + character: + readProvider().state.characterTheme, ); break; } diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index b4886e4c..a103ae84 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -11,22 +11,22 @@ import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; class PinballGame extends PinballForge2DGame with HasKeyboardHandlerComponents, MultiTouchTapDetector { PinballGame({ - required CharacterTheme characterTheme, + required CharacterThemeCubit characterThemeBloc, required this.leaderboardRepository, required GameBloc gameBloc, required AppLocalizations l10n, required PinballPlayer player, }) : _gameBloc = gameBloc, _player = player, - _characterTheme = characterTheme, + _characterThemeBloc = characterThemeBloc, _l10n = l10n, super( gravity: Vector2(0, 30), @@ -40,7 +40,7 @@ class PinballGame extends PinballForge2DGame @override Color backgroundColor() => Colors.transparent; - final CharacterTheme _characterTheme; + final CharacterThemeCubit _characterThemeBloc; final PinballPlayer _player; @@ -53,19 +53,27 @@ class PinballGame extends PinballForge2DGame @override Future onLoad() async { await add( - FlameBlocProvider.value( - value: _gameBloc, + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value( + value: _gameBloc, + ), + FlameBlocProvider.value( + value: _characterThemeBloc, + ), + ], children: [ MultiFlameProvider( providers: [ FlameProvider.value(_player), - FlameProvider.value(_characterTheme), + FlameProvider.value(_characterThemeBloc), FlameProvider.value(leaderboardRepository), FlameProvider.value(_l10n), ], children: [ GameBlocStatusListener(), BallSpawningBehavior(), + BallThemingBehavior(), CameraFocusingBehavior(), CanvasComponent( onSpritePainted: (paint) { @@ -157,13 +165,13 @@ class PinballGame extends PinballForge2DGame class DebugPinballGame extends PinballGame with FPSCounter, PanDetector { DebugPinballGame({ - required CharacterTheme characterTheme, + required CharacterThemeCubit characterThemeBloc, required LeaderboardRepository leaderboardRepository, required AppLocalizations l10n, required PinballPlayer player, required GameBloc gameBloc, }) : super( - characterTheme: characterTheme, + characterThemeBloc: characterThemeBloc, player: player, leaderboardRepository: leaderboardRepository, l10n: l10n, diff --git a/lib/game/view/pinball_game_page.dart b/lib/game/view/pinball_game_page.dart index c0d5d1d8..2fd37f05 100644 --- a/lib/game/view/pinball_game_page.dart +++ b/lib/game/view/pinball_game_page.dart @@ -35,8 +35,7 @@ class PinballGamePage extends StatelessWidget { @override Widget build(BuildContext context) { - final characterTheme = - context.read().state.characterTheme; + final characterThemeBloc = context.read(); final player = context.read(); final leaderboardRepository = context.read(); @@ -47,14 +46,14 @@ class PinballGamePage extends StatelessWidget { final gameBloc = context.read(); final game = isDebugMode ? DebugPinballGame( - characterTheme: characterTheme, + characterThemeBloc: characterThemeBloc, player: player, leaderboardRepository: leaderboardRepository, l10n: context.l10n, gameBloc: gameBloc, ) : PinballGame( - characterTheme: characterTheme, + characterThemeBloc: characterThemeBloc, player: player, leaderboardRepository: leaderboardRepository, l10n: context.l10n, diff --git a/test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart b/test/game/behaviors/ball_spawning_behavior_test.dart similarity index 96% rename from test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart rename to test/game/behaviors/ball_spawning_behavior_test.dart index f41487cd..263b508c 100644 --- a/test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart +++ b/test/game/behaviors/ball_spawning_behavior_test.dart @@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/behaviors/ball_spawning_behavior.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; @@ -25,8 +26,8 @@ class _TestGame extends Forge2DGame { FlameBlocProvider.value( value: gameBloc ?? GameBloc(), children: [ - FlameProvider.value( - const theme.DashTheme(), + FlameProvider.value( + CharacterThemeCubit(), children: children, ), ], diff --git a/test/game/behaviors/ball_theming_behavior_test.dart b/test/game/behaviors/ball_theming_behavior_test.dart new file mode 100644 index 00000000..e5aae373 --- /dev/null +++ b/test/game/behaviors/ball_theming_behavior_test.dart @@ -0,0 +1,86 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/select_character/select_character.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:pinball_theme/pinball_theme.dart' as theme; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + theme.Assets.images.dash.ball.keyName, + theme.Assets.images.dino.ball.keyName, + ]); + } + + Future pump( + List children, { + CharacterThemeCubit? characterThemeBloc, + }) async { + await ensureAdd( + FlameBlocProvider.value( + value: characterThemeBloc ?? CharacterThemeCubit(), + children: children, + ), + ); + } +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group( + 'BallThemingBehavior', + () { + final flameTester = FlameTester(_TestGame.new); + + test('can be instantiated', () { + expect( + BallThemingBehavior(), + isA(), + ); + }); + + flameTester.test( + 'loads', + (game) async { + final behavior = BallThemingBehavior(); + await game.pump([behavior]); + expect(game.descendants(), contains(behavior)); + }, + ); + + flameTester.test( + 'onNewState replaces the current ball with a new ball', + (game) async { + final behavior = BallThemingBehavior(); + await game.pump([ + behavior, + ZCanvasComponent(), + Plunger.test(compressionDistance: 10), + Ball(), + ]); + expect(game.descendants().whereType(), isNotEmpty); + final dashBall = game.descendants().whereType().single; + + const dinoTheme = CharacterThemeState(theme.DinoTheme()); + behavior.onNewState(dinoTheme); + await game.ready(); + + expect(game.descendants().whereType(), isNotEmpty); + final dinoBall = game.descendants().whereType().single; + + expect(dinoBall != dashBall, isTrue); + }, + ); + }, + ); +} diff --git a/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart b/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart index 3dcd870b..461d7881 100644 --- a/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart +++ b/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart @@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/components/flutter_forest/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; @@ -29,8 +30,8 @@ class _TestGame extends Forge2DGame { FlameBlocProvider.value( value: gameBloc, children: [ - FlameProvider.value( - const theme.DashTheme(), + FlameProvider.value( + CharacterThemeCubit(), children: [ ZCanvasComponent( children: [child], diff --git a/test/game/components/game_bloc_status_listener_test.dart b/test/game/components/game_bloc_status_listener_test.dart index 7118aa8d..e71baafa 100644 --- a/test/game/components/game_bloc_status_listener_test.dart +++ b/test/game/components/game_bloc_status_listener_test.dart @@ -8,10 +8,10 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart' as theme; class _TestGame extends Forge2DGame { @override @@ -33,8 +33,8 @@ class _TestGame extends Forge2DGame { FlameProvider.value( pinballPlayer ?? _MockPinballPlayer(), ), - FlameProvider.value( - const theme.DashTheme(), + FlameProvider.value( + CharacterThemeCubit(), ), ], children: children, diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index b983b0b8..43e3560a 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -13,14 +13,14 @@ import 'package:leaderboard_repository/src/leaderboard_repository.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/src/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_theme/pinball_theme.dart' as theme; class _TestPinballGame extends PinballGame { _TestPinballGame() : super( - characterTheme: const theme.DashTheme(), + characterThemeBloc: _MockCharacterThemeCubit(), leaderboardRepository: _MockLeaderboardRepository(), gameBloc: GameBloc(), l10n: _MockAppLocalizations(), @@ -39,7 +39,7 @@ class _TestPinballGame extends PinballGame { class _TestDebugPinballGame extends DebugPinballGame { _TestDebugPinballGame() : super( - characterTheme: const theme.DashTheme(), + characterThemeBloc: _MockCharacterThemeCubit(), leaderboardRepository: _MockLeaderboardRepository(), gameBloc: GameBloc(), l10n: _MockAppLocalizations(), @@ -57,6 +57,12 @@ class _TestDebugPinballGame extends DebugPinballGame { class _MockGameBloc extends Mock implements GameBloc {} +class _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit { + @override + Stream get stream => + const Stream.empty(); +} + class _MockAppLocalizations extends Mock implements AppLocalizations {} class _MockEventPosition extends Mock implements EventPosition {} @@ -109,6 +115,17 @@ void main() { }, ); + flameTester.test( + 'has only one BallThemingBehavior', + (game) async { + await game.ready(); + expect( + game.descendants().whereType().length, + equals(1), + ); + }, + ); + flameTester.test( 'has only one Drain', (game) async { diff --git a/test/game/view/pinball_game_page_test.dart b/test/game/view/pinball_game_page_test.dart index 5aef07dd..df66ba6d 100644 --- a/test/game/view/pinball_game_page_test.dart +++ b/test/game/view/pinball_game_page_test.dart @@ -12,14 +12,13 @@ import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/start_game.dart'; import 'package:pinball_audio/pinball_audio.dart'; -import 'package:pinball_theme/pinball_theme.dart' as theme; import '../../helpers/helpers.dart'; class _TestPinballGame extends PinballGame { _TestPinballGame() : super( - characterTheme: const theme.DashTheme(), + characterThemeBloc: _MockCharacterThemeCubit(), leaderboardRepository: _MockLeaderboardRepository(), gameBloc: GameBloc(), l10n: _MockAppLocalizations(),