@ -0,0 +1,2 @@
|
|||||||
|
export 'bumper_noisy_behavior.dart';
|
||||||
|
export 'scoring_behavior.dart';
|
@ -0,0 +1,14 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:pinball/game/pinball_game.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
class BumperNoisyBehavior extends ContactBehavior with HasGameRef<PinballGame> {
|
||||||
|
@override
|
||||||
|
void beginContact(Object other, Contact contact) {
|
||||||
|
super.beginContact(other, contact);
|
||||||
|
gameRef.audio.bumper();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
// ignore_for_file: avoid_renaming_method_parameters
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/effects.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
/// {@template scoring_behavior}
|
||||||
|
/// Adds [_points] to the score and shows a text effect.
|
||||||
|
///
|
||||||
|
/// The behavior removes itself after the duration.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class ScoringBehavior extends Component with HasGameRef<PinballGame> {
|
||||||
|
/// {@macto scoring_behavior}
|
||||||
|
ScoringBehavior({
|
||||||
|
required Points points,
|
||||||
|
required Vector2 position,
|
||||||
|
double duration = 1,
|
||||||
|
}) : _points = points,
|
||||||
|
_position = position,
|
||||||
|
_effectController = EffectController(
|
||||||
|
duration: duration,
|
||||||
|
);
|
||||||
|
|
||||||
|
final Points _points;
|
||||||
|
final Vector2 _position;
|
||||||
|
|
||||||
|
final EffectController _effectController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
if (_effectController.completed) {
|
||||||
|
removeFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
gameRef.read<GameBloc>().add(Scored(points: _points.value));
|
||||||
|
await gameRef.firstChild<ZCanvasComponent>()!.add(
|
||||||
|
ScoreComponent(
|
||||||
|
points: _points,
|
||||||
|
position: _position,
|
||||||
|
effectController: _effectController,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@template scoring_contact_behavior}
|
||||||
|
/// Adds points to the score when the [Ball] contacts the [parent].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class ScoringContactBehavior extends ContactBehavior
|
||||||
|
with HasGameRef<PinballGame> {
|
||||||
|
/// {@macro scoring_contact_behavior}
|
||||||
|
ScoringContactBehavior({
|
||||||
|
required Points points,
|
||||||
|
}) : _points = points;
|
||||||
|
|
||||||
|
final Points _points;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void beginContact(Object other, Contact contact) {
|
||||||
|
super.beginContact(other, contact);
|
||||||
|
if (other is! Ball) return;
|
||||||
|
|
||||||
|
parent.add(
|
||||||
|
ScoringBehavior(
|
||||||
|
points: _points,
|
||||||
|
position: other.body.position,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,53 +0,0 @@
|
|||||||
// ignore_for_file: avoid_renaming_method_parameters
|
|
||||||
|
|
||||||
import 'package:flame/components.dart';
|
|
||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
|
||||||
import 'package:pinball/game/game.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
import 'package:pinball_flame/pinball_flame.dart';
|
|
||||||
|
|
||||||
/// {@template scoring_behavior}
|
|
||||||
/// Adds points to the score when the ball contacts the [parent].
|
|
||||||
/// {@endtemplate}
|
|
||||||
class ScoringBehavior extends ContactBehavior with HasGameRef<PinballGame> {
|
|
||||||
/// {@macro scoring_behavior}
|
|
||||||
ScoringBehavior({
|
|
||||||
required Points points,
|
|
||||||
}) : _points = points;
|
|
||||||
|
|
||||||
final Points _points;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void beginContact(Object other, Contact contact) {
|
|
||||||
super.beginContact(other, contact);
|
|
||||||
if (other is! Ball) return;
|
|
||||||
|
|
||||||
gameRef.read<GameBloc>().add(Scored(points: _points.value));
|
|
||||||
gameRef.firstChild<ZCanvasComponent>()!.add(
|
|
||||||
ScoreComponent(
|
|
||||||
points: _points,
|
|
||||||
position: other.body.position,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// {@template bumper_scoring_behavior}
|
|
||||||
/// A specific [ScoringBehavior] used for Bumpers.
|
|
||||||
/// In addition to its parent logic, also plays the
|
|
||||||
/// SFX for bumpers
|
|
||||||
/// {@endtemplate}
|
|
||||||
class BumperScoringBehavior extends ScoringBehavior {
|
|
||||||
/// {@macro bumper_scoring_behavior}
|
|
||||||
BumperScoringBehavior({
|
|
||||||
required Points points,
|
|
||||||
}) : super(points: points);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void beginContact(Object other, Contact contact) {
|
|
||||||
super.beginContact(other, contact);
|
|
||||||
if (other is! Ball) return;
|
|
||||||
|
|
||||||
gameRef.audio.bumper();
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
@ -0,0 +1,81 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
/// {@template ball_turbo_charging_behavior}
|
||||||
|
/// Puts the [Ball] in flames and [_impulse]s it.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class BallTurboChargingBehavior extends TimerComponent with ParentIsA<Ball> {
|
||||||
|
/// {@macro ball_turbo_charging_behavior}
|
||||||
|
BallTurboChargingBehavior({
|
||||||
|
required Vector2 impulse,
|
||||||
|
}) : _impulse = impulse,
|
||||||
|
super(period: 5, removeOnFinish: true);
|
||||||
|
|
||||||
|
final Vector2 _impulse;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
parent.body.linearVelocity = _impulse;
|
||||||
|
await parent.add(_TurboChargeSpriteAnimationComponent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onRemove() {
|
||||||
|
parent
|
||||||
|
.firstChild<_TurboChargeSpriteAnimationComponent>()!
|
||||||
|
.removeFromParent();
|
||||||
|
super.onRemove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TurboChargeSpriteAnimationComponent extends SpriteAnimationComponent
|
||||||
|
with HasGameRef, ZIndex, ParentIsA<Ball> {
|
||||||
|
_TurboChargeSpriteAnimationComponent()
|
||||||
|
: super(
|
||||||
|
anchor: const Anchor(0.53, 0.72),
|
||||||
|
) {
|
||||||
|
zIndex = ZIndexes.turboChargeFlame;
|
||||||
|
}
|
||||||
|
|
||||||
|
late final Vector2 _textureSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
|
||||||
|
final direction = -parent.body.linearVelocity.normalized();
|
||||||
|
angle = math.atan2(direction.x, -direction.y);
|
||||||
|
size = (_textureSize / 45) * parent.body.fixtures.first.shape.radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
final spriteSheet = await gameRef.images.load(
|
||||||
|
Assets.images.ball.flameEffect.keyName,
|
||||||
|
);
|
||||||
|
|
||||||
|
const amountPerRow = 8;
|
||||||
|
const amountPerColumn = 4;
|
||||||
|
_textureSize = Vector2(
|
||||||
|
spriteSheet.width / amountPerRow,
|
||||||
|
spriteSheet.height / amountPerColumn,
|
||||||
|
);
|
||||||
|
|
||||||
|
animation = SpriteAnimation.fromFrameData(
|
||||||
|
spriteSheet,
|
||||||
|
SpriteAnimationData.sequenced(
|
||||||
|
amount: amountPerRow * amountPerColumn,
|
||||||
|
amountPerRow: amountPerRow,
|
||||||
|
stepTime: 1 / 24,
|
||||||
|
textureSize: _textureSize,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
export 'ball_gravitating_behavior.dart';
|
export 'ball_gravitating_behavior.dart';
|
||||||
export 'ball_scaling_behavior.dart';
|
export 'ball_scaling_behavior.dart';
|
||||||
|
export 'ball_turbo_charging_behavior.dart';
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
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:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group(
|
||||||
|
'BallTurboChargingBehavior',
|
||||||
|
() {
|
||||||
|
final assets = [Assets.images.ball.ball.keyName];
|
||||||
|
final flameTester = FlameTester(() => TestGame(assets));
|
||||||
|
const baseColor = Color(0xFFFFFFFF);
|
||||||
|
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
BallTurboChargingBehavior(impulse: Vector2.zero()),
|
||||||
|
isA<BallTurboChargingBehavior>(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('can be loaded', (game) async {
|
||||||
|
final ball = Ball.test(baseColor: baseColor);
|
||||||
|
final behavior = BallTurboChargingBehavior(impulse: Vector2.zero());
|
||||||
|
await ball.add(behavior);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
expect(
|
||||||
|
ball.firstChild<BallTurboChargingBehavior>(),
|
||||||
|
equals(behavior),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'impulses the ball velocity when loaded',
|
||||||
|
(game) async {
|
||||||
|
final ball = Ball.test(baseColor: baseColor);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
final impulse = Vector2.all(1);
|
||||||
|
final behavior = BallTurboChargingBehavior(impulse: impulse);
|
||||||
|
await ball.ensureAdd(behavior);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
ball.body.linearVelocity.x,
|
||||||
|
equals(impulse.x),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
ball.body.linearVelocity.y,
|
||||||
|
equals(impulse.y),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test('adds sprite', (game) async {
|
||||||
|
final ball = Ball(baseColor: baseColor);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
|
||||||
|
await ball.ensureAdd(
|
||||||
|
BallTurboChargingBehavior(impulse: Vector2.zero()),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
ball.children.whereType<SpriteAnimationComponent>().single,
|
||||||
|
isNotNull,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('removes sprite after it finishes', (game) async {
|
||||||
|
final ball = Ball(baseColor: baseColor);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
|
||||||
|
final behavior = BallTurboChargingBehavior(impulse: Vector2.zero());
|
||||||
|
await ball.ensureAdd(behavior);
|
||||||
|
|
||||||
|
final turboChargeSpriteAnimation =
|
||||||
|
ball.children.whereType<SpriteAnimationComponent>().single;
|
||||||
|
|
||||||
|
expect(ball.contains(turboChargeSpriteAnimation), isTrue);
|
||||||
|
|
||||||
|
game.update(behavior.timer.limit);
|
||||||
|
game.update(0.1);
|
||||||
|
|
||||||
|
expect(ball.contains(turboChargeSpriteAnimation), isFalse);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 80 KiB |
@ -0,0 +1,50 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/game/behaviors/behaviors.dart';
|
||||||
|
import 'package:pinball_audio/pinball_audio.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
class _TestBodyComponent extends BodyComponent {
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
return world.createBody(BodyDef());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MockPinballAudio extends Mock implements PinballAudio {}
|
||||||
|
|
||||||
|
class _MockContact extends Mock implements Contact {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('BumperNoisyBehavior', () {});
|
||||||
|
|
||||||
|
late PinballAudio audio;
|
||||||
|
final flameTester = FlameTester(
|
||||||
|
() => EmptyPinballTestGame(audio: audio),
|
||||||
|
);
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
audio = _MockPinballAudio();
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'plays bumper sound',
|
||||||
|
setUp: (game, _) async {
|
||||||
|
final behavior = BumperNoisyBehavior();
|
||||||
|
final parent = _TestBodyComponent();
|
||||||
|
await game.ensureAdd(parent);
|
||||||
|
await parent.ensureAdd(behavior);
|
||||||
|
behavior.beginContact(Object(), _MockContact());
|
||||||
|
},
|
||||||
|
verify: (_, __) async {
|
||||||
|
verify(audio.bumper).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,210 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/game/behaviors/behaviors.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
class _TestBodyComponent extends BodyComponent {
|
||||||
|
@override
|
||||||
|
Body createBody() => world.createBody(BodyDef());
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MockBall extends Mock implements Ball {}
|
||||||
|
|
||||||
|
class _MockBody extends Mock implements Body {}
|
||||||
|
|
||||||
|
class _MockGameBloc extends Mock implements GameBloc {}
|
||||||
|
|
||||||
|
class _MockContact extends Mock implements Contact {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final assets = [
|
||||||
|
Assets.images.score.fiveThousand.keyName,
|
||||||
|
Assets.images.score.twentyThousand.keyName,
|
||||||
|
Assets.images.score.twoHundredThousand.keyName,
|
||||||
|
Assets.images.score.oneMillion.keyName,
|
||||||
|
];
|
||||||
|
|
||||||
|
late GameBloc bloc;
|
||||||
|
late Ball ball;
|
||||||
|
late BodyComponent parent;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
ball = _MockBall();
|
||||||
|
final ballBody = _MockBody();
|
||||||
|
when(() => ball.body).thenReturn(ballBody);
|
||||||
|
when(() => ballBody.position).thenReturn(Vector2.all(4));
|
||||||
|
|
||||||
|
parent = _TestBodyComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
|
||||||
|
gameBuilder: EmptyPinballTestGame.new,
|
||||||
|
blocBuilder: () {
|
||||||
|
bloc = _MockGameBloc();
|
||||||
|
const state = GameState(
|
||||||
|
score: 0,
|
||||||
|
multiplier: 1,
|
||||||
|
rounds: 3,
|
||||||
|
bonusHistory: [],
|
||||||
|
);
|
||||||
|
whenListen(bloc, Stream.value(state), initialState: state);
|
||||||
|
return bloc;
|
||||||
|
},
|
||||||
|
assets: assets,
|
||||||
|
);
|
||||||
|
|
||||||
|
group('ScoringBehavior', () {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
ScoringBehavior(
|
||||||
|
points: Points.fiveThousand,
|
||||||
|
position: Vector2.zero(),
|
||||||
|
),
|
||||||
|
isA<ScoringBehavior>(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'can be loaded',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final canvas = ZCanvasComponent(children: [parent]);
|
||||||
|
final behavior = ScoringBehavior(
|
||||||
|
points: Points.fiveThousand,
|
||||||
|
position: Vector2.zero(),
|
||||||
|
);
|
||||||
|
await parent.add(behavior);
|
||||||
|
await game.ensureAdd(canvas);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
parent.firstChild<ScoringBehavior>(),
|
||||||
|
equals(behavior),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'emits Scored event with points when added',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
const points = Points.oneMillion;
|
||||||
|
final canvas = ZCanvasComponent(children: [parent]);
|
||||||
|
await game.ensureAdd(canvas);
|
||||||
|
|
||||||
|
final behavior = ScoringBehavior(
|
||||||
|
points: points,
|
||||||
|
position: Vector2(0, 0),
|
||||||
|
);
|
||||||
|
await parent.ensureAdd(behavior);
|
||||||
|
|
||||||
|
verify(
|
||||||
|
() => bloc.add(
|
||||||
|
Scored(points: points.value),
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'correctly renders text',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final canvas = ZCanvasComponent(children: [parent]);
|
||||||
|
await game.ensureAdd(canvas);
|
||||||
|
|
||||||
|
const points = Points.oneMillion;
|
||||||
|
final position = Vector2.all(1);
|
||||||
|
final behavior = ScoringBehavior(
|
||||||
|
points: points,
|
||||||
|
position: position,
|
||||||
|
);
|
||||||
|
await parent.ensureAdd(behavior);
|
||||||
|
|
||||||
|
final scoreText = game.descendants().whereType<ScoreComponent>();
|
||||||
|
expect(scoreText.length, equals(1));
|
||||||
|
expect(
|
||||||
|
scoreText.first.points,
|
||||||
|
equals(points),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
scoreText.first.position,
|
||||||
|
equals(position),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'is removed after duration',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final canvas = ZCanvasComponent(children: [parent]);
|
||||||
|
await game.ensureAdd(canvas);
|
||||||
|
|
||||||
|
const duration = 2.0;
|
||||||
|
final behavior = ScoringBehavior(
|
||||||
|
points: Points.oneMillion,
|
||||||
|
position: Vector2(0, 0),
|
||||||
|
duration: duration,
|
||||||
|
);
|
||||||
|
await parent.ensureAdd(behavior);
|
||||||
|
|
||||||
|
game.update(duration);
|
||||||
|
game.update(0);
|
||||||
|
await tester.pump();
|
||||||
|
},
|
||||||
|
verify: (game, _) async {
|
||||||
|
expect(
|
||||||
|
game.descendants().whereType<ScoringBehavior>(),
|
||||||
|
isEmpty,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('ScoringContactBehavior', () {
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'beginContact adds a ScoringBehavior',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final canvas = ZCanvasComponent(children: [parent]);
|
||||||
|
await game.ensureAdd(canvas);
|
||||||
|
|
||||||
|
final behavior = ScoringContactBehavior(points: Points.oneMillion);
|
||||||
|
await parent.ensureAdd(behavior);
|
||||||
|
|
||||||
|
behavior.beginContact(ball, _MockContact());
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
parent.firstChild<ScoringBehavior>(),
|
||||||
|
isNotNull,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
"beginContact positions text at contact's position",
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final canvas = ZCanvasComponent(children: [parent]);
|
||||||
|
await game.ensureAdd(canvas);
|
||||||
|
|
||||||
|
final behavior = ScoringContactBehavior(points: Points.oneMillion);
|
||||||
|
await parent.ensureAdd(behavior);
|
||||||
|
|
||||||
|
behavior.beginContact(ball, _MockContact());
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
final scoreText = game.descendants().whereType<ScoreComponent>();
|
||||||
|
expect(
|
||||||
|
scoreText.first.position,
|
||||||
|
equals(ball.body.position),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -1,172 +0,0 @@
|
|||||||
// ignore_for_file: cascade_invocations
|
|
||||||
|
|
||||||
import 'package:bloc_test/bloc_test.dart';
|
|
||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
|
||||||
import 'package:flame_test/flame_test.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:mocktail/mocktail.dart';
|
|
||||||
import 'package:pinball/game/game.dart';
|
|
||||||
import 'package:pinball_audio/pinball_audio.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
import 'package:pinball_flame/pinball_flame.dart';
|
|
||||||
|
|
||||||
import '../../helpers/helpers.dart';
|
|
||||||
|
|
||||||
class _TestBodyComponent extends BodyComponent {
|
|
||||||
@override
|
|
||||||
Body createBody() => world.createBody(BodyDef());
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MockPinballAudio extends Mock implements PinballAudio {}
|
|
||||||
|
|
||||||
class _MockBall extends Mock implements Ball {}
|
|
||||||
|
|
||||||
class _MockBody extends Mock implements Body {}
|
|
||||||
|
|
||||||
class _MockGameBloc extends Mock implements GameBloc {}
|
|
||||||
|
|
||||||
class _MockContact extends Mock implements Contact {}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
final assets = [
|
|
||||||
Assets.images.score.fiveThousand.keyName,
|
|
||||||
Assets.images.score.twentyThousand.keyName,
|
|
||||||
Assets.images.score.twoHundredThousand.keyName,
|
|
||||||
Assets.images.score.oneMillion.keyName,
|
|
||||||
];
|
|
||||||
|
|
||||||
group('ScoringBehavior', () {
|
|
||||||
group('beginContact', () {
|
|
||||||
late GameBloc bloc;
|
|
||||||
late PinballAudio audio;
|
|
||||||
late Ball ball;
|
|
||||||
late BodyComponent parent;
|
|
||||||
|
|
||||||
setUp(() {
|
|
||||||
audio = _MockPinballAudio();
|
|
||||||
ball = _MockBall();
|
|
||||||
final ballBody = _MockBody();
|
|
||||||
when(() => ball.body).thenReturn(ballBody);
|
|
||||||
when(() => ballBody.position).thenReturn(Vector2.all(4));
|
|
||||||
|
|
||||||
parent = _TestBodyComponent();
|
|
||||||
});
|
|
||||||
|
|
||||||
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
|
|
||||||
gameBuilder: () => EmptyPinballTestGame(
|
|
||||||
audio: audio,
|
|
||||||
),
|
|
||||||
blocBuilder: () {
|
|
||||||
bloc = _MockGameBloc();
|
|
||||||
const state = GameState(
|
|
||||||
score: 0,
|
|
||||||
multiplier: 1,
|
|
||||||
rounds: 3,
|
|
||||||
bonusHistory: [],
|
|
||||||
);
|
|
||||||
whenListen(bloc, Stream.value(state), initialState: state);
|
|
||||||
return bloc;
|
|
||||||
},
|
|
||||||
assets: assets,
|
|
||||||
);
|
|
||||||
|
|
||||||
flameBlocTester.testGameWidget(
|
|
||||||
'emits Scored event with points',
|
|
||||||
setUp: (game, tester) async {
|
|
||||||
const points = Points.oneMillion;
|
|
||||||
final scoringBehavior = ScoringBehavior(points: points);
|
|
||||||
await parent.add(scoringBehavior);
|
|
||||||
final canvas = ZCanvasComponent(children: [parent]);
|
|
||||||
await game.ensureAdd(canvas);
|
|
||||||
|
|
||||||
scoringBehavior.beginContact(ball, _MockContact());
|
|
||||||
|
|
||||||
verify(
|
|
||||||
() => bloc.add(
|
|
||||||
Scored(points: points.value),
|
|
||||||
),
|
|
||||||
).called(1);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
flameBlocTester.testGameWidget(
|
|
||||||
"adds a ScoreComponent at Ball's position with points",
|
|
||||||
setUp: (game, tester) async {
|
|
||||||
const points = Points.oneMillion;
|
|
||||||
final scoringBehavior = ScoringBehavior(points: points);
|
|
||||||
await parent.add(scoringBehavior);
|
|
||||||
final canvas = ZCanvasComponent(children: [parent]);
|
|
||||||
await game.ensureAdd(canvas);
|
|
||||||
|
|
||||||
scoringBehavior.beginContact(ball, _MockContact());
|
|
||||||
await game.ready();
|
|
||||||
|
|
||||||
final scoreText = game.descendants().whereType<ScoreComponent>();
|
|
||||||
expect(scoreText.length, equals(1));
|
|
||||||
expect(
|
|
||||||
scoreText.first.points,
|
|
||||||
equals(points),
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
scoreText.first.position,
|
|
||||||
equals(ball.body.position),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group('BumperScoringBehavior', () {
|
|
||||||
group('beginContact', () {
|
|
||||||
late GameBloc bloc;
|
|
||||||
late PinballAudio audio;
|
|
||||||
late Ball ball;
|
|
||||||
late BodyComponent parent;
|
|
||||||
|
|
||||||
setUp(() {
|
|
||||||
audio = _MockPinballAudio();
|
|
||||||
ball = _MockBall();
|
|
||||||
final ballBody = _MockBody();
|
|
||||||
when(() => ball.body).thenReturn(ballBody);
|
|
||||||
when(() => ballBody.position).thenReturn(Vector2.all(4));
|
|
||||||
|
|
||||||
parent = _TestBodyComponent();
|
|
||||||
});
|
|
||||||
|
|
||||||
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
|
|
||||||
gameBuilder: () => EmptyPinballTestGame(
|
|
||||||
audio: audio,
|
|
||||||
),
|
|
||||||
blocBuilder: () {
|
|
||||||
bloc = _MockGameBloc();
|
|
||||||
const state = GameState(
|
|
||||||
score: 0,
|
|
||||||
multiplier: 1,
|
|
||||||
rounds: 3,
|
|
||||||
bonusHistory: [],
|
|
||||||
);
|
|
||||||
whenListen(bloc, Stream.value(state), initialState: state);
|
|
||||||
return bloc;
|
|
||||||
},
|
|
||||||
assets: assets,
|
|
||||||
);
|
|
||||||
|
|
||||||
flameBlocTester.testGameWidget(
|
|
||||||
'plays bumper sound',
|
|
||||||
setUp: (game, tester) async {
|
|
||||||
final scoringBehavior = BumperScoringBehavior(
|
|
||||||
points: Points.oneMillion,
|
|
||||||
);
|
|
||||||
await parent.add(scoringBehavior);
|
|
||||||
final canvas = ZCanvasComponent(children: [parent]);
|
|
||||||
await game.ensureAdd(canvas);
|
|
||||||
|
|
||||||
scoringBehavior.beginContact(ball, _MockContact());
|
|
||||||
|
|
||||||
verify(audio.bumper).called(1);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|