diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 8a3a6dbd..b0b81239 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -4,7 +4,7 @@ export 'camera_controller.dart'; export 'controlled_ball.dart'; export 'controlled_flipper.dart'; export 'controlled_plunger.dart'; -export 'dino_desert.dart'; +export 'dino_desert/dino_desert.dart'; export 'drain.dart'; export 'flutter_forest/flutter_forest.dart'; export 'game_flow_controller.dart'; diff --git a/lib/game/components/dino_desert/behaviors/behaviors.dart b/lib/game/components/dino_desert/behaviors/behaviors.dart new file mode 100644 index 00000000..fe802c88 --- /dev/null +++ b/lib/game/components/dino_desert/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'chrome_dino_bonus_behavior.dart'; diff --git a/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart b/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart new file mode 100644 index 00000000..e4d69f9c --- /dev/null +++ b/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart @@ -0,0 +1,24 @@ +import 'package:flame/components.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// Adds a [GameBonus.dinoChomp] when a [Ball] is chomped by the [ChromeDino]. +class ChromeDinoBonusBehavior extends Component + with HasGameRef, ParentIsA { + @override + void onMount() { + super.onMount(); + final chromeDino = parent.firstChild()!; + + // TODO(alestiago): Refactor subscription management once the following is + // merged: + // https://github.com/flame-engine/flame/pull/1538 + chromeDino.bloc.stream.listen((state) { + final listenWhen = state.status == ChromeDinoStatus.chomping; + if (!listenWhen) return; + + gameRef.read().add(const BonusActivated(GameBonus.dinoChomp)); + }); + } +} diff --git a/lib/game/components/dino_desert.dart b/lib/game/components/dino_desert/dino_desert.dart similarity index 73% rename from lib/game/components/dino_desert.dart rename to lib/game/components/dino_desert/dino_desert.dart index b3ae4ab9..4d8cd7b6 100644 --- a/lib/game/components/dino_desert.dart +++ b/lib/game/components/dino_desert/dino_desert.dart @@ -1,11 +1,13 @@ import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball/game/components/dino_desert/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template dino_desert} -/// Area located next to the [Launcher] containing the [ChromeDino] and -/// [DinoWalls]. +/// Area located next to the [Launcher] containing the [ChromeDino], +/// [DinoWalls], and the [Slingshots]. /// {@endtemplate} class DinoDesert extends Component { /// {@macro dino_desert} @@ -21,8 +23,15 @@ class DinoDesert extends Component { _BarrierBehindDino(), DinoWalls(), Slingshots(), + ChromeDinoBonusBehavior(), ], ); + + /// Creates [DinoDesert] without any children. + /// + /// This can be used for testing [DinoDesert]'s behaviors in isolation. + @visibleForTesting + DinoDesert.test(); } class _BarrierBehindDino extends BodyComponent { diff --git a/packages/pinball_components/lib/src/components/chrome_dino/cubit/chrome_dino_cubit.dart b/packages/pinball_components/lib/src/components/chrome_dino/cubit/chrome_dino_cubit.dart index b98a4093..649e804b 100644 --- a/packages/pinball_components/lib/src/components/chrome_dino/cubit/chrome_dino_cubit.dart +++ b/packages/pinball_components/lib/src/components/chrome_dino/cubit/chrome_dino_cubit.dart @@ -18,10 +18,17 @@ class ChromeDinoCubit extends Cubit { } void onChomp(Ball ball) { - emit(state.copyWith(status: ChromeDinoStatus.chomping, ball: ball)); + if (ball != state.ball) { + emit(state.copyWith(status: ChromeDinoStatus.chomping, ball: ball)); + } } void onSpit() { - emit(state.copyWith(status: ChromeDinoStatus.idle)); + emit( + ChromeDinoState( + status: ChromeDinoStatus.idle, + isMouthOpen: state.isMouthOpen, + ), + ); } } diff --git a/packages/pinball_components/test/src/components/chrome_dino/cubit/chrome_dino_cubit_test.dart b/packages/pinball_components/test/src/components/chrome_dino/cubit/chrome_dino_cubit_test.dart index 30e67511..5b31be74 100644 --- a/packages/pinball_components/test/src/components/chrome_dino/cubit/chrome_dino_cubit_test.dart +++ b/packages/pinball_components/test/src/components/chrome_dino/cubit/chrome_dino_cubit_test.dart @@ -36,7 +36,8 @@ void main() { ); blocTest( - 'onChomp emits ChromeDinoStatus.chomping and chomped ball', + 'onChomp emits ChromeDinoStatus.chomping and chomped ball ' + 'when the ball is not in the mouth', build: ChromeDinoCubit.new, act: (bloc) => bloc.onChomp(ball), expect: () => [ @@ -55,7 +56,15 @@ void main() { ); blocTest( - 'onSpit emits ChromeDinoStatus.idle', + 'onChomp emits nothing when the ball is already in the mouth', + build: ChromeDinoCubit.new, + seed: () => const ChromeDinoState.inital().copyWith(ball: ball), + act: (bloc) => bloc.onChomp(ball), + expect: () => [], + ); + + blocTest( + 'onSpit emits ChromeDinoStatus.idle and removes ball', build: ChromeDinoCubit.new, act: (bloc) => bloc.onSpit(), expect: () => [ @@ -63,7 +72,11 @@ void main() { (state) => state.status, 'status', ChromeDinoStatus.idle, - ) + )..having( + (state) => state.ball, + 'ball', + null, + ) ], ); }, diff --git a/test/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior_test.dart b/test/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior_test.dart new file mode 100644 index 00000000..3b8f2dfa --- /dev/null +++ b/test/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior_test.dart @@ -0,0 +1,63 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/components/dino_desert/behaviors/behaviors.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + Assets.images.dino.animatronic.head.keyName, + Assets.images.dino.animatronic.mouth.keyName, + Assets.images.dino.topWall.keyName, + Assets.images.dino.bottomWall.keyName, + Assets.images.slingshot.upper.keyName, + Assets.images.slingshot.lower.keyName, + ]; + + group('ChromeDinoBonusBehavior', () { + late GameBloc gameBloc; + + setUp(() { + gameBloc = MockGameBloc(); + whenListen( + gameBloc, + const Stream.empty(), + initialState: const GameState.initial(), + ); + }); + + final flameBlocTester = FlameBlocTester( + gameBuilder: EmptyPinballTestGame.new, + blocBuilder: () => gameBloc, + assets: assets, + ); + + flameBlocTester.testGameWidget( + 'adds GameBonus.dinoChomp to the game ' + 'when ChromeDinoStatus.chomping is emitted', + setUp: (game, tester) async { + final behavior = ChromeDinoBonusBehavior(); + final parent = DinoDesert.test(); + final chromeDino = ChromeDino(); + + await parent.add(chromeDino); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + + chromeDino.bloc.onChomp(MockBall()); + await tester.pump(); + + verify( + () => gameBloc.add(const BonusActivated(GameBonus.dinoChomp)), + ).called(1); + }, + ); + }); +} diff --git a/test/game/components/dino_desert_test.dart b/test/game/components/dino_desert/dino_desert_test.dart similarity index 70% rename from test/game/components/dino_desert_test.dart rename to test/game/components/dino_desert/dino_desert_test.dart index 35a2d25b..262ddc2d 100644 --- a/test/game/components/dino_desert_test.dart +++ b/test/game/components/dino_desert/dino_desert_test.dart @@ -2,10 +2,11 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/game/components/dino_desert/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; -import '../../helpers/helpers.dart'; +import '../../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -63,17 +64,28 @@ void main() { ); }); - flameTester.test( - 'adds ScoringBehavior to ChromeDino', - (game) async { - await game.ensureAdd(DinoDesert()); + group('adds', () { + flameTester.test( + 'ScoringBehavior to ChromeDino', + (game) async { + await game.ensureAdd(DinoDesert()); - final chromeDino = game.descendants().whereType().single; + final chromeDino = game.descendants().whereType().single; + expect( + chromeDino.firstChild(), + isNotNull, + ); + }, + ); + + flameTester.test('a ChromeDinoBonusBehavior', (game) async { + final dinoDesert = DinoDesert(); + await game.ensureAdd(dinoDesert); expect( - chromeDino.firstChild(), + dinoDesert.children.whereType().single, isNotNull, ); - }, - ); + }); + }); }); }