Merge branch 'main' into fix/flipper-movement

pull/175/head
Alejandro Santiago 4 years ago committed by GitHub
commit f322ff1f65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,6 +7,7 @@ export 'controlled_sparky_computer.dart';
export 'flutter_forest.dart';
export 'game_flow_controller.dart';
export 'plunger.dart';
export 'score_effect_controller.dart';
export 'score_points.dart';
export 'sparky_fire_zone.dart';
export 'wall.dart';

@ -0,0 +1,45 @@
import 'dart:math';
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/flame/flame.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template score_effect_controller}
/// A [ComponentController] responsible for adding [ScoreText]s
/// on the game screen when the user earns points.
/// {@endtemplate}
class ScoreEffectController extends ComponentController<PinballGame>
with BlocComponent<GameBloc, GameState> {
/// {@macro score_effect_controller}
ScoreEffectController(PinballGame component) : super(component);
int _lastScore = 0;
final _random = Random();
double _noise() {
return _random.nextDouble() * 5 * (_random.nextBool() ? -1 : 1);
}
@override
bool listenWhen(GameState? previousState, GameState newState) {
return previousState?.score != newState.score;
}
@override
void onNewState(GameState state) {
final newScore = state.score - _lastScore;
_lastScore = state.score;
component.add(
ScoreText(
text: newScore.toString(),
position: Vector2(
_noise(),
_noise() + (-BoardDimensions.bounds.topCenter.dy + 10),
),
),
);
}
}

@ -1,21 +1,67 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball/flame/flame.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
// TODO(ruimiguel): create and add SparkyFireZone component here in other PR.
/// {@template sparky_fire_zone}
/// Area positioned at the top left of the [Board] where the [Ball]
/// can bounce off [SparkyBumper]s.
///
/// When a [Ball] hits [SparkyBumper]s, they toggle between activated and
/// deactivated states.
/// {@endtemplate}
class SparkyFireZone extends Component with HasGameRef<PinballGame> {
/// {@macro sparky_fire_zone}
SparkyFireZone();
@override
Future<void> onLoad() async {
await super.onLoad();
gameRef.addContactCallback(_ControlledSparkyBumperBallContactCallback());
// TODO(ruimiguel): make private and remove ignore once SparkyFireZone is done
// ignore: public_member_api_docs
final lowerLeftBumper = ControlledSparkyBumper.a()
..initialPosition = Vector2(-23.15, 41.65);
final upperLeftBumper = ControlledSparkyBumper.b()
..initialPosition = Vector2(-21.25, 58.15);
final rightBumper = ControlledSparkyBumper.c()
..initialPosition = Vector2(-3.56, 53.051);
await addAll([
lowerLeftBumper,
upperLeftBumper,
rightBumper,
]);
}
}
/// {@template controlled_sparky_bumper}
/// [SparkyBumper] with [_SparkyBumperController] attached.
/// {@endtemplate}
@visibleForTesting
class ControlledSparkyBumper extends SparkyBumper
with Controls<_SparkyBumperController> {
// TODO(ruimiguel): make private and remove ignore once SparkyFireZone is done
// ignore: public_member_api_docs
ControlledSparkyBumper() : super.a() {
with Controls<_SparkyBumperController>, ScorePoints {
///{@macro controlled_sparky_bumper}
ControlledSparkyBumper.a() : super.a() {
controller = _SparkyBumperController(this);
}
///{@macro controlled_sparky_bumper}
ControlledSparkyBumper.b() : super.b() {
controller = _SparkyBumperController(this);
}
///{@macro controlled_sparky_bumper}
ControlledSparkyBumper.c() : super.c() {
controller = _SparkyBumperController(this);
}
@override
int get points => 20;
}
/// {@template sparky_bumper_controller}
@ -42,3 +88,16 @@ class _SparkyBumperController extends ComponentController<SparkyBumper>
isActivated = !isActivated;
}
}
/// Listens when a [Ball] bounces bounces against a [SparkyBumper].
class _ControlledSparkyBumperBallContactCallback
extends ContactCallback<Controls<_SparkyBumperController>, Ball> {
@override
void begin(
Controls<_SparkyBumperController> controlledSparkyBumper,
Ball _,
Contact __,
) {
controlledSparkyBumper.controller.hit();
}
}

