feat: implemented FlutterForestBonusBehavior

pull/234/head
alestiago 3 years ago
parent 72a377b1b0
commit aabc4f21af

@ -4,7 +4,7 @@ export 'camera_controller.dart';
export 'controlled_ball.dart'; export 'controlled_ball.dart';
export 'controlled_flipper.dart'; export 'controlled_flipper.dart';
export 'controlled_plunger.dart'; export 'controlled_plunger.dart';
export 'flutter_forest.dart'; export 'flutter_forest/flutter_forest.dart';
export 'game_flow_controller.dart'; export 'game_flow_controller.dart';
export 'google_word/google_word.dart'; export 'google_word/google_word.dart';
export 'launcher.dart'; export 'launcher.dart';

@ -1,78 +0,0 @@
// ignore_for_file: avoid_renaming_method_parameters
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';
/// {@template flutter_forest}
/// Area positioned at the top right of the [Board] where the [Ball]
/// can bounce off [DashNestBumper]s.
///
/// When all [DashNestBumper]s are hit at least once, the [GameBonus.dashNest]
/// is awarded, and the [DashNestBumper.main] releases a new [Ball].
/// {@endtemplate}
class FlutterForest extends Component
with Controls<_FlutterForestController>, HasGameRef<PinballGame> {
/// {@macro flutter_forest}
FlutterForest()
: super(
children: [
Signpost(
children: [
ScoringBehavior(points: 20),
],
)..initialPosition = Vector2(8.35, -58.3),
DashNestBumper.main(
children: [
ScoringBehavior(points: 20),
],
)..initialPosition = Vector2(18.55, -59.35),
DashNestBumper.a(
children: [
ScoringBehavior(points: 20),
],
)..initialPosition = Vector2(8.95, -51.95),
DashNestBumper.b(
children: [
ScoringBehavior(points: 20),
],
)..initialPosition = Vector2(23.3, -46.75),
DashAnimatronic()..position = Vector2(20, -66),
],
) {
controller = _FlutterForestController(this);
}
}
class _FlutterForestController extends ComponentController<FlutterForest>
with HasGameRef<PinballGame> {
_FlutterForestController(FlutterForest flutterForest) : super(flutterForest);
final _activatedBumpers = <DashNestBumper>{};
void activateBumper(DashNestBumper dashNestBumper) {
if (!_activatedBumpers.add(dashNestBumper)) return;
dashNestBumper.activate();
final activatedBonus = _activatedBumpers.length == 3;
if (activatedBonus) {
_addBonusBall();
gameRef.read<GameBloc>().add(const BonusActivated(GameBonus.dashNest));
_activatedBumpers
..forEach((bumper) => bumper.deactivate())
..clear();
component.firstChild<DashAnimatronic>()?.playing = true;
}
}
Future<void> _addBonusBall() async {
await gameRef.add(
ControlledBall.bonus(characterTheme: gameRef.characterTheme)
..initialPosition = Vector2(17.2, -52.7),
);
}
}

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

@ -0,0 +1,44 @@
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';
/// When all [DashNestBumper]s are hit at least once, the [GameBonus.dashNest]
/// is awarded, and the [DashNestBumper.main] releases a new [Ball].
class FlutterForestBonusBehavior extends Component
with ParentIsA<FlutterForest>, HasGameRef<PinballGame> {
@override
void onMount() {
super.onMount();
// TODO(alestiago): Refactor subscription management once the following is
// merged:
// https://github.com/flame-engine/flame/pull/1538
parent.bloc.stream.listen(_onNewState);
final bumpers = parent.children.whereType<DashNestBumper>();
for (final bumper in bumpers) {
bumper.bloc.stream.listen((state) {
if (state == DashNestBumperState.active) {
parent.bloc.onBumperActivated(bumper);
}
});
}
}
void _onNewState(FlutterForestState state) {
if (state.hasBonus) {
gameRef.read<GameBloc>().add(const BonusActivated(GameBonus.dashNest));
gameRef.add(
ControlledBall.bonus(theme: gameRef.theme)
..initialPosition = Vector2(17.2, -52.7),
);
parent.firstChild<DashAnimatronic>()?.playing = true;
for (final bumper in state.activatedBumpers) {
bumper.bloc.onReset();
}
parent.bloc.onBonusApplied();
}
}
}

