diff --git a/.github/workflows/pinball_components.yaml b/.github/workflows/pinball_components.yaml new file mode 100644 index 00000000..cab60a54 --- /dev/null +++ b/.github/workflows/pinball_components.yaml @@ -0,0 +1,19 @@ +name: pinball_components + +on: + push: + paths: + - "packages/pinball_components/**" + - ".github/workflows/pinball_components.yaml" + + pull_request: + paths: + - "packages/pinball_components/**" + - ".github/workflows/pinball_components.yaml" + +jobs: + build: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 + with: + working_directory: packages/pinball_components + coverage_excludes: "lib/src/generated/*.dart" diff --git a/lib/game/bloc/game_bloc.dart b/lib/game/bloc/game_bloc.dart index 663fee35..c02417a7 100644 --- a/lib/game/bloc/game_bloc.dart +++ b/lib/game/bloc/game_bloc.dart @@ -12,6 +12,7 @@ class GameBloc extends Bloc { on(_onBallLost); on(_onScored); on(_onBonusLetterActivated); + on(_onDashNestActivated); } static const bonusWord = 'GOOGLE'; @@ -52,4 +53,28 @@ class GameBloc extends Bloc { ); } } + + void _onDashNestActivated(DashNestActivated event, Emitter emit) { + const nestsRequiredForBonus = 3; + + final newNests = { + ...state.activatedDashNests, + event.nestId, + }; + if (newNests.length == nestsRequiredForBonus) { + emit( + state.copyWith( + activatedDashNests: {}, + bonusHistory: [ + ...state.bonusHistory, + GameBonus.dashNest, + ], + ), + ); + } else { + emit( + state.copyWith(activatedDashNests: newNests), + ); + } + } } diff --git a/lib/game/bloc/game_event.dart b/lib/game/bloc/game_event.dart index 0edc91ab..b05c5336 100644 --- a/lib/game/bloc/game_event.dart +++ b/lib/game/bloc/game_event.dart @@ -45,3 +45,12 @@ class BonusLetterActivated extends GameEvent { @override List get props => [letterIndex]; } + +class DashNestActivated extends GameEvent { + const DashNestActivated(this.nestId); + + final String nestId; + + @override + List get props => [nestId]; +} diff --git a/lib/game/bloc/game_state.dart b/lib/game/bloc/game_state.dart index e2c39d1f..5c722946 100644 --- a/lib/game/bloc/game_state.dart +++ b/lib/game/bloc/game_state.dart @@ -7,6 +7,10 @@ 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 user activates all of the Dash + /// nests on the board, adding a new ball to the board. + dashNest, } /// {@template game_state} @@ -19,6 +23,7 @@ class GameState extends Equatable { required this.balls, required this.activatedBonusLetters, required this.bonusHistory, + required this.activatedDashNests, }) : assert(score >= 0, "Score can't be negative"), assert(balls >= 0, "Number of balls can't be negative"); @@ -26,6 +31,7 @@ class GameState extends Equatable { : score = 0, balls = 3, activatedBonusLetters = const [], + activatedDashNests = const {}, bonusHistory = const []; /// The current score of the game. @@ -39,6 +45,9 @@ class GameState extends Equatable { /// Active bonus letters. final List activatedBonusLetters; + /// Active dash nests. + final Set activatedDashNests; + /// Holds the history of all the [GameBonus]es earned by the player during a /// PinballGame. final List bonusHistory; @@ -57,6 +66,7 @@ class GameState extends Equatable { int? score, int? balls, List? activatedBonusLetters, + Set? activatedDashNests, List? bonusHistory, }) { assert( @@ -69,6 +79,7 @@ class GameState extends Equatable { balls: balls ?? this.balls, activatedBonusLetters: activatedBonusLetters ?? this.activatedBonusLetters, + activatedDashNests: activatedDashNests ?? this.activatedDashNests, bonusHistory: bonusHistory ?? this.bonusHistory, ); } @@ -78,6 +89,7 @@ class GameState extends Equatable { score, balls, activatedBonusLetters, + activatedDashNests, bonusHistory, ]; } diff --git a/lib/game/components/pathway.dart b/lib/game/components/pathway.dart index 0c29dd7b..8604e0f3 100644 --- a/lib/game/components/pathway.dart +++ b/lib/game/components/pathway.dart @@ -146,7 +146,8 @@ class Pathway extends BodyComponent with InitialPosition, Layered { final List> _paths; - List _createFixtureDefs() { + /// Constructs different [ChainShape]s to form the [Pathway] shape. + List createFixtureDefs() { final fixturesDef = []; for (final path in _paths) { @@ -161,7 +162,7 @@ class Pathway extends BodyComponent with InitialPosition, Layered { Body createBody() { final bodyDef = BodyDef()..position = initialPosition; final body = world.createBody(bodyDef); - _createFixtureDefs().forEach(body.createFixture); + createFixtureDefs().forEach(body.createFixture); return body; } diff --git a/packages/pinball_components/.gitignore b/packages/pinball_components/.gitignore new file mode 100644 index 00000000..d6130351 --- /dev/null +++ b/packages/pinball_components/.gitignore @@ -0,0 +1,39 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# VSCode related +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/packages/pinball_components/README.md b/packages/pinball_components/README.md new file mode 100644 index 00000000..81ea9893 --- /dev/null +++ b/packages/pinball_components/README.md @@ -0,0 +1,11 @@ +# pinball_components + +[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] +[![License: MIT][license_badge]][license_link] + +Package with the UI game components for the Pinball Game + +[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg +[license_link]: https://opensource.org/licenses/MIT +[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg +[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis \ No newline at end of file diff --git a/packages/pinball_components/analysis_options.yaml b/packages/pinball_components/analysis_options.yaml new file mode 100644 index 00000000..3742fc3d --- /dev/null +++ b/packages/pinball_components/analysis_options.yaml @@ -0,0 +1 @@ +include: package:very_good_analysis/analysis_options.2.4.0.yaml \ No newline at end of file diff --git a/packages/pinball_components/lib/pinball_components.dart b/packages/pinball_components/lib/pinball_components.dart new file mode 100644 index 00000000..a08579e5 --- /dev/null +++ b/packages/pinball_components/lib/pinball_components.dart @@ -0,0 +1,3 @@ +library pinball_components; + +export 'src/pinball_components.dart'; diff --git a/packages/pinball_components/lib/src/pinball_components.dart b/packages/pinball_components/lib/src/pinball_components.dart new file mode 100644 index 00000000..dbc4d6fd --- /dev/null +++ b/packages/pinball_components/lib/src/pinball_components.dart @@ -0,0 +1,7 @@ +/// {@template pinball_components} +/// Package with the UI game components for the Pinball Game +/// {@endtemplate} +class PinballComponents { + /// {@macro pinball_components} + const PinballComponents(); +} diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml new file mode 100644 index 00000000..a3a990a0 --- /dev/null +++ b/packages/pinball_components/pubspec.yaml @@ -0,0 +1,16 @@ +name: pinball_components +description: Package with the UI game components for the Pinball Game +version: 1.0.0+1 +publish_to: none + +environment: + sdk: ">=2.16.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + very_good_analysis: ^2.4.0 \ No newline at end of file diff --git a/packages/pinball_components/test/src/pinball_components_test.dart b/packages/pinball_components/test/src/pinball_components_test.dart new file mode 100644 index 00000000..7359ddcb --- /dev/null +++ b/packages/pinball_components/test/src/pinball_components_test.dart @@ -0,0 +1,11 @@ +// ignore_for_file: prefer_const_constructors +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group('PinballComponents', () { + test('can be instantiated', () { + expect(PinballComponents(), isNotNull); + }); + }); +} diff --git a/test/game/bloc/game_bloc_test.dart b/test/game/bloc/game_bloc_test.dart index 18e50858..f4b79001 100644 --- a/test/game/bloc/game_bloc_test.dart +++ b/test/game/bloc/game_bloc_test.dart @@ -25,18 +25,21 @@ void main() { 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: [], ), ], @@ -56,12 +59,14 @@ void main() { score: 2, balls: 3, activatedBonusLetters: [], + activatedDashNests: {}, bonusHistory: [], ), const GameState( score: 5, balls: 3, activatedBonusLetters: [], + activatedDashNests: {}, bonusHistory: [], ), ], @@ -82,18 +87,21 @@ void main() { 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: [], ), ], @@ -113,18 +121,21 @@ void main() { 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: [], ), ], @@ -145,46 +156,87 @@ void main() { 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', + build: GameBloc.new, + act: (bloc) => bloc + ..add(const DashNestActivated('0')) + ..add(const DashNestActivated('1')) + ..add(const DashNestActivated('2')), + expect: () => const [ + GameState( + score: 0, + balls: 3, + activatedBonusLetters: [], + activatedDashNests: {'0'}, + bonusHistory: [], + ), + GameState( + score: 0, + balls: 3, + activatedBonusLetters: [], + activatedDashNests: {'0', '1'}, + bonusHistory: [], + ), + GameState( + score: 0, + balls: 3, + activatedBonusLetters: [], + activatedDashNests: {}, + bonusHistory: [GameBonus.dashNest], + ), + ], + ); + }); }); } diff --git a/test/game/bloc/game_event_test.dart b/test/game/bloc/game_event_test.dart index d6d2278b..af9f6148 100644 --- a/test/game/bloc/game_event_test.dart +++ b/test/game/bloc/game_event_test.dart @@ -67,5 +67,22 @@ void main() { }, ); }); + + 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'))), + ); + }); + }); }); } diff --git a/test/game/bloc/game_state_test.dart b/test/game/bloc/game_state_test.dart index 8ab72e6c..aa8bdf66 100644 --- a/test/game/bloc/game_state_test.dart +++ b/test/game/bloc/game_state_test.dart @@ -11,6 +11,7 @@ void main() { score: 0, balls: 0, activatedBonusLetters: const [], + activatedDashNests: const {}, bonusHistory: const [], ), equals( @@ -18,6 +19,7 @@ void main() { score: 0, balls: 0, activatedBonusLetters: [], + activatedDashNests: {}, bonusHistory: [], ), ), @@ -31,6 +33,7 @@ void main() { score: 0, balls: 0, activatedBonusLetters: [], + activatedDashNests: {}, bonusHistory: [], ), isNotNull, @@ -47,6 +50,7 @@ void main() { balls: -1, score: 0, activatedBonusLetters: const [], + activatedDashNests: const {}, bonusHistory: const [], ), throwsAssertionError, @@ -63,6 +67,7 @@ void main() { balls: 0, score: -1, activatedBonusLetters: const [], + activatedDashNests: const {}, bonusHistory: const [], ), throwsAssertionError, @@ -78,6 +83,7 @@ void main() { balls: 0, score: 0, activatedBonusLetters: [], + activatedDashNests: {}, bonusHistory: [], ); expect(gameState.isGameOver, isTrue); @@ -90,6 +96,7 @@ void main() { balls: 1, score: 0, activatedBonusLetters: [], + activatedDashNests: {}, bonusHistory: [], ); expect(gameState.isGameOver, isFalse); @@ -105,6 +112,7 @@ void main() { balls: 1, score: 0, activatedBonusLetters: [], + activatedDashNests: {}, bonusHistory: [], ); expect(gameState.isLastBall, isTrue); @@ -119,6 +127,7 @@ void main() { balls: 2, score: 0, activatedBonusLetters: [], + activatedDashNests: {}, bonusHistory: [], ); expect(gameState.isLastBall, isFalse); @@ -134,6 +143,7 @@ void main() { balls: 3, score: 0, activatedBonusLetters: [1], + activatedDashNests: {}, bonusHistory: [], ); expect(gameState.isLetterActivated(1), isTrue); @@ -147,6 +157,7 @@ void main() { balls: 3, score: 0, activatedBonusLetters: [1], + activatedDashNests: {}, bonusHistory: [], ); expect(gameState.isLetterActivated(0), isFalse); @@ -163,6 +174,7 @@ void main() { balls: 0, score: 2, activatedBonusLetters: [], + activatedDashNests: {}, bonusHistory: [], ); expect( @@ -180,6 +192,7 @@ void main() { balls: 0, score: 2, activatedBonusLetters: [], + activatedDashNests: {}, bonusHistory: [], ); expect( @@ -197,12 +210,14 @@ void main() { 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], ); expect(gameState, isNot(equals(otherGameState))); @@ -212,6 +227,7 @@ void main() { score: otherGameState.score, balls: otherGameState.balls, activatedBonusLetters: otherGameState.activatedBonusLetters, + activatedDashNests: otherGameState.activatedDashNests, bonusHistory: otherGameState.bonusHistory, ), equals(otherGameState), diff --git a/test/game/components/ball_test.dart b/test/game/components/ball_test.dart index 7a48b21f..f94c1526 100644 --- a/test/game/components/ball_test.dart +++ b/test/game/components/ball_test.dart @@ -156,6 +156,7 @@ void main() { score: 10, balls: 1, activatedBonusLetters: [], + activatedDashNests: {}, bonusHistory: [], ), ); diff --git a/test/game/components/bonus_word_test.dart b/test/game/components/bonus_word_test.dart index 47a7e257..a12a5a74 100644 --- a/test/game/components/bonus_word_test.dart +++ b/test/game/components/bonus_word_test.dart @@ -234,6 +234,7 @@ void main() { score: 0, balls: 2, activatedBonusLetters: [0], + activatedDashNests: {}, bonusHistory: [], ); whenListen( @@ -259,6 +260,7 @@ void main() { score: 0, balls: 2, activatedBonusLetters: [0], + activatedDashNests: {}, bonusHistory: [], ); @@ -283,6 +285,7 @@ void main() { score: 0, balls: 2, activatedBonusLetters: [0], + activatedDashNests: {}, bonusHistory: [], ); diff --git a/test/game/view/game_hud_test.dart b/test/game/view/game_hud_test.dart index 536edbad..953b89eb 100644 --- a/test/game/view/game_hud_test.dart +++ b/test/game/view/game_hud_test.dart @@ -13,6 +13,7 @@ void main() { score: 10, balls: 2, activatedBonusLetters: [], + activatedDashNests: {}, bonusHistory: [], ); diff --git a/test/game/view/pinball_game_page_test.dart b/test/game/view/pinball_game_page_test.dart index dcf0c001..5298d6ac 100644 --- a/test/game/view/pinball_game_page_test.dart +++ b/test/game/view/pinball_game_page_test.dart @@ -88,6 +88,7 @@ void main() { score: 0, balls: 0, activatedBonusLetters: [], + activatedDashNests: {}, bonusHistory: [], );