From 3f8e524ad3b4c4c14427b69cbf43022598687e0d Mon Sep 17 00:00:00 2001 From: alestiago Date: Fri, 16 Feb 2024 07:12:47 +0000 Subject: [PATCH] feat: store when the user started playing --- .../lib/src/game/bloc/game_bloc.dart | 11 ++- .../lib/src/game/bloc/game_state.dart | 7 ++ .../test/src/game/bloc/game_bloc_test.dart | 90 +++++++++++++++++-- 3 files changed, 96 insertions(+), 12 deletions(-) diff --git a/packages/trashy_road/lib/src/game/bloc/game_bloc.dart b/packages/trashy_road/lib/src/game/bloc/game_bloc.dart index b081b12c..dcbfeb01 100644 --- a/packages/trashy_road/lib/src/game/bloc/game_bloc.dart +++ b/packages/trashy_road/lib/src/game/bloc/game_bloc.dart @@ -1,6 +1,7 @@ import 'dart:collection'; import 'package:bloc/bloc.dart'; +import 'package:clock/clock.dart'; import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; import 'package:tiled/tiled.dart'; @@ -31,7 +32,12 @@ class GameBloc extends Bloc { GameInteractedEvent event, Emitter emit, ) { - emit(state.copyWith(status: GameStatus.playing)); + emit( + state.copyWith( + status: GameStatus.playing, + startedAt: () => state.startedAt ?? clock.now(), + ), + ); } void _onCollectedTrash( @@ -82,9 +88,8 @@ class GameBloc extends Bloc { Emitter emit, ) { emit( - state.copyWith( + GameState.initial(map: state.map).copyWith( status: GameStatus.resetting, - inventory: Inventory.empty(), ), ); } diff --git a/packages/trashy_road/lib/src/game/bloc/game_state.dart b/packages/trashy_road/lib/src/game/bloc/game_state.dart index c941b81f..c34d0518 100644 --- a/packages/trashy_road/lib/src/game/bloc/game_state.dart +++ b/packages/trashy_road/lib/src/game/bloc/game_state.dart @@ -43,6 +43,7 @@ class GameState extends Equatable { required this.map, required this.inventory, this.collectedTrash = 0, + this.startedAt, }) : // TODO(alestiago): Remove magic string. _initialTrash = @@ -74,16 +75,21 @@ class GameState extends Equatable { /// {@macro Inventory} final Inventory inventory; + /// The time at which the game was started. + final DateTime? startedAt; + GameState copyWith({ GameStatus? status, Inventory? inventory, int? collectedTrash, + DateTime? Function()? startedAt, }) { return GameState( status: status ?? this.status, map: map, inventory: inventory ?? this.inventory, collectedTrash: collectedTrash ?? this.collectedTrash, + startedAt: startedAt != null ? startedAt() : this.startedAt, ); } @@ -93,6 +99,7 @@ class GameState extends Equatable { inventory, map, collectedTrash, + startedAt, ]; } diff --git a/packages/trashy_road/test/src/game/bloc/game_bloc_test.dart b/packages/trashy_road/test/src/game/bloc/game_bloc_test.dart index 830007a2..42ffbfdc 100644 --- a/packages/trashy_road/test/src/game/bloc/game_bloc_test.dart +++ b/packages/trashy_road/test/src/game/bloc/game_bloc_test.dart @@ -1,4 +1,6 @@ import 'package:bloc_test/bloc_test.dart'; +import 'package:clock/clock.dart'; +import 'package:flame/game.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:tiled/tiled.dart'; @@ -29,13 +31,19 @@ void main() { group('$GameInteractedEvent', () { blocTest( 'playing status after the user interacts with the game', - build: () => GameBloc(map: map), + build: () { + return withClock( + Clock.fixed(DateTime(0)), + () => GameBloc(map: map), + ); + }, act: (bloc) => bloc.add(const GameInteractedEvent()), expect: () => [ GameState( map: map, status: GameStatus.playing, inventory: Inventory.empty(), + startedAt: DateTime(0), ), ], ); @@ -54,7 +62,12 @@ void main() { blocTest( 'fills the inventory with trash when the user collects trash ' 'and the game is playing', - build: () => GameBloc(map: map), + build: () { + return withClock( + Clock.fixed(DateTime(0)), + () => GameBloc(map: map), + ); + }, act: (bloc) => bloc ..add(const GameInteractedEvent()) ..add(const GameCollectedTrashEvent(item: TrashType.plastic)), @@ -63,11 +76,13 @@ void main() { map: map, status: GameStatus.playing, inventory: Inventory.empty(), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.playing, inventory: Inventory(items: const [TrashType.plastic]), + startedAt: DateTime(0), ), ], ); @@ -75,7 +90,12 @@ void main() { blocTest( 'increments the correct type of trash when the user collects trash ' 'and the game is playing', - build: () => GameBloc(map: map), + build: () { + return withClock( + Clock.fixed(DateTime(0)), + () => GameBloc(map: map), + ); + }, act: (bloc) => bloc ..add(const GameInteractedEvent()) ..add(const GameCollectedTrashEvent(item: TrashType.plastic)) @@ -85,17 +105,20 @@ void main() { map: map, status: GameStatus.playing, inventory: Inventory.empty(), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.playing, inventory: Inventory(items: const [TrashType.plastic]), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.playing, inventory: Inventory(items: const [TrashType.plastic, TrashType.glass]), + startedAt: DateTime(0), ), ], ); @@ -113,7 +136,12 @@ void main() { blocTest( 'empties the inventory with trash when the user deposits trash ' 'and the game is playing', - build: () => GameBloc(map: map), + build: () { + return withClock( + Clock.fixed(DateTime(0)), + () => GameBloc(map: map), + ); + }, setUp: () => when(() => trashLayer.objects) .thenReturn([_MockTiledObject(), _MockTiledObject()]), act: (bloc) => bloc @@ -125,17 +153,20 @@ void main() { map: map, status: GameStatus.playing, inventory: Inventory.empty(), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.playing, inventory: Inventory(items: const [TrashType.plastic]), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.playing, inventory: Inventory.empty(), collectedTrash: 1, + startedAt: DateTime(0), ), ], ); @@ -143,7 +174,12 @@ void main() { blocTest( 'empties the correct type of trash when the user deposits trash ' 'and the game is playing', - build: () => GameBloc(map: map), + build: () { + return withClock( + Clock.fixed(DateTime(0)), + () => GameBloc(map: map), + ); + }, setUp: () => when(() => trashLayer.objects) .thenReturn([_MockTiledObject(), _MockTiledObject()]), act: (bloc) => bloc @@ -156,30 +192,39 @@ void main() { map: map, status: GameStatus.playing, inventory: Inventory.empty(), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.playing, inventory: Inventory(items: const [TrashType.plastic]), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.playing, inventory: Inventory(items: const [TrashType.plastic, TrashType.glass]), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.playing, inventory: Inventory(items: const [TrashType.glass]), collectedTrash: 1, + startedAt: DateTime(0), ), ], ); blocTest( 'completes the game when all the trash is deposited ', - build: () => GameBloc(map: map), + build: () { + return withClock( + Clock.fixed(DateTime(0)), + () => GameBloc(map: map), + ); + }, setUp: () => when(() => trashLayer.objects) .thenReturn([_MockTiledObject(), _MockTiledObject()]), act: (bloc) => bloc @@ -193,29 +238,34 @@ void main() { map: map, status: GameStatus.playing, inventory: Inventory.empty(), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.playing, inventory: Inventory(items: const [TrashType.plastic]), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.playing, inventory: Inventory(items: const [TrashType.plastic, TrashType.plastic]), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.playing, inventory: Inventory(items: const [TrashType.plastic]), collectedTrash: 1, + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.completed, inventory: Inventory.empty(), collectedTrash: 2, + startedAt: DateTime(0), ), ], ); @@ -224,7 +274,12 @@ void main() { group('$GamePausedEvent', () { blocTest( 'pauses the game when the user was previously playing the game', - build: () => GameBloc(map: map), + build: () { + return withClock( + Clock.fixed(DateTime(0)), + () => GameBloc(map: map), + ); + }, act: (bloc) => bloc ..add(const GameInteractedEvent()) ..add(const GamePausedEvent()), @@ -233,11 +288,13 @@ void main() { map: map, status: GameStatus.playing, inventory: Inventory.empty(), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.paused, inventory: Inventory.empty(), + startedAt: DateTime(0), ), ], ); @@ -253,7 +310,12 @@ void main() { blocTest( 'resumes the game when the user was previously paused', - build: () => GameBloc(map: map), + build: () { + return withClock( + Clock.fixed(DateTime(0)), + () => GameBloc(map: map), + ); + }, act: (bloc) => bloc ..add(const GameInteractedEvent()) ..add(const GamePausedEvent()) @@ -263,16 +325,19 @@ void main() { map: map, status: GameStatus.playing, inventory: Inventory.empty(), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.paused, inventory: Inventory.empty(), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.playing, inventory: Inventory.empty(), + startedAt: DateTime(0), ), ], ); @@ -281,7 +346,12 @@ void main() { group('$GameResetEvent', () { blocTest( 'resets the game', - build: () => GameBloc(map: map), + build: () { + return withClock( + Clock.fixed(DateTime(0)), + () => GameBloc(map: map), + ); + }, act: (bloc) => bloc ..add(const GameInteractedEvent()) ..add(const GameCollectedTrashEvent(item: TrashType.plastic)) @@ -291,11 +361,13 @@ void main() { map: map, status: GameStatus.playing, inventory: Inventory.empty(), + startedAt: DateTime(0), ), GameState( map: map, status: GameStatus.playing, inventory: Inventory(items: const [TrashType.plastic]), + startedAt: DateTime(0), ), GameState( map: map,