refactor: multiballs group refactored

pull/235/head
RuiAlonso 3 years ago
parent 2bd3061ebf
commit dee70426d5

@ -3,12 +3,12 @@ export 'board.dart';
export 'camera_controller.dart';
export 'controlled_ball.dart';
export 'controlled_flipper.dart';
export 'controlled_multiball.dart';
export 'controlled_plunger.dart';
export 'flutter_forest/flutter_forest.dart';
export 'game_flow_controller.dart';
export 'google_word/google_word.dart';
export 'launcher.dart';
export 'multiballs/multiballs.dart';
export 'scoring_behavior.dart';
export 'sparky_fire_zone.dart';
export 'wall.dart';

@ -1,77 +0,0 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template multiball_group_component}
/// A [SpriteGroupComponent] for the multiball over the board.
/// {@endtemplate}
class MultiballGroup extends Component
with Controls<MultiballController>, HasGameRef<PinballGame> {
/// {@macro multiball_group_component}
MultiballGroup() : super() {
controller = MultiballController(this);
}
/// Bottom left multiball.
late final Multiball multiballA;
/// Center left multiball.
late final Multiball multiballB;
/// Center right multiball.
late final Multiball multiballC;
/// Bottom right multiball.
late final Multiball multiballD;
@override
Future<void> onLoad() async {
await super.onLoad();
multiballA = Multiball.a();
multiballB = Multiball.b();
multiballC = Multiball.c();
multiballD = Multiball.d();
await addAll([
multiballA,
multiballB,
multiballC,
multiballD,
]);
}
}
/// {@template multiball_controller}
/// Controller attached to a [MultiballGroup] that handles its game related
/// logic.
/// {@endtemplate}
@visibleForTesting
class MultiballController extends ComponentController<MultiballGroup>
with BlocComponent<GameBloc, GameState>, HasGameRef<PinballGame> {
/// {@macro multiball_controller}
MultiballController(MultiballGroup multiballGroup) : super(multiballGroup);
@override
bool listenWhen(GameState? previousState, GameState newState) {
return previousState?.bonusHistory != newState.bonusHistory;
}
@override
void onNewState(GameState state) {
final hasMultiball = state.bonusHistory.contains(GameBonus.dashNest);
if (hasMultiball) {
// TODO(ruimiguel): change to animate every children without different
// properties using component.children.whereType<Multiball>().forEach
// once able to mock the children ComponentSet.
component.multiballA.animate();
component.multiballB.animate();
component.multiballC.animate();
component.multiballD.animate();
}
}
}

@ -0,0 +1 @@
export 'multiballs_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';
/// Toggle each [Multiball] when there is a bonus ball.
class MultiballsBehavior extends Component
with HasGameRef<PinballGame>, ParentIsA<Multiballs> {
@override
void onMount() {
super.onMount();
gameRef.read<GameBloc>().stream.listen((state) {
final hasMultiball = state.bonusHistory.contains(GameBonus.dashNest);
if (hasMultiball) {
final multiballs = parent.children.whereType<Multiball>();
for (final multiball in multiballs) {
multiball.bloc.animate();
}
}
});
}
}

@ -0,0 +1,27 @@
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/components/multiballs/behaviors/behaviors.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template multiballs_component}
/// A [SpriteGroupComponent] for the multiball over the board.
/// {@endtemplate}
class Multiballs extends Component {
/// {@macro multiballs_component}
Multiballs()
: super(
children: [
Multiball.a(),
Multiball.b(),
Multiball.c(),
Multiball.d(),
MultiballsBehavior(),
],
);
/// Creates a [Multiballs] without any children.
///
/// This can be used for testing [Multiballs]'s behaviors in isolation.
@visibleForTesting
Multiballs.test();
}

@ -53,7 +53,7 @@ class PinballGame extends Forge2DGame
final launcher = Launcher();
unawaited(addFromBlueprint(launcher));
unawaited(add(Board()));
unawaited(add(MultiballGroup()));
await add(Multiballs());
await addFromBlueprint(AlienZone());
await addFromBlueprint(SparkyFireZone());

@ -37,3 +37,17 @@ class MultiballBlinkingBehavior extends TimerComponent
parent.bloc.onBlinked();
}
}
/*
/// Animates the [Multiball].
Future<void> animate() async {
final spriteGroupComponent = firstChild<MultiballSpriteGroupComponent>();
for (var i = 0; i < 5; i++) {
spriteGroupComponent?.current = MultiballState.lit;
await Future<void>.delayed(const Duration(milliseconds: 100));
spriteGroupComponent?.current = MultiballState.dimmed;
await Future<void>.delayed(const Duration(milliseconds: 100));
}
}
*/

