diff --git a/lib/game/components/sparky_fire_zone.dart b/lib/game/components/sparky_fire_zone.dart index 9d88f0f5..ee8da614 100644 --- a/lib/game/components/sparky_fire_zone.dart +++ b/lib/game/components/sparky_fire_zone.dart @@ -1,21 +1,67 @@ // ignore_for_file: avoid_renaming_method_parameters import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; import 'package:pinball/flame/flame.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; -// TODO(ruimiguel): create and add SparkyFireZone component here in other PR. +/// {@template sparky_fire_zone} +/// Area positioned at the top left of the [Board] where the [Ball] +/// can bounce off [SparkyBumper]s. +/// +/// When a [Ball] hits [SparkyBumper]s, they toggle between activated and +/// deactivated states. +/// {@endtemplate} +class SparkyFireZone extends Component with HasGameRef { + /// {@macro sparky_fire_zone} + SparkyFireZone(); + + @override + Future onLoad() async { + await super.onLoad(); + + gameRef.addContactCallback(_ControlledSparkyBumperBallContactCallback()); -// TODO(ruimiguel): make private and remove ignore once SparkyFireZone is done -// ignore: public_member_api_docs + final lowerLeftBumper = ControlledSparkyBumper.a() + ..initialPosition = Vector2(-23.15, 41.65); + final upperLeftBumper = ControlledSparkyBumper.b() + ..initialPosition = Vector2(-21.25, 58.15); + final rightBumper = ControlledSparkyBumper.c() + ..initialPosition = Vector2(-3.56, 53.051); + + await addAll([ + lowerLeftBumper, + upperLeftBumper, + rightBumper, + ]); + } +} + +/// {@template controlled_sparky_bumper} +/// [SparkyBumper] with [_SparkyBumperController] attached. +/// {@endtemplate} +@visibleForTesting class ControlledSparkyBumper extends SparkyBumper - with Controls<_SparkyBumperController> { - // TODO(ruimiguel): make private and remove ignore once SparkyFireZone is done - // ignore: public_member_api_docs - ControlledSparkyBumper() : super.a() { + with Controls<_SparkyBumperController>, ScorePoints { + ///{@macro controlled_sparky_bumper} + ControlledSparkyBumper.a() : super.a() { + controller = _SparkyBumperController(this); + } + + ///{@macro controlled_sparky_bumper} + ControlledSparkyBumper.b() : super.b() { + controller = _SparkyBumperController(this); + } + + ///{@macro controlled_sparky_bumper} + ControlledSparkyBumper.c() : super.c() { controller = _SparkyBumperController(this); } + + @override + int get points => 20; } /// {@template sparky_bumper_controller} @@ -42,3 +88,16 @@ class _SparkyBumperController extends ComponentController isActivated = !isActivated; } } + +/// Listens when a [Ball] bounces bounces against a [SparkyBumper]. +class _ControlledSparkyBumperBallContactCallback + extends ContactCallback, Ball> { + @override + void begin( + Controls<_SparkyBumperController> controlledSparkyBumper, + Ball _, + Contact __, + ) { + controlledSparkyBumper.controller.hit(); + } +} diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 49992653..27a56743 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -52,6 +52,7 @@ class PinballGame extends Forge2DGame await add(plunger); unawaited(add(Board())); + unawaited(add(SparkyFireZone())); unawaited(addFromBlueprint(Slingshots())); unawaited(addFromBlueprint(DinoWalls())); unawaited(_addBonusWord()); diff --git a/test/game/components/sparky_fire_zone_test.dart b/test/game/components/sparky_fire_zone_test.dart index dceaa9cc..da8d8404 100644 --- a/test/game/components/sparky_fire_zone_test.dart +++ b/test/game/components/sparky_fire_zone_test.dart @@ -1,8 +1,13 @@ // ignore_for_file: cascade_invocations +import 'dart:ui'; + +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/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; import '../../helpers/helpers.dart'; @@ -11,13 +16,57 @@ void main() { final flameTester = FlameTester(EmptyPinballGameTest.new); group('SparkyFireZone', () { + flameTester.test( + 'loads correctly', + (game) async { + await game.ready(); + final sparkyFireZone = SparkyFireZone(); + await game.ensureAdd(sparkyFireZone); + + expect(game.contains(sparkyFireZone), isTrue); + }, + ); + + group('loads', () { + flameTester.test( + 'three SparkyBumper', + (game) async { + await game.ready(); + final sparkyFireZone = SparkyFireZone(); + await game.ensureAdd(sparkyFireZone); + + expect( + sparkyFireZone.descendants().whereType().length, + equals(3), + ); + }, + ); + }); + group('bumpers', () { late ControlledSparkyBumper controlledSparkyBumper; + late Ball ball; + late GameBloc gameBloc; + + setUp(() { + ball = Ball(baseColor: const Color(0xFF00FFFF)); + gameBloc = MockGameBloc(); + whenListen( + gameBloc, + const Stream.empty(), + initialState: const GameState.initial(), + ); + }); + + final flameBlocTester = FlameBlocTester( + gameBuilder: EmptyPinballGameTest.new, + blocBuilder: () => gameBloc, + ); flameTester.testGameWidget( 'activate when deactivated bumper is hit', setUp: (game, tester) async { - controlledSparkyBumper = ControlledSparkyBumper(); + controlledSparkyBumper = ControlledSparkyBumper.a(); await game.ensureAdd(controlledSparkyBumper); controlledSparkyBumper.controller.hit(); @@ -30,7 +79,7 @@ void main() { flameTester.testGameWidget( 'deactivate when activated bumper is hit', setUp: (game, tester) async { - controlledSparkyBumper = ControlledSparkyBumper(); + controlledSparkyBumper = ControlledSparkyBumper.a(); await game.ensureAdd(controlledSparkyBumper); controlledSparkyBumper.controller.hit(); @@ -40,6 +89,27 @@ void main() { expect(controlledSparkyBumper.controller.isActivated, isFalse); }, ); + + flameBlocTester.testGameWidget( + 'add Scored event', + setUp: (game, tester) async { + final sparkyFireZone = SparkyFireZone(); + await game.ensureAdd(sparkyFireZone); + await game.ensureAdd(ball); + game.addContactCallback(BallScorePointsCallback(game)); + + final bumpers = sparkyFireZone.descendants().whereType(); + + for (final bumper in bumpers) { + beginContact(game, bumper, ball); + verify( + () => gameBloc.add( + Scored(points: bumper.points), + ), + ).called(1); + } + }, + ); }); }); } diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index d83bb396..2dfd5d76 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -62,6 +62,14 @@ void main() { ); }); + flameTester.test( + 'one SparkyFireZone', + (game) async { + await game.ready(); + expect(game.children.whereType().length, equals(1)); + }, + ); + group('controller', () { // TODO(alestiago): Write test to be controller agnostic. group('listenWhen', () {