refactor: added behaviors to AndroidAcres

pull/296/head
RuiAlonso 3 years ago
parent 7c682c5e06
commit a154878465

@ -1,6 +1,8 @@
// ignore_for_file: avoid_renaming_method_parameters // ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
@ -31,6 +33,20 @@ class AndroidAcres extends Component {
ScoringBehavior(points: 20), ScoringBehavior(points: 20),
], ],
)..initialPosition = Vector2(-20.5, -13.8), )..initialPosition = Vector2(-20.5, -13.8),
RampShotBehavior(
points: 5000,
scorePosition: Vector2(0, -45),
),
RampBonusBehavior(
points: 1000000,
scorePosition: Vector2(0, -60),
),
], ],
); );
/// Creates a [AndroidAcres] without any children.
///
/// This can be used for testing [AndroidAcres]'s behaviors in isolation.
@visibleForTesting
AndroidAcres.test();
} }

@ -0,0 +1,2 @@
export 'ramp_bonus_behavior.dart';
export 'ramp_shot_behavior.dart';

@ -0,0 +1,84 @@
import 'dart:collection';
import 'dart:math';
import 'package:flame/components.dart';
import 'package:pinball/game/game.dart';
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.
/// {@endtemplate}
class RampBonusBehavior extends Component
with ParentIsA<AndroidAcres>, HasGameRef<PinballGame> {
/// {@macro ramp_bonus_behavior}
RampBonusBehavior({
required int points,
required Vector2 scorePosition,
}) : _points = points,
_scorePosition = scorePosition,
super();
final int _oneMillionPointsTimes = 10;
final int _points;
final Vector2 _scorePosition;
final Set<Ball> _balls = HashSet();
int _previousHits = 0;
@override
void onMount() {
super.onMount();
final sensors = parent.children.whereType<RampSensor>();
for (final sensor in sensors) {
sensor.bloc.stream.listen((state) {
switch (state.type) {
case RampSensorType.door:
_handleOnDoor(state.ball!);
break;
case RampSensorType.inside:
_handleOnInside(state.ball!);
break;
}
});
}
}
void _handleOnDoor(Ball ball) {
if (!_balls.contains(ball)) {
_balls.add(ball);
}
}
void _handleOnInside(Ball ball) {
if (_balls.contains(ball)) {
_balls.remove(ball);
_previousHits++;
_shot(_previousHits);
}
}
/// When a [Ball] shot the [SpaceshipRamp] it achieve improvements for the
/// current game, like multipliers or score.
void _shot(int currentHits) {
if (currentHits % _oneMillionPointsTimes == 0) {
gameRef.read<GameBloc>().add(Scored(points: _points));
gameRef.add(
ScoreText(
text: _points.toString(),
position: _getRandomPosition,
),
);
}
}
Vector2 get _getRandomPosition {
final randomX = Random().nextInt(2);
final randomY = Random().nextInt(2);
final sign = randomX + randomY % 2;
return _scorePosition +
Vector2(sign * randomX.toDouble(), sign * randomY.toDouble());
}
}

@ -0,0 +1,82 @@
import 'dart:collection';
import 'dart:math';
import 'package:flame/components.dart';
import 'package:pinball/game/game.dart';
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.
/// {@endtemplate}
class RampShotBehavior extends Component
with ParentIsA<AndroidAcres>, HasGameRef<PinballGame> {
/// {@macro ramp_shot_behavior}
RampShotBehavior({
required int points,
required Vector2 scorePosition,
}) : _points = points,
_scorePosition = scorePosition,
super();
final int _points;
final Vector2 _scorePosition;
final Set<Ball> _balls = HashSet();
@override
void onMount() {
super.onMount();
final sensors = parent.children.whereType<RampSensor>();
for (final sensor in sensors) {
sensor.bloc.stream.listen((state) {
switch (state.type) {
case RampSensorType.door:
_handleOnDoor(state.ball!);
break;
case RampSensorType.inside:
_handleOnInside(state.ball!);
break;
}
});
}
}
void _handleOnDoor(Ball ball) {
if (!_balls.contains(ball)) {
_balls.add(ball);
}
}
void _handleOnInside(Ball ball) {
if (_balls.contains(ball)) {
_balls.remove(ball);
_shot();
}
}
void _shot() {
parent.spaceshipRamp.progress();
gameRef.read<GameBloc>()
..add(const MultiplierIncreased())
..add(Scored(points: _points));
gameRef.add(
ScoreText(
text: _points.toString(),
position: _getRandomPosition,
),
);
}
Vector2 get _getRandomPosition {
final randomX = Random().nextInt(3);
final randomY = Random().nextInt(3);
final sign = randomX + randomY % 2;
return _scorePosition +
Vector2(sign * randomX.toDouble(), sign * randomY.toDouble());
}
}

