diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 324f379a..969ea1ac 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -6,7 +6,7 @@ export 'dino_desert/dino_desert.dart'; export 'drain/drain.dart'; export 'flutter_forest/flutter_forest.dart'; export 'game_bloc_status_listener.dart'; -export 'google_word/google_word.dart'; +export 'google_gallery/google_gallery.dart'; export 'launcher.dart'; export 'multiballs/multiballs.dart'; export 'multipliers/multipliers.dart'; diff --git a/lib/game/components/google_word/behaviors/behaviors.dart b/lib/game/components/google_gallery/behaviors/behaviors.dart similarity index 100% rename from lib/game/components/google_word/behaviors/behaviors.dart rename to lib/game/components/google_gallery/behaviors/behaviors.dart diff --git a/lib/game/components/google_gallery/behaviors/google_word_bonus_behavior.dart b/lib/game/components/google_gallery/behaviors/google_word_bonus_behavior.dart new file mode 100644 index 00000000..abb6de1e --- /dev/null +++ b/lib/game/components/google_gallery/behaviors/google_word_bonus_behavior.dart @@ -0,0 +1,24 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// Adds a [GameBonus.googleWord] when all [GoogleLetter]s are activated. +class GoogleWordBonusBehavior extends Component { + @override + Future onLoad() async { + await super.onLoad(); + await add( + FlameBlocListener( + listenWhen: (_, state) => state.letterSpriteStates.values + .every((element) => element == GoogleLetterSpriteState.lit), + onNewState: (state) { + readBloc() + .add(const BonusActivated(GameBonus.googleWord)); + readBloc().onBonusAwarded(); + }, + ), + ); + } +} diff --git a/lib/game/components/google_gallery/google_gallery.dart b/lib/game/components/google_gallery/google_gallery.dart new file mode 100644 index 00000000..0b3d4b10 --- /dev/null +++ b/lib/game/components/google_gallery/google_gallery.dart @@ -0,0 +1,47 @@ +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/google_gallery/behaviors/behaviors.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// {@template google_gallery} +/// Middle section of the board containing the [GoogleWord] and the +/// [GoogleRollover]s. +/// {@endtemplate} +class GoogleGallery extends Component with ZIndex { + /// {@macro google_gallery} + GoogleGallery() + : super( + children: [ + FlameBlocProvider( + create: GoogleWordCubit.new, + children: [ + GoogleRollover( + side: BoardSide.right, + children: [ + ScoringContactBehavior(points: Points.fiveThousand), + ], + ), + GoogleRollover( + side: BoardSide.left, + children: [ + ScoringContactBehavior(points: Points.fiveThousand), + ], + ), + GoogleWord(position: Vector2(-4.45, 1.8)), + GoogleWordBonusBehavior(), + ], + ), + ], + ) { + zIndex = ZIndexes.decal; + } + + /// Creates a [GoogleGallery] without any children. + /// + /// This can be used for testing [GoogleGallery]'s behaviors in isolation. + @visibleForTesting + GoogleGallery.test(); +} diff --git a/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart b/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart deleted file mode 100644 index c1c14ed5..00000000 --- a/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flame_bloc/flame_bloc.dart'; -import 'package:pinball/game/game.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -/// Adds a [GameBonus.googleWord] when all [GoogleLetter]s are activated. -class GoogleWordBonusBehavior extends Component - with ParentIsA, FlameBlocReader { - @override - void onMount() { - super.onMount(); - - final googleLetters = parent.children.whereType(); - for (final letter in googleLetters) { - letter.bloc.stream.listen((_) { - final achievedBonus = googleLetters - .every((letter) => letter.bloc.state == GoogleLetterState.lit); - - if (achievedBonus) { - bloc.add(const BonusActivated(GameBonus.googleWord)); - for (final letter in googleLetters) { - letter.bloc.onReset(); - } - } - }); - } - } -} diff --git a/lib/game/components/google_word/google_word.dart b/lib/game/components/google_word/google_word.dart deleted file mode 100644 index 76bac244..00000000 --- a/lib/game/components/google_word/google_word.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flutter/material.dart'; -import 'package:pinball/game/behaviors/scoring_behavior.dart'; -import 'package:pinball/game/components/google_word/behaviors/behaviors.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -/// {@template google_word} -/// Loads all [GoogleLetter]s to compose a [GoogleWord]. -/// {@endtemplate} -class GoogleWord extends Component with ZIndex { - /// {@macro google_word} - GoogleWord({ - required Vector2 position, - }) : super( - children: [ - GoogleLetter( - 0, - children: [ScoringContactBehavior(points: Points.fiveThousand)], - )..initialPosition = position + Vector2(-13.1, 1.72), - GoogleLetter( - 1, - children: [ScoringContactBehavior(points: Points.fiveThousand)], - )..initialPosition = position + Vector2(-8.33, -0.75), - GoogleLetter( - 2, - children: [ScoringContactBehavior(points: Points.fiveThousand)], - )..initialPosition = position + Vector2(-2.88, -1.85), - GoogleLetter( - 3, - children: [ScoringContactBehavior(points: Points.fiveThousand)], - )..initialPosition = position + Vector2(2.88, -1.85), - GoogleLetter( - 4, - children: [ScoringContactBehavior(points: Points.fiveThousand)], - )..initialPosition = position + Vector2(8.33, -0.75), - GoogleLetter( - 5, - children: [ScoringContactBehavior(points: Points.fiveThousand)], - )..initialPosition = position + Vector2(13.1, 1.72), - GoogleWordBonusBehavior(), - ], - ) { - zIndex = ZIndexes.decal; - } - - /// Creates a [GoogleWord] without any children. - /// - /// This can be used for testing [GoogleWord]'s behaviors in isolation. - @visibleForTesting - GoogleWord.test(); -} diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index 15a52afb..a770985b 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -118,6 +118,10 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.googleWord.letter5.dimmed.keyName), images.load(components.Assets.images.googleWord.letter6.lit.keyName), images.load(components.Assets.images.googleWord.letter6.dimmed.keyName), + images.load(components.Assets.images.googleRollover.left.decal.keyName), + images.load(components.Assets.images.googleRollover.left.pin.keyName), + images.load(components.Assets.images.googleRollover.right.decal.keyName), + images.load(components.Assets.images.googleRollover.right.pin.keyName), images.load(components.Assets.images.multiball.lit.keyName), images.load(components.Assets.images.multiball.dimmed.keyName), images.load(components.Assets.images.multiplier.x2.lit.keyName), diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 8a213d31..751b8d8e 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -114,7 +114,7 @@ class PinballGame extends PinballForge2DGame shareRepository: shareRepository, entries: _entries, ), - GoogleWord(position: Vector2(-4.45, 1.8)), + GoogleGallery(), Multipliers(), Multiballs(), SkillShot( diff --git a/packages/pinball_components/assets/images/google_rollover/left/decal.png b/packages/pinball_components/assets/images/google_rollover/left/decal.png new file mode 100644 index 00000000..da503131 Binary files /dev/null and b/packages/pinball_components/assets/images/google_rollover/left/decal.png differ diff --git a/packages/pinball_components/assets/images/google_rollover/left/pin.png b/packages/pinball_components/assets/images/google_rollover/left/pin.png new file mode 100644 index 00000000..50a95a6b Binary files /dev/null and b/packages/pinball_components/assets/images/google_rollover/left/pin.png differ diff --git a/packages/pinball_components/assets/images/google_rollover/right/decal.png b/packages/pinball_components/assets/images/google_rollover/right/decal.png new file mode 100644 index 00000000..68496caf Binary files /dev/null and b/packages/pinball_components/assets/images/google_rollover/right/decal.png differ diff --git a/packages/pinball_components/assets/images/google_rollover/right/pin.png b/packages/pinball_components/assets/images/google_rollover/right/pin.png new file mode 100644 index 00000000..19bba084 Binary files /dev/null and b/packages/pinball_components/assets/images/google_rollover/right/pin.png differ diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index 0708878f..19b1571a 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -32,6 +32,8 @@ class $AssetsImagesGen { $AssetsImagesFlapperGen get flapper => const $AssetsImagesFlapperGen(); $AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen(); + $AssetsImagesGoogleRolloverGen get googleRollover => + const $AssetsImagesGoogleRolloverGen(); $AssetsImagesGoogleWordGen get googleWord => const $AssetsImagesGoogleWordGen(); $AssetsImagesKickerGen get kicker => const $AssetsImagesKickerGen(); @@ -180,6 +182,15 @@ class $AssetsImagesFlipperGen { const AssetGenImage('assets/images/flipper/right.png'); } +class $AssetsImagesGoogleRolloverGen { + const $AssetsImagesGoogleRolloverGen(); + + $AssetsImagesGoogleRolloverLeftGen get left => + const $AssetsImagesGoogleRolloverLeftGen(); + $AssetsImagesGoogleRolloverRightGen get right => + const $AssetsImagesGoogleRolloverRightGen(); +} + class $AssetsImagesGoogleWordGen { const $AssetsImagesGoogleWordGen(); @@ -434,6 +445,24 @@ class $AssetsImagesDinoAnimatronicGen { const AssetGenImage('assets/images/dino/animatronic/mouth.png'); } +class $AssetsImagesGoogleRolloverLeftGen { + const $AssetsImagesGoogleRolloverLeftGen(); + + AssetGenImage get decal => + const AssetGenImage('assets/images/google_rollover/left/decal.png'); + AssetGenImage get pin => + const AssetGenImage('assets/images/google_rollover/left/pin.png'); +} + +class $AssetsImagesGoogleRolloverRightGen { + const $AssetsImagesGoogleRolloverRightGen(); + + AssetGenImage get decal => + const AssetGenImage('assets/images/google_rollover/right/decal.png'); + AssetGenImage get pin => + const AssetGenImage('assets/images/google_rollover/right/pin.png'); +} + class $AssetsImagesGoogleWordLetter1Gen { const $AssetsImagesGoogleWordLetter1Gen(); diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index 76d4e189..1116ee88 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -17,7 +17,9 @@ export 'dino_walls.dart'; export 'error_component.dart'; export 'flapper/flapper.dart'; export 'flipper/flipper.dart'; -export 'google_letter/google_letter.dart'; +export 'google_letter.dart'; +export 'google_rollover/google_rollover.dart'; +export 'google_word/google_word.dart'; export 'initial_position.dart'; export 'joint_anchor.dart'; export 'kicker/kicker.dart'; diff --git a/packages/pinball_components/lib/src/components/google_letter.dart b/packages/pinball_components/lib/src/components/google_letter.dart new file mode 100644 index 00000000..1c63f7ff --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_letter.dart @@ -0,0 +1,88 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +enum GoogleLetterSpriteState { + lit, + dimmed, +} + +/// {@template google_letter} +/// Circular decal that represents a letter in "GOOGLE" for a given index. +/// {@endtemplate} +class GoogleLetter extends SpriteGroupComponent + with HasGameRef, FlameBlocListenable { + /// {@macro google_letter} + GoogleLetter(int index) + : _litAssetPath = _spritePaths[index][GoogleLetterSpriteState.lit]!, + _dimmedAssetPath = _spritePaths[index][GoogleLetterSpriteState.dimmed]!, + _index = index, + super(anchor: Anchor.center); + + final String _litAssetPath; + final String _dimmedAssetPath; + final int _index; + + @override + bool listenWhen(GoogleWordState previousState, GoogleWordState newState) { + return previousState.letterSpriteStates[_index] != + newState.letterSpriteStates[_index]; + } + + @override + void onNewState(GoogleWordState state) => + current = state.letterSpriteStates[_index]; + + @override + Future onLoad() async { + await super.onLoad(); + + final sprites = { + GoogleLetterSpriteState.lit: Sprite( + gameRef.images.fromCache(_litAssetPath), + ), + GoogleLetterSpriteState.dimmed: Sprite( + gameRef.images.fromCache(_dimmedAssetPath), + ), + }; + this.sprites = sprites; + current = readBloc() + .state + .letterSpriteStates[_index]; + size = sprites[current]!.originalSize / 10; + } +} + +final _spritePaths = >[ + { + GoogleLetterSpriteState.lit: Assets.images.googleWord.letter1.lit.keyName, + GoogleLetterSpriteState.dimmed: + Assets.images.googleWord.letter1.dimmed.keyName, + }, + { + GoogleLetterSpriteState.lit: Assets.images.googleWord.letter2.lit.keyName, + GoogleLetterSpriteState.dimmed: + Assets.images.googleWord.letter2.dimmed.keyName, + }, + { + GoogleLetterSpriteState.lit: Assets.images.googleWord.letter3.lit.keyName, + GoogleLetterSpriteState.dimmed: + Assets.images.googleWord.letter3.dimmed.keyName, + }, + { + GoogleLetterSpriteState.lit: Assets.images.googleWord.letter4.lit.keyName, + GoogleLetterSpriteState.dimmed: + Assets.images.googleWord.letter4.dimmed.keyName, + }, + { + GoogleLetterSpriteState.lit: Assets.images.googleWord.letter5.lit.keyName, + GoogleLetterSpriteState.dimmed: + Assets.images.googleWord.letter5.dimmed.keyName, + }, + { + GoogleLetterSpriteState.lit: Assets.images.googleWord.letter6.lit.keyName, + GoogleLetterSpriteState.dimmed: + Assets.images.googleWord.letter6.dimmed.keyName, + }, +]; diff --git a/packages/pinball_components/lib/src/components/google_letter/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/google_letter/behaviors/behaviors.dart deleted file mode 100644 index df54c1f4..00000000 --- a/packages/pinball_components/lib/src/components/google_letter/behaviors/behaviors.dart +++ /dev/null @@ -1 +0,0 @@ -export 'google_letter_ball_contact_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart b/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart deleted file mode 100644 index 99b15702..00000000 --- a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:bloc/bloc.dart'; - -part 'google_letter_state.dart'; - -class GoogleLetterCubit extends Cubit { - GoogleLetterCubit() : super(GoogleLetterState.dimmed); - - void onBallContacted() { - emit(GoogleLetterState.lit); - } - - void onReset() { - emit(GoogleLetterState.dimmed); - } -} diff --git a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart b/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart deleted file mode 100644 index 12c7edd0..00000000 --- a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart +++ /dev/null @@ -1,6 +0,0 @@ -part of 'google_letter_cubit.dart'; - -enum GoogleLetterState { - lit, - dimmed, -} diff --git a/packages/pinball_components/lib/src/components/google_letter/google_letter.dart b/packages/pinball_components/lib/src/components/google_letter/google_letter.dart deleted file mode 100644 index 9d678e30..00000000 --- a/packages/pinball_components/lib/src/components/google_letter/google_letter.dart +++ /dev/null @@ -1,133 +0,0 @@ -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/google_letter/behaviors/behaviors.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -export 'cubit/google_letter_cubit.dart'; - -final _spritePaths = >[ - { - GoogleLetterState.lit: Assets.images.googleWord.letter1.lit.keyName, - GoogleLetterState.dimmed: Assets.images.googleWord.letter1.dimmed.keyName, - }, - { - GoogleLetterState.lit: Assets.images.googleWord.letter2.lit.keyName, - GoogleLetterState.dimmed: Assets.images.googleWord.letter2.dimmed.keyName, - }, - { - GoogleLetterState.lit: Assets.images.googleWord.letter3.lit.keyName, - GoogleLetterState.dimmed: Assets.images.googleWord.letter3.dimmed.keyName, - }, - { - GoogleLetterState.lit: Assets.images.googleWord.letter4.lit.keyName, - GoogleLetterState.dimmed: Assets.images.googleWord.letter4.dimmed.keyName, - }, - { - GoogleLetterState.lit: Assets.images.googleWord.letter5.lit.keyName, - GoogleLetterState.dimmed: Assets.images.googleWord.letter5.dimmed.keyName, - }, - { - GoogleLetterState.lit: Assets.images.googleWord.letter6.lit.keyName, - GoogleLetterState.dimmed: Assets.images.googleWord.letter6.dimmed.keyName, - }, -]; - -/// {@template google_letter} -/// Circular sensor that represents a letter in "GOOGLE" for a given index. -/// {@endtemplate} -class GoogleLetter extends BodyComponent with InitialPosition { - /// {@macro google_letter} - GoogleLetter( - int index, { - Iterable? children, - }) : this._( - index, - bloc: GoogleLetterCubit(), - children: children, - ); - - GoogleLetter._( - int index, { - required this.bloc, - Iterable? children, - }) : super( - children: [ - _GoogleLetterSpriteGroupComponent( - litAssetPath: _spritePaths[index][GoogleLetterState.lit]!, - dimmedAssetPath: _spritePaths[index][GoogleLetterState.dimmed]!, - current: bloc.state, - ), - GoogleLetterBallContactBehavior(), - ...?children, - ], - renderBody: false, - ); - - /// Creates a [GoogleLetter] without any children. - /// - /// This can be used for testing [GoogleLetter]'s behaviors in isolation. - @visibleForTesting - GoogleLetter.test({ - required this.bloc, - }); - - final GoogleLetterCubit bloc; - - @override - void onRemove() { - bloc.close(); - super.onRemove(); - } - - @override - Body createBody() { - final shape = CircleShape()..radius = 1.85; - final fixtureDef = FixtureDef( - shape, - isSensor: true, - ); - final bodyDef = BodyDef( - position: initialPosition, - userData: this, - ); - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } -} - -class _GoogleLetterSpriteGroupComponent - extends SpriteGroupComponent - with HasGameRef, ParentIsA { - _GoogleLetterSpriteGroupComponent({ - required String litAssetPath, - required String dimmedAssetPath, - required GoogleLetterState current, - }) : _litAssetPath = litAssetPath, - _dimmedAssetPath = dimmedAssetPath, - super( - anchor: Anchor.center, - current: current, - ); - - final String _litAssetPath; - final String _dimmedAssetPath; - - @override - Future onLoad() async { - await super.onLoad(); - parent.bloc.stream.listen((state) => current = state); - - final sprites = { - GoogleLetterState.lit: Sprite( - gameRef.images.fromCache(_litAssetPath), - ), - GoogleLetterState.dimmed: Sprite( - gameRef.images.fromCache(_dimmedAssetPath), - ), - }; - this.sprites = sprites; - size = sprites[current]!.originalSize / 10; - } -} diff --git a/packages/pinball_components/lib/src/components/google_rollover/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/google_rollover/behaviors/behaviors.dart new file mode 100644 index 00000000..0cb1ea1a --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_rollover/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'google_rollover_ball_contact_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/google_letter/behaviors/google_letter_ball_contact_behavior.dart b/packages/pinball_components/lib/src/components/google_rollover/behaviors/google_rollover_ball_contact_behavior.dart similarity index 53% rename from packages/pinball_components/lib/src/components/google_letter/behaviors/google_letter_ball_contact_behavior.dart rename to packages/pinball_components/lib/src/components/google_rollover/behaviors/google_rollover_ball_contact_behavior.dart index 84a210ef..bef08bb0 100644 --- a/packages/pinball_components/lib/src/components/google_letter/behaviors/google_letter_ball_contact_behavior.dart +++ b/packages/pinball_components/lib/src/components/google_rollover/behaviors/google_rollover_ball_contact_behavior.dart @@ -1,12 +1,15 @@ +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'; -class GoogleLetterBallContactBehavior extends ContactBehavior { +class GoogleRolloverBallContactBehavior + extends ContactBehavior { @override void beginContact(Object other, Contact contact) { super.beginContact(other, contact); if (other is! Ball) return; - parent.bloc.onBallContacted(); + readBloc().onRolloverContacted(); + parent.firstChild()?.playing = true; } } diff --git a/packages/pinball_components/lib/src/components/google_rollover/google_rollover.dart b/packages/pinball_components/lib/src/components/google_rollover/google_rollover.dart new file mode 100644 index 00000000..e04e9778 --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_rollover/google_rollover.dart @@ -0,0 +1,113 @@ +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/google_rollover/behaviors/behaviors.dart'; + +/// {@template google_rollover} +/// Rollover that lights up [GoogleLetter]s. +/// {@endtemplate} +class GoogleRollover extends BodyComponent { + /// {@macro google_rollover} + GoogleRollover({ + required BoardSide side, + Iterable? children, + }) : _side = side, + super( + renderBody: false, + children: [ + GoogleRolloverBallContactBehavior(), + _RolloverDecalSpriteComponent(side: side), + _PinSpriteAnimationComponent(side: side), + ...?children, + ], + ); + + final BoardSide _side; + + @override + Body createBody() { + final shape = PolygonShape() + ..setAsBox( + 0.1, + 3.4, + Vector2(_side.isLeft ? -14.8 : 5.9, -11), + 0.19 * _side.direction, + ); + final fixtureDef = FixtureDef(shape, isSensor: true); + return world.createBody(BodyDef())..createFixture(fixtureDef); + } +} + +class _RolloverDecalSpriteComponent extends SpriteComponent with HasGameRef { + _RolloverDecalSpriteComponent({required BoardSide side}) + : _side = side, + super( + anchor: Anchor.center, + position: Vector2(side.isLeft ? -14.8 : 5.9, -11), + angle: 0.18 * side.direction, + ); + + final BoardSide _side; + + @override + Future onLoad() async { + await super.onLoad(); + + final sprite = Sprite( + gameRef.images.fromCache( + (_side.isLeft) + ? Assets.images.googleRollover.left.decal.keyName + : Assets.images.googleRollover.right.decal.keyName, + ), + ); + this.sprite = sprite; + size = sprite.originalSize / 20; + } +} + +class _PinSpriteAnimationComponent extends SpriteAnimationComponent + with HasGameRef { + _PinSpriteAnimationComponent({required BoardSide side}) + : _side = side, + super( + anchor: Anchor.center, + position: Vector2(side.isLeft ? -14.9 : 5.95, -11), + angle: 0, + playing: false, + ); + + final BoardSide _side; + + @override + Future onLoad() async { + await super.onLoad(); + + final spriteSheet = gameRef.images.fromCache( + _side.isLeft + ? Assets.images.googleRollover.left.pin.keyName + : Assets.images.googleRollover.right.pin.keyName, + ); + + const amountPerRow = 3; + const amountPerColumn = 1; + 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, + loop: false, + ), + )..onComplete = () { + animation?.reset(); + playing = false; + }; + } +} diff --git a/packages/pinball_components/lib/src/components/google_word/cubit/google_word_cubit.dart b/packages/pinball_components/lib/src/components/google_word/cubit/google_word_cubit.dart new file mode 100644 index 00000000..197771d6 --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_word/cubit/google_word_cubit.dart @@ -0,0 +1,30 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:pinball_components/pinball_components.dart'; + +part 'google_word_state.dart'; + +class GoogleWordCubit extends Cubit { + GoogleWordCubit() : super(GoogleWordState.initial()); + + static const _lettersInGoogle = 6; + + int _lastLitLetter = 0; + + void onRolloverContacted() { + final spriteStatesMap = {...state.letterSpriteStates}; + if (_lastLitLetter < _lettersInGoogle) { + spriteStatesMap.update( + _lastLitLetter, + (_) => GoogleLetterSpriteState.lit, + ); + emit(GoogleWordState(letterSpriteStates: spriteStatesMap)); + _lastLitLetter++; + } + } + + void onBonusAwarded() { + emit(GoogleWordState.initial()); + _lastLitLetter = 0; + } +} diff --git a/packages/pinball_components/lib/src/components/google_word/cubit/google_word_state.dart b/packages/pinball_components/lib/src/components/google_word/cubit/google_word_state.dart new file mode 100644 index 00000000..a1ee2786 --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_word/cubit/google_word_state.dart @@ -0,0 +1,17 @@ +part of 'google_word_cubit.dart'; + +class GoogleWordState extends Equatable { + const GoogleWordState({required this.letterSpriteStates}); + + GoogleWordState.initial() + : this( + letterSpriteStates: { + for (var i = 0; i <= 5; i++) i: GoogleLetterSpriteState.dimmed + }, + ); + + final Map letterSpriteStates; + + @override + List get props => [...letterSpriteStates.values]; +} diff --git a/packages/pinball_components/lib/src/components/google_word/google_word.dart b/packages/pinball_components/lib/src/components/google_word/google_word.dart new file mode 100644 index 00000000..72126d2c --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_word/google_word.dart @@ -0,0 +1,24 @@ +import 'package:flame/components.dart'; +import 'package:pinball_components/pinball_components.dart'; + +export 'cubit/google_word_cubit.dart'; + +/// {@template google_word} +/// Loads all [GoogleLetter]s to compose a [GoogleWord]. +/// {@endtemplate} +class GoogleWord extends PositionComponent { + /// {@macro google_word} + GoogleWord({ + required Vector2 position, + }) : super( + position: position, + children: [ + GoogleLetter(0)..position = Vector2(-13.1, 1.72), + GoogleLetter(1)..position = Vector2(-8.33, -0.75), + GoogleLetter(2)..position = Vector2(-2.88, -1.85), + GoogleLetter(3)..position = Vector2(2.88, -1.85), + GoogleLetter(4)..position = Vector2(8.33, -0.75), + GoogleLetter(5)..position = Vector2(13.1, 1.72), + ], + ); +} diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index 60651eb2..fe52f8b8 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -81,6 +81,8 @@ flutter: - assets/images/google_word/letter4/ - assets/images/google_word/letter5/ - assets/images/google_word/letter6/ + - assets/images/google_rollover/left/ + - assets/images/google_rollover/right/ - assets/images/signpost/ - assets/images/multiball/ - assets/images/multiplier/x2/ diff --git a/packages/pinball_components/test/src/components/google_letter/behaviors/google_letter_ball_contact_behavior_test.dart b/packages/pinball_components/test/src/components/google_letter/behaviors/google_letter_ball_contact_behavior_test.dart deleted file mode 100644 index 6a6fd437..00000000 --- a/packages/pinball_components/test/src/components/google_letter/behaviors/google_letter_ball_contact_behavior_test.dart +++ /dev/null @@ -1,55 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:bloc_test/bloc_test.dart'; -import 'package:flame_forge2d/flame_forge2d.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/google_letter/behaviors/behaviors.dart'; - -import '../../../../helpers/helpers.dart'; - -class _MockGoogleLetterCubit extends Mock implements GoogleLetterCubit {} - -class _MockBall extends Mock implements Ball {} - -class _MockContact extends Mock implements Contact {} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(TestGame.new); - - group( - 'GoogleLetterBallContactBehavior', - () { - test('can be instantiated', () { - expect( - GoogleLetterBallContactBehavior(), - isA(), - ); - }); - - flameTester.test( - 'beginContact emits onBallContacted when contacts with a ball', - (game) async { - final behavior = GoogleLetterBallContactBehavior(); - final bloc = _MockGoogleLetterCubit(); - whenListen( - bloc, - const Stream.empty(), - initialState: GoogleLetterState.lit, - ); - - final googleLetter = GoogleLetter.test(bloc: bloc); - await googleLetter.add(behavior); - await game.ensureAdd(googleLetter); - - behavior.beginContact(_MockBall(), _MockContact()); - - verify(googleLetter.bloc.onBallContacted).called(1); - }, - ); - }, - ); -} diff --git a/packages/pinball_components/test/src/components/google_letter/cubit/google_letter_cubit_test.dart b/packages/pinball_components/test/src/components/google_letter/cubit/google_letter_cubit_test.dart deleted file mode 100644 index 812e86de..00000000 --- a/packages/pinball_components/test/src/components/google_letter/cubit/google_letter_cubit_test.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball_components/pinball_components.dart'; - -void main() { - group( - 'GoogleLetterCubit', - () { - blocTest( - 'onBallContacted emits active', - build: GoogleLetterCubit.new, - act: (bloc) => bloc.onBallContacted(), - expect: () => [GoogleLetterState.lit], - ); - - blocTest( - 'onReset emits inactive', - build: GoogleLetterCubit.new, - act: (bloc) => bloc.onReset(), - expect: () => [GoogleLetterState.dimmed], - ); - }, - ); -} diff --git a/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart b/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart deleted file mode 100644 index 1ef5e7a7..00000000 --- a/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart +++ /dev/null @@ -1,145 +0,0 @@ -// 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/google_letter/behaviors/behaviors.dart'; - -import '../../../helpers/helpers.dart'; - -class _MockGoogleLetterCubit extends Mock implements GoogleLetterCubit {} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.googleWord.letter1.lit.keyName, - Assets.images.googleWord.letter1.dimmed.keyName, - Assets.images.googleWord.letter2.lit.keyName, - Assets.images.googleWord.letter2.dimmed.keyName, - Assets.images.googleWord.letter3.lit.keyName, - Assets.images.googleWord.letter3.dimmed.keyName, - Assets.images.googleWord.letter4.lit.keyName, - Assets.images.googleWord.letter4.dimmed.keyName, - Assets.images.googleWord.letter5.lit.keyName, - Assets.images.googleWord.letter5.dimmed.keyName, - Assets.images.googleWord.letter6.lit.keyName, - Assets.images.googleWord.letter6.dimmed.keyName, - ]; - final flameTester = FlameTester(() => TestGame(assets)); - - group('Google Letter', () { - flameTester.test( - '0th loads correctly', - (game) async { - final googleLetter = GoogleLetter(0); - await game.ready(); - await game.ensureAdd(googleLetter); - - expect(game.contains(googleLetter), isTrue); - }, - ); - - flameTester.test( - '1st loads correctly', - (game) async { - final googleLetter = GoogleLetter(1); - await game.ready(); - await game.ensureAdd(googleLetter); - - expect(game.contains(googleLetter), isTrue); - }, - ); - - flameTester.test( - '2nd loads correctly', - (game) async { - final googleLetter = GoogleLetter(2); - await game.ready(); - await game.ensureAdd(googleLetter); - - expect(game.contains(googleLetter), isTrue); - }, - ); - - flameTester.test( - '3d loads correctly', - (game) async { - final googleLetter = GoogleLetter(3); - await game.ready(); - await game.ensureAdd(googleLetter); - - expect(game.contains(googleLetter), isTrue); - }, - ); - - flameTester.test( - '4th loads correctly', - (game) async { - final googleLetter = GoogleLetter(4); - await game.ready(); - await game.ensureAdd(googleLetter); - - expect(game.contains(googleLetter), isTrue); - }, - ); - - flameTester.test( - '5th loads correctly', - (game) async { - final googleLetter = GoogleLetter(5); - await game.ready(); - await game.ensureAdd(googleLetter); - - expect(game.contains(googleLetter), isTrue); - }, - ); - - test('throws error when index out of range', () { - expect(() => GoogleLetter(-1), throwsA(isA())); - expect(() => GoogleLetter(6), throwsA(isA())); - }); - - flameTester.test('closes bloc when removed', (game) async { - final bloc = _MockGoogleLetterCubit(); - whenListen( - bloc, - const Stream.empty(), - initialState: GoogleLetterState.lit, - ); - when(bloc.close).thenAnswer((_) async {}); - final googleLetter = GoogleLetter.test(bloc: bloc); - - await game.ensureAdd(googleLetter); - game.remove(googleLetter); - await game.ready(); - - verify(bloc.close).called(1); - }); - - group('adds', () { - flameTester.test('new children', (game) async { - final component = Component(); - final googleLetter = GoogleLetter( - 1, - children: [component], - ); - await game.ensureAdd(googleLetter); - expect(googleLetter.children, contains(component)); - }); - - flameTester.test('a GoogleLetterBallContactBehavior', (game) async { - final googleLetter = GoogleLetter(0); - await game.ensureAdd(googleLetter); - expect( - googleLetter.children - .whereType() - .single, - isNotNull, - ); - }); - }); - }); -} diff --git a/packages/pinball_components/test/src/components/google_letter_test.dart b/packages/pinball_components/test/src/components/google_letter_test.dart new file mode 100644 index 00000000..7deea645 --- /dev/null +++ b/packages/pinball_components/test/src/components/google_letter_test.dart @@ -0,0 +1,159 @@ +// ignore_for_file: cascade_invocations + +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'; +import 'package:pinball_components/pinball_components.dart'; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.googleWord.letter1.lit.keyName, + Assets.images.googleWord.letter1.dimmed.keyName, + Assets.images.googleWord.letter2.lit.keyName, + Assets.images.googleWord.letter2.dimmed.keyName, + Assets.images.googleWord.letter3.lit.keyName, + Assets.images.googleWord.letter3.dimmed.keyName, + Assets.images.googleWord.letter4.lit.keyName, + Assets.images.googleWord.letter4.dimmed.keyName, + Assets.images.googleWord.letter5.lit.keyName, + Assets.images.googleWord.letter5.dimmed.keyName, + Assets.images.googleWord.letter6.lit.keyName, + Assets.images.googleWord.letter6.dimmed.keyName, + ]); + } + + Future pump(GoogleLetter child) async { + await ensureAdd( + FlameBlocProvider.value( + value: GoogleWordCubit(), + children: [child], + ), + ); + } +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final flameTester = FlameTester(_TestGame.new); + + group('Google Letter', () { + test('can be instantiated', () { + expect(GoogleLetter(0), isA()); + }); + + flameTester.test( + '0th loads correctly', + (game) async { + final googleLetter = GoogleLetter(0); + await game.pump(googleLetter); + + expect(game.descendants().contains(googleLetter), isTrue); + }, + ); + + flameTester.test( + '1st loads correctly', + (game) async { + final googleLetter = GoogleLetter(1); + await game.pump(googleLetter); + + expect(game.descendants().contains(googleLetter), isTrue); + }, + ); + + flameTester.test( + '2nd loads correctly', + (game) async { + final googleLetter = GoogleLetter(2); + await game.pump(googleLetter); + + expect(game.descendants().contains(googleLetter), isTrue); + }, + ); + + flameTester.test( + '3d loads correctly', + (game) async { + final googleLetter = GoogleLetter(3); + await game.pump(googleLetter); + + expect(game.descendants().contains(googleLetter), isTrue); + }, + ); + + flameTester.test( + '4th loads correctly', + (game) async { + final googleLetter = GoogleLetter(4); + await game.pump(googleLetter); + + expect(game.descendants().contains(googleLetter), isTrue); + }, + ); + + flameTester.test( + '5th loads correctly', + (game) async { + final googleLetter = GoogleLetter(5); + await game.pump(googleLetter); + + expect(game.descendants().contains(googleLetter), isTrue); + }, + ); + + test('throws error when index out of range', () { + expect(() => GoogleLetter(-1), throwsA(isA())); + expect(() => GoogleLetter(6), throwsA(isA())); + }); + + group('sprite', () { + const firstLetterLitState = GoogleWordState( + letterSpriteStates: { + 0: GoogleLetterSpriteState.lit, + 1: GoogleLetterSpriteState.dimmed, + 2: GoogleLetterSpriteState.dimmed, + 3: GoogleLetterSpriteState.dimmed, + 4: GoogleLetterSpriteState.dimmed, + 5: GoogleLetterSpriteState.dimmed, + }, + ); + + flameTester.test( + "listens when its index's state changes", + (game) async { + final googleLetter = GoogleLetter(0); + await game.pump(googleLetter); + + expect( + googleLetter.listenWhen( + GoogleWordState.initial(), + firstLetterLitState, + ), + isTrue, + ); + }, + ); + + flameTester.test( + 'changes current sprite onNewState', + (game) async { + final googleLetter = GoogleLetter(0); + await game.pump(googleLetter); + + final originalSprite = googleLetter.current; + + googleLetter.onNewState(firstLetterLitState); + await game.ready(); + + final newSprite = googleLetter.current; + expect(newSprite != originalSprite, isTrue); + }, + ); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/google_rollover/behaviors/google_rollover_ball_contact_behavior_test.dart b/packages/pinball_components/test/src/components/google_rollover/behaviors/google_rollover_ball_contact_behavior_test.dart new file mode 100644 index 00000000..9d2e6fdf --- /dev/null +++ b/packages/pinball_components/test/src/components/google_rollover/behaviors/google_rollover_ball_contact_behavior_test.dart @@ -0,0 +1,81 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.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'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/google_rollover/behaviors/behaviors.dart'; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.googleRollover.left.decal.keyName, + Assets.images.googleRollover.left.pin.keyName, + ]); + } + + Future pump( + GoogleRollover child, { + GoogleWordCubit? bloc, + }) async { + // Not needed once https://github.com/flame-engine/flame/issues/1607 + // is fixed + await onLoad(); + await ensureAdd( + FlameBlocProvider.value( + value: bloc ?? GoogleWordCubit(), + children: [child], + ), + ); + } +} + +class _MockBall extends Mock implements Ball {} + +class _MockContact extends Mock implements Contact {} + +class _MockGoogleWordCubit extends Mock implements GoogleWordCubit {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final flameTester = FlameTester(_TestGame.new); + + group( + 'GoogleRolloverBallContactBehavior', + () { + test('can be instantiated', () { + expect( + GoogleRolloverBallContactBehavior(), + isA(), + ); + }); + + flameTester.testGameWidget( + 'beginContact animates pin and calls onRolloverContacted ' + 'when contacts with a ball', + setUp: (game, tester) async { + final behavior = GoogleRolloverBallContactBehavior(); + final bloc = _MockGoogleWordCubit(); + final googleRollover = GoogleRollover(side: BoardSide.left); + await googleRollover.add(behavior); + await game.pump(googleRollover, bloc: bloc); + + behavior.beginContact(_MockBall(), _MockContact()); + await tester.pump(); + + expect( + googleRollover.firstChild()!.playing, + isTrue, + ); + verify(bloc.onRolloverContacted).called(1); + }, + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/google_rollover/google_rollover_test.dart b/packages/pinball_components/test/src/components/google_rollover/google_rollover_test.dart new file mode 100644 index 00000000..199803a0 --- /dev/null +++ b/packages/pinball_components/test/src/components/google_rollover/google_rollover_test.dart @@ -0,0 +1,82 @@ +// 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_components/src/components/google_rollover/behaviors/behaviors.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + Assets.images.googleRollover.left.decal.keyName, + Assets.images.googleRollover.left.pin.keyName, + Assets.images.googleRollover.right.decal.keyName, + Assets.images.googleRollover.right.pin.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); + + group('GoogleRollover', () { + test('can be instantiated', () { + expect( + GoogleRollover(side: BoardSide.left), + isA(), + ); + }); + + flameTester.test('left loads correctly', (game) async { + final googleRollover = GoogleRollover(side: BoardSide.left); + await game.ensureAdd(googleRollover); + expect(game.contains(googleRollover), isTrue); + }); + + flameTester.test('right loads correctly', (game) async { + final googleRollover = GoogleRollover(side: BoardSide.right); + await game.ensureAdd(googleRollover); + expect(game.contains(googleRollover), isTrue); + }); + + group('adds', () { + flameTester.test('new children', (game) async { + final component = Component(); + final googleRollover = GoogleRollover( + side: BoardSide.left, + children: [component], + ); + await game.ensureAdd(googleRollover); + expect(googleRollover.children, contains(component)); + }); + + flameTester.test('a GoogleRolloverBallContactBehavior', (game) async { + final googleRollover = GoogleRollover(side: BoardSide.left); + await game.ensureAdd(googleRollover); + expect( + googleRollover.children + .whereType() + .single, + isNotNull, + ); + }); + }); + + flameTester.test( + 'pin stops animating after animation completes', + (game) async { + final googleRollover = GoogleRollover(side: BoardSide.left); + await game.ensureAdd(googleRollover); + + final pinSpriteAnimationComponent = + googleRollover.firstChild()!; + + pinSpriteAnimationComponent.playing = true; + game.update( + pinSpriteAnimationComponent.animation!.totalDuration() + 0.1, + ); + + expect(pinSpriteAnimationComponent.playing, isFalse); + }, + ); + }); +} diff --git a/packages/pinball_components/test/src/components/google_word/cubit/google_word_cubit_test.dart b/packages/pinball_components/test/src/components/google_word/cubit/google_word_cubit_test.dart new file mode 100644 index 00000000..08acfae8 --- /dev/null +++ b/packages/pinball_components/test/src/components/google_word/cubit/google_word_cubit_test.dart @@ -0,0 +1,35 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group( + 'GoogleWordCubit', + () { + blocTest( + 'onRolloverContacted emits first letter lit', + build: GoogleWordCubit.new, + act: (bloc) => bloc.onRolloverContacted(), + expect: () => [ + const GoogleWordState( + letterSpriteStates: { + 0: GoogleLetterSpriteState.lit, + 1: GoogleLetterSpriteState.dimmed, + 2: GoogleLetterSpriteState.dimmed, + 3: GoogleLetterSpriteState.dimmed, + 4: GoogleLetterSpriteState.dimmed, + 5: GoogleLetterSpriteState.dimmed, + }, + ), + ], + ); + + blocTest( + 'onBonusAwarded emits initial state', + build: GoogleWordCubit.new, + act: (bloc) => bloc.onBonusAwarded(), + expect: () => [GoogleWordState.initial()], + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/google_word/cubit/google_word_state_test.dart b/packages/pinball_components/test/src/components/google_word/cubit/google_word_state_test.dart new file mode 100644 index 00000000..6195c785 --- /dev/null +++ b/packages/pinball_components/test/src/components/google_word/cubit/google_word_state_test.dart @@ -0,0 +1,58 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group('GoogleWordState', () { + test('supports value equality', () { + expect( + GoogleWordState( + letterSpriteStates: const { + 0: GoogleLetterSpriteState.dimmed, + 1: GoogleLetterSpriteState.dimmed, + 2: GoogleLetterSpriteState.dimmed, + 3: GoogleLetterSpriteState.dimmed, + 4: GoogleLetterSpriteState.dimmed, + 5: GoogleLetterSpriteState.dimmed, + }, + ), + equals( + GoogleWordState( + letterSpriteStates: const { + 0: GoogleLetterSpriteState.dimmed, + 1: GoogleLetterSpriteState.dimmed, + 2: GoogleLetterSpriteState.dimmed, + 3: GoogleLetterSpriteState.dimmed, + 4: GoogleLetterSpriteState.dimmed, + 5: GoogleLetterSpriteState.dimmed, + }, + ), + ), + ); + }); + + group('constructor', () { + test('can be instantiated', () { + expect( + const GoogleWordState(letterSpriteStates: {}), + isNotNull, + ); + }); + + test('initial has all dimmed sprite states', () { + const initialState = GoogleWordState( + letterSpriteStates: { + 0: GoogleLetterSpriteState.dimmed, + 1: GoogleLetterSpriteState.dimmed, + 2: GoogleLetterSpriteState.dimmed, + 3: GoogleLetterSpriteState.dimmed, + 4: GoogleLetterSpriteState.dimmed, + 5: GoogleLetterSpriteState.dimmed, + }, + ); + expect(GoogleWordState.initial(), equals(initialState)); + }); + }); + }); +} diff --git a/test/game/components/google_word/google_word_test.dart b/packages/pinball_components/test/src/components/google_word/google_word_test.dart similarity index 65% rename from test/game/components/google_word/google_word_test.dart rename to packages/pinball_components/test/src/components/google_word/google_word_test.dart index c0258281..daee7d37 100644 --- a/test/game/components/google_word/google_word_test.dart +++ b/packages/pinball_components/test/src/components/google_word/google_word_test.dart @@ -4,8 +4,6 @@ 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'; -import 'package:pinball/game/components/google_word/behaviors/behaviors.dart'; -import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; class _TestGame extends Forge2DGame { @@ -28,10 +26,10 @@ class _TestGame extends Forge2DGame { ]); } - Future pump(GoogleWord child, {GameBloc? gameBloc}) { - return ensureAdd( - FlameBlocProvider.value( - value: gameBloc ?? GameBloc(), + Future pump(GoogleWord child) async { + await ensureAdd( + FlameBlocProvider.value( + value: GoogleWordCubit(), children: [child], ), ); @@ -44,25 +42,21 @@ void main() { final flameTester = FlameTester(_TestGame.new); group('GoogleWord', () { + test('can be instantiated', () { + expect(GoogleWord(position: Vector2.zero()), isA()); + }); + flameTester.test( - 'loads the letters correctly', + 'loads letters correctly', (game) async { - const word = 'Google'; final googleWord = GoogleWord(position: Vector2.zero()); await game.pump(googleWord); - final letters = googleWord.children.whereType(); - expect(letters.length, equals(word.length)); + expect( + googleWord.children.whereType().length, + equals(6), + ); }, ); - - flameTester.test('adds a GoogleWordBonusBehavior', (game) async { - final googleWord = GoogleWord(position: Vector2.zero()); - await game.pump(googleWord); - expect( - googleWord.children.whereType().single, - isNotNull, - ); - }); }); } diff --git a/packages/pinball_components/test/src/components/skill_shot/cubit/skill_shot_state_test.dart b/packages/pinball_components/test/src/components/skill_shot/cubit/skill_shot_state_test.dart index ee6e3e0d..b5aca68c 100644 --- a/packages/pinball_components/test/src/components/skill_shot/cubit/skill_shot_state_test.dart +++ b/packages/pinball_components/test/src/components/skill_shot/cubit/skill_shot_state_test.dart @@ -31,7 +31,7 @@ void main() { ); }); - test('initial is idle with mouth closed', () { + test('initial is dimmed and not blinking', () { const initialState = SkillShotState( spriteState: SkillShotSpriteState.dimmed, isBlinking: false, @@ -45,13 +45,13 @@ void main() { 'copies correctly ' 'when no argument specified', () { - const chromeDinoState = SkillShotState( + const skillShotState = SkillShotState( spriteState: SkillShotSpriteState.lit, isBlinking: true, ); expect( - chromeDinoState.copyWith(), - equals(chromeDinoState), + skillShotState.copyWith(), + equals(skillShotState), ); }, ); @@ -60,7 +60,7 @@ void main() { 'copies correctly ' 'when all arguments specified', () { - const chromeDinoState = SkillShotState( + const skillShotState = SkillShotState( spriteState: SkillShotSpriteState.lit, isBlinking: true, ); @@ -68,10 +68,10 @@ void main() { spriteState: SkillShotSpriteState.dimmed, isBlinking: false, ); - expect(chromeDinoState, isNot(equals(otherSkillShotState))); + expect(skillShotState, isNot(equals(otherSkillShotState))); expect( - chromeDinoState.copyWith( + skillShotState.copyWith( spriteState: SkillShotSpriteState.dimmed, isBlinking: false, ), diff --git a/test/game/components/android_acres/android_acres_test.dart b/test/game/components/android_acres/android_acres_test.dart index 5c750818..14d3d69e 100644 --- a/test/game/components/android_acres/android_acres_test.dart +++ b/test/game/components/android_acres/android_acres_test.dart @@ -62,7 +62,7 @@ void main() { group('loads', () { flameTester.test( - 'an AndroidSpaceship', + 'an AndroidSpaceship', (game) async { await game.pump(AndroidAcres()); expect( diff --git a/test/game/components/google_gallery/behaviors/google_word_bonus_behavior_test.dart b/test/game/components/google_gallery/behaviors/google_word_bonus_behavior_test.dart new file mode 100644 index 00000000..3d8d2b39 --- /dev/null +++ b/test/game/components/google_gallery/behaviors/google_word_bonus_behavior_test.dart @@ -0,0 +1,120 @@ +// 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'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/components/google_gallery/behaviors/behaviors.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.googleWord.letter1.lit.keyName, + Assets.images.googleWord.letter1.dimmed.keyName, + Assets.images.googleWord.letter2.lit.keyName, + Assets.images.googleWord.letter2.dimmed.keyName, + Assets.images.googleWord.letter3.lit.keyName, + Assets.images.googleWord.letter3.dimmed.keyName, + Assets.images.googleWord.letter4.lit.keyName, + Assets.images.googleWord.letter4.dimmed.keyName, + Assets.images.googleWord.letter5.lit.keyName, + Assets.images.googleWord.letter5.dimmed.keyName, + Assets.images.googleWord.letter6.lit.keyName, + Assets.images.googleWord.letter6.dimmed.keyName, + ]); + } + + Future pump( + GoogleGallery child, { + required GameBloc gameBloc, + required GoogleWordCubit googleWordBloc, + }) async { + // Not needed once https://github.com/flame-engine/flame/issues/1607 + // is fixed + await onLoad(); + await ensureAdd( + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value( + value: gameBloc, + ), + FlameBlocProvider.value( + value: googleWordBloc, + ), + ], + children: [child], + ), + ); + } +} + +class _MockGameBloc extends Mock implements GameBloc {} + +class _MockGoogleWordCubit extends Mock implements GoogleWordCubit {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('GoogleWordBonusBehavior', () { + late GameBloc gameBloc; + + setUp(() { + gameBloc = _MockGameBloc(); + }); + + final flameTester = FlameTester(_TestGame.new); + + flameTester.testGameWidget( + 'adds GameBonus.googleWord to the game when all letters ' + 'in google word are activated and calls onBonusAwarded', + setUp: (game, tester) async { + final behavior = GoogleWordBonusBehavior(); + final parent = GoogleGallery.test(); + final googleWord = GoogleWord(position: Vector2.zero()); + final googleWordBloc = _MockGoogleWordCubit(); + final streamController = StreamController(); + + whenListen( + googleWordBloc, + streamController.stream, + initialState: GoogleWordState.initial(), + ); + + await parent.add(googleWord); + await game.pump( + parent, + gameBloc: gameBloc, + googleWordBloc: googleWordBloc, + ); + await parent.ensureAdd(behavior); + + streamController.add( + const GoogleWordState( + letterSpriteStates: { + 0: GoogleLetterSpriteState.lit, + 1: GoogleLetterSpriteState.lit, + 2: GoogleLetterSpriteState.lit, + 3: GoogleLetterSpriteState.lit, + 4: GoogleLetterSpriteState.lit, + 5: GoogleLetterSpriteState.lit, + }, + ), + ); + await tester.pump(); + + verify( + () => gameBloc.add(const BonusActivated(GameBonus.googleWord)), + ).called(1); + verify(googleWordBloc.onBonusAwarded).called(1); + }, + ); + }); +} diff --git a/test/game/components/google_gallery/google_gallery_test.dart b/test/game/components/google_gallery/google_gallery_test.dart new file mode 100644 index 00000000..9551285f --- /dev/null +++ b/test/game/components/google_gallery/google_gallery_test.dart @@ -0,0 +1,110 @@ +// ignore_for_file: cascade_invocations + +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'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/game/components/google_gallery/behaviors/behaviors.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.googleWord.letter1.lit.keyName, + Assets.images.googleWord.letter1.dimmed.keyName, + Assets.images.googleWord.letter2.lit.keyName, + Assets.images.googleWord.letter2.dimmed.keyName, + Assets.images.googleWord.letter3.lit.keyName, + Assets.images.googleWord.letter3.dimmed.keyName, + Assets.images.googleWord.letter4.lit.keyName, + Assets.images.googleWord.letter4.dimmed.keyName, + Assets.images.googleWord.letter5.lit.keyName, + Assets.images.googleWord.letter5.dimmed.keyName, + Assets.images.googleWord.letter6.lit.keyName, + Assets.images.googleWord.letter6.dimmed.keyName, + Assets.images.googleRollover.left.decal.keyName, + Assets.images.googleRollover.left.pin.keyName, + Assets.images.googleRollover.right.decal.keyName, + Assets.images.googleRollover.right.pin.keyName, + ]); + } + + Future pump(GoogleGallery child) async { + await ensureAdd( + FlameBlocProvider.value( + value: _MockGameBloc(), + children: [child], + ), + ); + } +} + +class _MockGameBloc extends Mock implements GameBloc {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final flameTester = FlameTester(_TestGame.new); + + group('GoogleGallery', () { + flameTester.test('loads correctly', (game) async { + final component = GoogleGallery(); + await game.pump(component); + expect(game.descendants(), contains(component)); + }); + + group('loads', () { + flameTester.test( + 'two GoogleRollovers', + (game) async { + await game.pump(GoogleGallery()); + expect( + game.descendants().whereType().length, + equals(2), + ); + }, + ); + + flameTester.test( + 'a GoogleWord', + (game) async { + await game.pump(GoogleGallery()); + expect( + game.descendants().whereType().length, + equals(1), + ); + }, + ); + }); + + group('adds', () { + flameTester.test( + 'ScoringContactBehavior to GoogleRollovers', + (game) async { + await game.pump(GoogleGallery()); + + game.descendants().whereType().forEach( + (rollover) => expect( + rollover.firstChild(), + isNotNull, + ), + ); + }, + ); + + flameTester.test('a GoogleWordBonusBehavior', (game) async { + final component = GoogleGallery(); + await game.pump(component); + expect( + component.descendants().whereType().single, + isNotNull, + ); + }); + }); + }); +} diff --git a/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart b/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart deleted file mode 100644 index e23c1fd2..00000000 --- a/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart +++ /dev/null @@ -1,94 +0,0 @@ -// ignore_for_file: cascade_invocations - -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'; -import 'package:mocktail/mocktail.dart'; -import 'package:pinball/game/components/google_word/behaviors/behaviors.dart'; -import 'package:pinball/game/game.dart'; -import 'package:pinball_audio/pinball_audio.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -class _TestGame extends Forge2DGame { - @override - Future onLoad() async { - images.prefix = ''; - await images.loadAll([ - Assets.images.googleWord.letter1.lit.keyName, - Assets.images.googleWord.letter1.dimmed.keyName, - Assets.images.googleWord.letter2.lit.keyName, - Assets.images.googleWord.letter2.dimmed.keyName, - Assets.images.googleWord.letter3.lit.keyName, - Assets.images.googleWord.letter3.dimmed.keyName, - Assets.images.googleWord.letter4.lit.keyName, - Assets.images.googleWord.letter4.dimmed.keyName, - Assets.images.googleWord.letter5.lit.keyName, - Assets.images.googleWord.letter5.dimmed.keyName, - Assets.images.googleWord.letter6.lit.keyName, - Assets.images.googleWord.letter6.dimmed.keyName, - ]); - } - - Future pump(GoogleWord child, {required GameBloc gameBloc}) async { - await ensureAdd( - FlameBlocProvider.value( - value: gameBloc, - children: [ - FlameProvider.value( - _MockPinballAudioPlayer(), - children: [child], - ) - ], - ), - ); - } -} - -class _MockGameBloc extends Mock implements GameBloc {} - -class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group('GoogleWordBonusBehaviors', () { - late GameBloc gameBloc; - - setUp(() { - gameBloc = _MockGameBloc(); - }); - - final flameTester = FlameTester(_TestGame.new); - - flameTester.testGameWidget( - 'adds GameBonus.googleWord to the game when all letters are activated', - setUp: (game, tester) async { - await game.onLoad(); - final behavior = GoogleWordBonusBehavior(); - final parent = GoogleWord.test(); - final letters = [ - GoogleLetter(0), - GoogleLetter(1), - GoogleLetter(2), - GoogleLetter(3), - GoogleLetter(4), - GoogleLetter(5), - ]; - await parent.addAll(letters); - await game.pump(parent, gameBloc: gameBloc); - await parent.ensureAdd(behavior); - - for (final letter in letters) { - letter.bloc.onBallContacted(); - } - await tester.pump(); - - verify( - () => gameBloc.add(const BonusActivated(GameBonus.googleWord)), - ).called(1); - }, - ); - }); -} diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index 19760f64..52e8c97c 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -184,11 +184,11 @@ void main() { ); flameTester.test( - 'one GoogleWord', + 'one GoogleGallery', (game) async { await game.ready(); expect( - game.descendants().whereType().length, + game.descendants().whereType().length, equals(1), ); }, diff --git a/test/game/view/widgets/play_button_overlay_test.dart b/test/game/view/widgets/play_button_overlay_test.dart index f10c5f5b..8980c72d 100644 --- a/test/game/view/widgets/play_button_overlay_test.dart +++ b/test/game/view/widgets/play_button_overlay_test.dart @@ -29,7 +29,7 @@ void main() { expect(find.text('Play'), findsOneWidget); }); - testWidgets('adds PlayTapped event to StartGameBloc when taped', + testWidgets('adds PlayTapped event to StartGameBloc when tapped', (tester) async { await tester.pumpApp( const PlayButtonOverlay(),