@ -0,0 +1,23 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:pinball_components/pinball_components.dart';
part 'flutter_forest_state.dart';
class FlutterForestCubit extends Cubit<FlutterForestState> {
FlutterForestCubit() : super(FlutterForestState.initial());
void onBumperActivated(DashNestBumper dashNestBumper) {
emit(
state.copyWith(
activatedBumpers: state.activatedBumpers.union({dashNestBumper}),
),
);
}
void onBonusApplied() {
emit(
state.copyWith(activatedBumpers: const {}),
);
}
}

@ -0,0 +1,26 @@
// ignore_for_file: public_member_api_docs
part of 'flutter_forest_cubit.dart';
class FlutterForestState extends Equatable {
const FlutterForestState({
required this.activatedBumpers,
});
const FlutterForestState.initial() : this(activatedBumpers: const {});
final Set<DashNestBumper> activatedBumpers;
bool get hasBonus => activatedBumpers.length == 3;
FlutterForestState copyWith({
Set<DashNestBumper>? activatedBumpers,
}) {
return FlutterForestState(
activatedBumpers: activatedBumpers ?? this.activatedBumpers,
);
}
@override
List<Object> get props => [activatedBumpers];
}

@ -0,0 +1,62 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/components/flutter_forest/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
export 'cubit/flutter_forest_cubit.dart';
/// {@template flutter_forest}
/// Area positioned at the top right of the [Board] where the [Ball]
/// can bounce off [DashNestBumper]s.
/// {@endtemplate}
class FlutterForest extends Component {
/// {@macro flutter_forest}
FlutterForest()
: bloc = FlutterForestCubit(),
super(
children: [
Signpost(
children: [
ScoringBehavior(points: 20),
],
)..initialPosition = Vector2(8.35, -58.3),
DashNestBumper.main(
children: [
ScoringBehavior(points: 20),
],
)..initialPosition = Vector2(18.55, -59.35),
DashNestBumper.a(
children: [
ScoringBehavior(points: 20),
],
)..initialPosition = Vector2(8.95, -51.95),
DashNestBumper.b(
children: [
ScoringBehavior(points: 20),
],
)..initialPosition = Vector2(23.3, -46.75),
DashAnimatronic()..position = Vector2(20, -66),
FlutterForestBonusBehavior(),
],
);
/// {@macro flutter_forest}
@visibleForTesting
FlutterForest.test({
required this.bloc,
});
// TODO(alestiago): Consider refactoring once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
// ignore: public_member_api_docs
final FlutterForestCubit bloc;
@override
void onRemove() {
bloc.close();
super.onRemove();
}
}

@ -139,7 +139,7 @@ class DebugPinballGame extends PinballGame with FPSCounter, TapDetector {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
await _loadBackground(); // await _loadBackground();
await add(_DebugInformation()); await add(_DebugInformation());
} }