@ -102,18 +102,6 @@ class Multiball extends Component {
bloc.close();
super.onRemove();
}
/// Animates the [Multiball].
Future<void> animate() async {
final spriteGroupComponent = firstChild<MultiballSpriteGroupComponent>();
for (var i = 0; i < 5; i++) {
spriteGroupComponent?.current = MultiballState.lit;
await Future<void>.delayed(const Duration(milliseconds: 100));
spriteGroupComponent?.current = MultiballState.dimmed;
await Future<void>.delayed(const Duration(milliseconds: 100));
}
}
}
/// {@template multiball_sprite_group_component}

@ -8,14 +8,14 @@ class MultiballGame extends BallGame with KeyboardEvents {
MultiballGame()
: super(
imagesFileNames: [
Assets.images.multiball.a.active.keyName,
Assets.images.multiball.a.inactive.keyName,
Assets.images.multiball.b.active.keyName,
Assets.images.multiball.b.inactive.keyName,
Assets.images.multiball.c.active.keyName,
Assets.images.multiball.c.inactive.keyName,
Assets.images.multiball.d.active.keyName,
Assets.images.multiball.d.inactive.keyName,
Assets.images.multiball.a.lit.keyName,
Assets.images.multiball.a.dimmed.keyName,
Assets.images.multiball.b.lit.keyName,
Assets.images.multiball.b.dimmed.keyName,
Assets.images.multiball.c.lit.keyName,
Assets.images.multiball.c.dimmed.keyName,
Assets.images.multiball.d.lit.keyName,
Assets.images.multiball.d.dimmed.keyName,
],
);
@ -23,13 +23,15 @@ class MultiballGame extends BallGame with KeyboardEvents {
Shows how the Multiball are rendered.
- Tap anywhere on the screen to spawn a ball into the game.
- Press space bar for animate state multiballs.
- Press space bar to animate multiballs.
''';
late final Multiball _multiballA;
late final Multiball _multiballB;
late final Multiball _multiballC;
late final Multiball _multiballD;
final List<Multiball> multiballs = [
Multiball.a(),
Multiball.b(),
Multiball.c(),
Multiball.d(),
];
@override
Future<void> onLoad() async {
@ -37,12 +39,7 @@ class MultiballGame extends BallGame with KeyboardEvents {
camera.followVector2(Vector2.zero());
await addAll([
_multiballA = Multiball.a(),
_multiballB = Multiball.b(),
_multiballC = Multiball.c(),
_multiballD = Multiball.d(),
]);
await addAll(multiballs);
await traceAllBodies();
}
@ -53,10 +50,9 @@ class MultiballGame extends BallGame with KeyboardEvents {
) {
if (event is RawKeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.space) {
_multiballA.animate();
_multiballB.animate();
_multiballC.animate();
_multiballD.animate();
for (final multiball in multiballs) {
multiball.bloc.animate();
}
return KeyEventResult.handled;
}

@ -1,124 +0,0 @@
// ignore_for_file: cascade_invocations
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.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.multiball.a.active.keyName,
Assets.images.multiball.a.inactive.keyName,
Assets.images.multiball.b.active.keyName,
Assets.images.multiball.b.inactive.keyName,
Assets.images.multiball.c.active.keyName,
Assets.images.multiball.c.inactive.keyName,
Assets.images.multiball.d.active.keyName,
Assets.images.multiball.d.inactive.keyName,
];
final flameTester = FlameTester(() => EmptyPinballTestGame(assets));
group('MultiballGroup', () {
flameTester.test(
'loads correctly',
(game) async {
final multiballGroup = MultiballGroup();
await game.ensureAdd(multiballGroup);
expect(game.contains(multiballGroup), isTrue);
},
);
group('loads', () {
flameTester.test(
'four Multiball',
(game) async {
final multiballGroup = MultiballGroup();
await game.ensureAdd(multiballGroup);
expect(
multiballGroup.descendants().whereType<Multiball>().length,
equals(4),
);
},
);
});
});
group('MultiballController', () {
group('controller', () {
group('listenWhen', () {
flameTester.test(
'listens when obtain a multiball bonus',
(game) async {
const previous = GameState.initial();
final state = previous.copyWith(bonusHistory: [GameBonus.dashNest]);
final multiballGroup = MultiballGroup();
await game.ensureAdd(multiballGroup);
expect(
multiballGroup.controller.listenWhen(previous, state),
isTrue,
);
},
);
flameTester.test(
"doesn't listen when bonus is the same",
(game) async {
const previous = GameState.initial();
final multiballGroup = MultiballGroup();
await game.ensureAdd(multiballGroup);
expect(
multiballGroup.controller.listenWhen(previous, previous),
isFalse,
);
},
);
});
group(
'onNewState',
() {
flameTester.test(
'blink multiballs when state changes',
(game) async {
// TODO(ruimiguel): search how to mock MultiballGroup children
// ComponentSet to improve this test.
final multiballGroup = MockMultiballGroup();
final multiballA = MockMultiball();
final multiballB = MockMultiball();
final multiballC = MockMultiball();
final multiballD = MockMultiball();
final controller = MultiballController(multiballGroup);
when(() => multiballGroup.multiballA).thenReturn(multiballA);
when(() => multiballGroup.multiballB).thenReturn(multiballB);
when(() => multiballGroup.multiballC).thenReturn(multiballC);
when(() => multiballGroup.multiballD).thenReturn(multiballD);
when(multiballA.animate).thenAnswer((_) async => () {});
when(multiballB.animate).thenAnswer((_) async => () {});
when(multiballC.animate).thenAnswer((_) async => () {});
when(multiballD.animate).thenAnswer((_) async => () {});
controller.onNewState(
const GameState.initial()
.copyWith(bonusHistory: [GameBonus.dashNest]),
);
verify(multiballA.animate).called(1);
verify(multiballB.animate).called(1);
verify(multiballC.animate).called(1);
verify(multiballD.animate).called(1);
},
);
},
);
});
});
}

@ -0,0 +1,43 @@
import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/components/multiballs/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.multiball.a.lit.keyName,
Assets.images.multiball.a.dimmed.keyName,
Assets.images.multiball.b.lit.keyName,
Assets.images.multiball.b.dimmed.keyName,
Assets.images.multiball.c.lit.keyName,
Assets.images.multiball.c.dimmed.keyName,
Assets.images.multiball.d.lit.keyName,
Assets.images.multiball.d.dimmed.keyName,
];
group('MultiballsBehavior', () {
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,
);
});
}

@ -0,0 +1,61 @@
// ignore_for_file: cascade_invocations
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.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.multiball.a.lit.keyName,
Assets.images.multiball.a.dimmed.keyName,
Assets.images.multiball.b.lit.keyName,
Assets.images.multiball.b.dimmed.keyName,
Assets.images.multiball.c.lit.keyName,
Assets.images.multiball.c.dimmed.keyName,
Assets.images.multiball.d.lit.keyName,
Assets.images.multiball.d.dimmed.keyName,
];
late GameBloc gameBloc;
setUp(() {
gameBloc = GameBloc();
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
group('Multiballs', () {
flameBlocTester.testGameWidget(
'loads correctly',
setUp: (game, tester) async {
final multiballs = Multiballs();
await game.ensureAdd(multiballs);
expect(game.contains(multiballs), isTrue);
},
);
group('loads', () {
flameBlocTester.testGameWidget(
'four Multiball',
setUp: (game, tester) async {
final multiballs = Multiballs();
await game.ensureAdd(multiballs);
expect(
multiballs.descendants().whereType<Multiball>().length,
equals(4),
);
},
);
});
});
}

@ -56,14 +56,14 @@ void main() {
Assets.images.slingshot.lower.keyName,
Assets.images.dino.dinoLandTop.keyName,
Assets.images.dino.dinoLandBottom.keyName,
Assets.images.multiball.a.active.keyName,
Assets.images.multiball.a.inactive.keyName,
Assets.images.multiball.b.active.keyName,
Assets.images.multiball.b.inactive.keyName,
Assets.images.multiball.c.active.keyName,
Assets.images.multiball.c.inactive.keyName,
Assets.images.multiball.d.active.keyName,
Assets.images.multiball.d.inactive.keyName,
Assets.images.multiball.a.lit.keyName,
Assets.images.multiball.a.dimmed.keyName,
Assets.images.multiball.b.lit.keyName,
Assets.images.multiball.b.dimmed.keyName,
Assets.images.multiball.c.lit.keyName,
Assets.images.multiball.c.dimmed.keyName,
Assets.images.multiball.d.lit.keyName,
Assets.images.multiball.d.dimmed.keyName,
];
final flameTester = FlameTester(
() => PinballTestGame(assets: assets),
@ -108,12 +108,12 @@ void main() {
});
flameTester.test(
'has only one Multiball',
'has only one Multiballs',
(game) async {
await game.ready();
expect(
game.children.whereType<MultiballGroup>().length,
game.children.whereType<Multiballs>().length,
equals(1),
);
},

@ -88,4 +88,4 @@ class MockSparkyBumper extends Mock implements SparkyBumper {}
class MockMultiball extends Mock implements Multiball {}
class MockMultiballGroup extends Mock implements MultiballGroup {}
class MockMultiballGroup extends Mock implements Multiballs {}

Loading…
Cancel
Save