refactor: AndroidSpaceshipBonusBehavior use FlameBlocListener (#371)

pull/394/head
Felix Angelov 2 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
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
@ -15,42 +16,43 @@ class AndroidAcres extends Component {
AndroidAcres()
: super(
children: [
SpaceshipRamp(
FlameBlocProvider<AndroidSpaceshipCubit, AndroidSpaceshipState>(
create: AndroidSpaceshipCubit.new,
children: [
RampShotBehavior(
points: Points.fiveThousand,
),
RampBonusBehavior(
points: Points.oneMillion,
SpaceshipRamp(
children: [
RampShotBehavior(points: Points.fiveThousand),
RampBonusBehavior(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';
/// Adds a [GameBonus.androidSpaceship] when [AndroidSpaceship] has a bonus.
class AndroidSpaceshipBonusBehavior extends Component
with ParentIsA<AndroidAcres>, FlameBlocReader<GameBloc, GameState> {
class AndroidSpaceshipBonusBehavior extends Component {
@override
void onMount() {
super.onMount();
final androidSpaceship = parent.firstChild<AndroidSpaceship>()!;
androidSpaceship.bloc.stream.listen((state) {
final listenWhen = state == AndroidSpaceshipState.withBonus;
if (!listenWhen) return;
bloc.add(const BonusActivated(GameBonus.androidSpaceship));
androidSpaceship.bloc.onBonusAwarded();
});
Future<void> onLoad() async {
await super.onLoad();
await add(
FlameBlocListener<AndroidSpaceshipCubit, AndroidSpaceshipState>(
listenWhen: (_, state) => state == AndroidSpaceshipState.withBonus,
onNewState: (state) {
readBloc<GameBloc, GameState>().add(
const BonusActivated(GameBonus.androidSpaceship),
);
readBloc<AndroidSpaceshipCubit, AndroidSpaceshipState>()
.onBonusAwarded();
},
),
);
}
}

@ -11,10 +11,8 @@ import 'package:pinball_flame/pinball_flame.dart';
export 'cubit/android_spaceship_cubit.dart';
class AndroidSpaceship extends Component {
AndroidSpaceship({
required Vector2 position,
}) : bloc = AndroidSpaceshipCubit(),
super(
AndroidSpaceship({required Vector2 position})
: super(
children: [
_SpaceshipSaucer()..initialPosition = position,
_SpaceshipSaucerSpriteAnimationComponent()..position = position,
@ -38,17 +36,8 @@ class AndroidSpaceship extends Component {
/// This can be used for testing [AndroidSpaceship]'s behaviors in isolation.
@visibleForTesting
AndroidSpaceship.test({
required this.bloc,
Iterable<Component>? children,
}) : super(children: children);
final AndroidSpaceshipCubit bloc;
@override
void onRemove() {
bloc.close();
super.onRemove();
}
}
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:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
class AndroidSpaceshipEntranceBallContactBehavior
extends ContactBehavior<AndroidSpaceshipEntrance> {
extends ContactBehavior<AndroidSpaceshipEntrance>
with FlameBlocReader<AndroidSpaceshipCubit, AndroidSpaceshipState> {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
parent.parent.bloc.onBallEntered();
bloc.onBallEntered();
}
}

@ -1,7 +1,7 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
@ -21,9 +21,18 @@ void main() {
Assets.images.android.spaceship.lightBeam.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
late AndroidSpaceshipCubit bloc;
setUp(() {
bloc = _MockAndroidSpaceshipCubit();
});
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);
expect(game.contains(component), isTrue);
});
@ -33,7 +42,13 @@ void main() {
setUp: (game, tester) async {
await game.images.loadAll(assets);
final canvas = ZCanvasComponent(
children: [AndroidSpaceship(position: Vector2.zero())],
children: [
FlameBlocProvider<AndroidSpaceshipCubit,
AndroidSpaceshipState>.value(
value: bloc,
children: [AndroidSpaceship(position: Vector2.zero())],
),
],
);
await game.ensureAdd(canvas);
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(
'AndroidSpaceshipEntrance has an '
'AndroidSpaceshipEntranceBallContactBehavior', (game) async {
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 =
androidSpaceship.firstChild<AndroidSpaceshipEntrance>();

@ -1,6 +1,7 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.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';
@ -43,16 +44,19 @@ void main() {
);
final entrance = AndroidSpaceshipEntrance();
final androidSpaceship = AndroidSpaceship.test(
bloc: bloc,
children: [entrance],
final androidSpaceship = FlameBlocProvider<AndroidSpaceshipCubit,
AndroidSpaceshipState>.value(
value: bloc,
children: [
AndroidSpaceship.test(children: [entrance])
],
);
await entrance.add(behavior);
await game.ensureAdd(androidSpaceship);
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 {
final androidAcres = AndroidAcres();
await game.pump(androidAcres);
final provider = androidAcres.children
.whereType<
FlameBlocProvider<AndroidSpaceshipCubit, AndroidSpaceshipState>>()
.single;
expect(
androidAcres.children.whereType<AndroidSpaceshipBonusBehavior>().single,
provider.children.whereType<AndroidSpaceshipBonusBehavior>().single,
isNotNull,
);
});

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

Loading…
Cancel
Save