refactor: moved ramp sensor cubit to spaceship ramp

pull/296/head
RuiAlonso 3 years ago
parent ae687c4913
commit 96cc57b233

@ -1,28 +0,0 @@
// ignore_for_file: public_member_api_docs
import 'package:bloc/bloc.dart';
import 'package:pinball_components/pinball_components.dart';
part 'ramp_sensor_state.dart';
class RampSensorCubit extends Cubit<RampSensorState> {
RampSensorCubit() : super(const RampSensorState.initial());
void onDoor(Ball ball) {
emit(
state.copyWith(
type: RampSensorType.door,
ball: ball,
),
);
}
void onInside(Ball ball) {
emit(
state.copyWith(
type: RampSensorType.inside,
ball: ball,
),
);
}
}

@ -1,35 +0,0 @@
// ignore_for_file: public_member_api_docs
part of 'ramp_sensor_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,
}
class RampSensorState {
const RampSensorState({
required this.type,
this.ball,
});
const RampSensorState.initial() : this(type: RampSensorType.door);
final RampSensorType type;
final Ball? ball;
RampSensorState copyWith({
RampSensorType? type,
Ball? ball,
}) {
return RampSensorState(
type: type ?? this.type,
ball: ball ?? this.ball,
);
}
}

@ -0,0 +1,43 @@
// 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;
emit(
state.copyWith(
hits: hits,
balls: state.balls..remove(ball),
shot: shot,
status: bonus,
),
);
}
}
}

@ -0,0 +1,57 @@
// ignore_for_file: public_member_api_docs
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,
);
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];
}

@ -8,7 +8,7 @@ import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_components/src/components/spaceship_ramp/behavior/behavior.dart';
import 'package:pinball_flame/pinball_flame.dart';
export 'cubit/ramp_sensor_cubit.dart';
export 'cubit/spaceship_ramp_cubit.dart';
/// {@template spaceship_ramp}
/// Ramp leading into the [AndroidSpaceship].
@ -17,14 +17,23 @@ class SpaceshipRamp extends Component {
/// {@macro spaceship_ramp}
SpaceshipRamp({
Iterable<Component>? children,
}) : super(
}) : bloc = SpaceshipRampCubit(),
super(
children: [
// TODO(ruimiguel): refactor RampSensor and RampOpening to be in
// only one sensor.
RampSensor(type: RampSensorType.door)
..initialPosition = Vector2(1.7, -20.4),
RampSensor(type: RampSensorType.inside)
..initialPosition = Vector2(1.7, -22),
RampSensor(
type: RampSensorType.door,
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,
@ -53,7 +62,21 @@ class SpaceshipRamp extends Component {
///
/// This can be used for testing [SpaceshipRamp]'s behaviors in isolation.
@visibleForTesting
SpaceshipRamp.test();
SpaceshipRamp.test({
required this.bloc,
Iterable<Component>? children,
}) : super(children: children);
// TODO(alestiago): Consider refactoring once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
// ignore: public_member_api_docs
final SpaceshipRampCubit bloc;
@override
void onRemove() {
bloc.close();
super.onRemove();
}
/// Forwards the sprite to the next [SpaceshipRampArrowSpriteState].
///
@ -398,13 +421,12 @@ class _SpaceshipRampOpening extends LayerSensor {
class RampSensor extends BodyComponent
with ParentIsA<SpaceshipRamp>, InitialPosition, Layered {
/// {@macro ramp_sensor}
RampSensor({required this.type})
: bloc = RampSensorCubit(),
super(
children: [
RampContactBehavior(),
],
renderBody: true,
RampSensor({
required this.type,
Iterable<Component>? children,
}) : super(
children: children,
renderBody: false,
) {
layer = Layer.spaceshipEntranceRamp;
}
@ -414,23 +436,11 @@ class RampSensor extends BodyComponent
@visibleForTesting
RampSensor.test({
required this.type,
required this.bloc,
});
/// Type for the sensor, to know if it's the one at the door or inside ramp.
final RampSensorType type;
// TODO(alestiago): Consider refactoring once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
// ignore: public_member_api_docs
final RampSensorCubit bloc;
@override
void onRemove() {
bloc.close();
super.onRemove();
}
@override
Body createBody() {
final shape = PolygonShape()

@ -1,66 +0,0 @@
// 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('RampSensorCubit', () {
final ball = Ball(baseColor: Colors.red);
blocTest<RampSensorCubit, RampSensorState>(
'onDoor emits [door]',
build: RampSensorCubit.new,
act: (bloc) => bloc.onDoor(ball),
expect: () => [
isA<RampSensorState>()
..having((state) => state.type, 'type', RampSensorType.door)
..having((state) => state.ball, 'ball', ball),
],
);
blocTest<RampSensorCubit, RampSensorState>(
'onDoor twice emits [door, door]',
build: RampSensorCubit.new,
act: (bloc) => bloc
..onDoor(ball)
..onDoor(ball),
expect: () => [
isA<RampSensorState>()
..having((state) => state.type, 'type', RampSensorType.door)
..having((state) => state.ball, 'ball', ball),
isA<RampSensorState>()
..having((state) => state.type, 'type', RampSensorType.door)
..having((state) => state.ball, 'ball', ball),
],
);
blocTest<RampSensorCubit, RampSensorState>(
'onInside emits [inside]',
build: RampSensorCubit.new,
act: (bloc) => bloc.onInside(ball),
expect: () => [
isA<RampSensorState>()
..having((state) => state.type, 'type', RampSensorType.inside)
..having((state) => state.ball, 'ball', ball),
],
);
blocTest<RampSensorCubit, RampSensorState>(
'onInside twice emits [inside,inside]',
build: RampSensorCubit.new,
act: (bloc) => bloc
..onInside(ball)
..onInside(ball),
expect: () => [
isA<RampSensorState>()
..having((state) => state.type, 'type', RampSensorType.inside)
..having((state) => state.ball, 'ball', ball),
isA<RampSensorState>()
..having((state) => state.type, 'type', RampSensorType.inside)
..having((state) => state.ball, 'ball', ball),
],
);
});
}

@ -1,110 +0,0 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
void main() {
group('RampSensorState', () {
test('same states are different even though they have same content', () {
final ball = Ball(baseColor: Colors.red);
final rampSensorState = RampSensorState(
type: RampSensorType.door,
ball: ball,
);
final otherRampSensorState = RampSensorState(
type: RampSensorType.door,
ball: ball,
);
expect(
rampSensorState,
isNot(equals(otherRampSensorState)),
);
expect(
rampSensorState.type,
equals(otherRampSensorState.type),
);
expect(
rampSensorState.ball,
equals(otherRampSensorState.ball),
);
});
group('constructor', () {
test('can be instantiated', () {
expect(
RampSensorState(
type: RampSensorType.door,
ball: Ball(baseColor: Colors.red),
),
isNotNull,
);
});
});
group('copyWith', () {
test(
'copies correctly '
'when no argument specified',
() {
final rampSensorState = RampSensorState(
type: RampSensorType.door,
ball: Ball(baseColor: Colors.red),
);
final copiedRampSensorState = rampSensorState.copyWith();
expect(
copiedRampSensorState.type,
equals(rampSensorState.type),
);
expect(
copiedRampSensorState.ball,
equals(rampSensorState.ball),
);
},
);
test(
'copies correctly '
'when all arguments specified',
() {
final ball = Ball(baseColor: Colors.blue);
final rampSensorState = RampSensorState(
type: RampSensorType.door,
ball: Ball(baseColor: Colors.red),
);
final otherRampSensorState = RampSensorState(
type: RampSensorType.inside,
ball: ball,
);
final copiedRampSensorState = rampSensorState.copyWith(
type: RampSensorType.inside,
ball: ball,
);
expect(
rampSensorState,
isNot(equals(otherRampSensorState)),
);
expect(
copiedRampSensorState.type,
equals(otherRampSensorState.type),
);
expect(
copiedRampSensorState.ball,
equals(otherRampSensorState.ball),
);
},
);
});
});
}

@ -0,0 +1,98 @@
// 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',
build: SpaceshipRampCubit.new,
seed: () => SpaceshipRampState.initial().copyWith(
hits: 9,
balls: {ball},
),
act: (bloc) => bloc.onInside(ball),
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,
),
],
);
});
});
}

