feat: implemented `AndroidSpaceshipBonusBehavior` (#298)

* refactor: simplify ball entering spaceship

* feat: add bonus logic and scoring

* chore: add animatronic to spaceship sandbox

* chore: remove renderBody change

* chore: remove leftover children change

* chore: test typo

* refactor: re-add children property

* refactor: use firstChild

* refactor: PR suggestions

* Update packages/pinball_components/test/src/components/android_animatronic_test.dart

Co-authored-by: Alejandro Santiago <dev@alestiago.com>

Co-authored-by: Alejandro Santiago <dev@alestiago.com>
pull/305/head
Allison Ryan 2 years ago committed by GitHub
parent 6cca3b84b5
commit f2160feab9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,8 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
@ -16,6 +18,11 @@ class AndroidAcres extends Component {
SpaceshipRamp(),
SpaceshipRail(),
AndroidSpaceship(position: Vector2(-26.5, -28.5)),
AndroidAnimatronic(
children: [
ScoringBehavior(points: Points.twoHundredThousand),
],
)..initialPosition = Vector2(-26, -28.25),
AndroidBumper.a(
children: [
ScoringBehavior(points: Points.twentyThousand),
@ -31,6 +38,13 @@ class AndroidAcres extends Component {
ScoringBehavior(points: Points.twentyThousand),
],
)..initialPosition = Vector2(-20.5, -13.8),
AndroidSpaceshipBonusBehavior(),
],
);
/// Creates [AndroidAcres] without any children.
///
/// This can be used for testing [AndroidAcres]'s behaviors in isolation.
@visibleForTesting
AndroidAcres.test();
}

@ -0,0 +1,27 @@
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.androidSpaceship] when [AndroidSpaceship] has a bonus.
class AndroidSpaceshipBonusBehavior extends Component
with HasGameRef<PinballGame>, ParentIsA<AndroidAcres> {
@override
void onMount() {
super.onMount();
final androidSpaceship = parent.firstChild<AndroidSpaceship>()!;
// TODO(alestiago): Refactor subscription management once the following is
// merged:
// https://github.com/flame-engine/flame/pull/1538
androidSpaceship.bloc.stream.listen((state) {
final listenWhen = state == AndroidSpaceshipState.withBonus;
if (!listenWhen) return;
gameRef
.read<GameBloc>()
.add(const BonusActivated(GameBonus.androidSpaceship));
androidSpaceship.bloc.onBonusAwarded();
});
}
}

@ -0,0 +1 @@
export 'android_spaceship_bonus_behavior.dart';

@ -1,4 +1,4 @@
export 'android_acres.dart';
export 'android_acres/android_acres.dart';
export 'bottom_group.dart';
export 'camera_controller.dart';
export 'controlled_ball.dart';

@ -0,0 +1,71 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template android_animatronic}
/// Animated Android that sits on top of the [AndroidSpaceship].
/// {@endtemplate}
class AndroidAnimatronic extends BodyComponent
with InitialPosition, Layered, ZIndex {
/// {@macro android_animatronic}
AndroidAnimatronic({Iterable<Component>? children})
: super(
children: [
_AndroidAnimatronicSpriteAnimationComponent(),
...?children,
],
renderBody: false,
) {
layer = Layer.spaceship;
zIndex = ZIndexes.androidHead;
}
@override
Body createBody() {
final shape = EllipseShape(
center: Vector2.zero(),
majorRadius: 3.1,
minorRadius: 2,
)..rotate(1.4);
final bodyDef = BodyDef(position: initialPosition);
return world.createBody(bodyDef)..createFixtureFromShape(shape);
}
}
class _AndroidAnimatronicSpriteAnimationComponent
extends SpriteAnimationComponent with HasGameRef {
_AndroidAnimatronicSpriteAnimationComponent()
: super(
anchor: Anchor.center,
position: Vector2(-0.24, -2.6),
);
@override
Future<void> onLoad() async {
await super.onLoad();
final spriteSheet = gameRef.images.fromCache(
Assets.images.android.spaceship.animatronic.keyName,
);
const amountPerRow = 18;
const amountPerColumn = 4;
final textureSize = Vector2(
spriteSheet.width / amountPerRow,
spriteSheet.height / amountPerColumn,
);
size = textureSize / 10;
animation = SpriteAnimation.fromFrameData(
spriteSheet,
SpriteAnimationData.sequenced(
amount: amountPerRow * amountPerColumn,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: textureSize,
),
);
}
}