@ -9,12 +9,12 @@ class AlienBumperCubit extends Cubit<AlienBumperState> {
// ignore: public_member_api_docs // ignore: public_member_api_docs
AlienBumperCubit() : super(AlienBumperState.active); AlienBumperCubit() : super(AlienBumperState.active);
/// Event added when a bumper contacts with a ball. /// Event added when the bumper contacts with a ball.
void onBallContacted() { void onBallContacted() {
emit(AlienBumperState.inactive); emit(AlienBumperState.inactive);
} }
/// Event added when a bumper finishes blinking. /// Event added when the bumper finishes blinking.
void onBlinked() { void onBlinked() {
emit(AlienBumperState.active); emit(AlienBumperState.active);
} }

@ -8,7 +8,7 @@ export 'boundaries.dart';
export 'camera_zoom.dart'; export 'camera_zoom.dart';
export 'chrome_dino.dart'; export 'chrome_dino.dart';
export 'dash_animatronic.dart'; export 'dash_animatronic.dart';
export 'dash_nest_bumper.dart'; export 'dash_nest_bumper/dash_nest_bumper.dart';
export 'dino_walls.dart'; export 'dino_walls.dart';
export 'fire_effect.dart'; export 'fire_effect.dart';
export 'flipper.dart'; export 'flipper.dart';

@ -0,0 +1,15 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
// TODO(alestiago): Evaluate if there is any useful documentation that could
// be added to this class.
// ignore: public_member_api_docs
class DashBumperBallContactBehavior extends ContactBehavior<DashNestBumper> {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
parent.bloc.onBallContacted();
}
}

@ -0,0 +1,21 @@
import 'package:bloc/bloc.dart';
part 'dash_nest_bumper_state.dart';
// TODO(alestiago): Evaluate if there is any useful documentation that could
// be added to this class.
// ignore: public_member_api_docs
class DashNestBumperCubit extends Cubit<DashNestBumperState> {
// ignore: public_member_api_docs
DashNestBumperCubit() : super(DashNestBumperState.inactive);
/// Event added when the bumper contacts with a ball.
void onBallContacted() {
emit(DashNestBumperState.active);
}
/// Event added when the bumper should return to its initial configuration.
void onReset() {
emit(DashNestBumperState.inactive);
}
}

@ -0,0 +1,10 @@
part of 'dash_nest_bumper_cubit.dart';
/// Indicates the [DashNestBumperCubit]'s current state.
enum DashNestBumperState {
/// A lit up bumper.
active,
/// A dimmed bumper.
inactive,
}