@ -38,6 +38,7 @@ class PinballGame extends Forge2DGame
Future<void> onLoad() async {
_addContactCallbacks();
unawaited(add(ScoreEffectController(this)));
unawaited(add(gameFlowController = GameFlowController(this)));
unawaited(add(CameraController(this)));
unawaited(add(Backboard(position: Vector2(0, -88))));
@ -52,6 +53,7 @@ class PinballGame extends Forge2DGame
await add(plunger);
unawaited(add(Board()));
unawaited(add(SparkyFireZone()));
unawaited(addFromBlueprint(Slingshots()));
unawaited(addFromBlueprint(DinoWalls()));
unawaited(_addBonusWord());

@ -3,8 +3,6 @@
/// FlutterGen
/// *****************************************************
// ignore_for_file: directives_ordering,unnecessary_import
import 'package:flutter/widgets.dart';
class $AssetsImagesGen {
@ -17,11 +15,8 @@ class $AssetsImagesGen {
class $AssetsImagesComponentsGen {
const $AssetsImagesComponentsGen();
/// File path: assets/images/components/background.png
AssetGenImage get background =>
const AssetGenImage('assets/images/components/background.png');
/// File path: assets/images/components/plunger.png
AssetGenImage get plunger =>
const AssetGenImage('assets/images/components/plunger.png');
}

@ -0,0 +1,11 @@
/// GENERATED CODE - DO NOT MODIFY BY HAND
/// *****************************************************
/// FlutterGen
/// *****************************************************
class FontFamily {
FontFamily._();
static const String pixeloidMono = 'PixeloidMono';
static const String pixeloidSans = 'PixeloidSans';
}

@ -0,0 +1,16 @@
import 'package:pinball_components/gen/fonts.gen.dart';
String _prefixFont(String font) {
return 'packages/pinball_components/$font';
}
/// Class with the fonts available on the pinball game
class PinballFonts {
PinballFonts._();
/// Mono variation of the Pixeloid font
static final String pixeloidMono = _prefixFont(FontFamily.pixeloidMono);
/// Sans variation of the Pixeloid font
static final String pixeloidSans = _prefixFont(FontFamily.pixeloidMono);
}

@ -1,4 +1,5 @@
library pinball_components;
export 'gen/assets.gen.dart';
export 'gen/pinball_fonts.dart';
export 'src/pinball_components.dart';

@ -17,6 +17,7 @@ export 'kicker.dart';
export 'launch_ramp.dart';
export 'layer.dart';
export 'ramp_opening.dart';
export 'score_text.dart';
export 'shapes/shapes.dart';
export 'slingshot.dart';
export 'spaceship.dart';

@ -11,6 +11,9 @@ import 'package:pinball_components/pinball_components.dart';
/// [_LaunchRampForegroundRailing].
/// {@endtemplate}
class LaunchRamp extends Forge2DBlueprint {
/// Base priority for [Ball] while inside [LaunchRamp].
static const ballPriorityInsideRamp = 0;
@override
void build(_) {
addAllContactCallback([
@ -40,7 +43,10 @@ class LaunchRamp extends Forge2DBlueprint {
/// {@endtemplate}
class _LaunchRampBase extends BodyComponent with InitialPosition, Layered {
/// {@macro launch_ramp_base}
_LaunchRampBase() : super(priority: -1) {
_LaunchRampBase()
: super(
priority: LaunchRamp.ballPriorityInsideRamp - 1,
) {
layer = Layer.launcher;
}
@ -143,7 +149,10 @@ class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef {
class _LaunchRampForegroundRailing extends BodyComponent
with InitialPosition, Layered {
/// {@macro launch_ramp_foreground_railing}
_LaunchRampForegroundRailing() : super(priority: 1) {
_LaunchRampForegroundRailing()
: super(
priority: LaunchRamp.ballPriorityInsideRamp + 1,
) {
layer = Layer.launcher;
}
@ -227,7 +236,7 @@ class _LaunchRampExit extends RampOpening {
super(
insideLayer: Layer.launcher,
orientation: RampOrientation.down,
insidePriority: 3,
insidePriority: LaunchRamp.ballPriorityInsideRamp,
);
final double _rotation;

@ -0,0 +1,55 @@
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template score_text}
/// A [TextComponent] that spawns at a given [position] with a moving animation.
/// {@endtemplate}
class ScoreText extends TextComponent {
/// {@macro score_text}
ScoreText({
required String text,
required Vector2 position,
this.color = Colors.black,
}) : super(
text: text,
position: position,
anchor: Anchor.center,
priority: 100,
);
late final Effect _effect;
/// The [text]'s [Color].
final Color color;
@override
Future<void> onLoad() async {
textRenderer = TextPaint(
style: TextStyle(
fontFamily: PinballFonts.pixeloidMono,
color: color,
fontSize: 4,
),
);
await add(
_effect = MoveEffect.by(
Vector2(0, -5),
EffectController(duration: 1),
),
);
}
@override
void update(double dt) {
super.update(dt);
if (_effect.controller.completed) {
removeFromParent();
}
}
}

@ -37,7 +37,7 @@ class Spaceship extends Forge2DBlueprint {
AndroidHead()..initialPosition = position,
SpaceshipHole(
outsideLayer: Layer.spaceshipExitRail,
outsidePriority: SpaceshipRail.ballPriorityWhenOnSpaceshipRail,
outsidePriority: SpaceshipRail.ballPriorityInsideRail,
)..initialPosition = position - Vector2(5.2, 4.8),
SpaceshipHole()..initialPosition = position - Vector2(-7.2, 0.8),
SpaceshipWall()..initialPosition = position,

@ -14,8 +14,8 @@ class SpaceshipRail extends Forge2DBlueprint {
/// {@macro spaceship_rail}
SpaceshipRail();
/// Base priority for ball while be in [_SpaceshipRailRamp].
static const ballPriorityWhenOnSpaceshipRail = 2;
/// Base priority for [Ball] while inside [SpaceshipRail].
static const ballPriorityInsideRail = 2;
@override
void build(_) {
@ -45,9 +45,8 @@ class SpaceshipRail extends Forge2DBlueprint {
class _SpaceshipRailRamp extends BodyComponent with InitialPosition, Layered {
_SpaceshipRailRamp()
: super(
priority: SpaceshipRail.ballPriorityWhenOnSpaceshipRail - 1,
priority: SpaceshipRail.ballPriorityInsideRail - 1,
) {
renderBody = false;
layer = Layer.spaceshipExitRail;
}
@ -139,6 +138,8 @@ class _SpaceshipRailRamp extends BodyComponent with InitialPosition, Layered {
@override
Future<void> onLoad() async {
await super.onLoad();
renderBody = false;
await add(_SpaceshipRailRampSpriteComponent());
}
}
@ -161,11 +162,7 @@ class _SpaceshipRailRampSpriteComponent extends SpriteComponent
class _SpaceshipRailForeground extends SpriteComponent with HasGameRef {
_SpaceshipRailForeground()
: super(
anchor: Anchor.center,
position: Vector2(-28.5, 19.7),
priority: SpaceshipRail.ballPriorityWhenOnSpaceshipRail + 1,
);
: super(priority: SpaceshipRail.ballPriorityInsideRail + 1);
@override
Future<void> onLoad() async {
@ -176,6 +173,8 @@ class _SpaceshipRailForeground extends SpriteComponent with HasGameRef {
);
this.sprite = sprite;
size = sprite.originalSize / 10;
anchor = Anchor.center;
position = Vector2(-28.5, 19.7);
}
}
@ -183,7 +182,7 @@ class _SpaceshipRailForeground extends SpriteComponent with HasGameRef {
class _SpaceshipRailBase extends BodyComponent with InitialPosition, Layered {
_SpaceshipRailBase({required this.radius})
: super(
priority: SpaceshipRail.ballPriorityWhenOnSpaceshipRail + 1,
priority: SpaceshipRail.ballPriorityInsideRail + 1,
) {
renderBody = false;
layer = Layer.board;
@ -213,9 +212,9 @@ class SpaceshipRailExit extends RampOpening {
/// {@macro spaceship_rail_exit}
SpaceshipRailExit()
: super(
insideLayer: Layer.spaceshipExitRail,
orientation: RampOrientation.down,
insidePriority: 3,
insideLayer: Layer.spaceshipExitRail,
insidePriority: SpaceshipRail.ballPriorityInsideRail,
) {
renderBody = false;
layer = Layer.spaceshipExitRail;
@ -224,10 +223,10 @@ class SpaceshipRailExit extends RampOpening {
@override
Shape get shape {
return ArcShape(
center: Vector2(-28, -19),
center: Vector2(-29, -19),
arcRadius: 2.5,
angle: math.pi * 0.4,
rotation: -0.16,
rotation: 0.26,
);
}
}

@ -184,8 +184,6 @@ class _SpaceshipRampForegroundRailing extends BodyComponent
@override
Body createBody() {
renderBody = false;
final bodyDef = BodyDef()
..userData = this
..position = initialPosition;
@ -199,11 +197,13 @@ class _SpaceshipRampForegroundRailing extends BodyComponent
@override
Future<void> onLoad() async {
await super.onLoad();
await add(_SpaceshipRampForegroundRalingSpriteComponent());
renderBody = false;
await add(_SpaceshipRampForegroundRailingSpriteComponent());
}
}
class _SpaceshipRampForegroundRalingSpriteComponent extends SpriteComponent
class _SpaceshipRampForegroundRailingSpriteComponent extends SpriteComponent
with HasGameRef {
@override
Future<void> onLoad() async {
@ -221,13 +221,12 @@ class _SpaceshipRampForegroundRalingSpriteComponent extends SpriteComponent
/// Represents the ground right base of the [SpaceshipRamp].
class _SpaceshipRampBase extends BodyComponent with InitialPosition, Layered {
_SpaceshipRampBase() {
renderBody = false;
layer = Layer.board;
}
@override
Body createBody() {
renderBody = false;
const baseWidth = 6;
final baseShape = BezierCurveShape(
controlPoints: [
@ -266,7 +265,9 @@ class _SpaceshipRampOpening extends RampOpening {
orientation: RampOrientation.down,
insidePriority: SpaceshipRamp.ballPriorityInsideRamp,
outsidePriority: outsidePriority,
);
) {
renderBody = false;
}
final double _rotation;
@ -274,7 +275,6 @@ class _SpaceshipRampOpening extends RampOpening {
@override
Shape get shape {
renderBody = false;
return PolygonShape()
..setAsBox(
_size.x,

@ -24,6 +24,16 @@ dev_dependencies:
flutter:
generate: true
fonts:
- family: PixeloidSans
fonts:
- asset: fonts/PixeloidSans-nR3g1.ttf
- asset: fonts/PixeloidSansBold-RpeJo.ttf
weight: 700
- family: PixeloidMono
fonts:
- asset: fonts/PixeloidMono-1G8ae.ttf
assets:
- assets/images/
- assets/images/baseboard/

@ -25,5 +25,10 @@ void main() {
addSparkyBumperStories(dashbook);
addZoomStories(dashbook);
addBoundariesStories(dashbook);
addSpaceshipRampStories(dashbook);
addSpaceshipRailStories(dashbook);
addLaunchRampStories(dashbook);
addScoreTextStories(dashbook);
runApp(dashbook);
}

@ -4,7 +4,11 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class BasicBallGame extends BasicGame with TapDetector, Traceable {
BasicBallGame({required this.color});
BasicBallGame({
required this.color,
this.ballPriority = 0,
this.ballLayer = Layer.all,
});
static const info = '''
Shows how a Ball works.
@ -13,11 +17,16 @@ class BasicBallGame extends BasicGame with TapDetector, Traceable {
''';
final Color color;
final int ballPriority;
final Layer ballLayer;
@override
void onTapUp(TapUpInfo info) {
add(
Ball(baseColor: color)..initialPosition = info.eventPosition.game,
Ball(baseColor: color)
..initialPosition = info.eventPosition.game
..layer = ballLayer
..priority = ballPriority,
);
traceAllBodies();
}

@ -0,0 +1,36 @@
import 'dart:async';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class LaunchRampGame extends BasicBallGame {
LaunchRampGame()
: super(
color: Colors.blue,
ballPriority: LaunchRamp.ballPriorityInsideRamp,
ballLayer: Layer.launcher,
);
static const info = '''
Shows how LaunchRamp are rendered.
- Activate the "trace" parameter to overlay the body.
- Tap anywhere on the screen to spawn a ball into the game.
''';
@override
Future<void> onLoad() async {
await super.onLoad();
camera
..followVector2(Vector2(0, 0))
..zoom = 7.5;
final launchRamp = LaunchRamp();
unawaited(addFromBlueprint(launchRamp));
await traceAllBodies();
}
}

@ -0,0 +1,15 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/launch_ramp/launch_ramp_game.dart';
void addLaunchRampStories(Dashbook dashbook) {
dashbook.storiesOf('LaunchRamp').add(
'Basic',
(context) => GameWidget(
game: LaunchRampGame()..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('launch_ramp/basic.dart'),
info: LaunchRampGame.info,
);
}

@ -0,0 +1,32 @@
import 'dart:math';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class ScoreTextBasicGame extends BasicGame with TapDetector {
static const info = '''
Simple game to show how score text works,
- Tap anywhere on the screen to spawn an text on the given location.
''';
final random = Random();
@override
Future<void> onLoad() async {
camera.followVector2(Vector2.zero());
}
@override
void onTapUp(TapUpInfo info) {
add(
ScoreText(
text: random.nextInt(100000).toString(),
color: Colors.white,
position: info.eventPosition.game..multiply(Vector2(1, -1)),
),
);
}
}

@ -0,0 +1,15 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/score_text/basic.dart';
void addScoreTextStories(Dashbook dashbook) {
dashbook.storiesOf('ScoreText').add(
'Basic',
(context) => GameWidget(
game: ScoreTextBasicGame(),
),
codeLink: buildSourceLink('score_text/basic.dart'),
info: ScoreTextBasicGame.info,
);
}

@ -0,0 +1,34 @@
import 'dart:async';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class SpaceshipRailGame extends BasicBallGame {
SpaceshipRailGame()
: super(
color: Colors.blue,
ballPriority: SpaceshipRail.ballPriorityInsideRail,
ballLayer: Layer.spaceshipExitRail,
);
static const info = '''
Shows how SpaceshipRail are rendered.
- Activate the "trace" parameter to overlay the body.
- Tap anywhere on the screen to spawn a ball into the game.
''';
@override
Future<void> onLoad() async {
await super.onLoad();
camera.followVector2(Vector2(-30, -10));
final spaceshipRail = SpaceshipRail();
unawaited(addFromBlueprint(spaceshipRail));
await traceAllBodies();
}
}

@ -0,0 +1,16 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/spaceship_rail/spaceship_rail_game.dart';
void addSpaceshipRailStories(Dashbook dashbook) {
dashbook.storiesOf('SpaceshipRail').add(
'Basic',
(context) => GameWidget(
game: SpaceshipRailGame()
..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('spaceship_rail/basic.dart'),
info: SpaceshipRailGame.info,
);
}

@ -0,0 +1,34 @@
import 'dart:async';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class SpaceshipRampGame extends BasicBallGame {
SpaceshipRampGame()
: super(
color: Colors.blue,
ballPriority: SpaceshipRamp.ballPriorityInsideRamp,
ballLayer: Layer.spaceshipEntranceRamp,
);
static const info = '''
Shows how SpaceshipRamp are rendered.
- Activate the "trace" parameter to overlay the body.
- Tap anywhere on the screen to spawn a ball into the game.
''';
@override
Future<void> onLoad() async {
await super.onLoad();
camera.followVector2(Vector2(-10, -20));
final spaceshipRamp = SpaceshipRamp();
unawaited(addFromBlueprint(spaceshipRamp));
await traceAllBodies();
}
}

@ -0,0 +1,16 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/spaceship_ramp/spaceship_ramp_game.dart';
void addSpaceshipRampStories(Dashbook dashbook) {
dashbook.storiesOf('SpaceshipRamp').add(
'Basic',
(context) => GameWidget(
game: SpaceshipRampGame()
..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('spaceship_ramp/basic.dart'),
info: SpaceshipRampGame.info,
);
}

@ -5,8 +5,12 @@ export 'chrome_dino/stories.dart';
export 'effects/stories.dart';
export 'flipper/stories.dart';
export 'flutter_forest/stories.dart';
export 'launch_ramp/stories.dart';
export 'layer/stories.dart';
export 'score_text/stories.dart';
export 'slingshot/stories.dart';
export 'spaceship/stories.dart';
export 'spaceship_rail/stories.dart';
export 'spaceship_ramp/stories.dart';
export 'sparky_bumper/stories.dart';
export 'zoom/stories.dart';

@ -0,0 +1,75 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame/effects.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() {
group('ScoreText', () {
final flameTester = FlameTester(TestGame.new);
flameTester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
game.camera.followVector2(Vector2.zero());
await game.ensureAdd(
ScoreText(
text: '123',
position: Vector2.zero(),
color: Colors.white,
),
);
},
verify: (game, tester) async {
final texts = game.descendants().whereType<TextComponent>().length;
expect(texts, equals(1));
},
);
flameTester.testGameWidget(
'has a movement effect',
setUp: (game, tester) async {
game.camera.followVector2(Vector2.zero());
await game.ensureAdd(
ScoreText(
text: '123',
position: Vector2.zero(),
color: Colors.white,
),
);
game.update(0.5);
await tester.pump();
},
verify: (game, tester) async {
final text = game.descendants().whereType<TextComponent>().first;
expect(text.firstChild<MoveEffect>(), isNotNull);
},
);
flameTester.testGameWidget(
'is removed once finished',
setUp: (game, tester) async {
game.camera.followVector2(Vector2.zero());
await game.ensureAdd(
ScoreText(
text: '123',
position: Vector2.zero(),
color: Colors.white,
),
);
game.update(1);
game.update(0); // Ensure all component removals
},
verify: (game, tester) async {
expect(game.children.length, equals(0));
},
);
});
}

@ -0,0 +1,115 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
group('ScoreEffectController', () {
late ScoreEffectController controller;
late PinballGame game;
setUpAll(() {
registerFallbackValue(Component());
});
setUp(() {
game = MockPinballGame();
when(() => game.add(any())).thenAnswer((_) async {});
controller = ScoreEffectController(game);
});
group('listenWhen', () {
test('returns true when the user has earned points', () {
const previous = GameState.initial();
const current = GameState(
score: 10,
balls: 3,
activatedBonusLetters: [],
bonusHistory: [],
activatedDashNests: {},
);
expect(controller.listenWhen(previous, current), isTrue);
});
test(
'returns true when the user has earned points and there was no '
'previous state',
() {
const current = GameState(
score: 10,
balls: 3,
activatedBonusLetters: [],
bonusHistory: [],
activatedDashNests: {},
);
expect(controller.listenWhen(null, current), isTrue);
},
);
test(
'returns false when no points were earned',
() {
const current = GameState.initial();
const previous = GameState.initial();
expect(controller.listenWhen(previous, current), isFalse);
},
);
});
group('onNewState', () {
test(
'adds a ScoreText with the correct score for the '
'first time',
() {
const state = GameState(
score: 10,
balls: 3,
activatedBonusLetters: [],
bonusHistory: [],
activatedDashNests: {},
);
controller.onNewState(state);
final effect =
verify(() => game.add(captureAny())).captured.first as ScoreText;
expect(effect.text, equals('10'));
},
);
test('adds a ScoreTextEffect with the correct score', () {
controller.onNewState(
const GameState(
score: 10,
balls: 3,
activatedBonusLetters: [],
bonusHistory: [],
activatedDashNests: {},
),
);
controller.onNewState(
const GameState(
score: 14,
balls: 3,
activatedBonusLetters: [],
bonusHistory: [],
activatedDashNests: {},
),
);
final effect =
verify(() => game.add(captureAny())).captured.last as ScoreText;
expect(effect.text, equals('4'));
});
});
});
}

@ -1,8 +1,13 @@
// ignore_for_file: cascade_invocations
import 'dart:ui';
import 'package:bloc_test/bloc_test.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_components/pinball_components.dart';
import '../../helpers/helpers.dart';
@ -11,13 +16,57 @@ void main() {
final flameTester = FlameTester(EmptyPinballGameTest.new);
group('SparkyFireZone', () {
flameTester.test(
'loads correctly',
(game) async {
await game.ready();
final sparkyFireZone = SparkyFireZone();
await game.ensureAdd(sparkyFireZone);
expect(game.contains(sparkyFireZone), isTrue);
},
);
group('loads', () {
flameTester.test(
'three SparkyBumper',
(game) async {
await game.ready();
final sparkyFireZone = SparkyFireZone();
await game.ensureAdd(sparkyFireZone);
expect(
sparkyFireZone.descendants().whereType<SparkyBumper>().length,
equals(3),
);
},
);
});
group('bumpers', () {
late ControlledSparkyBumper controlledSparkyBumper;
late Ball ball;
late GameBloc gameBloc;
setUp(() {
ball = Ball(baseColor: const Color(0xFF00FFFF));
gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballGameTest.new,
blocBuilder: () => gameBloc,
);
flameTester.testGameWidget(
'activate when deactivated bumper is hit',
setUp: (game, tester) async {
controlledSparkyBumper = ControlledSparkyBumper();
controlledSparkyBumper = ControlledSparkyBumper.a();
await game.ensureAdd(controlledSparkyBumper);
controlledSparkyBumper.controller.hit();
@ -30,7 +79,7 @@ void main() {
flameTester.testGameWidget(
'deactivate when activated bumper is hit',
setUp: (game, tester) async {
controlledSparkyBumper = ControlledSparkyBumper();
controlledSparkyBumper = ControlledSparkyBumper.a();
await game.ensureAdd(controlledSparkyBumper);
controlledSparkyBumper.controller.hit();
@ -40,6 +89,27 @@ void main() {
expect(controlledSparkyBumper.controller.isActivated, isFalse);
},
);
flameBlocTester.testGameWidget(
'add Scored event',
setUp: (game, tester) async {
final sparkyFireZone = SparkyFireZone();
await game.ensureAdd(sparkyFireZone);
await game.ensureAdd(ball);
game.addContactCallback(BallScorePointsCallback(game));
final bumpers = sparkyFireZone.descendants().whereType<ScorePoints>();
for (final bumper in bumpers) {
beginContact(game, bumper, ball);
verify(
() => gameBloc.add(
Scored(points: bumper.points),
),
).called(1);
}
},
);
});
});
}

@ -62,6 +62,14 @@ void main() {
);
});
flameTester.test(
'one SparkyFireZone',
(game) async {
await game.ready();
expect(game.children.whereType<SparkyFireZone>().length, equals(1));
},
);
group('controller', () {
// TODO(alestiago): Write test to be controller agnostic.
group('listenWhen', () {

Loading…
Cancel
Save