refactor: changed ramp behaviors

pull/296/head
RuiAlonso 3 years ago
parent db3150b39e
commit fb0d08ffff

@ -19,7 +19,7 @@ class AppBlocObserver extends BlocObserver {
@override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
log('onChange(${bloc.runtimeType}, $change)');
//log('onChange(${bloc.runtimeType}, $change)');
}
@override

@ -19,11 +19,9 @@ class AndroidAcres extends Component {
children: [
RampShotBehavior(
points: Points.fiveThousand,
scorePosition: Vector2(0, -45),
),
RampBonusBehavior(
points: Points.oneMillion,
scorePosition: Vector2(0, -60),
),
],
),

@ -4,32 +4,33 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template ramp_bonus_behavior}
/// When a [Ball] shot inside the [SpaceshipRamp] 10 times increases score.
/// Increases the score when a [Ball] is shot 10 times into the [SpaceshipRamp].
/// {@endtemplate}
class RampBonusBehavior extends Component
with ParentIsA<SpaceshipRamp>, HasGameRef<PinballGame> {
/// {@macro ramp_bonus_behavior}
RampBonusBehavior({
required Points points,
required Vector2 scorePosition,
}) : _points = points,
_scorePosition = scorePosition,
super();
final Points _points;
final Vector2 _scorePosition;
@override
void onMount() {
super.onMount();
parent.bloc.stream.listen((state) {
if (state.status == SpaceshipRampStatus.withBonus) {
print("state.hits ${state.hits}");
final achievedOneMillionPoints = state.hits % 10 == 0;
if (achievedOneMillionPoints) {
print("achievedOneMillionPoints");
gameRef.read<GameBloc>().add(Scored(points: _points.value));
gameRef.add(
ScoreComponent(
points: _points,
position: _scorePosition,
position: Vector2(0, -60),
),
);
}

@ -4,37 +4,35 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template ramp_shot_behavior}
/// When a [Ball] shot inside the [SpaceshipRamp] it achieve improvements for
/// the current game, like multipliers or score.
/// Increases the score when a [Ball] is shot into the [SpaceshipRamp].
/// {@endtemplate}
class RampShotBehavior extends Component
with ParentIsA<SpaceshipRamp>, HasGameRef<PinballGame> {
/// {@macro ramp_shot_behavior}
RampShotBehavior({
required Points points,
required Vector2 scorePosition,
}) : _points = points,
_scorePosition = scorePosition,
super();
final Points _points;
final Vector2 _scorePosition;
@override
void onMount() {
super.onMount();
parent.bloc.stream.listen((state) {
if (state.shot) {
parent.progress();
final achievedOneMillionPoints = state.hits % 10 == 0;
if (!achievedOneMillionPoints) {
gameRef.read<GameBloc>()
..add(const MultiplierIncreased())
..add(Scored(points: _points.value));
gameRef.add(
ScoreComponent(
points: _points,
position: _scorePosition,
position: Vector2(0, -45),
),
);
}

@ -7,22 +7,18 @@ import 'package:pinball_flame/pinball_flame.dart';
/// {@template ramp_contact_behavior}
/// Detects a [Ball]that enters in the [SpaceshipRamp].
///
/// The [Ball] can hit with sensor at door or sensor inside, just to recognize
/// when if [Ball] comes from out.
/// The [Ball] can hit with sensor to recognize if [Ball] goes in or out the
/// [SpaceshipRamp].
/// {@endtemplate}
class RampContactBehavior extends ContactBehavior<RampSensor> {
class RampContactBehavior extends ContactBehavior<RampScoringSensor> {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
switch (parent.type) {
case RampSensorType.door:
parent.parent.bloc.onDoor(other);
break;
case RampSensorType.inside:
parent.parent.bloc.onInside(other);
break;
if (other.body.linearVelocity.y < 0) {
parent.parent.bloc.onInside();
}
}
}

@ -1,43 +1,18 @@
// ignore_for_file: public_member_api_docs
import 'dart:collection';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:pinball_components/pinball_components.dart';
part 'spaceship_ramp_state.dart';
class SpaceshipRampCubit extends Cubit<SpaceshipRampState> {
SpaceshipRampCubit() : super(SpaceshipRampState.initial());
void onDoor(Ball ball) {
if (!state.balls.contains(ball)) {
emit(
state.copyWith(
balls: state.balls..add(ball),
status: SpaceshipRampStatus.withoutBonus,
),
);
}
}
void onInside(Ball ball) {
if (state.balls.contains(ball)) {
final hits = state.hits + 1;
final bonus = (hits % 10 == 0)
? SpaceshipRampStatus.withBonus
: SpaceshipRampStatus.withoutBonus;
final shot = hits % 10 != 0;
void onInside() {
emit(
state.copyWith(
hits: hits,
balls: state.balls..remove(ball),
shot: shot,
status: bonus,
hits: state.hits + 1,
),
);
}
}
}

@ -2,56 +2,23 @@
part of 'spaceship_ramp_cubit.dart';
/// Used to know when a [Ball] gets into the [SpaceshipRamp] against every ball
/// that crosses the opening.
enum RampSensorType {
/// Sensor at the entrance of the opening.
door,
/// Sensor inside the [SpaceshipRamp].
inside,
}
enum SpaceshipRampStatus {
withoutBonus,
withBonus,
}
class SpaceshipRampState extends Equatable {
const SpaceshipRampState({
required this.hits,
required this.balls,
required this.shot,
required this.status,
}) : assert(hits >= 0, "Hits can't be negative");
SpaceshipRampState.initial()
: this(
hits: 0,
balls: HashSet(),
shot: false,
status: SpaceshipRampStatus.withoutBonus,
);
SpaceshipRampState.initial() : this(hits: 0);
final int hits;
final Set<Ball> balls;
final bool shot;
final SpaceshipRampStatus status;
SpaceshipRampState copyWith({
int? hits,
Set<Ball>? balls,
bool? shot,
SpaceshipRampStatus? status,
}) {
return SpaceshipRampState(
hits: hits ?? this.hits,
balls: balls ?? this.balls,
shot: shot ?? this.shot,
status: status ?? this.status,
);
}
@override
List<Object?> get props => [hits, balls, shot, status];
List<Object?> get props => [hits];
}

@ -22,18 +22,11 @@ class SpaceshipRamp extends Component {
children: [
// TODO(ruimiguel): refactor RampSensor and RampOpening to be in
// only one sensor.
RampSensor(
type: RampSensorType.door,
RampScoringSensor(
children: [
RampContactBehavior(),
],
)..initialPosition = Vector2(1.7, -20.4),
RampSensor(
type: RampSensorType.inside,
children: [
RampContactBehavior(),
],
)..initialPosition = Vector2(1.7, -22),
_SpaceshipRampOpening(
outsidePriority: ZIndexes.ballOnBoard,
rotation: math.pi,
@ -414,15 +407,14 @@ class _SpaceshipRampOpening extends LayerSensor {
}
}
/// {@template ramp_sensor}
/// {@template ramp_scoring_sensor}
/// Small sensor body used to detect when a ball has entered the
/// [SpaceshipRamp].
/// {@endtemplate}
class RampSensor extends BodyComponent
class RampScoringSensor extends BodyComponent
with ParentIsA<SpaceshipRamp>, InitialPosition, Layered {
/// {@macro ramp_sensor}
RampSensor({
required this.type,
/// {@macro ramp_scoring_sensor}
RampScoringSensor({
Iterable<Component>? children,
}) : super(
children: children,
@ -431,15 +423,10 @@ class RampSensor extends BodyComponent
layer = Layer.spaceshipEntranceRamp;
}
/// Creates a [RampSensor] without any children.
/// Creates a [RampScoringSensor] without any children.
///
@visibleForTesting
RampSensor.test({
required this.type,
});
/// Type for the sensor, to know if it's the one at the door or inside ramp.
final RampSensorType type;
RampScoringSensor.test();
@override
Body createBody() {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 187 KiB

@ -3,7 +3,6 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball_components/pinball_components.dart';
@ -13,6 +12,10 @@ import '../../../../helpers/helpers.dart';
class _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {}
class _MockBall extends Mock implements Ball {}
class _MockBody extends Mock implements Body {}
class _MockContact extends Mock implements Contact {}
void main() {
@ -42,10 +45,20 @@ void main() {
);
});
group('beginContact', () {
late Ball ball;
late Body body;
setUp(() {
ball = _MockBall();
body = _MockBody();
when(() => ball.body).thenReturn(body);
});
flameTester.test(
"beginContact with door sensor calls bloc 'onDoor'",
"calls 'onInside' when a ball enters into the ramp",
(game) async {
final ball = Ball(baseColor: Colors.red);
final behavior = RampContactBehavior();
final bloc = _MockSpaceshipRampCubit();
whenListen(
@ -54,27 +67,26 @@ void main() {
initialState: SpaceshipRampState.initial(),
);
final rampSensor = RampSensor.test(
type: RampSensorType.door,
);
final rampSensor = RampScoringSensor.test();
final spaceshipRamp = SpaceshipRamp.test(
bloc: bloc,
);
when(() => body.linearVelocity).thenReturn(Vector2(0, 1));
await spaceshipRamp.add(rampSensor);
await game.ensureAddAll([spaceshipRamp, ball]);
await rampSensor.add(behavior);
behavior.beginContact(ball, _MockContact());
verify(() => bloc.onDoor(ball)).called(1);
verify(bloc.onInside).called(1);
},
);
flameTester.test(
"beginContact with inside sensor calls bloc 'onInside'",
"doesn't call 'onInside' when a ball goes out the ramp",
(game) async {
final ball = Ball(baseColor: Colors.red);
final behavior = RampContactBehavior();
final bloc = _MockSpaceshipRampCubit();
whenListen(
@ -83,22 +95,23 @@ void main() {
initialState: SpaceshipRampState.initial(),
);
final rampSensor = RampSensor.test(
type: RampSensorType.inside,
);
final rampSensor = RampScoringSensor.test();
final spaceshipRamp = SpaceshipRamp.test(
bloc: bloc,
);
when(() => body.linearVelocity).thenReturn(Vector2(0, -1));
await spaceshipRamp.add(rampSensor);
await game.ensureAddAll([spaceshipRamp, ball]);
await rampSensor.add(behavior);
behavior.beginContact(ball, _MockContact());
verify(() => bloc.onInside(ball)).called(1);
verifyNever(bloc.onInside);
},
);
});
},
);
}

@ -1,96 +1,23 @@
// ignore_for_file: prefer_const_constructors
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
void main() {
group('SpaceshipRampCubit', () {
final ball = Ball(baseColor: Colors.red);
group('onDoor', () {
blocTest<SpaceshipRampCubit, SpaceshipRampState>(
'emits nothing if contains ball',
build: SpaceshipRampCubit.new,
seed: () => SpaceshipRampState.initial().copyWith(
balls: {ball},
),
act: (bloc) => bloc.onDoor(ball),
expect: () => <SpaceshipRampState>[],
);
blocTest<SpaceshipRampCubit, SpaceshipRampState>(
'emits [{ball}, withoutBonus] if not contains ball',
build: SpaceshipRampCubit.new,
act: (bloc) => bloc.onDoor(ball),
expect: () => [
isA<SpaceshipRampState>()
..having(
(state) => state.balls,
'balls',
contains(ball),
)
..having(
(state) => state.status,
'status',
SpaceshipRampStatus.withoutBonus,
),
],
);
});
group('onInside', () {
blocTest<SpaceshipRampCubit, SpaceshipRampState>(
'emits nothing if not contains ball',
build: SpaceshipRampCubit.new,
seed: () => SpaceshipRampState.initial().copyWith(
balls: {},
),
act: (bloc) => bloc.onInside(ball),
expect: () => <SpaceshipRampState>[],
);
blocTest<SpaceshipRampCubit, SpaceshipRampState>(
'emits withoutBonus if contains ball '
'and hits less than 10 times',
build: SpaceshipRampCubit.new,
seed: () => SpaceshipRampState.initial().copyWith(
hits: 5,
balls: {ball},
),
act: (bloc) => bloc.onInside(ball),
expect: () => [
isA<SpaceshipRampState>()
..having((state) => state.hits, 'hits', 6)
..having((state) => state.balls, 'balls', isNot(contains(ball)))
..having((state) => state.shot, 'shot', true)
..having(
(state) => state.status,
'status',
SpaceshipRampStatus.withoutBonus,
),
],
);
blocTest<SpaceshipRampCubit, SpaceshipRampState>(
'emits withBonus if contains ball and hits 10 times',
'emits hits incremented',
build: SpaceshipRampCubit.new,
seed: () => SpaceshipRampState.initial().copyWith(
hits: 9,
balls: {ball},
),
act: (bloc) => bloc.onInside(ball),
act: (bloc) => bloc
..onInside()
..onInside()
..onInside(),
expect: () => [
isA<SpaceshipRampState>()
..having((state) => state.hits, 'hits', 10)
..having((state) => state.balls, 'balls', contains(ball))
..having((state) => state.shot, 'shot', false)
..having(
(state) => state.status,
'status',
SpaceshipRampStatus.withBonus,
),
SpaceshipRampState(hits: 1),
SpaceshipRampState(hits: 2),
SpaceshipRampState(hits: 3),
],
);
});

@ -10,16 +10,10 @@ void main() {
expect(
SpaceshipRampState(
hits: 0,
balls: const {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
),
equals(
SpaceshipRampState(
hits: 0,
balls: const {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
),
),
);
@ -30,9 +24,6 @@ void main() {
expect(
SpaceshipRampState(
hits: 0,
balls: const {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
),
isNotNull,
);
@ -46,9 +37,6 @@ void main() {
expect(
() => SpaceshipRampState(
hits: -1,
balls: const {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
),
throwsAssertionError,
);
@ -62,9 +50,6 @@ void main() {
() {
const rampState = SpaceshipRampState(
hits: 0,
balls: {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
);
expect(
() => rampState.copyWith(hits: rampState.hits - 1),
@ -79,9 +64,6 @@ void main() {
() {
const rampState = SpaceshipRampState(
hits: 0,
balls: {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
);
expect(
rampState.copyWith(),
@ -98,24 +80,15 @@ void main() {
const rampState = SpaceshipRampState(
hits: 0,
balls: {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
);
final otherRampState = SpaceshipRampState(
hits: rampState.hits + 1,
balls: {ball},
shot: true,
status: SpaceshipRampStatus.withBonus,
);
expect(rampState, isNot(equals(otherRampState)));
expect(
rampState.copyWith(
hits: rampState.hits + 1,
balls: {ball},
shot: true,
status: SpaceshipRampStatus.withBonus,
),
equals(otherRampState),
);

@ -1,13 +1,17 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../helpers/helpers.dart';
class _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
@ -204,5 +208,37 @@ void main() {
},
);
});
flameTester.test('closes bloc when removed', (game) async {
final bloc = _MockSpaceshipRampCubit();
whenListen(
bloc,
const Stream<SpaceshipRampState>.empty(),
initialState: SpaceshipRampState.initial(),
);
when(bloc.close).thenAnswer((_) async {});
final ramp = SpaceshipRamp.test(
bloc: bloc,
);
await game.ensureAdd(ramp);
game.remove(ramp);
await game.ready();
verify(bloc.close).called(1);
});
group('adds', () {
flameTester.test('new children', (game) async {
final component = Component();
final ramp = SpaceshipRamp.test(
bloc: _MockSpaceshipRampCubit(),
children: [component],
);
await game.ensureAdd(ramp);
expect(ramp.children, contains(component));
});
});
});
}

@ -3,7 +3,6 @@
import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
@ -32,7 +31,7 @@ void main() {
Assets.images.android.ramp.arrow.active5.keyName,
Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName,
Assets.images.score.fiveThousand.keyName,
Assets.images.score.oneMillion.keyName,
];
group('RampBonusBehavior', () {
@ -56,17 +55,18 @@ void main() {
);
flameBlocTester.testGameWidget(
"when withoutBonus doesn't add any score or show any score points",
'when hits are multiples of 10 times '
'add score and show score points',
setUp: (game, tester) async {
final bloc = _MockSpaceshipRampCubit();
final streamController = StreamController<SpaceshipRampState>();
whenListen(
bloc,
const Stream<SpaceshipRampState>.empty(),
initialState: SpaceshipRampState.initial(),
streamController.stream,
initialState: SpaceshipRampState(hits: 9),
);
final behavior = RampShotBehavior(
final behavior = RampBonusBehavior(
points: shotPoints,
scorePosition: Vector2.zero(),
);
final parent = SpaceshipRamp.test(
bloc: bloc,
@ -75,30 +75,29 @@ void main() {
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
await tester.pump();
streamController.add(SpaceshipRampState(hits: 10));
final scores = game.descendants().whereType<ScoreComponent>();
await game.ready();
verifyNever(() => gameBloc.add(Scored(points: shotPoints.value)));
expect(scores.length, 0);
verify(() => gameBloc.add(Scored(points: shotPoints.value))).called(1);
expect(scores.length, 1);
},
);
flameBlocTester.testGameWidget(
'when withBonus add score and show score points',
'when hits are not multiple of 10 times '
"doesn't add score neither show score points",
setUp: (game, tester) async {
final bloc = _MockSpaceshipRampCubit();
final streamController = StreamController<SpaceshipRampState>();
whenListen(
bloc,
const Stream<SpaceshipRampState>.empty(),
initialState: SpaceshipRampState.initial().copyWith(
status: SpaceshipRampStatus.withBonus,
),
streamController.stream,
initialState: SpaceshipRampState.initial(),
);
final behavior = RampShotBehavior(
final behavior = RampBonusBehavior(
points: shotPoints,
scorePosition: Vector2.zero(),
);
final parent = SpaceshipRamp.test(
bloc: bloc,
@ -107,12 +106,11 @@ void main() {
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
await tester.pump();
streamController.add(SpaceshipRampState(hits: 1));
final scores = game.descendants().whereType<ScoreComponent>();
await game.ready();
verifyNever(() => gameBloc.add(MultiplierIncreased()));
verifyNever(() => gameBloc.add(Scored(points: shotPoints.value)));
expect(scores.length, 0);
},

@ -3,7 +3,6 @@
import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
@ -56,18 +55,18 @@ void main() {
);
flameBlocTester.testGameWidget(
"when not shot doesn't increase multiplier "
'neither add any score or show any score points',
'when hits are not multiple of 10 times '
'increase multiplier, add score and show score points',
setUp: (game, tester) async {
final bloc = _MockSpaceshipRampCubit();
final streamController = StreamController<SpaceshipRampState>();
whenListen(
bloc,
const Stream<SpaceshipRampState>.empty(),
streamController.stream,
initialState: SpaceshipRampState.initial(),
);
final behavior = RampShotBehavior(
points: shotPoints,
scorePosition: Vector2.zero(),
);
final parent = SpaceshipRamp.test(
bloc: bloc,
@ -76,31 +75,30 @@ void main() {
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
await tester.pump();
streamController.add(SpaceshipRampState(hits: 1));
final scores = game.descendants().whereType<ScoreComponent>();
await game.ready();
verifyNever(() => gameBloc.add(MultiplierIncreased()));
verifyNever(() => gameBloc.add(Scored(points: shotPoints.value)));
expect(scores.length, 0);
verify(() => gameBloc.add(MultiplierIncreased())).called(1);
verify(() => gameBloc.add(Scored(points: shotPoints.value))).called(1);
expect(scores.length, 1);
},
);
flameBlocTester.testGameWidget(
'when shot increase multiplier add score and show score points',
'when hits multiple of 10 times '
"doesn't increase multiplier, neither add score or show score points",
setUp: (game, tester) async {
final bloc = _MockSpaceshipRampCubit();
final streamController = StreamController<SpaceshipRampState>();
whenListen(
bloc,
const Stream<SpaceshipRampState>.empty(),
initialState: SpaceshipRampState.initial().copyWith(
shot: false,
),
streamController.stream,
initialState: SpaceshipRampState(hits: 9),
);
final behavior = RampShotBehavior(
points: shotPoints,
scorePosition: Vector2.zero(),
);
final parent = SpaceshipRamp.test(
bloc: bloc,
@ -109,7 +107,7 @@ void main() {
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
await tester.pump();
streamController.add(SpaceshipRampState(hits: 10));
final scores = game.descendants().whereType<ScoreComponent>();
await game.ready();

Loading…
Cancel
Save