@ -5,17 +5,25 @@ import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/android_spaceship/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.dart';
export 'cubit/android_spaceship_cubit.dart';
class AndroidSpaceship extends Component {
AndroidSpaceship({required Vector2 position})
: super(
AndroidSpaceship({
required Vector2 position,
}) : bloc = AndroidSpaceshipCubit(),
super(
children: [
_SpaceshipSaucer()..initialPosition = position,
_SpaceshipSaucerSpriteAnimationComponent()..position = position,
_LightBeamSpriteComponent()..position = position + Vector2(2.5, 5),
_AndroidHead()..initialPosition = position + Vector2(0.5, 0.25),
AndroidSpaceshipEntrance(
children: [AndroidSpaceshipEntranceBallContactBehavior()],
),
_SpaceshipHole(
outsideLayer: Layer.spaceshipExitRail,
outsidePriority: ZIndexes.ballOnSpaceshipRail,
@ -26,6 +34,27 @@ class AndroidSpaceship extends Component {
)..initialPosition = position - Vector2(-7.5, -1.1),
],
);
/// Creates an [AndroidSpaceship] without any children.
///
/// This can be used for testing [AndroidSpaceship]'s behaviors in isolation.
// TODO(alestiago): Refactor injecting bloc once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
@visibleForTesting
AndroidSpaceship.test({
required this.bloc,
Iterable<Component>? children,
}) : super(children: children);
// TODO(alestiago): Consider refactoring once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
final AndroidSpaceshipCubit bloc;
@override
void onRemove() {
bloc.close();
super.onRemove();
}
}
class _SpaceshipSaucer extends BodyComponent with InitialPosition, Layered {
@ -123,62 +152,32 @@ class _LightBeamSpriteComponent extends SpriteComponent
}
}
class _AndroidHead extends BodyComponent with InitialPosition, Layered, ZIndex {
_AndroidHead()
class AndroidSpaceshipEntrance extends BodyComponent
with ParentIsA<AndroidSpaceship>, Layered {
AndroidSpaceshipEntrance({Iterable<Component>? children})
: super(
children: [_AndroidHeadSpriteAnimationComponent()],
children: children,
renderBody: false,
) {
layer = Layer.spaceship;
zIndex = ZIndexes.androidHead;
}
@override
Body createBody() {
final shape = EllipseShape(
center: Vector2.zero(),
majorRadius: 3.1,
minorRadius: 2,
)..rotate(1.4);
final bodyDef = BodyDef(position: initialPosition);
return world.createBody(bodyDef)..createFixtureFromShape(shape);
}
}
class _AndroidHeadSpriteAnimationComponent extends SpriteAnimationComponent
with HasGameRef {
_AndroidHeadSpriteAnimationComponent()
: super(
anchor: Anchor.center,
position: Vector2(-0.24, -2.6),
);
@override
Future<void> onLoad() async {
await super.onLoad();
final spriteSheet = gameRef.images.fromCache(
Assets.images.android.spaceship.animatronic.keyName,
final shape = PolygonShape()
..setAsBox(
2,
0.1,
Vector2(-27.4, -37.2),
-0.12,
);
final fixtureDef = FixtureDef(
shape,
isSensor: true,
);
final bodyDef = BodyDef();
const amountPerRow = 18;
const amountPerColumn = 4;
final textureSize = Vector2(
spriteSheet.width / amountPerRow,
spriteSheet.height / amountPerColumn,
);
size = textureSize / 10;
animation = SpriteAnimation.fromFrameData(
spriteSheet,
SpriteAnimationData.sequenced(
amount: amountPerRow * amountPerColumn,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: textureSize,
),
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}

@ -0,0 +1,16 @@
// ignore_for_file: public_member_api_docs
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> {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
parent.parent.bloc.onBallEntered();
}
}

@ -0,0 +1 @@
export 'android_spaceship_entrance_ball_contact_behavior.dart.dart';

@ -0,0 +1,13 @@
// ignore_for_file: public_member_api_docs
import 'package:bloc/bloc.dart';
part 'android_spaceship_state.dart';
class AndroidSpaceshipCubit extends Cubit<AndroidSpaceshipState> {
AndroidSpaceshipCubit() : super(AndroidSpaceshipState.withoutBonus);
void onBallEntered() => emit(AndroidSpaceshipState.withBonus);
void onBonusAwarded() => emit(AndroidSpaceshipState.withoutBonus);
}

@ -0,0 +1,8 @@
// ignore_for_file: public_member_api_docs
part of 'android_spaceship_cubit.dart';
enum AndroidSpaceshipState {
withoutBonus,
withBonus,
}

@ -1,5 +1,6 @@
export 'android_animatronic.dart';
export 'android_bumper/android_bumper.dart';
export 'android_spaceship.dart';
export 'android_spaceship/android_spaceship.dart';
export 'backboard/backboard.dart';
export 'ball.dart';
export 'baseboard.dart';

@ -17,7 +17,7 @@ class AndroidSpaceshipGame extends BallGame {
);
static const description = '''
Shows how the AndroidSpaceship is rendered.
Shows how the AndroidSpaceship and AndroidAnimatronic are rendered.
- Activate the "trace" parameter to overlay the body.
- Tap anywhere on the screen to spawn a Ball into the game.
@ -28,8 +28,11 @@ class AndroidSpaceshipGame extends BallGame {
await super.onLoad();
camera.followVector2(Vector2.zero());
await add(
AndroidSpaceship(position: Vector2.zero()),
await addAll(
[
AndroidSpaceship(position: Vector2.zero()),
AndroidAnimatronic(),
],
);
await traceAllBodies();

@ -0,0 +1,70 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final asset = Assets.images.android.spaceship.animatronic.keyName;
final flameTester = FlameTester(() => TestGame([asset]));
group('AndroidAnimatronic', () {
flameTester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
await game.images.load(asset);
await game.ensureAdd(AndroidAnimatronic());
game.camera.followVector2(Vector2.zero());
await tester.pump();
},
verify: (game, tester) async {
final animationDuration = game
.firstChild<AndroidAnimatronic>()!
.firstChild<SpriteAnimationComponent>()!
.animation!
.totalDuration();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/android_animatronic/start.png'),
);
game.update(animationDuration * 0.5);
await tester.pump();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/android_animatronic/middle.png'),
);
game.update(animationDuration * 0.5);
await tester.pump();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/android_animatronic/end.png'),
);
},
);
flameTester.test(
'loads correctly',
(game) async {
final androidAnimatronic = AndroidAnimatronic();
await game.ensureAdd(androidAnimatronic);
expect(game.contains(androidAnimatronic), isTrue);
},
);
flameTester.test('adds new children', (game) async {
final component = Component();
final androidAnimatronic = AndroidAnimatronic(
children: [component],
);
await game.ensureAdd(androidAnimatronic);
expect(androidAnimatronic.children, contains(component));
});
});
}

@ -0,0 +1,109 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/android_spaceship/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../../helpers/helpers.dart';
class _MockAndroidSpaceshipCubit extends Mock implements AndroidSpaceshipCubit {
}
void main() {
group('AndroidSpaceship', () {
final assets = [
Assets.images.android.spaceship.saucer.keyName,
Assets.images.android.spaceship.lightBeam.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
flameTester.test('loads correctly', (game) async {
final component = AndroidSpaceship(position: Vector2.zero());
await game.ensureAdd(component);
expect(game.contains(component), isTrue);
});
flameTester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
await game.images.loadAll(assets);
final canvas = ZCanvasComponent(
children: [AndroidSpaceship(position: Vector2.zero())],
);
await game.ensureAdd(canvas);
game.camera.followVector2(Vector2.zero());
await game.ready();
await tester.pump();
},
verify: (game, tester) async {
const goldenFilePath = '../golden/android_spaceship/';
final animationDuration = game
.descendants()
.whereType<SpriteAnimationComponent>()
.single
.animation!
.totalDuration();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('${goldenFilePath}start.png'),
);
game.update(animationDuration * 0.5);
await tester.pump();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('${goldenFilePath}middle.png'),
);
game.update(animationDuration * 0.5);
await tester.pump();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('${goldenFilePath}end.png'),
);
},
);
// TODO(alestiago): Consider refactoring once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
// ignore: public_member_api_docs
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 androidSpaceshipEntrance =
androidSpaceship.firstChild<AndroidSpaceshipEntrance>();
expect(
androidSpaceshipEntrance!.children
.whereType<AndroidSpaceshipEntranceBallContactBehavior>()
.single,
isNotNull,
);
});
});
}

@ -0,0 +1,55 @@
// 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_components/pinball_components.dart';
import 'package:pinball_components/src/components/android_spaceship/behaviors/behaviors.dart';
import '../../../../helpers/helpers.dart';
class _MockAndroidSpaceshipCubit extends Mock implements AndroidSpaceshipCubit {
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
group(
'AndroidSpaceshipEntranceBallContactBehavior',
() {
test('can be instantiated', () {
expect(
AndroidSpaceshipEntranceBallContactBehavior(),
isA<AndroidSpaceshipEntranceBallContactBehavior>(),
);
});
flameTester.test(
'beginContact calls onBallEntered when entrance contacts with a ball',
(game) async {
final behavior = AndroidSpaceshipEntranceBallContactBehavior();
final bloc = _MockAndroidSpaceshipCubit();
whenListen(
bloc,
const Stream<AndroidSpaceshipState>.empty(),
initialState: AndroidSpaceshipState.withoutBonus,
);
final entrance = AndroidSpaceshipEntrance();
final androidSpaceship = AndroidSpaceship.test(
bloc: bloc,
children: [entrance],
);
await entrance.add(behavior);
await game.ensureAdd(androidSpaceship);
behavior.beginContact(MockBall(), MockContact());
verify(androidSpaceship.bloc.onBallEntered).called(1);
},
);
},
);
}

@ -0,0 +1,24 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
void main() {
group(
'AndroidSpaceshipCubit',
() {
blocTest<AndroidSpaceshipCubit, AndroidSpaceshipState>(
'onBallEntered emits withBonus',
build: AndroidSpaceshipCubit.new,
act: (bloc) => bloc.onBallEntered(),
expect: () => [AndroidSpaceshipState.withBonus],
);
blocTest<AndroidSpaceshipCubit, AndroidSpaceshipState>(
'onBonusAwarded emits withoutBonus',
build: AndroidSpaceshipCubit.new,
act: (bloc) => bloc.onBonusAwarded(),
expect: () => [AndroidSpaceshipState.withoutBonus],
);
},
);
}

@ -1,67 +0,0 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../helpers/helpers.dart';
void main() {
group('AndroidSpaceship', () {
final assets = [
Assets.images.android.spaceship.saucer.keyName,
Assets.images.android.spaceship.animatronic.keyName,
Assets.images.android.spaceship.lightBeam.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
flameTester.test('loads correctly', (game) async {
final component = AndroidSpaceship(position: Vector2.zero());
await game.ensureAdd(component);
expect(game.contains(component), isTrue);
});
flameTester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
await game.images.loadAll(assets);
final canvas = ZCanvasComponent(
children: [AndroidSpaceship(position: Vector2.zero())],
);
await game.ensureAdd(canvas);
game.camera.followVector2(Vector2.zero());
await game.ready();
await tester.pump();
},
verify: (game, tester) async {
final animationDuration = game
.descendants()
.whereType<SpriteAnimationComponent>()
.last
.animation!
.totalDuration();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/android_spaceship/start.png'),
);
game.update(animationDuration * 0.5);
await tester.pump();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/android_spaceship/middle.png'),
);
game.update(animationDuration * 0.5);
await tester.pump();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/android_spaceship/end.png'),
);
},
);
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 114 KiB

@ -2,10 +2,11 @@
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/components/android_acres/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();
@ -45,7 +46,7 @@ void main() {
group('loads', () {
flameTester.test(
'a Spaceship',
'an AndroidSpaceship',
(game) async {
await game.ensureAdd(AndroidAcres());
expect(
@ -55,6 +56,17 @@ void main() {
},
);
flameTester.test(
'an AndroidAnimatronic',
(game) async {
await game.ensureAdd(AndroidAcres());
expect(
game.descendants().whereType<AndroidAnimatronic>().length,
equals(1),
);
},
);
flameTester.test(
'a SpaceshipRamp',
(game) async {
@ -88,5 +100,14 @@ void main() {
},
);
});
flameTester.test('adds an AndroidSpaceshipBonusBehavior', (game) async {
final androidAcres = AndroidAcres();
await game.ensureAdd(androidAcres);
expect(
androidAcres.children.whereType<AndroidSpaceshipBonusBehavior>().single,
isNotNull,
);
});
});
}

@ -0,0 +1,79 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/extensions.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/android_acres/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.android.spaceship.saucer.keyName,
Assets.images.android.spaceship.animatronic.keyName,
Assets.images.android.spaceship.lightBeam.keyName,
Assets.images.android.ramp.boardOpening.keyName,
Assets.images.android.ramp.railingForeground.keyName,
Assets.images.android.ramp.railingBackground.keyName,
Assets.images.android.ramp.main.keyName,
Assets.images.android.ramp.arrow.inactive.keyName,
Assets.images.android.ramp.arrow.active1.keyName,
Assets.images.android.ramp.arrow.active2.keyName,
Assets.images.android.ramp.arrow.active3.keyName,
Assets.images.android.ramp.arrow.active4.keyName,
Assets.images.android.ramp.arrow.active5.keyName,
Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName,
Assets.images.android.bumper.a.lit.keyName,
Assets.images.android.bumper.a.dimmed.keyName,
Assets.images.android.bumper.b.lit.keyName,
Assets.images.android.bumper.b.dimmed.keyName,
Assets.images.android.bumper.cow.lit.keyName,
Assets.images.android.bumper.cow.dimmed.keyName,
];
group('AndroidSpaceshipBonusBehavior', () {
late GameBloc gameBloc;
setUp(() {
gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
flameBlocTester.testGameWidget(
'adds GameBonus.androidSpaceship to the game '
'when android spacehship has a bonus',
setUp: (game, tester) async {
final behavior = AndroidSpaceshipBonusBehavior();
final parent = AndroidAcres.test();
final androidSpaceship = AndroidSpaceship(position: Vector2.zero());
await parent.add(androidSpaceship);
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
androidSpaceship.bloc.onBallEntered();
await tester.pump();
verify(
() => gameBloc.add(const BonusActivated(GameBonus.androidSpaceship)),
).called(1);
},
);
});
}
Loading…
Cancel
Save