refactor: AndroidSpaceshipBonusBehavior use FlameBlocListener (#371)

pull/394/head
Felix Angelov 3 years ago committed by GitHub
parent 8d4b031350
commit 75527d1fe1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,7 @@
// ignore_for_file: avoid_renaming_method_parameters // ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart'; import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
@ -15,42 +16,43 @@ class AndroidAcres extends Component {
AndroidAcres() AndroidAcres()
: super( : super(
children: [ children: [
SpaceshipRamp( FlameBlocProvider<AndroidSpaceshipCubit, AndroidSpaceshipState>(
create: AndroidSpaceshipCubit.new,
children: [ children: [
RampShotBehavior( SpaceshipRamp(
points: Points.fiveThousand, children: [
), RampShotBehavior(points: Points.fiveThousand),
RampBonusBehavior( RampBonusBehavior(points: Points.oneMillion),
points: Points.oneMillion, ],
), ),
SpaceshipRail(),
AndroidSpaceship(position: Vector2(-26.5, -28.5)),
AndroidAnimatronic(
children: [
ScoringContactBehavior(points: Points.twoHundredThousand),
],
)..initialPosition = Vector2(-26, -28.25),
AndroidBumper.a(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(-25.2, 1.5),
AndroidBumper.b(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(-32.9, -9.3),
AndroidBumper.cow(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(-20.7, -13),
AndroidSpaceshipBonusBehavior(),
], ],
), ),
SpaceshipRail(),
AndroidSpaceship(position: Vector2(-26.5, -28.5)),
AndroidAnimatronic(
children: [
ScoringContactBehavior(points: Points.twoHundredThousand),
],
)..initialPosition = Vector2(-26, -28.25),
AndroidBumper.a(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(-25.2, 1.5),
AndroidBumper.b(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(-32.9, -9.3),
AndroidBumper.cow(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(-20.7, -13),
AndroidSpaceshipBonusBehavior(),
], ],
); );

@ -5,18 +5,21 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
/// Adds a [GameBonus.androidSpaceship] when [AndroidSpaceship] has a bonus. /// Adds a [GameBonus.androidSpaceship] when [AndroidSpaceship] has a bonus.
class AndroidSpaceshipBonusBehavior extends Component class AndroidSpaceshipBonusBehavior extends Component {
with ParentIsA<AndroidAcres>, FlameBlocReader<GameBloc, GameState> {
@override @override
void onMount() { Future<void> onLoad() async {
super.onMount(); await super.onLoad();
final androidSpaceship = parent.firstChild<AndroidSpaceship>()!; await add(
androidSpaceship.bloc.stream.listen((state) { FlameBlocListener<AndroidSpaceshipCubit, AndroidSpaceshipState>(
final listenWhen = state == AndroidSpaceshipState.withBonus; listenWhen: (_, state) => state == AndroidSpaceshipState.withBonus,
if (!listenWhen) return; onNewState: (state) {
readBloc<GameBloc, GameState>().add(
bloc.add(const BonusActivated(GameBonus.androidSpaceship)); const BonusActivated(GameBonus.androidSpaceship),
androidSpaceship.bloc.onBonusAwarded(); );
}); readBloc<AndroidSpaceshipCubit, AndroidSpaceshipState>()
.onBonusAwarded();
},
),
);
} }
} }

@ -11,10 +11,8 @@ import 'package:pinball_flame/pinball_flame.dart';
export 'cubit/android_spaceship_cubit.dart'; export 'cubit/android_spaceship_cubit.dart';
class AndroidSpaceship extends Component { class AndroidSpaceship extends Component {
AndroidSpaceship({ AndroidSpaceship({required Vector2 position})
required Vector2 position, : super(
}) : bloc = AndroidSpaceshipCubit(),
super(
children: [ children: [
_SpaceshipSaucer()..initialPosition = position, _SpaceshipSaucer()..initialPosition = position,
_SpaceshipSaucerSpriteAnimationComponent()..position = position, _SpaceshipSaucerSpriteAnimationComponent()..position = position,
@ -38,17 +36,8 @@ class AndroidSpaceship extends Component {
/// This can be used for testing [AndroidSpaceship]'s behaviors in isolation. /// This can be used for testing [AndroidSpaceship]'s behaviors in isolation.
@visibleForTesting @visibleForTesting
AndroidSpaceship.test({ AndroidSpaceship.test({
required this.bloc,
Iterable<Component>? children, Iterable<Component>? children,
}) : super(children: children); }) : super(children: children);
final AndroidSpaceshipCubit bloc;
@override
void onRemove() {
bloc.close();
super.onRemove();
}
} }
class _SpaceshipSaucer extends BodyComponent with InitialPosition, Layered { class _SpaceshipSaucer extends BodyComponent with InitialPosition, Layered {

@ -1,14 +1,18 @@
// ignore_for_file: public_member_api_docs
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
class AndroidSpaceshipEntranceBallContactBehavior class AndroidSpaceshipEntranceBallContactBehavior
extends ContactBehavior<AndroidSpaceshipEntrance> { extends ContactBehavior<AndroidSpaceshipEntrance>
with FlameBlocReader<AndroidSpaceshipCubit, AndroidSpaceshipState> {
@override @override
void beginContact(Object other, Contact contact) { void beginContact(Object other, Contact contact) {
super.beginContact(other, contact); super.beginContact(other, contact);
if (other is! Ball) return; if (other is! Ball) return;
parent.parent.bloc.onBallEntered(); bloc.onBallEntered();
} }
} }

@ -1,7 +1,7 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@ -21,9 +21,18 @@ void main() {
Assets.images.android.spaceship.lightBeam.keyName, Assets.images.android.spaceship.lightBeam.keyName,
]; ];
final flameTester = FlameTester(() => TestGame(assets)); final flameTester = FlameTester(() => TestGame(assets));
late AndroidSpaceshipCubit bloc;
setUp(() {
bloc = _MockAndroidSpaceshipCubit();
});
flameTester.test('loads correctly', (game) async { flameTester.test('loads correctly', (game) async {
final component = AndroidSpaceship(position: Vector2.zero()); final component =
FlameBlocProvider<AndroidSpaceshipCubit, AndroidSpaceshipState>.value(
value: bloc,
children: [AndroidSpaceship(position: Vector2.zero())],
);
await game.ensureAdd(component); await game.ensureAdd(component);
expect(game.contains(component), isTrue); expect(game.contains(component), isTrue);
}); });
@ -33,7 +42,13 @@ void main() {
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.images.loadAll(assets);
final canvas = ZCanvasComponent( final canvas = ZCanvasComponent(
children: [AndroidSpaceship(position: Vector2.zero())], children: [
FlameBlocProvider<AndroidSpaceshipCubit,
AndroidSpaceshipState>.value(
value: bloc,
children: [AndroidSpaceship(position: Vector2.zero())],
),
],
); );
await game.ensureAdd(canvas); await game.ensureAdd(canvas);
game.camera.followVector2(Vector2.zero()); game.camera.followVector2(Vector2.zero());
@ -70,28 +85,16 @@ void main() {
}, },
); );
flameTester.test('closes bloc when removed', (game) async {
final bloc = _MockAndroidSpaceshipCubit();
whenListen(
bloc,
const Stream<AndroidSpaceshipState>.empty(),
initialState: AndroidSpaceshipState.withoutBonus,
);
when(bloc.close).thenAnswer((_) async {});
final androidSpaceship = AndroidSpaceship.test(bloc: bloc);
await game.ensureAdd(androidSpaceship);
game.remove(androidSpaceship);
await game.ready();
verify(bloc.close).called(1);
});
flameTester.test( flameTester.test(
'AndroidSpaceshipEntrance has an ' 'AndroidSpaceshipEntrance has an '
'AndroidSpaceshipEntranceBallContactBehavior', (game) async { 'AndroidSpaceshipEntranceBallContactBehavior', (game) async {
final androidSpaceship = AndroidSpaceship(position: Vector2.zero()); final androidSpaceship = AndroidSpaceship(position: Vector2.zero());
await game.ensureAdd(androidSpaceship); final provider =
FlameBlocProvider<AndroidSpaceshipCubit, AndroidSpaceshipState>.value(
value: bloc,
children: [androidSpaceship],
);
await game.ensureAdd(provider);
final androidSpaceshipEntrance = final androidSpaceshipEntrance =
androidSpaceship.firstChild<AndroidSpaceshipEntrance>(); androidSpaceship.firstChild<AndroidSpaceshipEntrance>();

@ -1,6 +1,7 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -43,16 +44,19 @@ void main() {
); );
final entrance = AndroidSpaceshipEntrance(); final entrance = AndroidSpaceshipEntrance();
final androidSpaceship = AndroidSpaceship.test( final androidSpaceship = FlameBlocProvider<AndroidSpaceshipCubit,
bloc: bloc, AndroidSpaceshipState>.value(
children: [entrance], value: bloc,
children: [
AndroidSpaceship.test(children: [entrance])
],
); );
await entrance.add(behavior); await entrance.add(behavior);
await game.ensureAdd(androidSpaceship); await game.ensureAdd(androidSpaceship);
behavior.beginContact(_MockBall(), _MockContact()); behavior.beginContact(_MockBall(), _MockContact());
verify(androidSpaceship.bloc.onBallEntered).called(1); verify(bloc.onBallEntered).called(1);
}, },
); );
}, },

@ -131,11 +131,28 @@ void main() {
); );
}); });
flameTester.test('adds a FlameBlocProvider', (game) async {
final androidAcres = AndroidAcres();
await game.pump(androidAcres);
expect(
androidAcres.children
.whereType<
FlameBlocProvider<AndroidSpaceshipCubit,
AndroidSpaceshipState>>()
.single,
isNotNull,
);
});
flameTester.test('adds an AndroidSpaceshipBonusBehavior', (game) async { flameTester.test('adds an AndroidSpaceshipBonusBehavior', (game) async {
final androidAcres = AndroidAcres(); final androidAcres = AndroidAcres();
await game.pump(androidAcres); await game.pump(androidAcres);
final provider = androidAcres.children
.whereType<
FlameBlocProvider<AndroidSpaceshipCubit, AndroidSpaceshipState>>()
.single;
expect( expect(
androidAcres.children.whereType<AndroidSpaceshipBonusBehavior>().single, provider.children.whereType<AndroidSpaceshipBonusBehavior>().single,
isNotNull, isNotNull,
); );
}); });

@ -1,5 +1,8 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
@ -41,13 +44,21 @@ class _TestGame extends Forge2DGame {
Future<void> pump( Future<void> pump(
AndroidAcres child, { AndroidAcres child, {
required GameBloc gameBloc, required GameBloc gameBloc,
required AndroidSpaceshipCubit androidSpaceshipCubit,
}) async { }) async {
// Not needed once https://github.com/flame-engine/flame/issues/1607 // Not needed once https://github.com/flame-engine/flame/issues/1607
// is fixed // is fixed
await onLoad(); await onLoad();
await ensureAdd( await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value( FlameMultiBlocProvider(
value: gameBloc, providers: [
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
),
FlameBlocProvider<AndroidSpaceshipCubit, AndroidSpaceshipState>.value(
value: androidSpaceshipCubit,
),
],
children: [child], children: [child],
), ),
); );
@ -56,6 +67,9 @@ class _TestGame extends Forge2DGame {
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
class _MockAndroidSpaceshipCubit extends Mock implements AndroidSpaceshipCubit {
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
@ -70,20 +84,30 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'adds GameBonus.androidSpaceship to the game ' 'adds GameBonus.androidSpaceship to the game '
'when android spacehship has a bonus', 'when android spaceship has a bonus',
setUp: (game, tester) async { setUp: (game, tester) async {
final behavior = AndroidSpaceshipBonusBehavior(); final behavior = AndroidSpaceshipBonusBehavior();
final parent = AndroidAcres.test(); final parent = AndroidAcres.test();
final androidSpaceship = AndroidSpaceship(position: Vector2.zero()); final androidSpaceship = AndroidSpaceship(position: Vector2.zero());
final androidSpaceshipCubit = _MockAndroidSpaceshipCubit();
final streamController = StreamController<AndroidSpaceshipState>();
whenListen(
androidSpaceshipCubit,
streamController.stream,
initialState: AndroidSpaceshipState.withoutBonus,
);
await parent.add(androidSpaceship); await parent.add(androidSpaceship);
await game.pump( await game.pump(
parent, parent,
androidSpaceshipCubit: androidSpaceshipCubit,
gameBloc: gameBloc, gameBloc: gameBloc,
); );
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
androidSpaceship.bloc.onBallEntered(); streamController.add(AndroidSpaceshipState.withBonus);
await tester.pump(); await tester.pump();
verify( verify(

Loading…
Cancel
Save