@ -4,6 +4,10 @@ import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/dash_nest_bumper/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.dart';
export 'cubit/dash_nest_bumper_cubit.dart';
/// {@template dash_nest_bumper} /// {@template dash_nest_bumper}
/// Bumper with a nest appearance. /// Bumper with a nest appearance.
@ -17,6 +21,7 @@ class DashNestBumper extends BodyComponent with InitialPosition {
required String inactiveAssetPath, required String inactiveAssetPath,
required Vector2 spritePosition, required Vector2 spritePosition,
Iterable<Component>? children, Iterable<Component>? children,
required this.bloc,
}) : _majorRadius = majorRadius, }) : _majorRadius = majorRadius,
_minorRadius = minorRadius, _minorRadius = minorRadius,
super( super(
@ -26,7 +31,9 @@ class DashNestBumper extends BodyComponent with InitialPosition {
activeAssetPath: activeAssetPath, activeAssetPath: activeAssetPath,
inactiveAssetPath: inactiveAssetPath, inactiveAssetPath: inactiveAssetPath,
position: spritePosition, position: spritePosition,
current: bloc.state,
), ),
DashBumperBallContactBehavior(),
...?children, ...?children,
], ],
renderBody: false, renderBody: false,
@ -42,6 +49,7 @@ class DashNestBumper extends BodyComponent with InitialPosition {
inactiveAssetPath: Assets.images.dash.bumper.main.inactive.keyName, inactiveAssetPath: Assets.images.dash.bumper.main.inactive.keyName,
spritePosition: Vector2(0, -0.3), spritePosition: Vector2(0, -0.3),
children: children, children: children,
bloc: DashNestBumperCubit(),
); );
/// {@macro dash_nest_bumper} /// {@macro dash_nest_bumper}
@ -54,6 +62,7 @@ class DashNestBumper extends BodyComponent with InitialPosition {
inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName, inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName,
spritePosition: Vector2(0.35, -1.2), spritePosition: Vector2(0.35, -1.2),
children: children, children: children,
bloc: DashNestBumperCubit(),
); );
/// {@macro dash_nest_bumper} /// {@macro dash_nest_bumper}
@ -66,11 +75,29 @@ class DashNestBumper extends BodyComponent with InitialPosition {
inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName, inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName,
spritePosition: Vector2(0.35, -1.2), spritePosition: Vector2(0.35, -1.2),
children: children, children: children,
bloc: DashNestBumperCubit(),
); );
/// {@macro dash_nest_bumper}
@visibleForTesting
DashNestBumper.test({required this.bloc})
: _majorRadius = 3,
_minorRadius = 2.5;
final double _majorRadius; final double _majorRadius;
final double _minorRadius; final double _minorRadius;
// TODO(alestiago): Consider refactoring once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
// ignore: public_member_api_docs
final DashNestBumperCubit bloc;
@override
void onRemove() {
bloc.close();
super.onRemove();
}
@override @override
Body createBody() { Body createBody() {
final shape = EllipseShape( final shape = EllipseShape(
@ -86,41 +113,22 @@ class DashNestBumper extends BodyComponent with InitialPosition {
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }
/// Activates the [DashNestBumper].
void activate() {
firstChild<_DashNestBumperSpriteGroupComponent>()?.current =
DashNestBumperSpriteState.active;
}
/// Deactivates the [DashNestBumper].
void deactivate() {
firstChild<_DashNestBumperSpriteGroupComponent>()?.current =
DashNestBumperSpriteState.inactive;
}
}
/// Indicates the [DashNestBumper]'s current sprite state.
@visibleForTesting
enum DashNestBumperSpriteState {
/// A lit up bumper.
active,
/// A dimmed bumper.
inactive,
} }
class _DashNestBumperSpriteGroupComponent class _DashNestBumperSpriteGroupComponent
extends SpriteGroupComponent<DashNestBumperSpriteState> with HasGameRef { extends SpriteGroupComponent<DashNestBumperState>
with HasGameRef, ParentIsA<DashNestBumper> {
_DashNestBumperSpriteGroupComponent({ _DashNestBumperSpriteGroupComponent({
required String activeAssetPath, required String activeAssetPath,
required String inactiveAssetPath, required String inactiveAssetPath,
required Vector2 position, required Vector2 position,
required DashNestBumperState current,
}) : _activeAssetPath = activeAssetPath, }) : _activeAssetPath = activeAssetPath,
_inactiveAssetPath = inactiveAssetPath, _inactiveAssetPath = inactiveAssetPath,
super( super(
anchor: Anchor.center, anchor: Anchor.center,
position: position, position: position,
current: current,
); );
final String _activeAssetPath; final String _activeAssetPath;
@ -129,15 +137,15 @@ class _DashNestBumperSpriteGroupComponent
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
parent.bloc.stream.listen((state) => current = state);
final sprites = { final sprites = {
DashNestBumperSpriteState.active: DashNestBumperState.active:
Sprite(gameRef.images.fromCache(_activeAssetPath)), Sprite(gameRef.images.fromCache(_activeAssetPath)),
DashNestBumperSpriteState.inactive: DashNestBumperState.inactive:
Sprite(gameRef.images.fromCache(_inactiveAssetPath)), Sprite(gameRef.images.fromCache(_inactiveAssetPath)),
}; };
this.sprites = sprites; this.sprites = sprites;
current = DashNestBumperSpriteState.inactive;
size = sprites[current]!.originalSize / 10; size = sprites[current]!.originalSize / 10;
} }
} }

