diff --git a/lib/game/components/bonus_word.dart b/lib/game/components/bonus_word.dart index 35412ecf..49a1da1d 100644 --- a/lib/game/components/bonus_word.dart +++ b/lib/game/components/bonus_word.dart @@ -12,12 +12,59 @@ import 'package:pinball/game/game.dart'; /// {@template bonus_word} /// Loads all [BonusLetter]s to compose a [BonusWord]. /// {@endtemplate} -class BonusWord extends Component { +class BonusWord extends Component with BlocComponent { /// {@macro bonus_word} BonusWord({required Vector2 position}) : _position = position; final Vector2 _position; + @override + bool listenWhen(GameState? previousState, GameState newState) { + if ((previousState?.bonusHistory.length ?? 0) < + newState.bonusHistory.length && + newState.bonusHistory.last == GameBonus.word) { + return true; + } + + return false; + } + + @override + void onNewState(GameState state) { + if (state.bonusHistory.last == GameBonus.word) { + final letters = children.whereType().toList(); + + for (var i = 0; i < letters.length; i++) { + final letter = letters[i]; + letter.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.add( + ColorEffect( + BonusLetter._disableColor, + const Offset(0, 1), + EffectController(duration: 0.25), + ), + ); + }, + ); + } + } + } + @override Future onLoad() async { await super.onLoad(); diff --git a/test/game/components/bonus_word_test.dart b/test/game/components/bonus_word_test.dart index 129f68d1..012ef2d8 100644 --- a/test/game/components/bonus_word_test.dart +++ b/test/game/components/bonus_word_test.dart @@ -26,6 +26,95 @@ void main() { 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( + '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', () { diff --git a/test/helpers/mocks.dart b/test/helpers/mocks.dart index c1c59377..80820c1b 100644 --- a/test/helpers/mocks.dart +++ b/test/helpers/mocks.dart @@ -18,6 +18,8 @@ class MockContact extends Mock implements Contact {} class MockGameBloc extends Mock implements GameBloc {} +class MockGameState extends Mock implements GameState {} + class MockThemeCubit extends Mock implements ThemeCubit {} class MockRawKeyDownEvent extends Mock implements RawKeyDownEvent {