fix: replaying resets game state (#441)

* feat: added replay functionality

* feat: resetting google word bonus

* Merge remote-tracking branch 'origin' into feat/replay-functionality

* test: tested Replay overlay

* docs: fixed typo

* test: renamed test

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
pull/446/head
Alejandro Santiago 2 years ago committed by GitHub
parent 032618020e
commit 0ac9cb3140
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,7 +13,8 @@ class BallSpawningBehavior extends Component
bool listenWhen(GameState? previousState, GameState newState) {
if (!newState.status.isPlaying) return false;
final startedGame = previousState?.status.isWaiting ?? true;
final startedGame = (previousState?.status.isWaiting ?? true) ||
(previousState?.status.isGameOver ?? true);
final lostRound =
(previousState?.rounds ?? newState.rounds + 1) > newState.rounds;
return startedGame || lostRound;

@ -19,7 +19,7 @@ class GameBloc extends Bloc<GameEvent, GameState> {
static const _maxScore = 9999999999;
void _onGameStarted(GameStarted _, Emitter emit) {
emit(state.copyWith(status: GameStatus.playing));
emit(const GameState.initial().copyWith(status: GameStatus.playing));
}
void _onGameOver(GameOver _, Emitter emit) {

@ -66,7 +66,7 @@ class GameOverInfoDisplay extends Component with HasGameRef {
@override
Future<void> onLoad() async {
await super.onLoad();
gameRef.overlays.add(PinballGame.playButtonOverlay);
gameRef.overlays.add(PinballGame.replayButtonOverlay);
}
}

@ -22,6 +22,7 @@ class GameBlocStatusListener extends Component
break;
case GameStatus.playing:
readProvider<PinballAudioPlayer>().play(PinballAudio.backgroundMusic);
_resetBonuses();
gameRef
.descendants()
.whereType<Flipper>()
@ -32,6 +33,7 @@ class GameBlocStatusListener extends Component
.forEach(_addPlungerBehaviors);
gameRef.overlays.remove(PinballGame.playButtonOverlay);
gameRef.overlays.remove(PinballGame.replayButtonOverlay);
break;
case GameStatus.gameOver:
readProvider<PinballAudioPlayer>().play(PinballAudio.gameOverVoiceOver);
@ -54,6 +56,15 @@ class GameBlocStatusListener extends Component
}
}
void _resetBonuses() {
gameRef
.descendants()
.whereType<FlameBlocProvider<GoogleWordCubit, GoogleWordState>>()
.single
.bloc
.onReset();
}
void _addPlungerBehaviors(Plunger plunger) {
final platformHelper = readProvider<PlatformHelper>();
const pullingStrength = 7.0;

@ -17,7 +17,7 @@ class GoogleWordBonusBehavior extends Component {
onNewState: (state) {
readBloc<GameBloc, GameState>()
.add(const BonusActivated(GameBonus.googleWord));
readBloc<GoogleWordCubit, GoogleWordState>().onBonusAwarded();
readBloc<GoogleWordCubit, GoogleWordState>().onReset();
add(BonusBallSpawningBehavior());
add(GoogleWordAnimatingBehavior());
},

@ -38,10 +38,13 @@ class PinballGame extends PinballForge2DGame
images.prefix = '';
}
/// Identifier of the play button overlay
/// Identifier of the play button overlay.
static const playButtonOverlay = 'play_button';
/// Identifier of the mobile controls overlay
/// Identifier of the replay button overlay.
static const replayButtonOverlay = 'replay_button';
/// Identifier of the mobile controls overlay.
static const mobileControlsOverlay = 'mobile_controls';
@override

@ -100,22 +100,25 @@ class PinballGameLoadedView extends StatelessWidget {
focusNode: game.focusNode,
initialActiveOverlays: const [PinballGame.playButtonOverlay],
overlayBuilderMap: {
PinballGame.playButtonOverlay: (context, game) {
return const Positioned(
bottom: 20,
right: 0,
left: 0,
child: PlayButtonOverlay(),
);
},
PinballGame.mobileControlsOverlay: (context, game) {
return Positioned(
bottom: 0,
left: 0,
right: 0,
child: MobileControls(game: game),
);
},
PinballGame.playButtonOverlay: (_, game) => const Positioned(
bottom: 20,
right: 0,
left: 0,
child: PlayButtonOverlay(),
),
PinballGame.mobileControlsOverlay: (_, game) => Positioned(
bottom: 0,
left: 0,
right: 0,
child: MobileControls(game: game),
),
PinballGame.replayButtonOverlay: (context, game) =>
const Positioned(
bottom: 20,
right: 0,
left: 0,
child: ReplayButtonOverlay(),
)
},
),
),

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_ui/pinball_ui.dart';
@ -18,6 +19,7 @@ class ReplayButtonOverlay extends StatelessWidget {
return PinballButton(
text: l10n.replay,
onTap: () {
context.read<GameBloc>().add(const GameStarted());
context.read<StartGameBloc>().add(const ReplayTapped());
},
);

@ -17,7 +17,7 @@ class GoogleWordAnimatingBehavior extends TimerComponent
_blinks++;
} else {
timer.stop();
bloc.onAnimationFinished();
bloc.onReset();
shouldRemove = true;
}
}

@ -68,7 +68,7 @@ class GoogleWordCubit extends Cubit<GoogleWordState> {
);
}
void onAnimationFinished() {
void onReset() {
emit(GoogleWordState.initial());
_lastLitLetter = 0;
}

@ -41,7 +41,7 @@ void main() {
);
flameTester.testGameWidget(
'calls onAnimationFinished and removes itself '
'calls onReset and removes itself '
'after all blinks complete',
setUp: (game, tester) async {
final behavior = GoogleWordAnimatingBehavior();
@ -53,7 +53,7 @@ void main() {
}
await game.ready();
verify(bloc.onAnimationFinished).called(1);
verify(bloc.onReset).called(1);
expect(
game.descendants().whereType<GoogleWordAnimatingBehavior>().isEmpty,
isTrue,

@ -62,9 +62,9 @@ void main() {
);
blocTest<GoogleWordCubit, GoogleWordState>(
'onAnimationFinished emits initial state',
'onReset emits initial state',
build: GoogleWordCubit.new,
act: (bloc) => bloc.onAnimationFinished(),
act: (bloc) => bloc.onReset(),
expect: () => [GoogleWordState.initial()],
);
},

@ -37,6 +37,7 @@ class _TestGame extends Forge2DGame with HasTappables {
Iterable<Component> children, {
PinballAudioPlayer? pinballAudioPlayer,
PlatformHelper? platformHelper,
GoogleWordCubit? googleWordBloc,
}) async {
return ensureAdd(
FlameMultiBlocProvider(
@ -47,6 +48,9 @@ class _TestGame extends Forge2DGame with HasTappables {
FlameBlocProvider<CharacterThemeCubit, CharacterThemeState>.value(
value: CharacterThemeCubit(),
),
FlameBlocProvider<GoogleWordCubit, GoogleWordState>.value(
value: googleWordBloc ?? GoogleWordCubit(),
),
],
children: [
MultiFlameProvider(
@ -80,6 +84,8 @@ class _MockPlatformHelper extends Mock implements PlatformHelper {}
class _MockPlungerCubit extends Mock implements PlungerCubit {}
class _MockGoogleWordCubit extends Mock implements GoogleWordCubit {}
class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get score => '';
@ -332,6 +338,20 @@ void main() {
},
);
flameTester.test(
'resets the GoogleWordCubit',
(game) async {
final googleWordBloc = _MockGoogleWordCubit();
final component = GameBlocStatusListener();
await game.pump([component], googleWordBloc: googleWordBloc);
expect(state.status, equals(GameStatus.playing));
component.onNewState(state);
verify(googleWordBloc.onReset).called(1);
},
);
flameTester.test(
'adds FlipperKeyControllingBehavior to Flippers',
(game) async {

@ -75,7 +75,7 @@ void main() {
flameTester.testGameWidget(
'adds GameBonus.googleWord to the game when all letters '
'in google word are activated and calls onBonusAwarded',
'in google word are activated and calls onReset',
setUp: (game, tester) async {
final behavior = GoogleWordBonusBehavior();
final parent = GoogleGallery.test();
@ -114,7 +114,7 @@ void main() {
verify(
() => gameBloc.add(const BonusActivated(GameBonus.googleWord)),
).called(1);
verify(googleWordBloc.onBonusAwarded).called(1);
verify(googleWordBloc.onReset).called(1);
},
);

@ -307,6 +307,23 @@ void main() {
expect(find.byType(MobileControls), findsOneWidget);
});
testWidgets(
'ReplayButtonOverlay when the overlay is added',
(tester) async {
await tester.pumpApp(
PinballGameView(game),
gameBloc: gameBloc,
startGameBloc: startGameBloc,
);
game.overlays.add(PinballGame.replayButtonOverlay);
await tester.pump();
expect(find.byType(ReplayButtonOverlay), findsOneWidget);
},
);
group('info icon', () {
testWidgets('renders on game over', (tester) async {
final gameState = GameState.initial().copyWith(

@ -8,24 +8,32 @@ import '../../../helpers/helpers.dart';
class _MockStartGameBloc extends Mock implements StartGameBloc {}
class _MockGameBloc extends Mock implements GameBloc {}
void main() {
group('ReplayButtonOverlay', () {
late StartGameBloc startGameBloc;
late _MockGameBloc gameBloc;
setUp(() async {
await mockFlameImages();
startGameBloc = _MockStartGameBloc();
gameBloc = _MockGameBloc();
whenListen(
startGameBloc,
Stream.value(const StartGameState.initial()),
initialState: const StartGameState.initial(),
);
whenListen(
gameBloc,
Stream.value(const GameState.initial()),
initialState: const GameState.initial(),
);
});
testWidgets('renders correctly', (tester) async {
await tester.pumpApp(const ReplayButtonOverlay());
expect(find.text('Replay'), findsOneWidget);
});
@ -33,6 +41,7 @@ void main() {
(tester) async {
await tester.pumpApp(
const ReplayButtonOverlay(),
gameBloc: gameBloc,
startGameBloc: startGameBloc,
);
@ -41,5 +50,19 @@ void main() {
verify(() => startGameBloc.add(const ReplayTapped())).called(1);
});
testWidgets('adds GameStarted event to GameBloc when tapped',
(tester) async {
await tester.pumpApp(
const ReplayButtonOverlay(),
gameBloc: gameBloc,
startGameBloc: startGameBloc,
);
await tester.tap(find.text('Replay'));
await tester.pump();
verify(() => gameBloc.add(const GameStarted())).called(1);
});
});
}

Loading…
Cancel
Save