From 801deb9ffa675580017466dfbed8607783fb2201 Mon Sep 17 00:00:00 2001 From: alestiago Date: Thu, 5 May 2022 17:21:22 +0100 Subject: [PATCH] refactor: simplified CameraFocusingBehavior --- .../behaviors/camera_focusing_behavior.dart | 38 +++--- lib/game/bloc/game_state.dart | 2 +- .../components/game_bloc_status_listener.dart | 2 - .../camera_focusing_behavior_test.dart | 122 ++++++++++++++++++ .../components/camera_controller_test.dart | 113 ---------------- .../game_bloc_status_listener_test.dart | 26 ++-- 6 files changed, 149 insertions(+), 154 deletions(-) create mode 100644 test/game/behaviors/camera_focusing_behavior_test.dart delete mode 100644 test/game/components/camera_controller_test.dart diff --git a/lib/game/behaviors/camera_focusing_behavior.dart b/lib/game/behaviors/camera_focusing_behavior.dart index 5f1148df..025eff44 100644 --- a/lib/game/behaviors/camera_focusing_behavior.dart +++ b/lib/game/behaviors/camera_focusing_behavior.dart @@ -6,7 +6,7 @@ import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; /// {@template focus_data} -/// Model class that defines a focus point of the camera. +/// Defines a [Camera] focus point. /// {@endtemplate} class FocusData { /// {@template focus_data} @@ -22,31 +22,33 @@ class FocusData { final Vector2 position; } -/// +/// Changes the game focus when the [GameBloc] status changes. class CameraFocusingBehavior extends Component with ParentIsA, BlocComponent { final Map _focuses = {}; - // @override - // bool listenWhen(GameState? previousState, GameState newState) { - // print('listen'); - // return true; - // return previousState?.isGameOver != newState.isGameOver; - // } + @override + bool listenWhen(GameState? previousState, GameState newState) { + return previousState?.status != newState.status; + } @override void onNewState(GameState state) { - print(state); - if (state.isGameOver) { - _zoom(_focuses['backbox']!); - } else { - _zoom(_focuses['game']!); + switch (state.status) { + case GameStatus.waiting: + break; + case GameStatus.playing: + _zoom(_focuses['game']!); + break; + case GameStatus.gameOver: + _zoom(_focuses['backbox']!); + break; } } @override - void onGameResize(Vector2 size) { - super.onGameResize(size); + Future onLoad() async { + await super.onLoad(); _focuses['game'] = FocusData( zoom: parent.size.y / 16, position: Vector2(0, -7.8), @@ -59,11 +61,7 @@ class CameraFocusingBehavior extends Component zoom: parent.size.y / 10, position: Vector2(0, -111), ); - } - @override - Future onLoad() async { - await super.onLoad(); _snap(_focuses['waiting']!); } @@ -79,6 +77,6 @@ class CameraFocusingBehavior extends Component zoom.completed.then((_) { parent.camera.moveTo(data.position); }); - parent.add(zoom); + add(zoom); } } diff --git a/lib/game/bloc/game_state.dart b/lib/game/bloc/game_state.dart index a9e86720..7edefce7 100644 --- a/lib/game/bloc/game_state.dart +++ b/lib/game/bloc/game_state.dart @@ -53,7 +53,7 @@ class GameState extends Equatable { totalScore = 0, roundScore = 0, multiplier = 1, - rounds = 3, + rounds = 1, bonusHistory = const []; /// The score for the current round of the game. diff --git a/lib/game/components/game_bloc_status_listener.dart b/lib/game/components/game_bloc_status_listener.dart index 0012f62b..e9018176 100644 --- a/lib/game/components/game_bloc_status_listener.dart +++ b/lib/game/components/game_bloc_status_listener.dart @@ -18,7 +18,6 @@ class GameBlocStatusListener extends Component break; case GameStatus.playing: gameRef.player.play(PinballAudio.backgroundMusic); - gameRef.firstChild()?.focusOnGame(); gameRef.overlays.remove(PinballGame.playButtonOverlay); break; case GameStatus.gameOver: @@ -26,7 +25,6 @@ class GameBlocStatusListener extends Component score: state.displayScore, characterIconPath: gameRef.characterTheme.leaderboardIcon.keyName, ); - gameRef.firstChild()!.focusOnGameOverBackbox(); break; } } diff --git a/test/game/behaviors/camera_focusing_behavior_test.dart b/test/game/behaviors/camera_focusing_behavior_test.dart new file mode 100644 index 00000000..480fd039 --- /dev/null +++ b/test/game/behaviors/camera_focusing_behavior_test.dart @@ -0,0 +1,122 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/game/behaviors/camera_focusing_behavior.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group( + 'CameraFocusingBehavior', + () { + final flameTester = FlameTester( + EmptyPinballTestGame.new, + ); + + test('can be instantiated', () { + expect( + CameraFocusingBehavior(), + isA(), + ); + }); + + flameTester.test('loads', (game) async { + final behavior = CameraFocusingBehavior(); + await game.ensureAdd(behavior); + expect(game.contains(behavior), isTrue); + }); + + flameTester.test( + 'changes focus when loaded', + (game) async { + final behavior = CameraFocusingBehavior(); + final previousZoom = game.camera.zoom; + expect(game.camera.follow, isNull); + + await game.ensureAdd(behavior); + + expect(game.camera.follow, isNotNull); + expect(game.camera.zoom, isNot(equals(previousZoom))); + }, + ); + + flameTester.test( + 'listenWhen only listens when status changes', + (game) async { + final behavior = CameraFocusingBehavior(); + const waiting = GameState.initial(); + final playing = + const GameState.initial().copyWith(status: GameStatus.playing); + final gameOver = + const GameState.initial().copyWith(status: GameStatus.gameOver); + + expect(behavior.listenWhen(waiting, waiting), isFalse); + expect(behavior.listenWhen(waiting, playing), isTrue); + expect(behavior.listenWhen(waiting, gameOver), isTrue); + + expect(behavior.listenWhen(playing, playing), isFalse); + expect(behavior.listenWhen(playing, waiting), isTrue); + expect(behavior.listenWhen(playing, gameOver), isTrue); + + expect(behavior.listenWhen(gameOver, gameOver), isFalse); + expect(behavior.listenWhen(gameOver, waiting), isTrue); + expect(behavior.listenWhen(gameOver, playing), isTrue); + }, + ); + + group('onNewState', () { + flameTester.test( + 'zooms when started playing', + (game) async { + final playing = + const GameState.initial().copyWith(status: GameStatus.playing); + + final behavior = CameraFocusingBehavior(); + await game.ensureAdd(behavior); + + behavior.onNewState(playing); + final previousPosition = game.camera.position.clone(); + await game.ready(); + + final zoom = behavior.children.whereType().single; + game.update(zoom.controller.duration!); + + expect( + game.camera.position, + isNot(equals(previousPosition)), + ); + }, + ); + + flameTester.test( + 'zooms when lost', + (game) async { + final playing = const GameState.initial().copyWith( + status: GameStatus.gameOver, + ); + + final behavior = CameraFocusingBehavior(); + await game.ensureAdd(behavior); + + behavior.onNewState(playing); + final previousPosition = game.camera.position.clone(); + await game.ready(); + + final zoom = behavior.children.whereType().single; + game.update(zoom.controller.duration!); + + expect( + game.camera.position, + isNot(equals(previousPosition)), + ); + }, + ); + }); + }, + ); +} diff --git a/test/game/components/camera_controller_test.dart b/test/game/components/camera_controller_test.dart deleted file mode 100644 index 934f6340..00000000 --- a/test/game/components/camera_controller_test.dart +++ /dev/null @@ -1,113 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:flame/game.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball/game/components/camera_controller.dart'; -import 'package:pinball_components/pinball_components.dart'; - -void main() { - group('CameraController', () { - late FlameGame game; - late CameraController controller; - - setUp(() async { - game = FlameGame()..onGameResize(Vector2(100, 200)); - - controller = CameraController(game); - await game.ensureAdd(controller); - }); - - test('loads correctly', () async { - expect(game.firstChild(), isNotNull); - }); - - test('correctly calculates the zooms', () async { - expect(controller.gameFocus.zoom.toInt(), equals(12)); - expect(controller.waitingBackboxFocus.zoom.toInt(), equals(11)); - }); - - test('correctly sets the initial zoom and position', () async { - expect(game.camera.zoom, equals(controller.waitingBackboxFocus.zoom)); - expect( - game.camera.follow, - equals(controller.waitingBackboxFocus.position), - ); - }); - - group('focusOnGame', () { - test('changes the zoom', () async { - controller.focusOnGame(); - - await game.ready(); - final zoom = game.firstChild(); - expect(zoom, isNotNull); - expect(zoom?.value, equals(controller.gameFocus.zoom)); - }); - - test('moves the camera after the zoom is completed', () async { - controller.focusOnGame(); - await game.ready(); - final cameraZoom = game.firstChild()!; - final future = cameraZoom.completed; - - game.update(10); - game.update(0); // Ensure that the component was removed - - await future; - - expect(game.camera.position, Vector2(-4, -120)); - }); - }); - - group('focusOnWaitingBackbox', () { - test('changes the zoom', () async { - controller.focusOnWaitingBackbox(); - - await game.ready(); - final zoom = game.firstChild(); - expect(zoom, isNotNull); - expect(zoom?.value, equals(controller.waitingBackboxFocus.zoom)); - }); - - test('moves the camera after the zoom is completed', () async { - controller.focusOnWaitingBackbox(); - await game.ready(); - final cameraZoom = game.firstChild()!; - final future = cameraZoom.completed; - - game.update(10); - game.update(0); // Ensure that the component was removed - - await future; - - expect(game.camera.position, Vector2(-4.5, -121)); - }); - }); - - group('focusOnGameOverBackbox', () { - test('changes the zoom', () async { - controller.focusOnGameOverBackbox(); - - await game.ready(); - final zoom = game.firstChild(); - expect(zoom, isNotNull); - expect(zoom?.value, equals(controller.gameOverBackboxFocus.zoom)); - }); - - test('moves the camera after the zoom is completed', () async { - controller.focusOnGameOverBackbox(); - await game.ready(); - final cameraZoom = game.firstChild()!; - final future = cameraZoom.completed; - - game.update(10); - game.update(0); // Ensure that the component was removed - - await future; - - expect(game.camera.position, Vector2(-2.5, -117)); - }); - }); - }); -} diff --git a/test/game/components/game_bloc_status_listener_test.dart b/test/game/components/game_bloc_status_listener_test.dart index 39c81115..1a92b618 100644 --- a/test/game/components/game_bloc_status_listener_test.dart +++ b/test/game/components/game_bloc_status_listener_test.dart @@ -11,8 +11,6 @@ class _MockPinballGame extends Mock implements PinballGame {} class _MockBackbox extends Mock implements Backbox {} -class _MockCameraController extends Mock implements CameraController {} - class _MockActiveOverlaysNotifier extends Mock implements ActiveOverlaysNotifier {} @@ -42,20 +40,18 @@ void main() { group('onNewState', () { late PinballGame game; late Backbox backbox; - late CameraController cameraController; - late GameBlocStatusListener gameFlowController; + late GameBlocStatusListener gameBlocStatusListener; late PinballPlayer pinballPlayer; late ActiveOverlaysNotifier overlays; setUp(() { game = _MockPinballGame(); backbox = _MockBackbox(); - cameraController = _MockCameraController(); - gameFlowController = GameBlocStatusListener(); + gameBlocStatusListener = GameBlocStatusListener(); overlays = _MockActiveOverlaysNotifier(); pinballPlayer = _MockPinballPlayer(); - gameFlowController.mockGameRef(game); + gameBlocStatusListener.mockGameRef(game); when( () => backbox.initialsInput( @@ -64,22 +60,18 @@ void main() { onSubmit: any(named: 'onSubmit'), ), ).thenAnswer((_) async {}); - when(cameraController.focusOnWaitingBackbox).thenAnswer((_) async {}); - when(cameraController.focusOnGame).thenAnswer((_) async {}); when(() => overlays.remove(any())).thenAnswer((_) => true); when(() => game.descendants().whereType()) .thenReturn([backbox]); - when(game.firstChild).thenReturn(cameraController); when(() => game.overlays).thenReturn(overlays); when(() => game.characterTheme).thenReturn(DashTheme()); when(() => game.player).thenReturn(pinballPlayer); }); test( - 'changes the backbox display and camera correctly ' - 'when the game is over', + 'changes the backbox display when the game is over', () { final state = GameState( totalScore: 0, @@ -89,7 +81,7 @@ void main() { bonusHistory: const [], status: GameStatus.gameOver, ); - gameFlowController.onNewState(state); + gameBlocStatusListener.onNewState(state); verify( () => backbox.initialsInput( @@ -98,18 +90,16 @@ void main() { onSubmit: any(named: 'onSubmit'), ), ).called(1); - verify(cameraController.focusOnGameOverBackbox).called(1); }, ); test( - 'changes the backbox and camera correctly when it is not a game over', + 'changes the backbox when it is not a game over', () { - gameFlowController.onNewState( + gameBlocStatusListener.onNewState( GameState.initial().copyWith(status: GameStatus.playing), ); - verify(cameraController.focusOnGame).called(1); verify(() => overlays.remove(PinballGame.playButtonOverlay)) .called(1); }, @@ -118,7 +108,7 @@ void main() { test( 'plays the background music on start', () { - gameFlowController.onNewState( + gameBlocStatusListener.onNewState( GameState.initial().copyWith(status: GameStatus.playing), );