refactor: added round to game state and changed gameover and ball lost logic

pull/213/head
RuiAlonso 3 years ago
parent 091fd58c63
commit a7ab9905fa

@ -12,18 +12,29 @@ class GameBloc extends Bloc<GameEvent, GameState> {
on<BallLost>(_onBallLost);
on<Scored>(_onScored);
on<MultiplierIncreased>(_onIncreasedMultiplier);
on<MultiplierApplied>(_onAppliedMultiplier);
on<MultiplierReset>(_onResetMultiplier);
on<BonusActivated>(_onBonusActivated);
on<SparkyTurboChargeActivated>(_onSparkyTurboChargeActivated);
}
void _onBallLost(BallLost event, Emitter emit) {
var score = state.score;
var multiplier = state.multiplier;
var ballsLeft = event.balls;
var rounds = state.rounds;
if (ballsLeft < 1) {
score = score * state.multiplier;
multiplier = 1;
ballsLeft = 1;
rounds = state.rounds - 1;
}
emit(
state.copyWith(
balls: state.balls - 1,
score: state.score * state.multiplier,
multiplier: 1,
score: score,
multiplier: multiplier,
balls: ballsLeft,
rounds: rounds,
),
);
}
@ -42,18 +53,6 @@ class GameBloc extends Bloc<GameEvent, GameState> {
}
}
void _onAppliedMultiplier(MultiplierApplied event, Emitter emit) {
if (!state.isGameOver) {
emit(state.copyWith(score: state.score * state.multiplier));
}
}
void _onResetMultiplier(MultiplierReset event, Emitter emit) {
if (!state.isGameOver) {
emit(state.copyWith(multiplier: 1));
}
}
void _onBonusActivated(BonusActivated event, Emitter emit) {
emit(
state.copyWith(

@ -12,10 +12,14 @@ abstract class GameEvent extends Equatable {
/// {@endtemplate}
class BallLost extends GameEvent {
/// {@macro ball_lost_game_event}
const BallLost();
const BallLost({
required this.balls,
}) : assert(balls >= 0, "Balls left can't be negative");
final int balls;
@override
List<Object?> get props => [];
List<Object?> get props => [balls];
}
/// {@template scored_game_event}

@ -23,15 +23,18 @@ class GameState extends Equatable {
required this.score,
required this.multiplier,
required this.balls,
required this.rounds,
required this.bonusHistory,
}) : assert(score >= 0, "Score can't be negative"),
assert(multiplier > 0, 'Multiplier must be greater than zero'),
assert(balls >= 0, "Number of balls can't be negative");
assert(balls >= 0, "Number of balls can't be negative"),
assert(rounds >= 0, "Number of rounds can't be negative");
const GameState.initial()
: score = 0,
multiplier = 1,
balls = 3,
balls = 1,
rounds = 3,
bonusHistory = const [];
/// The current score of the game.
@ -42,20 +45,29 @@ class GameState extends Equatable {
/// The number of balls left in the game.
///
/// When the number of balls is 0, the game is over.
/// When the number of balls is 0, round is lost.
final int balls;
/// The number of rounds left in the game.
///
/// When the number of rounds is 0, the game is over.
final int rounds;
/// Holds the history of all the [GameBonus]es earned by the player during a
/// PinballGame.
final List<GameBonus> bonusHistory;
/// Determines when the round is over.
bool get isRoundOver => balls == 0;
/// Determines when the game is over.
bool get isGameOver => balls == 0;
bool get isGameOver => rounds == 0;
GameState copyWith({
int? score,
int? multiplier,
int? balls,
int? rounds,
List<GameBonus>? bonusHistory,
}) {
assert(
@ -67,6 +79,7 @@ class GameState extends Equatable {
score: score ?? this.score,
multiplier: multiplier ?? this.multiplier,
balls: balls ?? this.balls,
rounds: rounds ?? this.rounds,
bonusHistory: bonusHistory ?? this.bonusHistory,
);
}
@ -76,6 +89,7 @@ class GameState extends Equatable {
score,
multiplier,
balls,
rounds,
bonusHistory,
];
}

@ -73,7 +73,8 @@ class BallController extends ComponentController<Ball>
@override
void onRemove() {
super.onRemove();
gameRef.read<GameBloc>().add(const BallLost());
final remainingBalls = gameRef.children.whereType<Ball>().length;
gameRef.read<GameBloc>().add(BallLost(balls: remainingBalls));
}
}
@ -81,7 +82,4 @@ class BallController extends ComponentController<Ball>
class DebugBallController extends BallController {
/// {@macro ball_controller}
DebugBallController(Ball<Forge2DGame> component) : super(component);
@override
void onRemove() {}
}

@ -29,7 +29,7 @@ class GameHud extends StatelessWidget {
Wrap(
direction: Axis.vertical,
children: [
for (var i = 0; i < state.balls; i++)
for (var i = 0; i < state.rounds; i++)
const Padding(
padding: EdgeInsets.only(top: 6, right: 6),
child: CircleAvatar(

@ -4,24 +4,26 @@ import 'package:pinball/game/game.dart';
void main() {
group('GameBloc', () {
test('initial state has 3 balls and empty score', () {
test('initial state has 3 rounds, 1 ball and empty score', () {
final gameBloc = GameBloc();
expect(gameBloc.state.score, equals(0));
expect(gameBloc.state.balls, equals(3));
expect(gameBloc.state.balls, equals(1));
expect(gameBloc.state.rounds, equals(3));
});
group('LostBall', () {
group('BallLost', () {
blocTest<GameBloc, GameState>(
'decreases number of balls',
build: GameBloc.new,
act: (bloc) {
bloc.add(const BallLost());
bloc.add(const BallLost(balls: 0));
},
expect: () => [
const GameState(
score: 0,
multiplier: 1,
balls: 2,
balls: 1,
rounds: 2,
bonusHistory: [],
),
],
@ -40,13 +42,15 @@ void main() {
const GameState(
score: 2,
multiplier: 1,
balls: 3,
balls: 1,
rounds: 1,
bonusHistory: [],
),
const GameState(
score: 5,
multiplier: 1,
balls: 3,
balls: 1,
rounds: 1,
bonusHistory: [],
),
],
@ -57,8 +61,8 @@ void main() {
'when game is over',
build: GameBloc.new,
act: (bloc) {
for (var i = 0; i < bloc.state.balls; i++) {
bloc.add(const BallLost());
for (var i = 0; i < bloc.state.rounds; i++) {
bloc.add(const BallLost(balls: 0));
}
bloc.add(const Scored(points: 2));
},
@ -66,19 +70,22 @@ void main() {
const GameState(
score: 0,
multiplier: 1,
balls: 2,
balls: 1,
rounds: 2,
bonusHistory: [],
),
const GameState(
score: 0,
multiplier: 1,
balls: 1,
rounds: 1,
bonusHistory: [],
),
const GameState(
score: 0,
multiplier: 1,
balls: 0,
rounds: 0,
bonusHistory: [],
),
],
@ -98,12 +105,14 @@ void main() {
score: 0,
multiplier: 2,
balls: 3,
rounds: 3,
bonusHistory: [],
),
const GameState(
score: 0,
multiplier: 3,
balls: 3,
rounds: 3,
bonusHistory: [],
),
],
@ -114,8 +123,8 @@ void main() {
'when game is over',
build: GameBloc.new,
act: (bloc) {
for (var i = 0; i < bloc.state.balls; i++) {
bloc.add(const BallLost());
for (var i = 0; i < bloc.state.rounds; i++) {
bloc.add(const BallLost(balls: 0));
}
bloc.add(const MultiplierIncreased());
},
@ -124,18 +133,21 @@ void main() {
score: 0,
multiplier: 1,
balls: 2,
rounds: 2,
bonusHistory: [],
),
const GameState(
score: 0,
multiplier: 1,
balls: 1,
rounds: 1,
bonusHistory: [],
),
const GameState(
score: 0,
multiplier: 1,
balls: 0,
rounds: 0,
bonusHistory: [],
),
],
@ -150,6 +162,7 @@ void main() {
score: 5,
multiplier: 3,
balls: 2,
rounds: 2,
bonusHistory: [],
),
act: (bloc) {
@ -160,6 +173,7 @@ void main() {
score: 15,
multiplier: 3,
balls: 2,
rounds: 2,
bonusHistory: [],
),
],
@ -174,6 +188,7 @@ void main() {
score: 0,
multiplier: 3,
balls: 2,
rounds: 2,
bonusHistory: [],
),
act: (bloc) {
@ -184,6 +199,7 @@ void main() {
score: 0,
multiplier: 1,
balls: 2,
rounds: 2,
bonusHistory: [],
),
],
@ -203,12 +219,14 @@ void main() {
score: 0,
multiplier: 1,
balls: 3,
rounds: 2,
bonusHistory: [GameBonus.googleWord],
),
GameState(
score: 0,
multiplier: 1,
balls: 3,
rounds: 2,
bonusHistory: [GameBonus.googleWord, GameBonus.dashNest],
),
],
@ -226,6 +244,7 @@ void main() {
score: 0,
multiplier: 1,
balls: 3,
rounds: 2,
bonusHistory: [GameBonus.sparkyTurboCharge],
),
],

@ -7,13 +7,17 @@ void main() {
group('GameEvent', () {
group('BallLost', () {
test('can be instantiated', () {
expect(const BallLost(), isNotNull);
expect(const BallLost(balls: 1), isNotNull);
});
test('supports value equality', () {
expect(
BallLost(),
equals(const BallLost()),
BallLost(balls: 1),
equals(const BallLost(balls: 1)),
);
expect(
BallLost(balls: 2),
isNot(equals(const BallLost(balls: 1))),
);
});
});

@ -11,6 +11,7 @@ void main() {
score: 0,
multiplier: 1,
balls: 0,
rounds: 3,
bonusHistory: const [],
),
equals(
@ -18,6 +19,7 @@ void main() {
score: 0,
multiplier: 1,
balls: 0,
rounds: 3,
bonusHistory: [],
),
),
@ -31,6 +33,7 @@ void main() {
score: 0,
multiplier: 1,
balls: 0,
rounds: 3,
bonusHistory: [],
),
isNotNull,
@ -44,9 +47,10 @@ void main() {
() {
expect(
() => GameState(
balls: -1,
score: 0,
multiplier: 1,
balls: -1,
rounds: 3,
bonusHistory: const [],
),
throwsAssertionError,
@ -60,9 +64,10 @@ void main() {
() {
expect(
() => GameState(
balls: 0,
score: -1,
multiplier: 1,
balls: 0,
rounds: 3,
bonusHistory: const [],
),
throwsAssertionError,
@ -76,9 +81,10 @@ void main() {
() {
expect(
() => GameState(
balls: 0,
score: 1,
multiplier: 0,
balls: 0,
rounds: 3,
bonusHistory: const [],
),
throwsAssertionError,
@ -86,26 +92,73 @@ void main() {
},
);
group('isGameOver', () {
test(
'throws AssertionError '
'when rounds is negative',
() {
expect(
() => GameState(
score: 1,
multiplier: 1,
balls: 0,
rounds: -1,
bonusHistory: const [],
),
throwsAssertionError,
);
},
);
group('isRoundOver', () {
test(
'is true '
'when no balls are left', () {
const gameState = GameState(
balls: 0,
score: 0,
multiplier: 1,
balls: 0,
rounds: 1,
bonusHistory: [],
);
expect(gameState.isGameOver, isTrue);
expect(gameState.isRoundOver, isTrue);
});
test(
'is false '
'when one 1 ball left', () {
const gameState = GameState(
score: 0,
multiplier: 1,
balls: 1,
rounds: 0,
bonusHistory: [],
);
expect(gameState.isRoundOver, isFalse);
});
});
group('isGameOver', () {
test(
'is true '
'when no rounds are left', () {
const gameState = GameState(
score: 0,
multiplier: 1,
balls: 0,
rounds: 0,
bonusHistory: [],
);
expect(gameState.isGameOver, isTrue);
});
test(
'is false '
'when one 1 round left', () {
const gameState = GameState(
score: 0,
multiplier: 1,
balls: 0,
rounds: 1,
bonusHistory: [],
);
expect(gameState.isGameOver, isFalse);
@ -118,9 +171,10 @@ void main() {
'when scored is decreased',
() {
const gameState = GameState(
balls: 0,
score: 2,
multiplier: 1,
balls: 0,
rounds: 3,
bonusHistory: [],
);
expect(
@ -135,9 +189,10 @@ void main() {
'when no argument specified',
() {
const gameState = GameState(
balls: 0,
score: 2,
multiplier: 1,
balls: 0,
rounds: 3,
bonusHistory: [],
);
expect(
@ -155,12 +210,14 @@ void main() {
score: 2,
multiplier: 1,
balls: 0,
rounds: 3,
bonusHistory: [],
);
final otherGameState = GameState(
score: gameState.score + 1,
multiplier: gameState.multiplier + 1,
balls: gameState.balls + 1,
rounds: gameState.rounds + 1,
bonusHistory: const [GameBonus.googleWord],
);
expect(gameState, isNot(equals(otherGameState)));
@ -170,6 +227,7 @@ void main() {
score: otherGameState.score,
multiplier: otherGameState.multiplier,
balls: otherGameState.balls,
rounds: otherGameState.rounds,
bonusHistory: otherGameState.bonusHistory,
),
equals(otherGameState),

@ -62,7 +62,7 @@ void main() {
controller.lost();
},
verify: (game, tester) async {
verify(() => gameBloc.add(const BallLost())).called(1);
verify(() => gameBloc.add(const BallLost(balls: 0))).called(1);
},
);

@ -25,6 +25,7 @@ void main() {
score: 0,
multiplier: 1,
balls: 0,
rounds: 3,
bonusHistory: [],
);
whenListen(bloc, Stream.value(state), initialState: state);

@ -22,6 +22,7 @@ void main() {
score: 0,
multiplier: 1,
balls: 0,
rounds: 3,
bonusHistory: [],
);
whenListen(bloc, Stream.value(state), initialState: state);

@ -17,6 +17,7 @@ void main() {
score: 10,
multiplier: 1,
balls: 0,
rounds: 0,
bonusHistory: const [],
);
@ -70,6 +71,7 @@ void main() {
score: 10,
multiplier: 1,
balls: 0,
rounds: 0,
bonusHistory: const [],
),
);

@ -13,6 +13,7 @@ void main() {
score: 10,
multiplier: 1,
balls: 2,
rounds: 3,
bonusHistory: [],
);
@ -45,12 +46,12 @@ void main() {
);
testWidgets(
'renders the current ball number',
'renders the current round number',
(tester) async {
await _pumpHud(tester);
expect(
find.byType(CircleAvatar),
findsNWidgets(initialState.balls),
findsNWidgets(initialState.rounds),
);
},
);
@ -65,14 +66,14 @@ void main() {
expect(find.text('20'), findsOneWidget);
});
testWidgets('updates the ball number', (tester) async {
testWidgets('updates the rounds number', (tester) async {
await _pumpHud(tester);
expect(
find.byType(CircleAvatar),
findsNWidgets(initialState.balls),
findsNWidgets(initialState.rounds),
);
_mockState(initialState.copyWith(balls: 1));
_mockState(initialState.copyWith(rounds: 1));
await tester.pump();
expect(

Loading…
Cancel
Save