@ -1,4 +1,4 @@
export 'android_acres.dart'; export 'android_acres/android_acres.dart';
export 'bottom_group.dart'; export 'bottom_group.dart';
export 'camera_controller.dart'; export 'camera_controller.dart';
export 'controlled_ball.dart'; export 'controlled_ball.dart';

@ -0,0 +1,208 @@
// ignore_for_file: cascade_invocations, prefer_const_constructors
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/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockingjay/mockingjay.dart';
import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.android.spaceship.saucer.keyName,
Assets.images.android.spaceship.animatronic.keyName,
Assets.images.android.spaceship.lightBeam.keyName,
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,
Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName,
Assets.images.android.bumper.a.lit.keyName,
Assets.images.android.bumper.a.dimmed.keyName,
Assets.images.android.bumper.b.lit.keyName,
Assets.images.android.bumper.b.dimmed.keyName,
Assets.images.android.bumper.cow.lit.keyName,
Assets.images.android.bumper.cow.dimmed.keyName,
];
group('RampBonusBehavior', () {
const bonusPoints = 1000000;
late GameBloc gameBloc;
setUp(() {
gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
flameBlocTester.testGameWidget(
"hit on door sensor doesn't add any score",
setUp: (game, tester) async {
final ball = Ball(baseColor: Colors.red);
final behavior = RampBonusBehavior(
points: bonusPoints,
scorePosition: Vector2.zero(),
);
final parent = AndroidAcres.test();
final sensors = [
RampSensor.test(
type: RampSensorType.door,
bloc: RampSensorCubit(),
),
];
await parent.addAll(sensors);
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
for (final sensor in sensors) {
sensor.bloc.onDoor(ball);
}
await tester.pump();
final scores = game.descendants().whereType<ScoreText>();
await game.ready();
verifyNever(() => gameBloc.add(Scored(points: bonusPoints)));
expect(scores.length, 0);
},
);
flameBlocTester.testGameWidget(
'hit on inside sensor without previous hit on door sensor '
"doesn't add any score",
setUp: (game, tester) async {
final ball = Ball(baseColor: Colors.red);
final behavior = RampBonusBehavior(
points: bonusPoints,
scorePosition: Vector2.zero(),
);
final parent = AndroidAcres.test();
final doorSensor = RampSensor.test(
type: RampSensorType.door,
bloc: RampSensorCubit(),
);
final insideSensor = RampSensor.test(
type: RampSensorType.inside,
bloc: RampSensorCubit(),
);
await parent.addAll([doorSensor, insideSensor]);
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
insideSensor.bloc.onInside(ball);
await tester.pump();
final scores = game.descendants().whereType<ScoreText>();
await game.ready();
verifyNever(() => gameBloc.add(Scored(points: bonusPoints)));
expect(scores.length, 0);
},
);
flameBlocTester.testGameWidget(
'hit on inside sensor after hit on door sensor '
"less than 10 times doesn't add any score neither shows score points",
setUp: (game, tester) async {
final ball = Ball(baseColor: Colors.red);
const bonusPoints = 1000000;
final behavior = RampBonusBehavior(
points: bonusPoints,
scorePosition: Vector2.zero(),
);
final parent = AndroidAcres.test();
final doorSensor = RampSensor.test(
type: RampSensorType.door,
bloc: RampSensorCubit(),
);
final insideSensor = RampSensor.test(
type: RampSensorType.inside,
bloc: RampSensorCubit(),
);
await parent.addAll([doorSensor, insideSensor]);
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
doorSensor.bloc.onDoor(ball);
insideSensor.bloc.onInside(ball);
await tester.pump();
final scores = game.descendants().whereType<ScoreText>();
await game.ready();
verifyNever(() => gameBloc.add(Scored(points: bonusPoints)));
expect(scores.length, 0);
},
);
flameBlocTester.testGameWidget(
'hit on inside sensor after hit on door sensor '
'10 times add score and show score point',
setUp: (game, tester) async {
final ball = Ball(baseColor: Colors.red);
const bonusPoints = 1000000;
final behavior = RampBonusBehavior(
points: bonusPoints,
scorePosition: Vector2.zero(),
);
final parent = AndroidAcres.test();
final doorSensor = RampSensor.test(
type: RampSensorType.door,
bloc: RampSensorCubit(),
);
final insideSensor = RampSensor.test(
type: RampSensorType.inside,
bloc: RampSensorCubit(),
);
await parent.addAll([doorSensor, insideSensor]);
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
for (var i = 0; i < 10; i++) {
doorSensor.bloc.onDoor(ball);
insideSensor.bloc.onInside(ball);
}
await tester.pump();
final scores = game.descendants().whereType<ScoreText>();
await game.ready();
verify(() => gameBloc.add(Scored(points: bonusPoints))).called(1);
expect(scores.length, 1);
},
);
});
}

@ -0,0 +1,204 @@
// ignore_for_file: cascade_invocations, prefer_const_constructors
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/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockingjay/mockingjay.dart';
import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.android.spaceship.saucer.keyName,
Assets.images.android.spaceship.animatronic.keyName,
Assets.images.android.spaceship.lightBeam.keyName,
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,
Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName,
Assets.images.android.bumper.a.lit.keyName,
Assets.images.android.bumper.a.dimmed.keyName,
Assets.images.android.bumper.b.lit.keyName,
Assets.images.android.bumper.b.dimmed.keyName,
Assets.images.android.bumper.cow.lit.keyName,
Assets.images.android.bumper.cow.dimmed.keyName,
];
group('RampShotBehavior', () {
late GameBloc gameBloc;
setUp(() {
gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
flameBlocTester.testGameWidget(
"hit on door sensor doesn't increase multiplier "
'neither add any score or show any score points',
setUp: (game, tester) async {
final ball = Ball(baseColor: Colors.red);
const shotPoints = 5000;
final behavior = RampShotBehavior(
points: shotPoints,
scorePosition: Vector2.zero(),
);
final parent = AndroidAcres.test();
final sensors = [
RampSensor.test(
type: RampSensorType.door,
bloc: RampSensorCubit(),
),
];
await parent.addAll(sensors);
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
for (final sensor in sensors) {
sensor.bloc.onDoor(ball);
}
await tester.pump();
final scores = game.descendants().whereType<ScoreText>();
await game.ready();
verifyNever(() => gameBloc.add(MultiplierIncreased()));
verifyNever(() => gameBloc.add(Scored(points: shotPoints)));
expect(scores.length, 0);
},
);
flameBlocTester.testGameWidget(
'hit on inside sensor without previous hit on door sensor '
"doesn't increase multiplier neither add any score or shows score points",
setUp: (game, tester) async {
final ball = Ball(baseColor: Colors.red);
const shotPoints = 5000;
final behavior = RampShotBehavior(
points: shotPoints,
scorePosition: Vector2.zero(),
);
final parent = AndroidAcres.test();
final doorSensor = RampSensor.test(
type: RampSensorType.door,
bloc: RampSensorCubit(),
);
final insideSensor = RampSensor.test(
type: RampSensorType.inside,
bloc: RampSensorCubit(),
);
await parent.addAll([doorSensor, insideSensor]);
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
insideSensor.bloc.onInside(ball);
await tester.pump();
final scores = game.descendants().whereType<ScoreText>();
await game.ready();
verifyNever(() => gameBloc.add(MultiplierIncreased()));
verifyNever(() => gameBloc.add(Scored(points: shotPoints)));
expect(scores.length, 0);
},
);
flameBlocTester.testGameWidget(
'hit on inside sensor after hit on door sensor '
'increase multiplier',
setUp: (game, tester) async {
final ball = Ball(baseColor: Colors.red);
const shotPoints = 5000;
final behavior = RampShotBehavior(
points: shotPoints,
scorePosition: Vector2.zero(),
);
final parent = AndroidAcres.test();
final doorSensor = RampSensor.test(
type: RampSensorType.door,
bloc: RampSensorCubit(),
);
final insideSensor = RampSensor.test(
type: RampSensorType.inside,
bloc: RampSensorCubit(),
);
await parent.addAll([doorSensor, insideSensor]);
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
doorSensor.bloc.onDoor(ball);
insideSensor.bloc.onInside(ball);
await tester.pump();
verify(() => gameBloc.add(MultiplierIncreased())).called(1);
},
);
flameBlocTester.testGameWidget(
'hit on inside sensor after hit on door sensor '
'add score and show score points',
setUp: (game, tester) async {
final ball = Ball(baseColor: Colors.red);
const shotPoints = 5000;
final behavior = RampShotBehavior(
points: shotPoints,
scorePosition: Vector2.zero(),
);
final parent = AndroidAcres.test();
final doorSensor = RampSensor.test(
type: RampSensorType.door,
bloc: RampSensorCubit(),
);
final insideSensor = RampSensor.test(
type: RampSensorType.inside,
bloc: RampSensorCubit(),
);
await parent.addAll([doorSensor, insideSensor]);
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
doorSensor.bloc.onDoor(ball);
insideSensor.bloc.onInside(ball);
await tester.pump();
final scores = game.descendants().whereType<ScoreText>();
await game.ready();
verify(() => gameBloc.add(Scored(points: shotPoints))).called(1);
expect(scores.length, 1);
},
);
});
}
Loading…
Cancel
Save