@ -0,0 +1,126 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/src/components/components.dart';
void main() {
group('SpaceshipRampState', () {
test('supports value equality', () {
expect(
SpaceshipRampState(
hits: 0,
balls: const {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
),
equals(
SpaceshipRampState(
hits: 0,
balls: const {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
),
),
);
});
group('constructor', () {
test('can be instantiated', () {
expect(
SpaceshipRampState(
hits: 0,
balls: const {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
),
isNotNull,
);
});
});
test(
'throws AssertionError '
'when hits is negative',
() {
expect(
() => SpaceshipRampState(
hits: -1,
balls: const {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
),
throwsAssertionError,
);
},
);
group('copyWith', () {
test(
'throws AssertionError '
'when hits is decreased',
() {
const rampState = SpaceshipRampState(
hits: 0,
balls: {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
);
expect(
() => rampState.copyWith(hits: rampState.hits - 1),
throwsAssertionError,
);
},
);
test(
'copies correctly '
'when no argument specified',
() {
const rampState = SpaceshipRampState(
hits: 0,
balls: {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
);
expect(
rampState.copyWith(),
equals(rampState),
);
},
);
test(
'copies correctly '
'when all arguments specified',
() {
final ball = Ball(baseColor: Colors.black);
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),
);
},
);
});
});
}

@ -8,7 +8,7 @@ import 'package:pinball_components/pinball_components.dart';
import '../../../helpers/helpers.dart';
class MockRampSensorCubit extends Mock implements RampSensorCubit {}
class _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@ -22,27 +22,23 @@ void main() {
];
final flameTester = FlameTester(() => TestGame(assets));
group('RampSensor', () {
flameTester.test('closes bloc when removed', (game) async {
final bloc = MockRampSensorCubit();
whenListen(
bloc,
const Stream<RampSensorState>.empty(),
initialState: const RampSensorState.initial(),
);
when(bloc.close).thenAnswer((_) async {});
final rampSensor = RampSensor.test(
type: RampSensorType.door,
bloc: bloc,
);
final parent = SpaceshipRamp.test();
await game.ensureAdd(parent);
await parent.ensureAdd(rampSensor);
parent.remove(rampSensor);
await game.ready();
verify(bloc.close).called(1);
});
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);
});
}

Loading…
Cancel
Save