@ -9,12 +9,12 @@ class GoogleLetterCubit extends Cubit<GoogleLetterState> {
// ignore: public_member_api_docs // ignore: public_member_api_docs
GoogleLetterCubit() : super(GoogleLetterState.inactive); GoogleLetterCubit() : super(GoogleLetterState.inactive);
/// Event added when a letter contacts with a ball. /// Event added when the letter contacts with a ball.
void onBallContacted() { void onBallContacted() {
emit(GoogleLetterState.active); emit(GoogleLetterState.active);
} }
/// Event added when a letter should return to its initial configuration. /// Event added when the letter should return to its initial configuration.
void onReset() { void onReset() {
emit(GoogleLetterState.inactive); emit(GoogleLetterState.inactive);
} }

@ -9,12 +9,12 @@ class SparkyBumperCubit extends Cubit<SparkyBumperState> {
// ignore: public_member_api_docs // ignore: public_member_api_docs
SparkyBumperCubit() : super(SparkyBumperState.active); SparkyBumperCubit() : super(SparkyBumperState.active);
/// Event added when a bumper contacts with a ball. /// Event added when the bumper contacts with a ball.
void onBallContacted() { void onBallContacted() {
emit(SparkyBumperState.inactive); emit(SparkyBumperState.inactive);
} }
/// Event added when a bumper finishes blinking. /// Event added when the bumper finishes blinking.
void onBlinked() { void onBlinked() {
emit(SparkyBumperState.active); emit(SparkyBumperState.active);
} }

@ -141,6 +141,9 @@ class _SparkyBumperSpriteGroupComponent
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
// TODO(alestiago): Consider refactoring once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
// ignore: public_member_api_docs
parent.bloc.stream.listen((state) => current = state); parent.bloc.stream.listen((state) => current = state);
final sprites = { final sprites = {

@ -40,40 +40,6 @@ void main() {
expect(game.contains(bumper), isTrue); expect(game.contains(bumper), isTrue);
}); });
flameTester.test('activate switches to active sprite', (game) async {
final bumper = DashNestBumper.main();
await game.ensureAdd(bumper);
final spriteGroupComponent = bumper.firstChild<SpriteGroupComponent>()!;
expect(
spriteGroupComponent.current,
equals(DashNestBumperSpriteState.inactive),
);
bumper.activate();
expect(
spriteGroupComponent.current,
equals(DashNestBumperSpriteState.active),
);
});
flameTester.test('deactivate switches to inactive sprite', (game) async {
final bumper = DashNestBumper.main();
await game.ensureAdd(bumper);
final spriteGroupComponent = bumper.firstChild<SpriteGroupComponent>()!
..current = DashNestBumperSpriteState.active;
bumper.deactivate();
expect(
spriteGroupComponent.current,
equals(DashNestBumperSpriteState.inactive),
);
});
flameTester.test('adds new children', (game) async { flameTester.test('adds new children', (game) async {
final component = Component(); final component = Component();
final dashNestBumper = DashNestBumper.a( final dashNestBumper = DashNestBumper.a(

@ -8,9 +8,7 @@ import 'package:pinball_flame/pinball_flame.dart';
class _TestBodyComponent extends BodyComponent { class _TestBodyComponent extends BodyComponent {
@override @override
Body createBody() { Body createBody() => world.createBody(BodyDef());
return world.createBody(BodyDef());
}
} }
class _TestContactBehavior extends ContactBehavior { class _TestContactBehavior extends ContactBehavior {

@ -1,10 +1,10 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.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:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../helpers/helpers.dart'; import '../../helpers/helpers.dart';
@ -24,10 +24,8 @@ void main() {
flameTester.test( flameTester.test(
'loads correctly', 'loads correctly',
(game) async { (game) async {
final alienZone = AlienZone(); await game.addFromBlueprint(SparkyFireZone());
await game.ensureAdd(alienZone); await game.ready();
expect(game.contains(alienZone), isTrue);
}, },
); );
@ -36,33 +34,15 @@ void main() {
'two AlienBumper', 'two AlienBumper',
(game) async { (game) async {
final alienZone = AlienZone(); final alienZone = AlienZone();
await game.ensureAdd(alienZone); await game.addFromBlueprint(alienZone);
await game.ready();
expect( expect(
alienZone.descendants().whereType<AlienBumper>().length, game.descendants().whereType<AlienBumper>().length,
equals(2), equals(2),
); );
}, },
); );
}); });
group('bumpers', () {
late GameBloc gameBloc;
setUp(() {
gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
});
}); });
} }

@ -80,72 +80,5 @@ void main() {
}, },
); );
}); });
group('bumpers', () {
late GameBloc gameBloc;
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: () => EmptyPinballTestGame(assets: assets),
blocBuilder: () {
gameBloc = MockGameBloc();
const state = GameState.initial();
whenListen(gameBloc, Stream.value(state), initialState: state);
return gameBloc;
},
assets: assets,
);
flameBlocTester.testGameWidget(
'adds GameBonus.dashNest to the game when 3 bumpers are activated',
setUp: (game, _) async {
final ball = Ball(baseColor: const Color(0xFFFF0000));
final flutterForest = FlutterForest();
await game.ensureAddAll([flutterForest, ball]);
final bumpers = flutterForest.children.whereType<DashNestBumper>();
expect(bumpers.length, equals(3));
for (final bumper in bumpers) {
beginContact(game, bumper, ball);
await game.ready();
if (bumper == bumpers.last) {
verify(
() => gameBloc.add(const BonusActivated(GameBonus.dashNest)),
).called(1);
} else {
verifyNever(
() => gameBloc.add(const BonusActivated(GameBonus.dashNest)),
);
}
}
},
);
flameBlocTester.testGameWidget(
'deactivates bumpers when 3 are active',
setUp: (game, _) async {
final ball = Ball(baseColor: const Color(0xFFFF0000));
final flutterForest = FlutterForest();
await game.ensureAddAll([flutterForest, ball]);
final bumpers = [
MockDashNestBumper(),
MockDashNestBumper(),
MockDashNestBumper(),
];
for (final bumper in bumpers) {
flutterForest.controller.activateBumper(bumper);
await game.ready();
if (bumper == bumpers.last) {
for (final bumper in bumpers) {
verify(bumper.deactivate).called(1);
}
}
}
},
);
});
}); });
} }

