feat: spaceship ramp added cubit and behavior to sensor

pull/296/head
RuiAlonso 3 years ago
parent 431257f831
commit 323756fd73

@ -29,7 +29,7 @@ export 'shapes/shapes.dart';
export 'signpost.dart'; export 'signpost.dart';
export 'slingshot.dart'; export 'slingshot.dart';
export 'spaceship_rail.dart'; export 'spaceship_rail.dart';
export 'spaceship_ramp.dart'; export 'spaceship_ramp/spaceship_ramp.dart';
export 'sparky_animatronic.dart'; export 'sparky_animatronic.dart';
export 'sparky_bumper/sparky_bumper.dart'; export 'sparky_bumper/sparky_bumper.dart';
export 'sparky_computer.dart'; export 'sparky_computer.dart';

@ -0,0 +1,28 @@
// ignore_for_file: public_member_api_docs
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template ramp_shot_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.
/// {@endtemplate}
class RampShotBehavior extends ContactBehavior<RampSensor> {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
switch (parent.type) {
case RampSensorType.door:
parent.bloc.onDoor(other);
break;
case RampSensorType.inside:
parent.bloc.onInside(other);
break;
}
}
}

@ -0,0 +1,28 @@
// 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,
),
);
}
}

@ -0,0 +1,35 @@
// 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,
);
}
}

@ -5,8 +5,11 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball_components/gen/assets.gen.dart'; import 'package:pinball_components/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets; 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'; import 'package:pinball_flame/pinball_flame.dart';
export 'cubit/ramp_sensor_cubit.dart';
/// {@template spaceship_ramp} /// {@template spaceship_ramp}
/// Ramp leading into the [AndroidSpaceship]. /// Ramp leading into the [AndroidSpaceship].
/// {@endtemplate} /// {@endtemplate}
@ -15,6 +18,12 @@ class SpaceshipRamp extends Component {
SpaceshipRamp() SpaceshipRamp()
: super( : super(
children: [ 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),
_SpaceshipRampOpening( _SpaceshipRampOpening(
outsidePriority: ZIndexes.ballOnBoard, outsidePriority: ZIndexes.ballOnBoard,
rotation: math.pi, rotation: math.pi,
@ -373,3 +382,67 @@ class _SpaceshipRampOpening extends LayerSensor {
); );
} }
} }
/// {@template ramp_sensor}
/// Small sensor body used to detect when a ball has entered the
/// [SpaceshipRamp].
/// {@endtemplate}
@visibleForTesting
class RampSensor extends BodyComponent
with ParentIsA<SpaceshipRamp>, InitialPosition, Layered {
/// {@macro ramp_sensor}
RampSensor({required this.type})
: bloc = RampSensorCubit(),
super(
children: [
RampShotBehavior(),
],
renderBody: true,
) {
layer = Layer.spaceshipEntranceRamp;
}
/// Creates a [RampSensor] without any children.
///
@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()
..setAsBox(
2.6,
.5,
initialPosition,
-5 * math.pi / 180,
);
final fixtureDef = FixtureDef(
shape,
isSensor: true,
);
final bodyDef = BodyDef(
position: initialPosition,
userData: this,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}

@ -28,3 +28,5 @@ class MockDashNestBumperCubit extends Mock implements DashNestBumperCubit {}
class MockMultiplierCubit extends Mock implements MultiplierCubit {} class MockMultiplierCubit extends Mock implements MultiplierCubit {}
class MockChromeDinoCubit extends Mock implements ChromeDinoCubit {} class MockChromeDinoCubit extends Mock implements ChromeDinoCubit {}
class MockRampSensorCubit extends Mock implements RampSensorCubit {}

@ -0,0 +1,103 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.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';
import 'package:pinball_components/src/components/spaceship_ramp/behavior/behavior.dart';
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.android.ramp.boardOpening.keyName,
Assets.images.android.ramp.railingForeground.keyName,
Assets.images.android.ramp.railingBackground.keyName,
Assets.images.android.ramp.main.keyName,
Assets.images.android.ramp.arrow.inactive.keyName,
Assets.images.android.ramp.arrow.active1.keyName,
Assets.images.android.ramp.arrow.active2.keyName,
Assets.images.android.ramp.arrow.active3.keyName,
Assets.images.android.ramp.arrow.active4.keyName,
Assets.images.android.ramp.arrow.active5.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
group(
'RampShotBehavior',
() {
test('can be instantiated', () {
expect(
RampShotBehavior(),
isA<RampShotBehavior>(),
);
});
flameTester.test(
"beginContact with door sensor calls bloc 'onDoor'",
(game) async {
final ball = Ball(baseColor: Colors.red);
final behavior = RampShotBehavior();
final bloc = MockRampSensorCubit();
whenListen(
bloc,
const Stream<RampSensorState>.empty(),
initialState: RampSensorState(
type: RampSensorType.door,
ball: ball,
),
);
final rampSensor = RampSensor.test(
type: RampSensorType.door,
bloc: bloc,
);
final spaceshipRamp = SpaceshipRamp();
await spaceshipRamp.add(rampSensor);
await game.ensureAddAll([spaceshipRamp, ball]);
await rampSensor.add(behavior);
behavior.beginContact(ball, MockContact());
verify(() => bloc.onDoor(ball)).called(1);
},
);
flameTester.test(
"beginContact with inside sensor calls bloc 'onInside'",
(game) async {
final ball = Ball(baseColor: Colors.red);
final behavior = RampShotBehavior();
final bloc = MockRampSensorCubit();
whenListen(
bloc,
const Stream<RampSensorState>.empty(),
initialState: RampSensorState(
type: RampSensorType.inside,
ball: ball,
),
);
final rampSensor = RampSensor.test(
type: RampSensorType.inside,
bloc: bloc,
);
final spaceshipRamp = SpaceshipRamp();
await spaceshipRamp.add(rampSensor);
await game.ensureAddAll([spaceshipRamp, ball]);
await rampSensor.add(behavior);
behavior.beginContact(ball, MockContact());
verify(() => bloc.onInside(ball)).called(1);
},
);
},
);
}

@ -0,0 +1,66 @@
// 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),
],
);
});
}

@ -0,0 +1,110 @@
// 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),
);
},
);
});
});
}
Loading…
Cancel
Save