From d729a9ac69b86e6f09df85025c7d8f721fe6ae2a Mon Sep 17 00:00:00 2001 From: Rui Miguel Alonso Date: Wed, 4 May 2022 19:28:03 +0200 Subject: [PATCH] refactor: split `GameState` score into `totalScore`, `roundScore` and `displayScore` (#331) * refactor: GameState adding totalScore, roundScore and displayScore * chore: trailing commas * chore: doc Co-authored-by: Tom Arra --- lib/game/bloc/game_bloc.dart | 7 ++- lib/game/bloc/game_state.dart | 36 ++++++++--- lib/game/components/game_flow_controller.dart | 2 +- lib/game/view/widgets/game_hud.dart | 4 +- lib/game/view/widgets/score_view.dart | 2 +- .../game/behaviors/scoring_behavior_test.dart | 3 +- test/game/bloc/game_bloc_test.dart | 43 +++++-------- test/game/bloc/game_state_test.dart | 62 ++++++++++++++----- .../components/controlled_flipper_test.dart | 3 +- .../components/controlled_plunger_test.dart | 3 +- .../components/game_flow_controller_test.dart | 6 +- .../behaviors/multiballs_behavior_test.dart | 3 +- .../behaviors/multipliers_behavior_test.dart | 6 +- test/game/view/widgets/game_hud_test.dart | 8 ++- .../widgets/round_count_display_test.dart | 3 +- test/game/view/widgets/score_view_test.dart | 23 +++++-- 16 files changed, 138 insertions(+), 76 deletions(-) diff --git a/lib/game/bloc/game_bloc.dart b/lib/game/bloc/game_bloc.dart index 49f40d1f..43d6005b 100644 --- a/lib/game/bloc/game_bloc.dart +++ b/lib/game/bloc/game_bloc.dart @@ -17,12 +17,13 @@ class GameBloc extends Bloc { } void _onRoundLost(RoundLost event, Emitter emit) { - final score = state.score * state.multiplier; + final score = state.totalScore + state.roundScore * state.multiplier; final roundsLeft = math.max(state.rounds - 1, 0); emit( state.copyWith( - score: score, + totalScore: score, + roundScore: 0, multiplier: 1, rounds: roundsLeft, ), @@ -32,7 +33,7 @@ class GameBloc extends Bloc { void _onScored(Scored event, Emitter emit) { if (!state.isGameOver) { emit( - state.copyWith(score: state.score + event.points), + state.copyWith(roundScore: state.roundScore + event.points), ); } } diff --git a/lib/game/bloc/game_state.dart b/lib/game/bloc/game_state.dart index 4ce9042d..2ccb4405 100644 --- a/lib/game/bloc/game_state.dart +++ b/lib/game/bloc/game_state.dart @@ -26,22 +26,32 @@ enum GameBonus { class GameState extends Equatable { /// {@macro game_state} const GameState({ - required this.score, + required this.totalScore, + required this.roundScore, required this.multiplier, required this.rounds, required this.bonusHistory, - }) : assert(score >= 0, "Score can't be negative"), + }) : assert(totalScore >= 0, "TotalScore can't be negative"), + assert(roundScore >= 0, "Round score can't be negative"), assert(multiplier > 0, 'Multiplier must be greater than zero'), assert(rounds >= 0, "Number of rounds can't be negative"); const GameState.initial() - : score = 0, + : totalScore = 0, + roundScore = 0, multiplier = 1, rounds = 3, bonusHistory = const []; - /// The current score of the game. - final int score; + /// The score for the current round of the game. + /// + /// Multipliers are only applied to the score for the current round once is + /// lost. Then the [roundScore] is added to the [totalScore] and reset to 0 + /// for the next round. + final int roundScore; + + /// The total score of the game. + final int totalScore; /// The current multiplier for the score. final int multiplier; @@ -58,20 +68,25 @@ class GameState extends Equatable { /// Determines when the game is over. bool get isGameOver => rounds == 0; + /// The score displayed at the game. + int get displayScore => roundScore + totalScore; + GameState copyWith({ - int? score, + int? totalScore, + int? roundScore, int? multiplier, int? balls, int? rounds, List? bonusHistory, }) { assert( - score == null || score >= this.score, - "Score can't be decreased", + totalScore == null || totalScore >= this.totalScore, + "Total score can't be decreased", ); return GameState( - score: score ?? this.score, + totalScore: totalScore ?? this.totalScore, + roundScore: roundScore ?? this.roundScore, multiplier: multiplier ?? this.multiplier, rounds: rounds ?? this.rounds, bonusHistory: bonusHistory ?? this.bonusHistory, @@ -80,7 +95,8 @@ class GameState extends Equatable { @override List get props => [ - score, + totalScore, + roundScore, multiplier, rounds, bonusHistory, diff --git a/lib/game/components/game_flow_controller.dart b/lib/game/components/game_flow_controller.dart index 4af93610..1299e6eb 100644 --- a/lib/game/components/game_flow_controller.dart +++ b/lib/game/components/game_flow_controller.dart @@ -30,7 +30,7 @@ class GameFlowController extends ComponentController // TODO(erickzanardo): implement score submission and "navigate" to the // next page component.descendants().whereType().first.initialsInput( - score: state?.score ?? 0, + score: state?.displayScore ?? 0, characterIconPath: component.characterTheme.leaderboardIcon.keyName, ); component.firstChild()!.focusOnGameOverBackbox(); diff --git a/lib/game/view/widgets/game_hud.dart b/lib/game/view/widgets/game_hud.dart index 605bceb4..b40536aa 100644 --- a/lib/game/view/widgets/game_hud.dart +++ b/lib/game/view/widgets/game_hud.dart @@ -7,8 +7,8 @@ import 'package:pinball_ui/pinball_ui.dart'; /// {@template game_hud} /// Overlay on the [PinballGame]. /// -/// Displays the current [GameState.score], [GameState.rounds] and animates when -/// the player gets a [GameBonus]. +/// Displays the current [GameState.displayScore], [GameState.rounds] and +/// animates when the player gets a [GameBonus]. /// {@endtemplate} class GameHud extends StatefulWidget { /// {@macro game_hud} diff --git a/lib/game/view/widgets/score_view.dart b/lib/game/view/widgets/score_view.dart index 1fe57eb1..76ab9fa4 100644 --- a/lib/game/view/widgets/score_view.dart +++ b/lib/game/view/widgets/score_view.dart @@ -69,7 +69,7 @@ class _ScoreText extends StatelessWidget { @override Widget build(BuildContext context) { - final score = context.select((GameBloc bloc) => bloc.state.score); + final score = context.select((GameBloc bloc) => bloc.state.displayScore); return Text( score.formatScore(), diff --git a/test/game/behaviors/scoring_behavior_test.dart b/test/game/behaviors/scoring_behavior_test.dart index 3e710641..47903d8a 100644 --- a/test/game/behaviors/scoring_behavior_test.dart +++ b/test/game/behaviors/scoring_behavior_test.dart @@ -52,7 +52,8 @@ void main() { blocBuilder: () { bloc = _MockGameBloc(); const state = GameState( - score: 0, + totalScore: 0, + roundScore: 0, multiplier: 1, rounds: 3, bonusHistory: [], diff --git a/test/game/bloc/game_bloc_test.dart b/test/game/bloc/game_bloc_test.dart index 3711105e..5927291b 100644 --- a/test/game/bloc/game_bloc_test.dart +++ b/test/game/bloc/game_bloc_test.dart @@ -6,7 +6,7 @@ void main() { group('GameBloc', () { test('initial state has 3 rounds and empty score', () { final gameBloc = GameBloc(); - expect(gameBloc.state.score, equals(0)); + expect(gameBloc.state.roundScore, equals(0)); expect(gameBloc.state.rounds, equals(3)); }); @@ -19,21 +19,17 @@ void main() { bloc.add(const RoundLost()); }, expect: () => [ - const GameState( - score: 0, - multiplier: 1, - rounds: 2, - bonusHistory: [], - ), + isA()..having((state) => state.rounds, 'rounds', 2), ], ); blocTest( - 'apply multiplier to score ' + 'apply multiplier to roundScore and add it to totalScore ' 'when round is lost', build: GameBloc.new, seed: () => const GameState( - score: 5, + totalScore: 10, + roundScore: 5, multiplier: 3, rounds: 2, bonusHistory: [], @@ -43,8 +39,8 @@ void main() { }, expect: () => [ isA() - ..having((state) => state.score, 'score', 15) - ..having((state) => state.rounds, 'rounds', 1), + ..having((state) => state.totalScore, 'totalScore', 25) + ..having((state) => state.roundScore, 'roundScore', 0) ], ); @@ -53,7 +49,8 @@ void main() { 'when round is lost', build: GameBloc.new, seed: () => const GameState( - score: 5, + totalScore: 10, + roundScore: 5, multiplier: 3, rounds: 2, bonusHistory: [], @@ -62,9 +59,7 @@ void main() { bloc.add(const RoundLost()); }, expect: () => [ - isA() - ..having((state) => state.multiplier, 'multiplier', 1) - ..having((state) => state.rounds, 'rounds', 1), + isA()..having((state) => state.multiplier, 'multiplier', 1) ], ); }); @@ -79,10 +74,10 @@ void main() { ..add(const Scored(points: 3)), expect: () => [ isA() - ..having((state) => state.score, 'score', 2) + ..having((state) => state.roundScore, 'roundScore', 2) ..having((state) => state.isGameOver, 'isGameOver', false), isA() - ..having((state) => state.score, 'score', 5) + ..having((state) => state.roundScore, 'roundScore', 5) ..having((state) => state.isGameOver, 'isGameOver', false), ], ); @@ -99,15 +94,15 @@ void main() { }, expect: () => [ isA() - ..having((state) => state.score, 'score', 0) + ..having((state) => state.roundScore, 'roundScore', 0) ..having((state) => state.rounds, 'rounds', 2) ..having((state) => state.isGameOver, 'isGameOver', false), isA() - ..having((state) => state.score, 'score', 0) + ..having((state) => state.roundScore, 'roundScore', 0) ..having((state) => state.rounds, 'rounds', 1) ..having((state) => state.isGameOver, 'isGameOver', false), isA() - ..having((state) => state.score, 'score', 0) + ..having((state) => state.roundScore, 'roundScore', 0) ..having((state) => state.rounds, 'rounds', 0) ..having((state) => state.isGameOver, 'isGameOver', true), ], @@ -124,11 +119,9 @@ void main() { ..add(const MultiplierIncreased()), expect: () => [ isA() - ..having((state) => state.score, 'score', 0) ..having((state) => state.multiplier, 'multiplier', 2) ..having((state) => state.isGameOver, 'isGameOver', false), isA() - ..having((state) => state.score, 'score', 0) ..having((state) => state.multiplier, 'multiplier', 3) ..having((state) => state.isGameOver, 'isGameOver', false), ], @@ -139,7 +132,8 @@ void main() { 'when multiplier is 6 and game is not over', build: GameBloc.new, seed: () => const GameState( - score: 0, + totalScore: 10, + roundScore: 0, multiplier: 6, rounds: 3, bonusHistory: [], @@ -160,15 +154,12 @@ void main() { }, expect: () => [ isA() - ..having((state) => state.score, 'score', 0) ..having((state) => state.multiplier, 'multiplier', 1) ..having((state) => state.isGameOver, 'isGameOver', false), isA() - ..having((state) => state.score, 'score', 0) ..having((state) => state.multiplier, 'multiplier', 1) ..having((state) => state.isGameOver, 'isGameOver', false), isA() - ..having((state) => state.score, 'score', 0) ..having((state) => state.multiplier, 'multiplier', 1) ..having((state) => state.isGameOver, 'isGameOver', true), ], diff --git a/test/game/bloc/game_state_test.dart b/test/game/bloc/game_state_test.dart index add25e05..b59115a3 100644 --- a/test/game/bloc/game_state_test.dart +++ b/test/game/bloc/game_state_test.dart @@ -8,14 +8,16 @@ void main() { test('supports value equality', () { expect( GameState( - score: 0, + totalScore: 0, + roundScore: 0, multiplier: 1, rounds: 3, bonusHistory: const [], ), equals( const GameState( - score: 0, + totalScore: 0, + roundScore: 0, multiplier: 1, rounds: 3, bonusHistory: [], @@ -28,7 +30,8 @@ void main() { test('can be instantiated', () { expect( const GameState( - score: 0, + totalScore: 0, + roundScore: 0, multiplier: 1, rounds: 3, bonusHistory: [], @@ -40,11 +43,29 @@ void main() { test( 'throws AssertionError ' - 'when score is negative', + 'when totalScore is negative', () { expect( () => GameState( - score: -1, + totalScore: -1, + roundScore: 0, + multiplier: 1, + rounds: 3, + bonusHistory: const [], + ), + throwsAssertionError, + ); + }, + ); + + test( + 'throws AssertionError ' + 'when roundScore is negative', + () { + expect( + () => GameState( + totalScore: 0, + roundScore: -1, multiplier: 1, rounds: 3, bonusHistory: const [], @@ -60,7 +81,8 @@ void main() { () { expect( () => GameState( - score: 1, + totalScore: 0, + roundScore: 1, multiplier: 0, rounds: 3, bonusHistory: const [], @@ -76,7 +98,8 @@ void main() { () { expect( () => GameState( - score: 1, + totalScore: 0, + roundScore: 1, multiplier: 1, rounds: -1, bonusHistory: const [], @@ -91,7 +114,8 @@ void main() { 'is true ' 'when no rounds are left', () { const gameState = GameState( - score: 0, + totalScore: 0, + roundScore: 0, multiplier: 1, rounds: 0, bonusHistory: [], @@ -103,7 +127,8 @@ void main() { 'is false ' 'when one 1 round left', () { const gameState = GameState( - score: 0, + totalScore: 0, + roundScore: 0, multiplier: 1, rounds: 1, bonusHistory: [], @@ -115,16 +140,17 @@ void main() { group('copyWith', () { test( 'throws AssertionError ' - 'when scored is decreased', + 'when totalScore is decreased', () { const gameState = GameState( - score: 2, + totalScore: 2, + roundScore: 2, multiplier: 1, rounds: 3, bonusHistory: [], ); expect( - () => gameState.copyWith(score: gameState.score - 1), + () => gameState.copyWith(totalScore: gameState.totalScore - 1), throwsAssertionError, ); }, @@ -135,7 +161,8 @@ void main() { 'when no argument specified', () { const gameState = GameState( - score: 2, + totalScore: 0, + roundScore: 2, multiplier: 1, rounds: 3, bonusHistory: [], @@ -152,13 +179,15 @@ void main() { 'when all arguments specified', () { const gameState = GameState( - score: 2, + totalScore: 0, + roundScore: 2, multiplier: 1, rounds: 3, bonusHistory: [], ); final otherGameState = GameState( - score: gameState.score + 1, + totalScore: gameState.totalScore + 1, + roundScore: gameState.roundScore + 1, multiplier: gameState.multiplier + 1, rounds: gameState.rounds + 1, bonusHistory: const [GameBonus.googleWord], @@ -167,7 +196,8 @@ void main() { expect( gameState.copyWith( - score: otherGameState.score, + totalScore: otherGameState.totalScore, + roundScore: otherGameState.roundScore, multiplier: otherGameState.multiplier, rounds: otherGameState.rounds, bonusHistory: otherGameState.bonusHistory, diff --git a/test/game/components/controlled_flipper_test.dart b/test/game/components/controlled_flipper_test.dart index e8b7aaf3..f9561b60 100644 --- a/test/game/components/controlled_flipper_test.dart +++ b/test/game/components/controlled_flipper_test.dart @@ -27,7 +27,8 @@ void main() { blocBuilder: () { final bloc = _MockGameBloc(); const state = GameState( - score: 0, + totalScore: 0, + roundScore: 0, multiplier: 1, rounds: 0, bonusHistory: [], diff --git a/test/game/components/controlled_plunger_test.dart b/test/game/components/controlled_plunger_test.dart index c832e24a..4d9c9c74 100644 --- a/test/game/components/controlled_plunger_test.dart +++ b/test/game/components/controlled_plunger_test.dart @@ -22,7 +22,8 @@ void main() { blocBuilder: () { final bloc = _MockGameBloc(); const state = GameState( - score: 0, + totalScore: 0, + roundScore: 0, multiplier: 1, rounds: 0, bonusHistory: [], diff --git a/test/game/components/game_flow_controller_test.dart b/test/game/components/game_flow_controller_test.dart index c7196057..e403396d 100644 --- a/test/game/components/game_flow_controller_test.dart +++ b/test/game/components/game_flow_controller_test.dart @@ -23,7 +23,8 @@ void main() { group('listenWhen', () { test('is true when the game over state has changed', () { final state = GameState( - score: 10, + totalScore: 0, + roundScore: 10, multiplier: 1, rounds: 0, bonusHistory: const [], @@ -79,7 +80,8 @@ void main() { () { gameFlowController.onNewState( GameState( - score: 0, + totalScore: 0, + roundScore: 10, multiplier: 1, rounds: 0, bonusHistory: const [], diff --git a/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart b/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart index 5f8b1400..b294d350 100644 --- a/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart +++ b/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart @@ -74,7 +74,8 @@ void main() { test('is false when the bonusHistory state is the same', () { final previous = GameState.initial(); final state = GameState( - score: 10, + totalScore: 0, + roundScore: 10, multiplier: 1, rounds: 0, bonusHistory: const [], diff --git a/test/game/components/multipliers/behaviors/multipliers_behavior_test.dart b/test/game/components/multipliers/behaviors/multipliers_behavior_test.dart index 40a952f1..ca3c5921 100644 --- a/test/game/components/multipliers/behaviors/multipliers_behavior_test.dart +++ b/test/game/components/multipliers/behaviors/multipliers_behavior_test.dart @@ -56,7 +56,8 @@ void main() { group('listenWhen', () { test('is true when the multiplier has changed', () { final state = GameState( - score: 10, + totalScore: 0, + roundScore: 10, multiplier: 2, rounds: 0, bonusHistory: const [], @@ -71,7 +72,8 @@ void main() { test('is false when the multiplier state is the same', () { final state = GameState( - score: 10, + totalScore: 0, + roundScore: 10, multiplier: 1, rounds: 0, bonusHistory: const [], diff --git a/test/game/view/widgets/game_hud_test.dart b/test/game/view/widgets/game_hud_test.dart index 75fa7439..19c860da 100644 --- a/test/game/view/widgets/game_hud_test.dart +++ b/test/game/view/widgets/game_hud_test.dart @@ -22,7 +22,8 @@ void main() { late GameBloc gameBloc; const initialState = GameState( - score: 1000, + totalScore: 0, + roundScore: 1000, multiplier: 1, rounds: 1, bonusHistory: [], @@ -70,7 +71,10 @@ void main() { gameBloc: gameBloc, ); - expect(find.text(initialState.score.formatScore()), findsOneWidget); + expect( + find.text(initialState.roundScore.formatScore()), + findsOneWidget, + ); }, ); diff --git a/test/game/view/widgets/round_count_display_test.dart b/test/game/view/widgets/round_count_display_test.dart index e3a4b887..049aba95 100644 --- a/test/game/view/widgets/round_count_display_test.dart +++ b/test/game/view/widgets/round_count_display_test.dart @@ -13,7 +13,8 @@ void main() { group('RoundCountDisplay renders', () { late GameBloc gameBloc; const initialState = GameState( - score: 0, + totalScore: 0, + roundScore: 0, multiplier: 1, rounds: 3, bonusHistory: [], diff --git a/test/game/view/widgets/score_view_test.dart b/test/game/view/widgets/score_view_test.dart index 695dc6e1..0e4acafc 100644 --- a/test/game/view/widgets/score_view_test.dart +++ b/test/game/view/widgets/score_view_test.dart @@ -15,9 +15,11 @@ class _MockGameBloc extends Mock implements GameBloc {} void main() { late GameBloc gameBloc; late StreamController stateController; - const score = 123456789; + const totalScore = 123456789; + const roundScore = 1234; const initialState = GameState( - score: score, + totalScore: totalScore, + roundScore: roundScore, multiplier: 1, rounds: 1, bonusHistory: [], @@ -42,7 +44,10 @@ void main() { ); await tester.pump(); - expect(find.text(score.formatScore()), findsOneWidget); + expect( + find.text(initialState.displayScore.formatScore()), + findsOneWidget, + ); }); testWidgets('renders game over', (tester) async { @@ -69,17 +74,23 @@ void main() { gameBloc: gameBloc, ); - expect(find.text(score.formatScore()), findsOneWidget); + expect( + find.text(initialState.displayScore.formatScore()), + findsOneWidget, + ); final newState = initialState.copyWith( - score: 987654321, + roundScore: 5678, ); stateController.add(newState); await tester.pump(); - expect(find.text(newState.score.formatScore()), findsOneWidget); + expect( + find.text(newState.displayScore.formatScore()), + findsOneWidget, + ); }); }); }