@ -11,12 +11,18 @@ import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart'; import '../../helpers/helpers.dart';
class _TestBodyComponent extends BodyComponent {
@override
Body createBody() => world.createBody(BodyDef());
}
void main() { void main() {
group('ScoringBehavior', () { group('ScoringBehavior', () {
group('beginContact', () { group('beginContact', () {
late GameBloc bloc; late GameBloc bloc;
late PinballAudio audio; late PinballAudio audio;
late Ball ball; late Ball ball;
late BodyComponent parent;
setUp(() { setUp(() {
audio = MockPinballAudio(); audio = MockPinballAudio();
@ -25,6 +31,8 @@ void main() {
final ballBody = MockBody(); final ballBody = MockBody();
when(() => ball.body).thenReturn(ballBody); when(() => ball.body).thenReturn(ballBody);
when(() => ballBody.position).thenReturn(Vector2.all(4)); when(() => ballBody.position).thenReturn(Vector2.all(4));
parent = _TestBodyComponent();
}); });
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>( final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
@ -48,7 +56,8 @@ void main() {
setUp: (game, tester) async { setUp: (game, tester) async {
const points = 20; const points = 20;
final scoringBehavior = ScoringBehavior(points: points); final scoringBehavior = ScoringBehavior(points: points);
await game.ensureAdd(scoringBehavior); await parent.add(scoringBehavior);
await game.ensureAdd(parent);
scoringBehavior.beginContact(ball, MockContact()); scoringBehavior.beginContact(ball, MockContact());
@ -65,7 +74,8 @@ void main() {
setUp: (game, tester) async { setUp: (game, tester) async {
const points = 20; const points = 20;
final scoringBehavior = ScoringBehavior(points: points); final scoringBehavior = ScoringBehavior(points: points);
await game.ensureAdd(scoringBehavior); await parent.add(scoringBehavior);
await game.ensureAdd(parent);
scoringBehavior.beginContact(ball, MockContact()); scoringBehavior.beginContact(ball, MockContact());
@ -78,7 +88,8 @@ void main() {
setUp: (game, tester) async { setUp: (game, tester) async {
const points = 20; const points = 20;
final scoringBehavior = ScoringBehavior(points: points); final scoringBehavior = ScoringBehavior(points: points);
await game.ensureAdd(scoringBehavior); await parent.add(scoringBehavior);
await game.ensureAdd(parent);
scoringBehavior.beginContact(ball, MockContact()); scoringBehavior.beginContact(ball, MockContact());
await game.ready(); await game.ready();

@ -1,6 +1,5 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.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';
@ -71,54 +70,40 @@ void main() {
}, },
); );
}); });
group('bumpers', () {
late GameBloc gameBloc;
setUp(() {
gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
});
}); });
group('SparkyTurboChargeSensorBallContactCallback', () { group('SparkyComputerSensor', () {
flameTester.test('calls turboCharge', (game) async { flameTester.test('calls turboCharge', (game) async {
final sensor = SparkyComputerSensor();
final ball = MockControlledBall(); final ball = MockControlledBall();
final controller = MockBallController(); final controller = MockBallController();
when(() => ball.controller).thenReturn(controller); when(() => ball.controller).thenReturn(controller);
when(() => ball.gameRef).thenReturn(game);
when(controller.turboCharge).thenAnswer((_) async {}); when(controller.turboCharge).thenAnswer((_) async {});
await game.ensureAddAll([
sensor,
SparkyAnimatronic(),
]);
sensor.beginContact(ball, MockContact());
verify(() => ball.controller.turboCharge()).called(1); verify(() => ball.controller.turboCharge()).called(1);
}); });
flameTester.test('plays SparkyAnimatronic', (game) async { flameTester.test('plays SparkyAnimatronic', (game) async {
final sensor = SparkyComputerSensor();
final sparkyAnimatronic = SparkyAnimatronic();
final ball = MockControlledBall(); final ball = MockControlledBall();
final controller = MockBallController(); final controller = MockBallController();
when(() => ball.controller).thenReturn(controller); when(() => ball.controller).thenReturn(controller);
when(() => ball.gameRef).thenReturn(game);
when(controller.turboCharge).thenAnswer((_) async {}); when(controller.turboCharge).thenAnswer((_) async {});
await game.ensureAddAll([
final sparkyFireZone = SparkyFireZone(); sensor,
await game.addFromBlueprint(sparkyFireZone); sparkyAnimatronic,
await game.ready(); ]);
final sparkyAnimatronic =
sparkyFireZone.components.whereType<SparkyAnimatronic>().single;
expect(sparkyAnimatronic.playing, isFalse); expect(sparkyAnimatronic.playing, isFalse);
sensor.beginContact(ball, MockContact());
expect(sparkyAnimatronic.playing, isTrue); expect(sparkyAnimatronic.playing, isTrue);
}); });
}); });

@ -66,6 +66,8 @@ void main() {
group('PinballGame', () { group('PinballGame', () {
group('components', () { group('components', () {
// TODO(alestiago): tests that Blueprints get added once the Blueprint
// class is removed.
flameTester.test( flameTester.test(
'has only one BottomWall', 'has only one BottomWall',
(game) async { (game) async {
@ -97,14 +99,6 @@ void main() {
); );
}); });
flameTester.test(
'one AlienZone',
(game) async {
await game.ready();
expect(game.children.whereType<AlienZone>().length, equals(1));
},
);
group('controller', () { group('controller', () {
// TODO(alestiago): Write test to be controller agnostic. // TODO(alestiago): Write test to be controller agnostic.
group('listenWhen', () { group('listenWhen', () {

Loading…
Cancel
Save