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) {
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<GameEvent, GameState> {
void _onScored(Scored event, Emitter emit) {
if (!state.isGameOver) {
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 {
/// {@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<GameBonus>? 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<Object?> get props => [
score,
totalScore,
roundScore,
multiplier,
rounds,
bonusHistory,

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

@ -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}

@ -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(),

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

@ -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<GameState>()..having((state) => state.rounds, 'rounds', 2),
],
);
blocTest<GameBloc, GameState>(
'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<GameState>()
..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<GameState>()
..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.rounds, 'rounds', 1),
isA<GameState>()..having((state) => state.multiplier, 'multiplier', 1)
],
);
});
@ -79,10 +74,10 @@ void main() {
..add(const Scored(points: 3)),
expect: () => [
isA<GameState>()
..having((state) => state.score, 'score', 2)
..having((state) => state.roundScore, 'roundScore', 2)
..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>()
..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<GameState>()
..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<GameState>()
..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<GameState>()
..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<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 2)
..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>()
..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<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.isGameOver, 'isGameOver', false),
isA<GameState>()
..having((state) => state.score, 'score', 0)
..having((state) => state.multiplier, 'multiplier', 1)
..having((state) => state.isGameOver, 'isGameOver', true),
],

@ -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,

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

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

@ -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 [],

@ -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 [],

@ -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 [],

@ -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,
);
},
);

@ -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: [],

@ -15,9 +15,11 @@ class _MockGameBloc extends Mock implements GameBloc {}
void main() {
late GameBloc gameBloc;
late StreamController<GameState> 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,
);
});
});
}

Loading…
Cancel
Save