From a65fb692b58513694ba229c68225d00ced9ed0cd Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Fri, 15 Apr 2022 09:18:22 +0100 Subject: [PATCH] feat: implement GoogleWord (#176) --- lib/game/bloc/game_bloc.dart | 34 +- lib/game/bloc/game_event.dart | 12 +- lib/game/bloc/game_state.dart | 18 +- lib/game/components/bonus_word.dart | 208 ---------- lib/game/components/components.dart | 2 +- lib/game/components/google_word.dart | 83 ++++ lib/game/game_assets.dart | 6 + lib/game/pinball_game.dart | 3 +- test/game/bloc/game_bloc_test.dart | 134 ++----- test/game/bloc/game_event_test.dart | 70 ++-- test/game/bloc/game_state_test.dart | 44 +- test/game/components/bonus_word_test.dart | 376 ------------------ .../components/controlled_flipper_test.dart | 1 - .../components/controlled_plunger_test.dart | 1 - test/game/components/flutter_forest_test.dart | 1 - .../components/game_flow_controller_test.dart | 2 - test/game/components/google_word_test.dart | 73 ++++ .../score_effect_controller_test.dart | 5 - test/game/view/game_hud_test.dart | 1 - test/helpers/mocks.dart | 2 - 20 files changed, 235 insertions(+), 841 deletions(-) delete mode 100644 lib/game/components/bonus_word.dart create mode 100644 lib/game/components/google_word.dart delete mode 100644 test/game/components/bonus_word_test.dart create mode 100644 test/game/components/google_word_test.dart diff --git a/lib/game/bloc/game_bloc.dart b/lib/game/bloc/game_bloc.dart index ce1a78b4..ba604f17 100644 --- a/lib/game/bloc/game_bloc.dart +++ b/lib/game/bloc/game_bloc.dart @@ -11,14 +11,11 @@ class GameBloc extends Bloc { GameBloc() : super(const GameState.initial()) { on(_onBallLost); on(_onScored); - on(_onBonusLetterActivated); + on(_onBonusActivated); on(_onDashNestActivated); on(_onSparkyTurboChargeActivated); } - static const bonusWord = 'GOOGLE'; - static const bonusWordScore = 10000; - void _onBallLost(BallLost event, Emitter emit) { emit(state.copyWith(balls: state.balls - 1)); } @@ -29,29 +26,12 @@ class GameBloc extends Bloc { } } - void _onBonusLetterActivated(BonusLetterActivated event, Emitter emit) { - final newBonusLetters = [ - ...state.activatedBonusLetters, - event.letterIndex, - ]; - - final achievedBonus = newBonusLetters.length == bonusWord.length; - if (achievedBonus) { - emit( - state.copyWith( - activatedBonusLetters: [], - bonusHistory: [ - ...state.bonusHistory, - GameBonus.word, - ], - ), - ); - add(const Scored(points: bonusWordScore)); - } else { - emit( - state.copyWith(activatedBonusLetters: newBonusLetters), - ); - } + void _onBonusActivated(BonusActivated event, Emitter emit) { + emit( + state.copyWith( + bonusHistory: [...state.bonusHistory, event.bonus], + ), + ); } void _onDashNestActivated(DashNestActivated event, Emitter emit) { diff --git a/lib/game/bloc/game_event.dart b/lib/game/bloc/game_event.dart index ee5315ad..392cc50f 100644 --- a/lib/game/bloc/game_event.dart +++ b/lib/game/bloc/game_event.dart @@ -33,17 +33,13 @@ class Scored extends GameEvent { List get props => [points]; } -class BonusLetterActivated extends GameEvent { - const BonusLetterActivated(this.letterIndex) - : assert( - letterIndex < GameBloc.bonusWord.length, - 'Index must be smaller than the length of the word', - ); +class BonusActivated extends GameEvent { + const BonusActivated(this.bonus); - final int letterIndex; + final GameBonus bonus; @override - List get props => [letterIndex]; + List get props => [bonus]; } class DashNestActivated extends GameEvent { diff --git a/lib/game/bloc/game_state.dart b/lib/game/bloc/game_state.dart index 0d9485e9..aa1144c0 100644 --- a/lib/game/bloc/game_state.dart +++ b/lib/game/bloc/game_state.dart @@ -4,9 +4,8 @@ part of 'game_bloc.dart'; /// Defines bonuses that a player can gain during a PinballGame. enum GameBonus { - /// Bonus achieved when the user activate all of the bonus - /// letters on the board, forming the bonus word. - word, + /// Bonus achieved when the ball activates all Google letters. + googleWord, /// Bonus achieved when the user activates all dash nest bumpers. dashNest, @@ -23,7 +22,6 @@ class GameState extends Equatable { const GameState({ required this.score, required this.balls, - required this.activatedBonusLetters, required this.bonusHistory, required this.activatedDashNests, }) : assert(score >= 0, "Score can't be negative"), @@ -32,7 +30,6 @@ class GameState extends Equatable { const GameState.initial() : score = 0, balls = 3, - activatedBonusLetters = const [], activatedDashNests = const {}, bonusHistory = const []; @@ -44,9 +41,6 @@ class GameState extends Equatable { /// When the number of balls is 0, the game is over. final int balls; - /// Active bonus letters. - final List activatedBonusLetters; - /// Active dash nests. final Set activatedDashNests; @@ -57,14 +51,9 @@ class GameState extends Equatable { /// Determines when the game is over. bool get isGameOver => balls == 0; - /// Shortcut method to check if the given [i] - /// is activated. - bool isLetterActivated(int i) => activatedBonusLetters.contains(i); - GameState copyWith({ int? score, int? balls, - List? activatedBonusLetters, Set? activatedDashNests, List? bonusHistory, }) { @@ -76,8 +65,6 @@ class GameState extends Equatable { return GameState( score: score ?? this.score, balls: balls ?? this.balls, - activatedBonusLetters: - activatedBonusLetters ?? this.activatedBonusLetters, activatedDashNests: activatedDashNests ?? this.activatedDashNests, bonusHistory: bonusHistory ?? this.bonusHistory, ); @@ -87,7 +74,6 @@ class GameState extends Equatable { List get props => [ score, balls, - activatedBonusLetters, activatedDashNests, bonusHistory, ]; diff --git a/lib/game/components/bonus_word.dart b/lib/game/components/bonus_word.dart deleted file mode 100644 index f3b9743c..00000000 --- a/lib/game/components/bonus_word.dart +++ /dev/null @@ -1,208 +0,0 @@ -// ignore_for_file: avoid_renaming_method_parameters - -import 'dart:async'; - -import 'package:flame/components.dart'; -import 'package:flame/effects.dart'; -import 'package:flame_bloc/flame_bloc.dart'; -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flutter/material.dart'; -import 'package:pinball/game/game.dart'; -import 'package:pinball_components/pinball_components.dart'; - -/// {@template bonus_word} -/// Loads all [BonusLetter]s to compose a [BonusWord]. -/// {@endtemplate} -class BonusWord extends Component - with BlocComponent, HasGameRef { - /// {@macro bonus_word} - BonusWord({required Vector2 position}) : _position = position; - - final Vector2 _position; - - @override - bool listenWhen(GameState? previousState, GameState newState) { - return (previousState?.bonusHistory.length ?? 0) < - newState.bonusHistory.length && - newState.bonusHistory.last == GameBonus.word; - } - - @override - void onNewState(GameState state) { - if (state.bonusHistory.last == GameBonus.word) { - gameRef.audio.googleBonus(); - - final letters = children.whereType().toList(); - - for (var i = 0; i < letters.length; i++) { - final letter = letters[i]; - letter - ..isEnabled = false - ..add( - SequenceEffect( - [ - ColorEffect( - i.isOdd - ? BonusLetter._activeColor - : BonusLetter._disableColor, - const Offset(0, 1), - EffectController(duration: 0.25), - ), - ColorEffect( - i.isOdd - ? BonusLetter._disableColor - : BonusLetter._activeColor, - const Offset(0, 1), - EffectController(duration: 0.25), - ), - ], - repeatCount: 4, - )..onFinishCallback = () { - letter - ..isEnabled = true - ..add( - ColorEffect( - BonusLetter._disableColor, - const Offset(0, 1), - EffectController(duration: 0.25), - ), - ); - }, - ); - } - } - } - - @override - Future onLoad() async { - await super.onLoad(); - - final offsets = [ - Vector2(-12.92, 1.82), - Vector2(-8.33, -0.65), - Vector2(-2.88, -1.75), - ]; - offsets.addAll( - offsets.reversed - .map( - (offset) => Vector2(-offset.x, offset.y), - ) - .toList(), - ); - assert(offsets.length == GameBloc.bonusWord.length, 'Invalid positions'); - - final letters = []; - for (var i = 0; i < GameBloc.bonusWord.length; i++) { - letters.add( - BonusLetter( - letter: GameBloc.bonusWord[i], - index: i, - )..initialPosition = _position + offsets[i], - ); - } - - await addAll(letters); - } -} - -/// {@template bonus_letter} -/// [BodyType.static] sensor component, part of a word bonus, -/// which will activate its letter after contact with a [Ball]. -/// {@endtemplate} -class BonusLetter extends BodyComponent - with BlocComponent, InitialPosition { - /// {@macro bonus_letter} - BonusLetter({ - required String letter, - required int index, - }) : _letter = letter, - _index = index { - paint = Paint()..color = _disableColor; - } - - /// The size of the [BonusLetter]. - static final size = Vector2.all(3.7); - - static const _activeColor = Colors.green; - static const _disableColor = Colors.red; - - final String _letter; - final int _index; - - /// Indicates if a [BonusLetter] can be activated on [Ball] contact. - /// - /// It is disabled whilst animating and enabled again once the animation - /// completes. The animation is triggered when [GameBonus.word] is - /// awarded. - bool isEnabled = true; - - @override - Future onLoad() async { - await super.onLoad(); - - await add( - TextComponent( - position: Vector2(-1, -1), - text: _letter, - textRenderer: TextPaint( - style: const TextStyle(fontSize: 2, color: Colors.white), - ), - ), - ); - } - - @override - Body createBody() { - final shape = CircleShape()..radius = size.x / 2; - - final fixtureDef = FixtureDef(shape)..isSensor = true; - - final bodyDef = BodyDef() - ..position = initialPosition - ..userData = this - ..type = BodyType.static; - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } - - @override - bool listenWhen(GameState? previousState, GameState newState) { - final wasActive = previousState?.isLetterActivated(_index) ?? false; - final isActive = newState.isLetterActivated(_index); - - return wasActive != isActive; - } - - @override - void onNewState(GameState state) { - final isActive = state.isLetterActivated(_index); - - add( - ColorEffect( - isActive ? _activeColor : _disableColor, - const Offset(0, 1), - EffectController(duration: 0.25), - ), - ); - } - - /// Activates this [BonusLetter], if it's not already activated. - void activate() { - final isActive = state?.isLetterActivated(_index) ?? false; - if (!isActive) { - gameRef.read().add(BonusLetterActivated(_index)); - } - } -} - -/// Triggers [BonusLetter.activate] method when a [BonusLetter] and a [Ball] -/// come in contact. -class BonusLetterBallContactCallback - extends ContactCallback { - @override - void begin(Ball ball, BonusLetter bonusLetter, Contact contact) { - if (bonusLetter.isEnabled) { - bonusLetter.activate(); - } - } -} diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 058bfe20..e05f9f00 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -1,6 +1,5 @@ export 'alien_zone.dart'; export 'board.dart'; -export 'bonus_word.dart'; export 'camera_controller.dart'; export 'controlled_ball.dart'; export 'controlled_flipper.dart'; @@ -8,6 +7,7 @@ export 'controlled_plunger.dart'; export 'controlled_sparky_computer.dart'; export 'flutter_forest.dart'; export 'game_flow_controller.dart'; +export 'google_word.dart'; export 'launcher.dart'; export 'score_effect_controller.dart'; export 'score_points.dart'; diff --git a/lib/game/components/google_word.dart b/lib/game/components/google_word.dart new file mode 100644 index 00000000..754a0fff --- /dev/null +++ b/lib/game/components/google_word.dart @@ -0,0 +1,83 @@ +// ignore_for_file: avoid_renaming_method_parameters + +import 'dart:async'; + +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball/flame/flame.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; + +/// {@template google_word} +/// Loads all [GoogleLetter]s to compose a [GoogleWord]. +/// {@endtemplate} +class GoogleWord extends Component + with HasGameRef, Controls<_GoogleWordController> { + /// {@macro google_word} + GoogleWord({ + required Vector2 position, + }) : _position = position { + controller = _GoogleWordController(this); + } + + final Vector2 _position; + + @override + Future onLoad() async { + await super.onLoad(); + gameRef.addContactCallback(_GoogleLetterBallContactCallback()); + + final offsets = [ + Vector2(-12.92, 1.82), + Vector2(-8.33, -0.65), + Vector2(-2.88, -1.75), + Vector2(2.88, -1.75), + Vector2(8.33, -0.65), + Vector2(12.92, 1.82), + ]; + + final letters = []; + for (var index = 0; index < offsets.length; index++) { + letters.add( + GoogleLetter(index)..initialPosition = _position + offsets[index], + ); + } + + await addAll(letters); + } +} + +class _GoogleWordController extends ComponentController + with HasGameRef { + _GoogleWordController(GoogleWord googleWord) : super(googleWord); + + final _activatedLetters = {}; + + void activate(GoogleLetter googleLetter) { + if (!_activatedLetters.add(googleLetter)) return; + + googleLetter.activate(); + + final activatedBonus = _activatedLetters.length == 6; + if (activatedBonus) { + gameRef.audio.googleBonus(); + gameRef.read().add(const BonusActivated(GameBonus.googleWord)); + component.children.whereType().forEach( + (letter) => letter.deactivate(), + ); + _activatedLetters.clear(); + } + } +} + +/// Activates a [GoogleLetter] when it contacts with a [Ball]. +class _GoogleLetterBallContactCallback + extends ContactCallback { + @override + void begin(GoogleLetter googleLetter, _, __) { + final parent = googleLetter.parent; + if (parent is GoogleWord) { + parent.controller.activate(googleLetter); + } + } +} diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index 4cc8bee5..e7c7a343 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -58,6 +58,12 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.sparky.bumper.c.inactive.keyName), images.load(components.Assets.images.backboard.backboardScores.keyName), images.load(components.Assets.images.backboard.backboardGameOver.keyName), + images.load(components.Assets.images.googleWord.letter1.keyName), + images.load(components.Assets.images.googleWord.letter2.keyName), + images.load(components.Assets.images.googleWord.letter3.keyName), + images.load(components.Assets.images.googleWord.letter4.keyName), + images.load(components.Assets.images.googleWord.letter5.keyName), + images.load(components.Assets.images.googleWord.letter6.keyName), images.load(components.Assets.images.backboard.display.keyName), images.load(Assets.images.components.background.path), ]; diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 07d637c0..c7892eb7 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -72,7 +72,6 @@ class PinballGame extends Forge2DGame void _addContactCallbacks() { addContactCallback(BallScorePointsCallback(this)); addContactCallback(BottomWallBallContactCallback()); - addContactCallback(BonusLetterBallContactCallback()); } Future _addGameBoundaries() async { @@ -82,7 +81,7 @@ class PinballGame extends Forge2DGame Future _addBonusWord() async { await add( - BonusWord( + GoogleWord( position: Vector2( BoardDimensions.bounds.center.dx - 4.1, BoardDimensions.bounds.center.dy + 1.8, diff --git a/test/game/bloc/game_bloc_test.dart b/test/game/bloc/game_bloc_test.dart index fb543814..e83c35d3 100644 --- a/test/game/bloc/game_bloc_test.dart +++ b/test/game/bloc/game_bloc_test.dart @@ -21,7 +21,6 @@ void main() { const GameState( score: 0, balls: 2, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ), @@ -41,14 +40,12 @@ void main() { const GameState( score: 2, balls: 3, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ), const GameState( score: 5, balls: 3, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ), @@ -69,21 +66,18 @@ void main() { const GameState( score: 0, balls: 2, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ), const GameState( score: 0, balls: 1, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ), const GameState( score: 0, balls: 0, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ), @@ -91,103 +85,6 @@ void main() { ); }); - group('BonusLetterActivated', () { - blocTest( - 'adds the letter to the state', - build: GameBloc.new, - act: (bloc) => bloc - ..add(const BonusLetterActivated(0)) - ..add(const BonusLetterActivated(1)) - ..add(const BonusLetterActivated(2)), - expect: () => const [ - GameState( - score: 0, - balls: 3, - activatedBonusLetters: [0], - activatedDashNests: {}, - bonusHistory: [], - ), - GameState( - score: 0, - balls: 3, - activatedBonusLetters: [0, 1], - activatedDashNests: {}, - bonusHistory: [], - ), - GameState( - score: 0, - balls: 3, - activatedBonusLetters: [0, 1, 2], - activatedDashNests: {}, - bonusHistory: [], - ), - ], - ); - - blocTest( - 'adds the bonus when the bonusWord is completed', - build: GameBloc.new, - act: (bloc) => bloc - ..add(const BonusLetterActivated(0)) - ..add(const BonusLetterActivated(1)) - ..add(const BonusLetterActivated(2)) - ..add(const BonusLetterActivated(3)) - ..add(const BonusLetterActivated(4)) - ..add(const BonusLetterActivated(5)), - expect: () => const [ - GameState( - score: 0, - balls: 3, - activatedBonusLetters: [0], - activatedDashNests: {}, - bonusHistory: [], - ), - GameState( - score: 0, - balls: 3, - activatedBonusLetters: [0, 1], - activatedDashNests: {}, - bonusHistory: [], - ), - GameState( - score: 0, - balls: 3, - activatedBonusLetters: [0, 1, 2], - activatedDashNests: {}, - bonusHistory: [], - ), - GameState( - score: 0, - balls: 3, - activatedBonusLetters: [0, 1, 2, 3], - activatedDashNests: {}, - bonusHistory: [], - ), - GameState( - score: 0, - balls: 3, - activatedBonusLetters: [0, 1, 2, 3, 4], - activatedDashNests: {}, - bonusHistory: [], - ), - GameState( - score: 0, - balls: 3, - activatedBonusLetters: [], - activatedDashNests: {}, - bonusHistory: [GameBonus.word], - ), - GameState( - score: GameBloc.bonusWordScore, - balls: 3, - activatedBonusLetters: [], - activatedDashNests: {}, - bonusHistory: [GameBonus.word], - ), - ], - ); - }); - group('DashNestActivated', () { blocTest( 'adds the bonus when all nests are activated', @@ -200,21 +97,18 @@ void main() { GameState( score: 0, balls: 3, - activatedBonusLetters: [], activatedDashNests: {'0'}, bonusHistory: [], ), GameState( score: 0, balls: 3, - activatedBonusLetters: [], activatedDashNests: {'0', '1'}, bonusHistory: [], ), GameState( score: 0, balls: 4, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [GameBonus.dashNest], ), @@ -222,6 +116,33 @@ void main() { ); }); + group( + 'BonusActivated', + () { + blocTest( + 'adds bonus to history', + build: GameBloc.new, + act: (bloc) => bloc + ..add(const BonusActivated(GameBonus.googleWord)) + ..add(const BonusActivated(GameBonus.dashNest)), + expect: () => const [ + GameState( + score: 0, + balls: 3, + activatedDashNests: {}, + bonusHistory: [GameBonus.googleWord], + ), + GameState( + score: 0, + balls: 3, + activatedDashNests: {}, + bonusHistory: [GameBonus.googleWord, GameBonus.dashNest], + ), + ], + ); + }, + ); + group('SparkyTurboChargeActivated', () { blocTest( 'adds game bonus', @@ -231,7 +152,6 @@ void main() { GameState( score: 0, balls: 3, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [GameBonus.sparkyTurboCharge], ), diff --git a/test/game/bloc/game_event_test.dart b/test/game/bloc/game_event_test.dart index 68530aae..ef2a9f54 100644 --- a/test/game/bloc/game_event_test.dart +++ b/test/game/bloc/game_event_test.dart @@ -41,61 +41,51 @@ void main() { }); }); - group('BonusLetterActivated', () { + group('BonusActivated', () { test('can be instantiated', () { - expect(const BonusLetterActivated(0), isNotNull); + expect(const BonusActivated(GameBonus.dashNest), isNotNull); }); test('supports value equality', () { expect( - BonusLetterActivated(0), - equals(BonusLetterActivated(0)), + BonusActivated(GameBonus.googleWord), + equals(const BonusActivated(GameBonus.googleWord)), ); expect( - BonusLetterActivated(0), - isNot(equals(BonusLetterActivated(1))), + const BonusActivated(GameBonus.googleWord), + isNot(equals(const BonusActivated(GameBonus.dashNest))), ); }); - - test( - 'throws assertion error if index is bigger than the word length', - () { - expect( - () => BonusLetterActivated(8), - throwsAssertionError, - ); - }, - ); }); + }); - group('DashNestActivated', () { - test('can be instantiated', () { - expect(const DashNestActivated('0'), isNotNull); - }); + group('DashNestActivated', () { + test('can be instantiated', () { + expect(const DashNestActivated('0'), isNotNull); + }); - test('supports value equality', () { - expect( - DashNestActivated('0'), - equals(DashNestActivated('0')), - ); - expect( - DashNestActivated('0'), - isNot(equals(DashNestActivated('1'))), - ); - }); + test('supports value equality', () { + expect( + DashNestActivated('0'), + equals(DashNestActivated('0')), + ); + expect( + DashNestActivated('0'), + isNot(equals(DashNestActivated('1'))), + ); }); + }); - group('SparkyTurboChargeActivated', () { - test('can be instantiated', () { - expect(const SparkyTurboChargeActivated(), isNotNull); - }); + group('SparkyTurboChargeActivated', () { + test('can be instantiated', () { + expect(const SparkyTurboChargeActivated(), isNotNull); + }); - test('supports value equality', () { - expect( - SparkyTurboChargeActivated(), - equals(SparkyTurboChargeActivated()), - ); - }); + test('supports value equality', () { + expect( + SparkyTurboChargeActivated(), + equals(SparkyTurboChargeActivated()), + ); }); }); } diff --git a/test/game/bloc/game_state_test.dart b/test/game/bloc/game_state_test.dart index ed80d192..81ca29f1 100644 --- a/test/game/bloc/game_state_test.dart +++ b/test/game/bloc/game_state_test.dart @@ -10,7 +10,6 @@ void main() { GameState( score: 0, balls: 0, - activatedBonusLetters: const [], activatedDashNests: const {}, bonusHistory: const [], ), @@ -18,7 +17,6 @@ void main() { const GameState( score: 0, balls: 0, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ), @@ -32,7 +30,6 @@ void main() { const GameState( score: 0, balls: 0, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ), @@ -49,7 +46,6 @@ void main() { () => GameState( balls: -1, score: 0, - activatedBonusLetters: const [], activatedDashNests: const {}, bonusHistory: const [], ), @@ -66,7 +62,6 @@ void main() { () => GameState( balls: 0, score: -1, - activatedBonusLetters: const [], activatedDashNests: const {}, bonusHistory: const [], ), @@ -82,7 +77,6 @@ void main() { const gameState = GameState( balls: 0, score: 0, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ); @@ -95,7 +89,6 @@ void main() { const gameState = GameState( balls: 1, score: 0, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ); @@ -103,36 +96,6 @@ void main() { }); }); - group('isLetterActivated', () { - test( - 'is true when the letter is activated', - () { - const gameState = GameState( - balls: 3, - score: 0, - activatedBonusLetters: [1], - activatedDashNests: {}, - bonusHistory: [], - ); - expect(gameState.isLetterActivated(1), isTrue); - }, - ); - - test( - 'is false when the letter is not activated', - () { - const gameState = GameState( - balls: 3, - score: 0, - activatedBonusLetters: [1], - activatedDashNests: {}, - bonusHistory: [], - ); - expect(gameState.isLetterActivated(0), isFalse); - }, - ); - }); - group('copyWith', () { test( 'throws AssertionError ' @@ -141,7 +104,6 @@ void main() { const gameState = GameState( balls: 0, score: 2, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ); @@ -159,7 +121,6 @@ void main() { const gameState = GameState( balls: 0, score: 2, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ); @@ -177,16 +138,14 @@ void main() { const gameState = GameState( score: 2, balls: 0, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ); final otherGameState = GameState( score: gameState.score + 1, balls: gameState.balls + 1, - activatedBonusLetters: const [0], activatedDashNests: const {'1'}, - bonusHistory: const [GameBonus.word], + bonusHistory: const [GameBonus.googleWord], ); expect(gameState, isNot(equals(otherGameState))); @@ -194,7 +153,6 @@ void main() { gameState.copyWith( score: otherGameState.score, balls: otherGameState.balls, - activatedBonusLetters: otherGameState.activatedBonusLetters, activatedDashNests: otherGameState.activatedDashNests, bonusHistory: otherGameState.bonusHistory, ), diff --git a/test/game/components/bonus_word_test.dart b/test/game/components/bonus_word_test.dart deleted file mode 100644 index f01fced9..00000000 --- a/test/game/components/bonus_word_test.dart +++ /dev/null @@ -1,376 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:bloc_test/bloc_test.dart'; -import 'package:flame/effects.dart'; -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:pinball/game/game.dart'; -import 'package:pinball_audio/pinball_audio.dart'; - -import '../../helpers/helpers.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(EmptyPinballGameTest.new); - - group('BonusWord', () { - flameTester.test( - 'loads the letters correctly', - (game) async { - final bonusWord = BonusWord( - position: Vector2.zero(), - ); - await game.ensureAdd(bonusWord); - - final letters = bonusWord.descendants().whereType(); - expect(letters.length, equals(GameBloc.bonusWord.length)); - }, - ); - - group('listenWhen', () { - final previousState = MockGameState(); - final currentState = MockGameState(); - - test( - 'returns true when there is a new word bonus awarded', - () { - when(() => previousState.bonusHistory).thenReturn([]); - when(() => currentState.bonusHistory).thenReturn([GameBonus.word]); - - expect( - BonusWord(position: Vector2.zero()).listenWhen( - previousState, - currentState, - ), - isTrue, - ); - }, - ); - - test( - 'returns false when there is no new word bonus awarded', - () { - when(() => previousState.bonusHistory).thenReturn([GameBonus.word]); - when(() => currentState.bonusHistory).thenReturn([GameBonus.word]); - - expect( - BonusWord(position: Vector2.zero()).listenWhen( - previousState, - currentState, - ), - isFalse, - ); - }, - ); - }); - - group('onNewState', () { - final state = MockGameState(); - flameTester.test( - 'adds sequence effect to the letters when the player receives a bonus', - (game) async { - when(() => state.bonusHistory).thenReturn([GameBonus.word]); - - final bonusWord = BonusWord(position: Vector2.zero()); - await game.ensureAdd(bonusWord); - await game.ready(); - - bonusWord.onNewState(state); - game.update(0); // Run one frame so the effects are added - - final letters = bonusWord.children.whereType(); - expect(letters.length, equals(GameBloc.bonusWord.length)); - - for (final letter in letters) { - expect( - letter.children.whereType().length, - equals(1), - ); - } - }, - ); - - flameTester.test( - 'plays the google bonus sound', - (game) async { - when(() => state.bonusHistory).thenReturn([GameBonus.word]); - - final bonusWord = BonusWord(position: Vector2.zero()); - await game.ensureAdd(bonusWord); - await game.ready(); - - bonusWord.onNewState(state); - - verify(bonusWord.gameRef.audio.googleBonus).called(1); - }, - ); - - flameTester.test( - 'adds a color effect to reset the color when the sequence is finished', - (game) async { - when(() => state.bonusHistory).thenReturn([GameBonus.word]); - - final bonusWord = BonusWord(position: Vector2.zero()); - await game.ensureAdd(bonusWord); - await game.ready(); - - bonusWord.onNewState(state); - // Run the amount of time necessary for the animation to finish - game.update(3); - game.update(0); // Run one additional frame so the effects are added - - final letters = bonusWord.children.whereType(); - expect(letters.length, equals(GameBloc.bonusWord.length)); - - for (final letter in letters) { - expect( - letter.children.whereType().length, - equals(1), - ); - } - }, - ); - }); - }); - - group('BonusLetter', () { - final flameTester = FlameTester(EmptyPinballGameTest.new); - - flameTester.test( - 'loads correctly', - (game) async { - final bonusLetter = BonusLetter( - letter: 'G', - index: 0, - ); - await game.ensureAdd(bonusLetter); - await game.ready(); - - expect(game.contains(bonusLetter), isTrue); - }, - ); - - group('body', () { - flameTester.test( - 'is static', - (game) async { - final bonusLetter = BonusLetter( - letter: 'G', - index: 0, - ); - await game.ensureAdd(bonusLetter); - - expect(bonusLetter.body.bodyType, equals(BodyType.static)); - }, - ); - }); - - group('fixture', () { - flameTester.test( - 'exists', - (game) async { - final bonusLetter = BonusLetter( - letter: 'G', - index: 0, - ); - await game.ensureAdd(bonusLetter); - - expect(bonusLetter.body.fixtures[0], isA()); - }, - ); - - flameTester.test( - 'is sensor', - (game) async { - final bonusLetter = BonusLetter( - letter: 'G', - index: 0, - ); - await game.ensureAdd(bonusLetter); - - final fixture = bonusLetter.body.fixtures[0]; - expect(fixture.isSensor, isTrue); - }, - ); - - flameTester.test( - 'shape is circular', - (game) async { - final bonusLetter = BonusLetter( - letter: 'G', - index: 0, - ); - await game.ensureAdd(bonusLetter); - - final fixture = bonusLetter.body.fixtures[0]; - expect(fixture.shape.shapeType, equals(ShapeType.circle)); - expect(fixture.shape.radius, equals(1.85)); - }, - ); - }); - - group('bonus letter activation', () { - late GameBloc gameBloc; - late PinballAudio pinballAudio; - - final flameBlocTester = FlameBlocTester( - gameBuilder: EmptyPinballGameTest.new, - blocBuilder: () => gameBloc, - repositories: () => [ - RepositoryProvider.value(value: pinballAudio), - ], - ); - - setUp(() { - gameBloc = MockGameBloc(); - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial(), - ); - - pinballAudio = MockPinballAudio(); - when(pinballAudio.googleBonus).thenAnswer((_) {}); - }); - - flameBlocTester.testGameWidget( - 'adds BonusLetterActivated to GameBloc when not activated', - setUp: (game, tester) async { - final bonusWord = BonusWord( - position: Vector2.zero(), - ); - await game.ensureAdd(bonusWord); - - final bonusLetters = - game.descendants().whereType().toList(); - for (var index = 0; index < bonusLetters.length; index++) { - final bonusLetter = bonusLetters[index]; - bonusLetter.activate(); - await game.ready(); - - verify(() => gameBloc.add(BonusLetterActivated(index))).called(1); - } - }, - ); - - flameBlocTester.testGameWidget( - "doesn't add BonusLetterActivated to GameBloc when already activated", - setUp: (game, tester) async { - const state = GameState( - score: 0, - balls: 2, - activatedBonusLetters: [0], - activatedDashNests: {}, - bonusHistory: [], - ); - whenListen( - gameBloc, - Stream.value(state), - initialState: state, - ); - - final bonusLetter = BonusLetter(letter: '', index: 0); - await game.add(bonusLetter); - await game.ready(); - - bonusLetter.activate(); - await game.ready(); - }, - verify: (game, tester) async { - verifyNever(() => gameBloc.add(const BonusLetterActivated(0))); - }, - ); - - flameBlocTester.testGameWidget( - 'adds a ColorEffect', - setUp: (game, tester) async { - const state = GameState( - score: 0, - balls: 2, - activatedBonusLetters: [0], - activatedDashNests: {}, - bonusHistory: [], - ); - - final bonusLetter = BonusLetter(letter: '', index: 0); - await game.add(bonusLetter); - await game.ready(); - - bonusLetter.activate(); - - bonusLetter.onNewState(state); - await tester.pump(); - }, - verify: (game, tester) async { - // TODO(aleastiago): Look into making `testGameWidget` pass the - // subject. - final bonusLetter = game.descendants().whereType().last; - expect( - bonusLetter.children.whereType().length, - equals(1), - ); - }, - ); - - flameBlocTester.testGameWidget( - 'listens when there is a change on the letter status', - setUp: (game, tester) async { - final bonusWord = BonusWord( - position: Vector2.zero(), - ); - await game.ensureAdd(bonusWord); - - final bonusLetters = - game.descendants().whereType().toList(); - for (var index = 0; index < bonusLetters.length; index++) { - final bonusLetter = bonusLetters[index]; - bonusLetter.activate(); - await game.ready(); - - final state = GameState( - score: 0, - balls: 2, - activatedBonusLetters: [index], - activatedDashNests: const {}, - bonusHistory: const [], - ); - - expect( - bonusLetter.listenWhen(const GameState.initial(), state), - isTrue, - ); - } - }, - ); - }); - - group('BonusLetterBallContactCallback', () { - test('calls ball.activate', () { - final ball = MockBall(); - final bonusLetter = MockBonusLetter(); - final contactCallback = BonusLetterBallContactCallback(); - - when(() => bonusLetter.isEnabled).thenReturn(true); - - contactCallback.begin(ball, bonusLetter, MockContact()); - - verify(bonusLetter.activate).called(1); - }); - - test("doesn't call ball.activate when letter is disabled", () { - final ball = MockBall(); - final bonusLetter = MockBonusLetter(); - final contactCallback = BonusLetterBallContactCallback(); - - when(() => bonusLetter.isEnabled).thenReturn(false); - - contactCallback.begin(ball, bonusLetter, MockContact()); - - verifyNever(bonusLetter.activate); - }); - }); - }); -} diff --git a/test/game/components/controlled_flipper_test.dart b/test/game/components/controlled_flipper_test.dart index a005de30..c347736e 100644 --- a/test/game/components/controlled_flipper_test.dart +++ b/test/game/components/controlled_flipper_test.dart @@ -21,7 +21,6 @@ void main() { score: 0, balls: 0, bonusHistory: [], - activatedBonusLetters: [], activatedDashNests: {}, ); whenListen(bloc, Stream.value(state), initialState: state); diff --git a/test/game/components/controlled_plunger_test.dart b/test/game/components/controlled_plunger_test.dart index 8a722c9b..dddf407b 100644 --- a/test/game/components/controlled_plunger_test.dart +++ b/test/game/components/controlled_plunger_test.dart @@ -22,7 +22,6 @@ void main() { score: 0, balls: 0, bonusHistory: [], - activatedBonusLetters: [], activatedDashNests: {}, ); whenListen(bloc, Stream.value(state), initialState: state); diff --git a/test/game/components/flutter_forest_test.dart b/test/game/components/flutter_forest_test.dart index e9e58985..ac35e363 100644 --- a/test/game/components/flutter_forest_test.dart +++ b/test/game/components/flutter_forest_test.dart @@ -95,7 +95,6 @@ void main() { const state = GameState( score: 0, balls: 3, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [GameBonus.dashNest], ); diff --git a/test/game/components/game_flow_controller_test.dart b/test/game/components/game_flow_controller_test.dart index 42ce82d7..8bb81a6c 100644 --- a/test/game/components/game_flow_controller_test.dart +++ b/test/game/components/game_flow_controller_test.dart @@ -15,7 +15,6 @@ void main() { final state = GameState( score: 10, balls: 0, - activatedBonusLetters: const [], bonusHistory: const [], activatedDashNests: const {}, ); @@ -66,7 +65,6 @@ void main() { GameState( score: 10, balls: 0, - activatedBonusLetters: const [], bonusHistory: const [], activatedDashNests: const {}, ), diff --git a/test/game/components/google_word_test.dart b/test/game/components/google_word_test.dart new file mode 100644 index 00000000..a7f467db --- /dev/null +++ b/test/game/components/google_word_test.dart @@ -0,0 +1,73 @@ +// 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/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockingjay/mockingjay.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('GoogleWord', () { + late GameBloc gameBloc; + + setUp(() { + gameBloc = MockGameBloc(); + whenListen( + gameBloc, + const Stream.empty(), + initialState: const GameState.initial(), + ); + }); + + final flameTester = FlameTester(EmptyPinballGameTest.new); + final flameBlocTester = FlameBlocTester( + gameBuilder: EmptyPinballGameTest.new, + blocBuilder: () => gameBloc, + ); + + flameTester.test( + 'loads the letters correctly', + (game) async { + const word = 'Google'; + final googleWord = GoogleWord(position: Vector2.zero()); + await game.ensureAdd(googleWord); + + final letters = googleWord.children.whereType(); + expect(letters.length, equals(word.length)); + }, + ); + + flameBlocTester.testGameWidget( + 'adds GameBonus.googleWord to the game when all letters are activated', + setUp: (game, _) async { + final ball = Ball(baseColor: const Color(0xFFFF0000)); + final googleWord = GoogleWord(position: Vector2.zero()); + await game.ensureAddAll([googleWord, ball]); + + final letters = googleWord.children.whereType(); + expect(letters, isNotEmpty); + for (final letter in letters) { + beginContact(game, letter, ball); + await game.ready(); + + if (letter == letters.last) { + verify( + () => gameBloc.add(const BonusActivated(GameBonus.googleWord)), + ).called(1); + } else { + verifyNever( + () => gameBloc.add(const BonusActivated(GameBonus.googleWord)), + ); + } + } + }, + ); + }); +} diff --git a/test/game/components/score_effect_controller_test.dart b/test/game/components/score_effect_controller_test.dart index 241f040b..9d2b5310 100644 --- a/test/game/components/score_effect_controller_test.dart +++ b/test/game/components/score_effect_controller_test.dart @@ -30,7 +30,6 @@ void main() { const current = GameState( score: 10, balls: 3, - activatedBonusLetters: [], bonusHistory: [], activatedDashNests: {}, ); @@ -44,7 +43,6 @@ void main() { const current = GameState( score: 10, balls: 3, - activatedBonusLetters: [], bonusHistory: [], activatedDashNests: {}, ); @@ -70,7 +68,6 @@ void main() { const state = GameState( score: 10, balls: 3, - activatedBonusLetters: [], bonusHistory: [], activatedDashNests: {}, ); @@ -89,7 +86,6 @@ void main() { const GameState( score: 10, balls: 3, - activatedBonusLetters: [], bonusHistory: [], activatedDashNests: {}, ), @@ -99,7 +95,6 @@ void main() { const GameState( score: 14, balls: 3, - activatedBonusLetters: [], bonusHistory: [], activatedDashNests: {}, ), diff --git a/test/game/view/game_hud_test.dart b/test/game/view/game_hud_test.dart index 953b89eb..2d5f50d9 100644 --- a/test/game/view/game_hud_test.dart +++ b/test/game/view/game_hud_test.dart @@ -12,7 +12,6 @@ void main() { const initialState = GameState( score: 10, balls: 2, - activatedBonusLetters: [], activatedDashNests: {}, bonusHistory: [], ); diff --git a/test/helpers/mocks.dart b/test/helpers/mocks.dart index df6728cc..12e6d366 100644 --- a/test/helpers/mocks.dart +++ b/test/helpers/mocks.dart @@ -64,8 +64,6 @@ class MockTapUpInfo extends Mock implements TapUpInfo {} class MockEventPosition extends Mock implements EventPosition {} -class MockBonusLetter extends Mock implements BonusLetter {} - class MockFilter extends Mock implements Filter {} class MockFixture extends Mock implements Fixture {}