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 <tarra3@gmail.com>
pull/336/head
Rui Miguel Alonso 3 years ago committed by GitHub
parent 94206bddaa
commit d729a9ac69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -17,12 +17,13 @@ class GameBloc extends Bloc<GameEvent, GameState> {
} }
void _onRoundLost(RoundLost event, Emitter emit) { 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); final roundsLeft = math.max(state.rounds - 1, 0);
emit( emit(
state.copyWith( state.copyWith(
score: score, totalScore: score,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: roundsLeft, rounds: roundsLeft,
), ),
@ -32,7 +33,7 @@ class GameBloc extends Bloc<GameEvent, GameState> {
void _onScored(Scored event, Emitter emit) { void _onScored(Scored event, Emitter emit) {
if (!state.isGameOver) { if (!state.isGameOver) {
emit( emit(
state.copyWith(score: state.score + event.points), state.copyWith(roundScore: state.roundScore + event.points),
); );
} }
} }

@ -26,22 +26,32 @@ enum GameBonus {
class GameState extends Equatable { class GameState extends Equatable {
/// {@macro game_state} /// {@macro game_state}
const GameState({ const GameState({
required this.score, required this.totalScore,
required this.roundScore,
required this.multiplier, required this.multiplier,
required this.rounds, required this.rounds,
required this.bonusHistory, 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(multiplier > 0, 'Multiplier must be greater than zero'),
assert(rounds >= 0, "Number of rounds can't be negative"); assert(rounds >= 0, "Number of rounds can't be negative");
const GameState.initial() const GameState.initial()
: score = 0, : totalScore = 0,
roundScore = 0,
multiplier = 1, multiplier = 1,
rounds = 3, rounds = 3,
bonusHistory = const []; bonusHistory = const [];
/// The current score of the game. /// The score for the current round of the game.
final int score; ///
/// 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. /// The current multiplier for the score.
final int multiplier; final int multiplier;
@ -58,20 +68,25 @@ class GameState extends Equatable {
/// Determines when the game is over. /// Determines when the game is over.
bool get isGameOver => rounds == 0; bool get isGameOver => rounds == 0;
/// The score displayed at the game.
int get displayScore => roundScore + totalScore;
GameState copyWith({ GameState copyWith({
int? score, int? totalScore,
int? roundScore,
int? multiplier, int? multiplier,
int? balls, int? balls,
int? rounds, int? rounds,
List<GameBonus>? bonusHistory, List<GameBonus>? bonusHistory,
}) { }) {
assert( assert(
score == null || score >= this.score, totalScore == null || totalScore >= this.totalScore,
"Score can't be decreased", "Total score can't be decreased",
); );
return GameState( return GameState(
score: score ?? this.score, totalScore: totalScore ?? this.totalScore,
roundScore: roundScore ?? this.roundScore,
multiplier: multiplier ?? this.multiplier, multiplier: multiplier ?? this.multiplier,
rounds: rounds ?? this.rounds, rounds: rounds ?? this.rounds,
bonusHistory: bonusHistory ?? this.bonusHistory, bonusHistory: bonusHistory ?? this.bonusHistory,
@ -80,7 +95,8 @@ class GameState extends Equatable {
@override @override
List<Object?> get props => [ List<Object?> get props => [
score, totalScore,
roundScore,
multiplier, multiplier,
rounds, rounds,
bonusHistory, bonusHistory,

@ -30,7 +30,7 @@ class GameFlowController extends ComponentController<PinballGame>
// TODO(erickzanardo): implement score submission and "navigate" to the // TODO(erickzanardo): implement score submission and "navigate" to the
// next page // next page
component.descendants().whereType<Backbox>().first.initialsInput( component.descendants().whereType<Backbox>().first.initialsInput(
score: state?.score ?? 0, score: state?.displayScore ?? 0,
characterIconPath: component.characterTheme.leaderboardIcon.keyName, characterIconPath: component.characterTheme.leaderboardIcon.keyName,
); );
component.firstChild<CameraController>()!.focusOnGameOverBackbox(); component.firstChild<CameraController>()!.focusOnGameOverBackbox();

@ -7,8 +7,8 @@ import 'package:pinball_ui/pinball_ui.dart';
/// {@template game_hud} /// {@template game_hud}
/// Overlay on the [PinballGame]. /// Overlay on the [PinballGame].
/// ///
/// Displays the current [GameState.score], [GameState.rounds] and animates when /// Displays the current [GameState.displayScore], [GameState.rounds] and
/// the player gets a [GameBonus]. /// animates when the player gets a [GameBonus].
/// {@endtemplate} /// {@endtemplate}
class GameHud extends StatefulWidget { class GameHud extends StatefulWidget {
/// {@macro game_hud} /// {@macro game_hud}

@ -69,7 +69,7 @@ class _ScoreText extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final score = context.select((GameBloc bloc) => bloc.state.score); final score = context.select((GameBloc bloc) => bloc.state.displayScore);
return Text( return Text(
score.formatScore(), score.formatScore(),

@ -52,7 +52,8 @@ void main() {
blocBuilder: () { blocBuilder: () {
bloc = _MockGameBloc(); bloc = _MockGameBloc();
const state = GameState( const state = GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],

@ -6,7 +6,7 @@ void main() {
group('GameBloc', () { group('GameBloc', () {
test('initial state has 3 rounds and empty score', () { test('initial state has 3 rounds and empty score', () {
final gameBloc = GameBloc(); final gameBloc = GameBloc();
expect(gameBloc.state.score, equals(0)); expect(gameBloc.state.roundScore, equals(0));
expect(gameBloc.state.rounds, equals(3)); expect(gameBloc.state.rounds, equals(3));
}); });
@ -19,21 +19,17 @@ void main() {
bloc.add(const RoundLost()); bloc.add(const RoundLost());
}, },
expect: () => [ expect: () => [
const GameState( isA<GameState>()..having((state) => state.rounds, 'rounds', 2),
score: 0,
multiplier: 1,
rounds: 2,
bonusHistory: [],
),
], ],
); );
blocTest<GameBloc, GameState>( blocTest<GameBloc, GameState>(
'apply multiplier to score ' 'apply multiplier to roundScore and add it to totalScore '
'when round is lost', 'when round is lost',
build: GameBloc.new, build: GameBloc.new,
seed: () => const GameState( seed: () => const GameState(
score: 5, totalScore: 10,
roundScore: 5,
multiplier: 3, multiplier: 3,
rounds: 2, rounds: 2,
bonusHistory: [], bonusHistory: [],
@ -43,8 +39,8 @@ void main() {
}, },
expect: () => [ expect: () => [
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 15) ..having((state) => state.totalScore, 'totalScore', 25)
..having((state) => state.rounds, 'rounds', 1), ..having((state) => state.roundScore, 'roundScore', 0)
], ],
); );
@ -53,7 +49,8 @@ void main() {
'when round is lost', 'when round is lost',
build: GameBloc.new, build: GameBloc.new,
seed: () => const GameState( seed: () => const GameState(
score: 5, totalScore: 10,
roundScore: 5,
multiplier: 3, multiplier: 3,
rounds: 2, rounds: 2,
bonusHistory: [], bonusHistory: [],
@ -62,9 +59,7 @@ void main() {
bloc.add(const RoundLost()); bloc.add(const RoundLost());
}, },
expect: () => [ expect: () => [
isA<GameState>() isA<GameState>()..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.rounds, 'rounds', 1),
], ],
); );
}); });
@ -79,10 +74,10 @@ void main() {
..add(const Scored(points: 3)), ..add(const Scored(points: 3)),
expect: () => [ expect: () => [
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 2) ..having((state) => state.roundScore, 'roundScore', 2)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 5) ..having((state) => state.roundScore, 'roundScore', 5)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
], ],
); );
@ -99,15 +94,15 @@ void main() {
}, },
expect: () => [ expect: () => [
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0) ..having((state) => state.roundScore, 'roundScore', 0)
..having((state) => state.rounds, 'rounds', 2) ..having((state) => state.rounds, 'rounds', 2)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0) ..having((state) => state.roundScore, 'roundScore', 0)
..having((state) => state.rounds, 'rounds', 1) ..having((state) => state.rounds, 'rounds', 1)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0) ..having((state) => state.roundScore, 'roundScore', 0)
..having((state) => state.rounds, 'rounds', 0) ..having((state) => state.rounds, 'rounds', 0)
..having((state) => state.isGameOver, 'isGameOver', true), ..having((state) => state.isGameOver, 'isGameOver', true),
], ],
@ -124,11 +119,9 @@ void main() {
..add(const MultiplierIncreased()), ..add(const MultiplierIncreased()),
expect: () => [ expect: () => [
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 2) ..having((state) => state.multiplier, 'multiplier', 2)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 3) ..having((state) => state.multiplier, 'multiplier', 3)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
], ],
@ -139,7 +132,8 @@ void main() {
'when multiplier is 6 and game is not over', 'when multiplier is 6 and game is not over',
build: GameBloc.new, build: GameBloc.new,
seed: () => const GameState( seed: () => const GameState(
score: 0, totalScore: 10,
roundScore: 0,
multiplier: 6, multiplier: 6,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],
@ -160,15 +154,12 @@ void main() {
}, },
expect: () => [ expect: () => [
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 1) ..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 1) ..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.isGameOver, 'isGameOver', false), ..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>() isA<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 1) ..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.isGameOver, 'isGameOver', true), ..having((state) => state.isGameOver, 'isGameOver', true),
], ],

@ -8,14 +8,16 @@ void main() {
test('supports value equality', () { test('supports value equality', () {
expect( expect(
GameState( GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: const [], bonusHistory: const [],
), ),
equals( equals(
const GameState( const GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],
@ -28,7 +30,8 @@ void main() {
test('can be instantiated', () { test('can be instantiated', () {
expect( expect(
const GameState( const GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],
@ -40,11 +43,29 @@ void main() {
test( test(
'throws AssertionError ' 'throws AssertionError '
'when score is negative', 'when totalScore is negative',
() { () {
expect( expect(
() => GameState( () => 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, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: const [], bonusHistory: const [],
@ -60,7 +81,8 @@ void main() {
() { () {
expect( expect(
() => GameState( () => GameState(
score: 1, totalScore: 0,
roundScore: 1,
multiplier: 0, multiplier: 0,
rounds: 3, rounds: 3,
bonusHistory: const [], bonusHistory: const [],
@ -76,7 +98,8 @@ void main() {
() { () {
expect( expect(
() => GameState( () => GameState(
score: 1, totalScore: 0,
roundScore: 1,
multiplier: 1, multiplier: 1,
rounds: -1, rounds: -1,
bonusHistory: const [], bonusHistory: const [],
@ -91,7 +114,8 @@ void main() {
'is true ' 'is true '
'when no rounds are left', () { 'when no rounds are left', () {
const gameState = GameState( const gameState = GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: [], bonusHistory: [],
@ -103,7 +127,8 @@ void main() {
'is false ' 'is false '
'when one 1 round left', () { 'when one 1 round left', () {
const gameState = GameState( const gameState = GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 1, rounds: 1,
bonusHistory: [], bonusHistory: [],
@ -115,16 +140,17 @@ void main() {
group('copyWith', () { group('copyWith', () {
test( test(
'throws AssertionError ' 'throws AssertionError '
'when scored is decreased', 'when totalScore is decreased',
() { () {
const gameState = GameState( const gameState = GameState(
score: 2, totalScore: 2,
roundScore: 2,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],
); );
expect( expect(
() => gameState.copyWith(score: gameState.score - 1), () => gameState.copyWith(totalScore: gameState.totalScore - 1),
throwsAssertionError, throwsAssertionError,
); );
}, },
@ -135,7 +161,8 @@ void main() {
'when no argument specified', 'when no argument specified',
() { () {
const gameState = GameState( const gameState = GameState(
score: 2, totalScore: 0,
roundScore: 2,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],
@ -152,13 +179,15 @@ void main() {
'when all arguments specified', 'when all arguments specified',
() { () {
const gameState = GameState( const gameState = GameState(
score: 2, totalScore: 0,
roundScore: 2,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],
); );
final otherGameState = GameState( final otherGameState = GameState(
score: gameState.score + 1, totalScore: gameState.totalScore + 1,
roundScore: gameState.roundScore + 1,
multiplier: gameState.multiplier + 1, multiplier: gameState.multiplier + 1,
rounds: gameState.rounds + 1, rounds: gameState.rounds + 1,
bonusHistory: const [GameBonus.googleWord], bonusHistory: const [GameBonus.googleWord],
@ -167,7 +196,8 @@ void main() {
expect( expect(
gameState.copyWith( gameState.copyWith(
score: otherGameState.score, totalScore: otherGameState.totalScore,
roundScore: otherGameState.roundScore,
multiplier: otherGameState.multiplier, multiplier: otherGameState.multiplier,
rounds: otherGameState.rounds, rounds: otherGameState.rounds,
bonusHistory: otherGameState.bonusHistory, bonusHistory: otherGameState.bonusHistory,

@ -27,7 +27,8 @@ void main() {
blocBuilder: () { blocBuilder: () {
final bloc = _MockGameBloc(); final bloc = _MockGameBloc();
const state = GameState( const state = GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: [], bonusHistory: [],

@ -22,7 +22,8 @@ void main() {
blocBuilder: () { blocBuilder: () {
final bloc = _MockGameBloc(); final bloc = _MockGameBloc();
const state = GameState( const state = GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: [], bonusHistory: [],

@ -23,7 +23,8 @@ void main() {
group('listenWhen', () { group('listenWhen', () {
test('is true when the game over state has changed', () { test('is true when the game over state has changed', () {
final state = GameState( final state = GameState(
score: 10, totalScore: 0,
roundScore: 10,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: const [], bonusHistory: const [],
@ -79,7 +80,8 @@ void main() {
() { () {
gameFlowController.onNewState( gameFlowController.onNewState(
GameState( GameState(
score: 0, totalScore: 0,
roundScore: 10,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: const [], bonusHistory: const [],

@ -74,7 +74,8 @@ void main() {
test('is false when the bonusHistory state is the same', () { test('is false when the bonusHistory state is the same', () {
final previous = GameState.initial(); final previous = GameState.initial();
final state = GameState( final state = GameState(
score: 10, totalScore: 0,
roundScore: 10,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: const [], bonusHistory: const [],

@ -56,7 +56,8 @@ void main() {
group('listenWhen', () { group('listenWhen', () {
test('is true when the multiplier has changed', () { test('is true when the multiplier has changed', () {
final state = GameState( final state = GameState(
score: 10, totalScore: 0,
roundScore: 10,
multiplier: 2, multiplier: 2,
rounds: 0, rounds: 0,
bonusHistory: const [], bonusHistory: const [],
@ -71,7 +72,8 @@ void main() {
test('is false when the multiplier state is the same', () { test('is false when the multiplier state is the same', () {
final state = GameState( final state = GameState(
score: 10, totalScore: 0,
roundScore: 10,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: const [], bonusHistory: const [],

@ -22,7 +22,8 @@ void main() {
late GameBloc gameBloc; late GameBloc gameBloc;
const initialState = GameState( const initialState = GameState(
score: 1000, totalScore: 0,
roundScore: 1000,
multiplier: 1, multiplier: 1,
rounds: 1, rounds: 1,
bonusHistory: [], bonusHistory: [],
@ -70,7 +71,10 @@ void main() {
gameBloc: gameBloc, gameBloc: gameBloc,
); );
expect(find.text(initialState.score.formatScore()), findsOneWidget); expect(
find.text(initialState.roundScore.formatScore()),
findsOneWidget,
);
}, },
); );

@ -13,7 +13,8 @@ void main() {
group('RoundCountDisplay renders', () { group('RoundCountDisplay renders', () {
late GameBloc gameBloc; late GameBloc gameBloc;
const initialState = GameState( const initialState = GameState(
score: 0, totalScore: 0,
roundScore: 0,
multiplier: 1, multiplier: 1,
rounds: 3, rounds: 3,
bonusHistory: [], bonusHistory: [],

@ -15,9 +15,11 @@ class _MockGameBloc extends Mock implements GameBloc {}
void main() { void main() {
late GameBloc gameBloc; late GameBloc gameBloc;
late StreamController<GameState> stateController; late StreamController<GameState> stateController;
const score = 123456789; const totalScore = 123456789;
const roundScore = 1234;
const initialState = GameState( const initialState = GameState(
score: score, totalScore: totalScore,
roundScore: roundScore,
multiplier: 1, multiplier: 1,
rounds: 1, rounds: 1,
bonusHistory: [], bonusHistory: [],
@ -42,7 +44,10 @@ void main() {
); );
await tester.pump(); await tester.pump();
expect(find.text(score.formatScore()), findsOneWidget); expect(
find.text(initialState.displayScore.formatScore()),
findsOneWidget,
);
}); });
testWidgets('renders game over', (tester) async { testWidgets('renders game over', (tester) async {
@ -69,17 +74,23 @@ void main() {
gameBloc: gameBloc, gameBloc: gameBloc,
); );
expect(find.text(score.formatScore()), findsOneWidget); expect(
find.text(initialState.displayScore.formatScore()),
findsOneWidget,
);
final newState = initialState.copyWith( final newState = initialState.copyWith(
score: 987654321, roundScore: 5678,
); );
stateController.add(newState); stateController.add(newState);
await tester.pump(); await tester.pump();
expect(find.text(newState.score.formatScore()), findsOneWidget); expect(
find.text(newState.displayScore.formatScore()),
findsOneWidget,
);
}); });
}); });
} }

Loading…
Cancel
Save