fix: fixed merge conflicts on spaceship

pull/79/head
RuiAlonso 4 years ago
commit 482848e155

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

@ -1,5 +1,4 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:pinball/flame/blueprint.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';

@ -1,7 +1,6 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
/// {@template baseboard} /// {@template baseboard}

@ -1,5 +1,6 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template board} /// {@template board}
/// The main flat surface of the [PinballGame]. /// The main flat surface of the [PinballGame].
@ -95,12 +96,15 @@ class _BottomGroupSide extends Component {
final flipper = Flipper( final flipper = Flipper(
side: _side, side: _side,
)..initialPosition = _position; )..initialPosition = _position;
await flipper.add(FlipperController(flipper));
final baseboard = Baseboard(side: _side) final baseboard = Baseboard(side: _side)
..initialPosition = _position + ..initialPosition = _position +
Vector2( Vector2(
(Baseboard.size.x / 1.6 * direction), (Baseboard.size.x / 1.6 * direction),
Baseboard.size.y - 2, Baseboard.size.y - 2,
); );
final kicker = Kicker( final kicker = Kicker(
side: _side, side: _side,
)..initialPosition = _position + )..initialPosition = _position +

@ -4,7 +4,6 @@ import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart' hide Timer; import 'package:flame_forge2d/flame_forge2d.dart' hide Timer;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
/// {@template chrome_dino} /// {@template chrome_dino}

@ -1,18 +1,14 @@
export 'ball.dart'; export 'ball.dart';
export 'baseboard.dart'; export 'baseboard.dart';
export 'board.dart'; export 'board.dart';
export 'board_side.dart';
export 'bonus_word.dart'; export 'bonus_word.dart';
export 'chrome_dino.dart'; export 'chrome_dino.dart';
export 'flipper.dart'; export 'flipper_controller.dart';
export 'flutter_forest.dart'; export 'flutter_forest.dart';
export 'jetpack_ramp.dart'; export 'jetpack_ramp.dart';
export 'joint_anchor.dart';
export 'kicker.dart'; export 'kicker.dart';
export 'launcher_ramp.dart'; export 'launcher_ramp.dart';
export 'plunger.dart'; export 'plunger.dart';
export 'ramp_opening.dart';
export 'score_points.dart'; export 'score_points.dart';
export 'spaceship.dart';
export 'spaceship_exit_rail.dart'; export 'spaceship_exit_rail.dart';
export 'wall.dart'; export 'wall.dart';

@ -0,0 +1,52 @@
import 'package:flame/components.dart';
import 'package:flutter/services.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template flipper_controller}
/// A [Component] that controls the [Flipper]s movement.
/// {@endtemplate}
class FlipperController extends Component with KeyboardHandler {
/// {@macro flipper_controller}
FlipperController(this.flipper) : _keys = flipper.side.flipperKeys;
/// The [Flipper] this controller is controlling.
final Flipper flipper;
/// The [LogicalKeyboardKey]s that will control the [Flipper].
///
/// [onKeyEvent] method listens to when one of these keys is pressed.
final List<LogicalKeyboardKey> _keys;
@override
bool onKeyEvent(
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
if (!_keys.contains(event.logicalKey)) return true;
if (event is RawKeyDownEvent) {
flipper.moveUp();
} else if (event is RawKeyUpEvent) {
flipper.moveDown();
}
return false;
}
}
extension on BoardSide {
List<LogicalKeyboardKey> get flipperKeys {
switch (this) {
case BoardSide.left:
return [
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.keyA,
];
case BoardSide.right:
return [
LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.keyD,
];
}
}
}

@ -6,7 +6,6 @@ import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/flame/blueprint.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';

@ -5,7 +5,6 @@ import 'dart:math' as math;
import 'package:flame/extensions.dart'; import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/flame/blueprint.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';

@ -4,7 +4,6 @@ import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:geometry/geometry.dart' as geometry show centroid; import 'package:geometry/geometry.dart' as geometry show centroid;
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
/// {@template kicker} /// {@template kicker}

@ -5,7 +5,6 @@ import 'dart:math' as math;
import 'package:flame/extensions.dart'; import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/flame/blueprint.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';

@ -5,7 +5,6 @@ import 'dart:ui';
import 'package:flame/extensions.dart'; import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/flame/blueprint.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets; import 'package:pinball_components/pinball_components.dart' hide Assets;
@ -177,7 +176,7 @@ class SpaceshipExitRailEnd extends RampOpening {
@override @override
Shape get shape { Shape get shape {
return CircleShape()..radius = Spaceship.radius / 40; return CircleShape()..radius = 1.5;
} }
} }

@ -8,12 +8,9 @@ extension PinballGameAssetsX on PinballGame {
Future<void> preLoadAssets() async { Future<void> preLoadAssets() async {
await Future.wait([ await Future.wait([
images.load(components.Assets.images.ball.keyName), images.load(components.Assets.images.ball.keyName),
images.load(Assets.images.components.flipper.path), images.load(components.Assets.images.flipper.left.keyName),
images.load(components.Assets.images.flipper.right.keyName),
images.load(Assets.images.components.background.path), images.load(Assets.images.components.background.path),
images.load(Assets.images.components.spaceship.androidTop.path),
images.load(Assets.images.components.spaceship.androidBottom.path),
images.load(Assets.images.components.spaceship.lower.path),
images.load(Assets.images.components.spaceship.upper.path),
]); ]);
} }
} }

@ -7,9 +7,9 @@ import 'package:flame/extensions.dart';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/flame/blueprint.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/gen/assets.gen.dart'; import 'package:pinball/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_theme/pinball_theme.dart' hide Assets; import 'package:pinball_theme/pinball_theme.dart' hide Assets;
class PinballGame extends Forge2DGame class PinballGame extends Forge2DGame
@ -46,7 +46,13 @@ class PinballGame extends Forge2DGame
unawaited(_addPlunger()); unawaited(_addPlunger());
unawaited(_addBonusWord()); unawaited(_addBonusWord());
unawaited(_addPaths()); unawaited(_addPaths());
unawaited(addFromBlueprint(Spaceship())); unawaited(
addFromBlueprint(
Spaceship(
position: Vector2(-25, 32),
),
),
);
unawaited(addFromBlueprint(SpaceshipExitRail())); unawaited(addFromBlueprint(SpaceshipExitRail()));
// Fix camera on the center of the board. // Fix camera on the center of the board.

@ -70,7 +70,10 @@ class _PinballGameViewState extends State<PinballGameView> {
showDialog<void>( showDialog<void>(
context: context, context: context,
builder: (_) { builder: (_) {
return GameOverDialog(theme: widget.theme.characterTheme); return GameOverDialog(
score: state.score,
theme: widget.theme.characterTheme,
);
}, },
); );
} }

@ -1,4 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/leaderboard/leaderboard.dart'; import 'package:pinball/leaderboard/leaderboard.dart';
@ -9,34 +11,162 @@ import 'package:pinball_theme/pinball_theme.dart';
/// {@endtemplate} /// {@endtemplate}
class GameOverDialog extends StatelessWidget { class GameOverDialog extends StatelessWidget {
/// {@macro game_over_dialog} /// {@macro game_over_dialog}
const GameOverDialog({Key? key, required this.theme}) : super(key: key); const GameOverDialog({Key? key, required this.score, required this.theme})
: super(key: key);
/// Current [CharacterTheme] to customize dialog /// Score achieved by the current user.
final int score;
/// Theme of the current user.
final CharacterTheme theme;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LeaderboardBloc(
context.read<LeaderboardRepository>(),
),
child: GameOverDialogView(score: score, theme: theme),
);
}
}
/// {@template game_over_dialog_view}
/// View for showing final score when the game is finished.
/// {@endtemplate}
@visibleForTesting
class GameOverDialogView extends StatefulWidget {
/// {@macro game_over_dialog_view}
const GameOverDialogView({
Key? key,
required this.score,
required this.theme,
}) : super(key: key);
/// Score achieved by the current user.
final int score;
/// Theme of the current user.
final CharacterTheme theme; final CharacterTheme theme;
@override
State<GameOverDialogView> createState() => _GameOverDialogViewState();
}
class _GameOverDialogViewState extends State<GameOverDialogView> {
final playerInitialsInputController = TextEditingController();
@override
void dispose() {
playerInitialsInputController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
// TODO(ruimiguel): refactor this view once UI design finished.
return Dialog( return Dialog(
child: SizedBox( child: SizedBox(
width: 200, width: 200,
height: 200, height: 250,
child: Center( child: Center(
child: Column( child: Padding(
mainAxisAlignment: MainAxisAlignment.center, padding: const EdgeInsets.all(10),
children: [ child: SingleChildScrollView(
Text(l10n.gameOver), child: Column(
TextButton( mainAxisAlignment: MainAxisAlignment.center,
onPressed: () => Navigator.of(context).push<void>( children: [
LeaderboardPage.route(theme: theme), Text(
), l10n.gameOver,
child: Text(l10n.leaderboard), style: Theme.of(context).textTheme.headline4,
),
const SizedBox(
height: 20,
),
Text(
'${l10n.yourScore} ${widget.score}',
style: Theme.of(context).textTheme.headline6,
),
const SizedBox(
height: 15,
),
TextField(
key: const Key('player_initials_text_field'),
controller: playerInitialsInputController,
textCapitalization: TextCapitalization.characters,
decoration: InputDecoration(
border: const OutlineInputBorder(),
hintText: l10n.enterInitials,
),
maxLength: 3,
),
const SizedBox(
height: 10,
),
_GameOverDialogActions(
score: widget.score,
theme: widget.theme,
playerInitialsInputController:
playerInitialsInputController,
),
],
), ),
], ),
), ),
), ),
), ),
); );
} }
} }
class _GameOverDialogActions extends StatelessWidget {
const _GameOverDialogActions({
Key? key,
required this.score,
required this.theme,
required this.playerInitialsInputController,
}) : super(key: key);
final int score;
final CharacterTheme theme;
final TextEditingController playerInitialsInputController;
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return BlocBuilder<LeaderboardBloc, LeaderboardState>(
builder: (context, state) {
switch (state.status) {
case LeaderboardStatus.loading:
return TextButton(
onPressed: () {
context.read<LeaderboardBloc>().add(
LeaderboardEntryAdded(
entry: LeaderboardEntryData(
playerInitials:
playerInitialsInputController.text.toUpperCase(),
score: score,
character: theme.toType,
),
),
);
},
child: Text(l10n.addUser),
);
case LeaderboardStatus.success:
return TextButton(
onPressed: () => Navigator.of(context).push<void>(
LeaderboardPage.route(theme: theme),
),
child: Text(l10n.leaderboard),
);
case LeaderboardStatus.error:
return Text(l10n.error);
}
},
);
}
}

@ -3,8 +3,6 @@
/// FlutterGen /// FlutterGen
/// ***************************************************** /// *****************************************************
// ignore_for_file: directives_ordering,unnecessary_import
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
class $AssetsImagesGen { class $AssetsImagesGen {
@ -17,40 +15,8 @@ class $AssetsImagesGen {
class $AssetsImagesComponentsGen { class $AssetsImagesComponentsGen {
const $AssetsImagesComponentsGen(); const $AssetsImagesComponentsGen();
/// File path: assets/images/components/background.png
AssetGenImage get background => AssetGenImage get background =>
const AssetGenImage('assets/images/components/background.png'); const AssetGenImage('assets/images/components/background.png');
/// File path: assets/images/components/flipper.png
AssetGenImage get flipper =>
const AssetGenImage('assets/images/components/flipper.png');
$AssetsImagesComponentsSpaceshipGen get spaceship =>
const $AssetsImagesComponentsSpaceshipGen();
}
class $AssetsImagesComponentsSpaceshipGen {
const $AssetsImagesComponentsSpaceshipGen();
/// File path: assets/images/components/spaceship/android-bottom.png
AssetGenImage get androidBottom => const AssetGenImage(
'assets/images/components/spaceship/android-bottom.png');
/// File path: assets/images/components/spaceship/android-top.png
AssetGenImage get androidTop =>
const AssetGenImage('assets/images/components/spaceship/android-top.png');
/// File path: assets/images/components/spaceship/lower.png
AssetGenImage get lower =>
const AssetGenImage('assets/images/components/spaceship/lower.png');
/// File path: assets/images/components/spaceship/saucer.png
AssetGenImage get saucer =>
const AssetGenImage('assets/images/components/spaceship/saucer.png');
/// File path: assets/images/components/spaceship/upper.png
AssetGenImage get upper =>
const AssetGenImage('assets/images/components/spaceship/upper.png');
} }
class Assets { class Assets {

@ -51,5 +51,21 @@
"retry": "Retry", "retry": "Retry",
"@retry": { "@retry": {
"description": "Text displayed on the retry button leaders board page" "description": "Text displayed on the retry button leaders board page"
},
"addUser": "Add User",
"@addUser": {
"description": "Text displayed on the add user button at ending dialog"
},
"error": "Error",
"@error": {
"description": "Text displayed on the ending dialog when there is any error on sending user"
},
"yourScore": "Your score is",
"@yourScore": {
"description": "Text displayed on the ending dialog when game finishes to show the final score"
},
"enterInitials": "Enter your initials",
"@enterInitials": {
"description": "Text displayed on the ending dialog when game finishes to ask the user for his initials"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

@ -9,6 +9,20 @@ class $AssetsImagesGen {
const $AssetsImagesGen(); const $AssetsImagesGen();
AssetGenImage get ball => const AssetGenImage('assets/images/ball.png'); AssetGenImage get ball => const AssetGenImage('assets/images/ball.png');
$AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen();
AssetGenImage get spaceshipBridge =>
const AssetGenImage('assets/images/spaceship_bridge.png');
AssetGenImage get spaceshipSaucer =>
const AssetGenImage('assets/images/spaceship_saucer.png');
}
class $AssetsImagesFlipperGen {
const $AssetsImagesFlipperGen();
AssetGenImage get left =>
const AssetGenImage('assets/images/flipper/left.png');
AssetGenImage get right =>
const AssetGenImage('assets/images/flipper/right.png');
} }
class Assets { class Assets {

@ -6,7 +6,7 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
/// {@template ball} /// {@template ball}
/// A solid, [BodyType.dynamic] sphere that rolls and bounces around /// A solid, [BodyType.dynamic] sphere that rolls and bounces around.
/// {@endtemplate} /// {@endtemplate}
class Ball<T extends Forge2DGame> extends BodyComponent<T> class Ball<T extends Forge2DGame> extends BodyComponent<T>
with Layered, InitialPosition { with Layered, InitialPosition {
@ -90,7 +90,7 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
} }
} }
/// Applies a boost on this [Ball] /// Applies a boost on this [Ball].
void boost(Vector2 impulse) { void boost(Vector2 impulse) {
body.applyLinearImpulse(impulse); body.applyLinearImpulse(impulse);
_boostTimer = _boostDuration; _boostTimer = _boostDuration;

@ -1,4 +1,8 @@
import 'package:pinball/game/game.dart'; // ignore_for_file: comment_references
// TODO(alestiago): Revisit ignore lint rule once Kicker is moved to this
// package.
import 'package:pinball_components/pinball_components.dart';
/// Indicates a side of the board. /// Indicates a side of the board.
/// ///

@ -1,5 +1,10 @@
export 'ball.dart'; export 'ball.dart';
export 'board_side.dart';
export 'fire_effect.dart'; export 'fire_effect.dart';
export 'flipper.dart';
export 'initial_position.dart'; export 'initial_position.dart';
export 'joint_anchor.dart';
export 'layer.dart'; export 'layer.dart';
export 'ramp_opening.dart';
export 'shapes/shapes.dart'; export 'shapes/shapes.dart';
export 'spaceship.dart';

@ -3,20 +3,7 @@ import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/services.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
const _leftFlipperKeys = [
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.keyA,
];
const _rightFlipperKeys = [
LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.keyD,
];
/// {@template flipper} /// {@template flipper}
/// A bat, typically found in pairs at the bottom of the board. /// A bat, typically found in pairs at the bottom of the board.
@ -27,10 +14,10 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
/// {@macro flipper} /// {@macro flipper}
Flipper({ Flipper({
required this.side, required this.side,
}) : _keys = side.isLeft ? _leftFlipperKeys : _rightFlipperKeys; });
/// The size of the [Flipper]. /// The size of the [Flipper].
static final size = Vector2(12, 2.8); static final size = Vector2(13.5, 4.3);
/// The speed required to move the [Flipper] to its highest position. /// The speed required to move the [Flipper] to its highest position.
/// ///
@ -43,27 +30,24 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
/// whereas a [Flipper] with [BoardSide.right] has a clockwise arc motion. /// whereas a [Flipper] with [BoardSide.right] has a clockwise arc motion.
final BoardSide side; final BoardSide side;
/// The [LogicalKeyboardKey]s that will control the [Flipper].
///
/// [onKeyEvent] method listens to when one of these keys is pressed.
final List<LogicalKeyboardKey> _keys;
/// Applies downward linear velocity to the [Flipper], moving it to its /// Applies downward linear velocity to the [Flipper], moving it to its
/// resting position. /// resting position.
void _moveDown() { void moveDown() {
body.linearVelocity = Vector2(0, -_speed); body.linearVelocity = Vector2(0, -_speed);
} }
/// Applies upward linear velocity to the [Flipper], moving it to its highest /// Applies upward linear velocity to the [Flipper], moving it to its highest
/// position. /// position.
void _moveUp() { void moveUp() {
body.linearVelocity = Vector2(0, _speed); body.linearVelocity = Vector2(0, _speed);
} }
/// Loads the sprite that renders with the [Flipper]. /// Loads the sprite that renders with the [Flipper].
Future<void> _loadSprite() async { Future<void> _loadSprite() async {
final sprite = await gameRef.loadSprite( final sprite = await gameRef.loadSprite(
Assets.images.components.flipper.path, (side.isLeft)
? Assets.images.flipper.left.keyName
: Assets.images.flipper.right.keyName,
); );
final spriteComponent = SpriteComponent( final spriteComponent = SpriteComponent(
sprite: sprite, sprite: sprite,
@ -71,10 +55,6 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
anchor: Anchor.center, anchor: Anchor.center,
); );
if (side.isRight) {
spriteComponent.flipHorizontally();
}
await add(spriteComponent); await add(spriteComponent);
} }
@ -87,30 +67,36 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
flipper: this, flipper: this,
anchor: anchor, anchor: anchor,
); );
final joint = _FlipperJoint(jointDef)..create(world); final joint = _FlipperJoint(jointDef);
world.createJoint2(joint);
// FIXME(erickzanardo): when mounted the initial position is not fully unawaited(mounted.whenComplete(joint.unlock));
// reached.
unawaited(
mounted.whenComplete(joint.unlock),
);
} }
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[]; final fixturesDef = <FixtureDef>[];
final direction = side.direction; final direction = side.direction;
final bigCircleShape = CircleShape()..radius = 1.75; final assetShadow = Flipper.size.x * 0.012 * -direction;
final size = Vector2(
Flipper.size.x - (assetShadow * 2),
Flipper.size.y,
);
final bigCircleShape = CircleShape()..radius = size.y / 2 - 0.2;
bigCircleShape.position.setValues( bigCircleShape.position.setValues(
((size.x / 2) * direction) + (bigCircleShape.radius * -direction), ((size.x / 2) * direction) +
(bigCircleShape.radius * -direction) +
assetShadow,
0, 0,
); );
final bigCircleFixtureDef = FixtureDef(bigCircleShape); final bigCircleFixtureDef = FixtureDef(bigCircleShape);
fixturesDef.add(bigCircleFixtureDef); fixturesDef.add(bigCircleFixtureDef);
final smallCircleShape = CircleShape()..radius = 0.9; final smallCircleShape = CircleShape()..radius = size.y * 0.23;
smallCircleShape.position.setValues( smallCircleShape.position.setValues(
((size.x / 2) * -direction) + (smallCircleShape.radius * direction), ((size.x / 2) * -direction) +
(smallCircleShape.radius * direction) -
assetShadow,
0, 0,
); );
final smallCircleFixtureDef = FixtureDef(smallCircleShape); final smallCircleFixtureDef = FixtureDef(smallCircleShape);
@ -143,7 +129,7 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
await super.onLoad(); await super.onLoad();
renderBody = false; renderBody = false;
await Future.wait([ await Future.wait<void>([
_loadSprite(), _loadSprite(),
_anchorToJoint(), _anchorToJoint(),
]); ]);
@ -160,22 +146,6 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
return body; return body;
} }
@override
bool onKeyEvent(
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
if (!_keys.contains(event.logicalKey)) return true;
if (event is RawKeyDownEvent) {
_moveUp();
} else if (event is RawKeyUpEvent) {
_moveDown();
}
return false;
}
} }
/// {@template flipper_anchor} /// {@template flipper_anchor}
@ -204,45 +174,60 @@ class _FlipperAnchorRevoluteJointDef extends RevoluteJointDef {
required Flipper flipper, required Flipper flipper,
required _FlipperAnchor anchor, required _FlipperAnchor anchor,
}) : side = flipper.side { }) : side = flipper.side {
enableLimit = true;
initialize( initialize(
flipper.body, flipper.body,
anchor.body, anchor.body,
anchor.body.position, anchor.body.position,
); );
enableLimit = true;
final angle = (_sweepingAngle * -side.direction) / 2;
lowerAngle = upperAngle = angle;
} }
/// The total angle of the arc motion.
static const _sweepingAngle = math.pi / 3.5;
final BoardSide side; final BoardSide side;
} }
/// {@template flipper_joint}
/// [RevoluteJoint] that controls the arc motion of a [Flipper].
/// {@endtemplate}
class _FlipperJoint extends RevoluteJoint { class _FlipperJoint extends RevoluteJoint {
/// {@macro flipper_joint}
_FlipperJoint(_FlipperAnchorRevoluteJointDef def) _FlipperJoint(_FlipperAnchorRevoluteJointDef def)
: side = def.side, : side = def.side,
super(def); super(def) {
lock();
}
/// The total angle of the arc motion.
static const _sweepingAngle = math.pi / 3.5;
final BoardSide side; final BoardSide side;
// TODO(alestiago): Remove once Forge2D supports custom joints. /// Locks the [Flipper] to its resting position.
void create(World world) { ///
world.joints.add(this); /// The joint is locked when initialized in order to force the [Flipper]
bodyA.joints.add(this); /// at its resting position.
bodyB.joints.add(this); void lock() {
const angle = _sweepingAngle / 2;
setLimits(
-angle * side.direction,
-angle * side.direction,
);
} }
/// Unlocks the [Flipper] from its resting position. /// Unlocks the [Flipper] from its resting position.
///
/// The [Flipper] is locked when initialized in order to force it to be at
/// its resting position.
void unlock() { void unlock() {
setLimits( const angle = _sweepingAngle / 2;
lowerLimit * side.direction, setLimits(-angle, angle);
-upperLimit * side.direction, }
); }
// TODO(alestiago): Remove once Forge2D supports custom joints.
extension on World {
void createJoint2(Joint joint) {
assert(!isLocked, '');
joints.add(joint);
joint.bodyA.joints.add(joint);
joint.bodyB.joints.add(joint);
} }
} }

@ -1,17 +1,16 @@
// ignore_for_file: avoid_renaming_method_parameters // ignore_for_file: avoid_renaming_method_parameters
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
/// {@template ramp_orientation} /// {@template ramp_orientation}
/// Determines if a ramp is facing [up] or [down] on the [Board]. /// Determines if a ramp is facing [up] or [down] on the Board.
/// {@endtemplate} /// {@endtemplate}
enum RampOrientation { enum RampOrientation {
/// Facing up on the [Board]. /// Facing up on the Board.
up, up,
/// Facing down on the [Board]. /// Facing down on the Board.
down, down,
} }

@ -5,23 +5,24 @@ import 'dart:math';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/flame/blueprint.dart'; import 'package:pinball_components/gen/assets.gen.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets; import 'package:pinball_components/pinball_components.dart' hide Assets;
/// {@template spaceship}
/// A [Blueprint] which creates the spaceship feature. /// A [Blueprint] which creates the spaceship feature.
/// {@endtemplate}
class Spaceship extends Forge2DBlueprint { class Spaceship extends Forge2DBlueprint {
/// Total size of the spaceship /// {@macro spaceship}
static const radius = 10.0; Spaceship({required this.position});
/// Total size of the spaceship.
static final size = Vector2(24, 18);
/// The [position] where the elements will be created
final Vector2 position;
@override @override
void build(_) { void build(_) {
final position = Vector2(
PinballGame.boardBounds.left + radius + 15,
PinballGame.boardBounds.center.dy + 30,
);
addAllContactCallback([ addAllContactCallback([
SpaceshipHoleBallContactCallback(), SpaceshipHoleBallContactCallback(),
SpaceshipEntranceBallContactCallback(), SpaceshipEntranceBallContactCallback(),
@ -30,13 +31,12 @@ class Spaceship extends Forge2DBlueprint {
addAll([ addAll([
SpaceshipSaucer()..initialPosition = position, SpaceshipSaucer()..initialPosition = position,
SpaceshipEntrance()..initialPosition = position, SpaceshipEntrance()..initialPosition = position,
SpaceshipBridge()..initialPosition = position, AndroidHead()..initialPosition = position,
SpaceshipBridgeTop()..initialPosition = position + Vector2(0, 5.5),
SpaceshipHole( SpaceshipHole(
onExitLayer: Layer.spaceshipExitRail, onExitLayer: Layer.spaceshipExitRail,
onExitElevation: 2, onExitElevation: 2,
)..initialPosition = position - Vector2(5, 4), )..initialPosition = position - Vector2(4.8, 4.2),
SpaceshipHole()..initialPosition = position - Vector2(-5, 4), SpaceshipHole()..initialPosition = position - Vector2(-7.2, 0.6),
SpaceshipWall()..initialPosition = position, SpaceshipWall()..initialPosition = position,
]); ]);
} }
@ -55,25 +55,15 @@ class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprites = await Future.wait([ final sprite = await gameRef.loadSprite(
gameRef.loadSprite(Assets.images.components.spaceship.saucer.path), Assets.images.spaceshipSaucer.keyName,
gameRef.loadSprite(Assets.images.components.spaceship.upper.path),
]);
await add(
SpriteComponent(
sprite: sprites.first,
size: Vector2.all(Spaceship.radius * 2),
anchor: Anchor.center,
),
); );
await add( await add(
SpriteComponent( SpriteComponent(
sprite: sprites.last, sprite: sprite,
size: Vector2((Spaceship.radius * 2) + 0.5, Spaceship.radius), size: Spaceship.size,
anchor: Anchor.center, anchor: Anchor.center,
position: Vector2(0, -((Spaceship.radius * 2) / 3.5)),
), ),
); );
@ -82,7 +72,7 @@ class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered {
@override @override
Body createBody() { Body createBody() {
final circleShape = CircleShape()..radius = Spaceship.radius; final circleShape = CircleShape()..radius = 3;
final bodyDef = BodyDef() final bodyDef = BodyDef()
..userData = this ..userData = this
@ -96,50 +86,13 @@ class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered {
} }
} }
/// {@spaceship_bridge_top}
/// The bridge of the spaceship (the android head) is divided in two
// [BodyComponent]s, this is the top part of it which contains a single sprite
/// {@endtemplate}
class SpaceshipBridgeTop extends BodyComponent with InitialPosition {
/// {@macro spaceship_bridge_top}
// TODO(ruimiguel): apply Elevated when PR merged.
SpaceshipBridgeTop() : super(priority: 6);
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(
Assets.images.components.spaceship.androidTop.path,
);
await add(
SpriteComponent(
sprite: sprite,
anchor: Anchor.center,
size: Vector2((Spaceship.radius * 2) / 2.5 - 1, Spaceship.radius / 2.5),
),
);
}
@override
Body createBody() {
final bodyDef = BodyDef()
..userData = this
..position = initialPosition
..type = BodyType.static;
return world.createBody(bodyDef);
}
}
/// {@template spaceship_bridge} /// {@template spaceship_bridge}
/// The main part of the [SpaceshipBridge], this [BodyComponent] /// A [BodyComponent] that provides both the collision and the rotation
/// provides both the collision and the rotation animation for the bridge. /// animation for the bridge.
/// {@endtemplate} /// {@endtemplate}
class SpaceshipBridge extends BodyComponent with InitialPosition, Layered { class AndroidHead extends BodyComponent with InitialPosition, Layered {
/// {@macro spaceship_bridge} /// {@macro spaceship_bridge}
// TODO(ruimiguel): apply Elevated when PR merged. AndroidHead() : super(priority: 3) {
SpaceshipBridge() : super(priority: 4) {
layer = Layer.spaceship; layer = Layer.spaceship;
} }
@ -150,17 +103,20 @@ class SpaceshipBridge extends BodyComponent with InitialPosition, Layered {
renderBody = false; renderBody = false;
final sprite = await gameRef.images.load( final sprite = await gameRef.images.load(
Assets.images.components.spaceship.androidBottom.path, Assets.images.spaceshipBridge.keyName,
); );
await add( await add(
SpriteAnimationComponent.fromFrameData( SpriteAnimationComponent.fromFrameData(
sprite, sprite,
SpriteAnimationData.sequenced( SpriteAnimationData.sequenced(
amount: 14, amount: 72,
stepTime: 0.2, amountPerRow: 24,
textureSize: Vector2(160, 114), stepTime: 0.05,
textureSize: Vector2(82, 100),
), ),
size: Vector2.all((Spaceship.radius * 2) / 2.5), size: Vector2(8.2, 10),
position: Vector2(0, -2),
anchor: Anchor.center, anchor: Anchor.center,
), ),
); );
@ -168,7 +124,7 @@ class SpaceshipBridge extends BodyComponent with InitialPosition, Layered {
@override @override
Body createBody() { Body createBody() {
final circleShape = CircleShape()..radius = Spaceship.radius / 2.5; final circleShape = CircleShape()..radius = 2;
final bodyDef = BodyDef() final bodyDef = BodyDef()
..userData = this ..userData = this
@ -203,7 +159,8 @@ class SpaceshipEntrance extends RampOpening {
@override @override
Shape get shape { Shape get shape {
const radius = Spaceship.radius * 2; renderBody = false;
final radius = Spaceship.size.y / 2;
return PolygonShape() return PolygonShape()
..setAsEdge( ..setAsEdge(
Vector2( Vector2(
@ -239,7 +196,29 @@ class SpaceshipHole extends RampOpening {
@override @override
Shape get shape { Shape get shape {
return CircleShape()..radius = Spaceship.radius / 40; return CircleShape()..radius = 1.5;
}
}
/// {@template spaceship_wall_shape}
/// The [ChainShape] that defines the shape of the [SpaceshipWall].
/// {@endtemplate}
class _SpaceshipWallShape extends ChainShape {
/// {@macro spaceship_wall_shape}
_SpaceshipWallShape() {
final minorRadius = (Spaceship.size.y - 2) / 2;
final majorRadius = (Spaceship.size.x - 2) / 2;
createChain(
[
// TODO(alestiago): Try converting this logic to radian.
for (var angle = 20; angle <= 340; angle++)
Vector2(
minorRadius * cos(angle * pi / 180),
majorRadius * sin(angle * pi / 180),
),
],
);
} }
} }
@ -256,39 +235,11 @@ class SpaceshipWall extends BodyComponent with InitialPosition, Layered {
layer = Layer.spaceship; layer = Layer.spaceship;
} }
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(
Assets.images.components.spaceship.lower.path,
);
await add(
SpriteComponent(
sprite: sprite,
size: Vector2(Spaceship.radius * 2, Spaceship.radius + 1),
anchor: Anchor.center,
position: Vector2(-Spaceship.radius / 2, 0),
angle: 90 * pi / 180,
),
);
}
@override @override
Body createBody() { Body createBody() {
renderBody = false; renderBody = false;
final wallShape = ChainShape() final wallShape = _SpaceshipWallShape();
..createChain(
[
for (var angle = 20; angle <= 340; angle++)
Vector2(
Spaceship.radius * cos(angle * pi / 180),
Spaceship.radius * sin(angle * pi / 180),
),
],
);
final bodyDef = BodyDef() final bodyDef = BodyDef()
..userData = this ..userData = this

@ -1 +1,2 @@
export 'components/components.dart'; export 'components/components.dart';
export 'flame/flame.dart';

@ -26,6 +26,7 @@ flutter:
generate: true generate: true
assets: assets:
- assets/images/ - assets/images/
- assets/images/flipper/
flutter_gen: flutter_gen:
line_length: 80 line_length: 80

@ -5,7 +5,7 @@ import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class BasicGame extends Forge2DGame { abstract class BasicGame extends Forge2DGame {
BasicGame() { BasicGame() {
images.prefix = ''; images.prefix = '';
} }

@ -7,6 +7,7 @@
import 'package:dashbook/dashbook.dart'; import 'package:dashbook/dashbook.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:sandbox/stories/effects/effects.dart'; import 'package:sandbox/stories/effects/effects.dart';
import 'package:sandbox/stories/spaceship/spaceship.dart';
import 'package:sandbox/stories/stories.dart'; import 'package:sandbox/stories/stories.dart';
void main() { void main() {
@ -15,5 +16,7 @@ void main() {
addBallStories(dashbook); addBallStories(dashbook);
addLayerStories(dashbook); addLayerStories(dashbook);
addEffectsStories(dashbook); addEffectsStories(dashbook);
addFlipperStories(dashbook);
addSpaceshipStories(dashbook);
runApp(dashbook); runApp(dashbook);
} }

@ -7,8 +7,9 @@ class BasicBallGame extends BasicGame with TapDetector {
BasicBallGame({required this.color}); BasicBallGame({required this.color});
static const info = ''' static const info = '''
Basic example of how a Ball works, tap anywhere on the Basic example of how a Ball works.
screen to spawn a ball into the game.
Tap anywhere on the screen to spawn a ball into the game.
'''; ''';
final Color color; final Color color;

@ -0,0 +1,26 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class BasicFlipperGame extends BasicGame {
static const info = '''
Basic example of how a Flipper works.
''';
@override
Future<void> onLoad() async {
await super.onLoad();
final center = screenToWorld(camera.viewport.canvasSize! / 2);
final leftFlipper = Flipper(side: BoardSide.left)
..initialPosition = center - Vector2(Flipper.size.x, 0);
final rightFlipper = Flipper(side: BoardSide.right)
..initialPosition = center + Vector2(Flipper.size.x, 0);
await addAll([
leftFlipper,
rightFlipper,
]);
}
}

@ -0,0 +1,25 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/flipper/basic.dart';
import 'package:sandbox/stories/flipper/tracing.dart';
void addFlipperStories(Dashbook dashbook) {
dashbook.storiesOf('Flipper')
..add(
'Basic',
(context) => GameWidget(
game: BasicFlipperGame(),
),
codeLink: buildSourceLink('flipper/basic.dart'),
info: BasicFlipperGame.info,
)
..add(
'Tracing',
(context) => GameWidget(
game: FlipperTracingGame(),
),
codeLink: buildSourceLink('flipper/tracing.dart'),
info: FlipperTracingGame.info,
);
}

@ -0,0 +1,49 @@
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class FlipperTracingGame extends BasicGame {
static const info = '''
Basic example of how the Flipper body overlays the sprite.
''';
@override
Future<void> onLoad() async {
await super.onLoad();
final center = screenToWorld(camera.viewport.canvasSize! / 2);
final leftFlipper = Flipper(side: BoardSide.left)
..initialPosition = center - Vector2(Flipper.size.x, 0);
final rightFlipper = Flipper(side: BoardSide.right)
..initialPosition = center + Vector2(Flipper.size.x, 0);
await addAll([
leftFlipper,
rightFlipper,
]);
leftFlipper.trace();
rightFlipper.trace();
}
}
extension on BodyComponent {
void trace({Color color = Colors.red}) {
paint = Paint()..color = color;
renderBody = true;
body.joints.whereType<RevoluteJoint>().forEach(
(joint) => joint.setLimits(0, 0),
);
body.setType(BodyType.static);
unawaited(
mounted.whenComplete(() {
final sprite = children.whereType<SpriteComponent>().first;
sprite.paint.color = sprite.paint.color.withOpacity(0.5);
}),
);
}
}

@ -8,8 +8,9 @@ class BasicLayerGame extends BasicGame with TapDetector {
BasicLayerGame({required this.color}); BasicLayerGame({required this.color});
static const info = ''' static const info = '''
Basic example of how layers work with a Ball hitting other components, Basic example of how layers work when a Ball hits other components.
tap anywhere on the screen to spawn a ball into the game.
Tap anywhere on the screen to spawn a ball into the game.
'''; ''';
final Color color; final Color color;

@ -0,0 +1,31 @@
import 'dart:async';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class BasicSpaceship extends BasicGame with TapDetector {
static String info = 'Renders a spaceship and allows balls to be '
'spawned upon click to test their interactions';
@override
Future<void> onLoad() async {
await super.onLoad();
camera.followVector2(Vector2.zero());
unawaited(
addFromBlueprint(Spaceship(position: Vector2.zero())),
);
}
@override
void onTapUp(TapUpInfo info) {
add(
Ball(baseColor: Colors.blue)
..initialPosition = info.eventPosition.game
..layer = Layer.jetpack,
);
}
}

@ -0,0 +1,13 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/spaceship/basic.dart';
void addSpaceshipStories(Dashbook dashbook) {
dashbook.storiesOf('Spaceship').add(
'Basic',
(context) => GameWidget(game: BasicSpaceship()),
codeLink: buildSourceLink('spaceship/basic.dart'),
info: BasicSpaceship.info,
);
}

@ -1,2 +1,3 @@
export 'ball/ball.dart'; export 'ball/ball.dart';
export 'flipper/flipper.dart';
export 'layer/layer.dart'; export 'layer/layer.dart';

@ -1,5 +1,26 @@
import 'dart:ui'; import 'dart:ui';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball_components/pinball_components.dart';
class MockCanvas extends Mock implements Canvas {} class MockCanvas extends Mock implements Canvas {}
class MockFilter extends Mock implements Filter {}
class MockFixture extends Mock implements Fixture {}
class MockBody extends Mock implements Body {}
class MockBall extends Mock implements Ball {}
class MockGame extends Mock implements Forge2DGame {}
class MockSpaceshipEntrance extends Mock implements SpaceshipEntrance {}
class MockSpaceshipHole extends Mock implements SpaceshipHole {}
class MockContact extends Mock implements Contact {}
class MockContactCallback extends Mock
implements ContactCallback<Object, Object> {}

@ -1,5 +1,5 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart';
void main() { void main() {
group( group(

@ -0,0 +1,133 @@
// ignore_for_file: cascade_invocations
import 'package:flame_forge2d/flame_forge2d.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();
final flameTester = FlameTester(TestGame.new);
group('Flipper', () {
// TODO(alestiago): Add golden tests.
// TODO(alestiago): Consider testing always both left and right Flipper.
flameTester.test(
'loads correctly',
(game) async {
final leftFlipper = Flipper(side: BoardSide.left);
final rightFlipper = Flipper(side: BoardSide.right);
await game.ready();
await game.ensureAddAll([leftFlipper, rightFlipper]);
expect(game.contains(leftFlipper), isTrue);
expect(game.contains(rightFlipper), isTrue);
},
);
group('constructor', () {
test('sets BoardSide', () {
final leftFlipper = Flipper(side: BoardSide.left);
expect(leftFlipper.side, equals(leftFlipper.side));
final rightFlipper = Flipper(side: BoardSide.right);
expect(rightFlipper.side, equals(rightFlipper.side));
});
});
group('body', () {
flameTester.test(
'is dynamic',
(game) async {
final flipper = Flipper(side: BoardSide.left);
await game.ensureAdd(flipper);
expect(flipper.body.bodyType, equals(BodyType.dynamic));
},
);
flameTester.test(
'ignores gravity',
(game) async {
final flipper = Flipper(side: BoardSide.left);
await game.ensureAdd(flipper);
expect(flipper.body.gravityScale, isZero);
},
);
flameTester.test(
'has greater mass than Ball',
(game) async {
final flipper = Flipper(side: BoardSide.left);
final ball = Ball(baseColor: Colors.white);
await game.ready();
await game.ensureAddAll([flipper, ball]);
expect(
flipper.body.getMassData().mass,
greaterThan(ball.body.getMassData().mass),
);
},
);
});
group('fixtures', () {
flameTester.test(
'has three',
(game) async {
final flipper = Flipper(side: BoardSide.left);
await game.ensureAdd(flipper);
expect(flipper.body.fixtures.length, equals(3));
},
);
flameTester.test(
'has density',
(game) async {
final flipper = Flipper(side: BoardSide.left);
await game.ensureAdd(flipper);
final fixtures = flipper.body.fixtures;
final density = fixtures.fold<double>(
0,
(sum, fixture) => sum + fixture.density,
);
expect(density, greaterThan(0));
},
);
});
flameTester.test(
'moveDown applies downward velocity',
(game) async {
final flipper = Flipper(side: BoardSide.left);
await game.ensureAdd(flipper);
expect(flipper.body.linearVelocity, equals(Vector2.zero()));
flipper.moveDown();
expect(flipper.body.linearVelocity.y, lessThan(0));
},
);
flameTester.test(
'moveUp applies upward velocity',
(game) async {
final flipper = Flipper(side: BoardSide.left);
await game.ensureAdd(flipper);
expect(flipper.body.linearVelocity, equals(Vector2.zero()));
flipper.moveUp();
expect(flipper.body.linearVelocity.y, greaterThan(0));
},
);
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

@ -3,7 +3,7 @@
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();

@ -2,8 +2,7 @@
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mockingjay/mockingjay.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart'; import '../../helpers/helpers.dart';
@ -34,11 +33,11 @@ class TestRampOpeningBallContactCallback
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGameTest.create); final flameTester = FlameTester(TestGame.new);
group('RampOpening', () { group('RampOpening', () {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGameTest.create); final flameTester = FlameTester(TestGame.new);
flameTester.test( flameTester.test(
'loads correctly', 'loads correctly',

@ -1,7 +1,10 @@
// ignore_for_file: cascade_invocations
import 'package:flame/game.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart'; import '../../helpers/helpers.dart';
@ -11,10 +14,10 @@ void main() {
late Filter filterData; late Filter filterData;
late Fixture fixture; late Fixture fixture;
late Body body; late Body body;
late PinballGame game;
late Ball ball; late Ball ball;
late SpaceshipEntrance entrance; late SpaceshipEntrance entrance;
late SpaceshipHole hole; late SpaceshipHole hole;
late Forge2DGame game;
setUp(() { setUp(() {
filterData = MockFilter(); filterData = MockFilter();
@ -25,7 +28,7 @@ void main() {
body = MockBody(); body = MockBody();
when(() => body.fixtures).thenReturn([fixture]); when(() => body.fixtures).thenReturn([fixture]);
game = MockPinballGame(); game = MockGame();
ball = MockBall(); ball = MockBall();
when(() => ball.gameRef).thenReturn(game); when(() => ball.gameRef).thenReturn(game);
@ -35,6 +38,27 @@ void main() {
hole = MockSpaceshipHole(); hole = MockSpaceshipHole();
}); });
group('Spaceship', () {
testWidgets('renders correctly', (tester) async {
final game = TestGame();
// TODO(erickzanardo): This should be handled by flame test.
// refctor it when https://github.com/flame-engine/flame/pull/1501 is merged
await tester.runAsync(() async {
await tester.pumpWidget(GameWidget(game: game));
await game.ready();
await game.addFromBlueprint(Spaceship(position: Vector2(30, -30)));
await game.ready();
await tester.pump();
});
await expectLater(
find.byGame<Forge2DGame>(),
matchesGoldenFile('golden/spaceship.png'),
);
});
});
group('SpaceshipEntranceBallContactCallback', () { group('SpaceshipEntranceBallContactCallback', () {
test('changes the ball priority on contact', () { test('changes the ball priority on contact', () {
when(() => entrance.onEnterElevation).thenReturn(3); when(() => entrance.onEnterElevation).thenReturn(3);

@ -1,10 +1,9 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/flame/blueprint.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball/game/game.dart';
import '../helpers/helpers.dart'; import '../../helpers/helpers.dart';
class MyBlueprint extends Blueprint { class MyBlueprint extends Blueprint {
@override @override
@ -52,19 +51,19 @@ void main() {
}); });
test('components can be added to it', () { test('components can be added to it', () {
final blueprint = MyBlueprint()..build(MockPinballGame()); final blueprint = MyBlueprint()..build(MockGame());
expect(blueprint.components.length, equals(3)); expect(blueprint.components.length, equals(3));
}); });
test('blueprints can be added to it', () { test('blueprints can be added to it', () {
final blueprint = MyComposedBlueprint()..build(MockPinballGame()); final blueprint = MyComposedBlueprint()..build(MockGame());
expect(blueprint.blueprints.length, equals(3)); expect(blueprint.blueprints.length, equals(3));
}); });
test('adds the components to a game on attach', () { test('adds the components to a game on attach', () {
final mockGame = MockPinballGame(); final mockGame = MockGame();
when(() => mockGame.addAll(any())).thenAnswer((_) async {}); when(() => mockGame.addAll(any())).thenAnswer((_) async {});
MyBlueprint().attach(mockGame); MyBlueprint().attach(mockGame);
@ -72,7 +71,7 @@ void main() {
}); });
test('adds components from a child Blueprint the to a game on attach', () { test('adds components from a child Blueprint the to a game on attach', () {
final mockGame = MockPinballGame(); final mockGame = MockGame();
when(() => mockGame.addAll(any())).thenAnswer((_) async {}); when(() => mockGame.addAll(any())).thenAnswer((_) async {});
MyComposedBlueprint().attach(mockGame); MyComposedBlueprint().attach(mockGame);
@ -82,7 +81,7 @@ void main() {
test( test(
'throws assertion error when adding to an already attached blueprint', 'throws assertion error when adding to an already attached blueprint',
() async { () async {
final mockGame = MockPinballGame(); final mockGame = MockGame();
when(() => mockGame.addAll(any())).thenAnswer((_) async {}); when(() => mockGame.addAll(any())).thenAnswer((_) async {});
final blueprint = MyBlueprint(); final blueprint = MyBlueprint();
await blueprint.attach(mockGame); await blueprint.attach(mockGame);
@ -99,13 +98,13 @@ void main() {
}); });
test('callbacks can be added to it', () { test('callbacks can be added to it', () {
final blueprint = MyForge2dBlueprint()..build(MockPinballGame()); final blueprint = MyForge2dBlueprint()..build(MockGame());
expect(blueprint.callbacks.length, equals(3)); expect(blueprint.callbacks.length, equals(3));
}); });
test('adds the callbacks to a game on attach', () async { test('adds the callbacks to a game on attach', () async {
final mockGame = MockPinballGame(); final mockGame = MockGame();
when(() => mockGame.addAll(any())).thenAnswer((_) async {}); when(() => mockGame.addAll(any())).thenAnswer((_) async {});
when(() => mockGame.addContactCallback(any())).thenAnswer((_) async {}); when(() => mockGame.addContactCallback(any())).thenAnswer((_) async {});
await MyForge2dBlueprint().attach(mockGame); await MyForge2dBlueprint().attach(mockGame);
@ -116,7 +115,7 @@ void main() {
test( test(
'throws assertion error when adding to an already attached blueprint', 'throws assertion error when adding to an already attached blueprint',
() async { () async {
final mockGame = MockPinballGame(); final mockGame = MockGame();
when(() => mockGame.addAll(any())).thenAnswer((_) async {}); when(() => mockGame.addAll(any())).thenAnswer((_) async {});
when(() => mockGame.addContactCallback(any())).thenAnswer((_) async {}); when(() => mockGame.addContactCallback(any())).thenAnswer((_) async {});
final blueprint = MyForge2dBlueprint(); final blueprint = MyForge2dBlueprint();

@ -43,7 +43,6 @@ flutter:
assets: assets:
- assets/images/components/ - assets/images/components/
- assets/images/components/spaceship/
flutter_gen: flutter_gen:
line_length: 80 line_length: 80

@ -4,6 +4,7 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
void main() { void main() {
group('Baseboard', () { group('Baseboard', () {

@ -3,6 +3,7 @@
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart'; import '../../helpers/helpers.dart';

@ -29,6 +29,7 @@ void main() {
group('listenWhen', () { group('listenWhen', () {
final previousState = MockGameState(); final previousState = MockGameState();
final currentState = MockGameState(); final currentState = MockGameState();
test( test(
'returns true when there is a new word bonus awarded', 'returns true when there is a new word bonus awarded',
() { () {

@ -0,0 +1,169 @@
import 'dart:collection';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGameTest.create);
group('FlipperController', () {
group('onKeyEvent', () {
final leftKeys = UnmodifiableListView([
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.keyA,
]);
final rightKeys = UnmodifiableListView([
LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.keyD,
]);
group('and Flipper is left', () {
late Flipper flipper;
late FlipperController controller;
setUp(() {
flipper = Flipper(side: BoardSide.left);
controller = FlipperController(flipper);
flipper.add(controller);
});
testRawKeyDownEvents(leftKeys, (event) {
flameTester.test(
'moves upwards '
'when ${event.logicalKey.keyLabel} is pressed',
(game) async {
await game.ready();
await game.add(flipper);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isPositive);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(leftKeys, (event) {
flameTester.test(
'moves downwards '
'when ${event.logicalKey.keyLabel} is released',
(game) async {
await game.ready();
await game.add(flipper);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isNegative);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(rightKeys, (event) {
flameTester.test(
'does nothing '
'when ${event.logicalKey.keyLabel} is released',
(game) async {
await game.ready();
await game.add(flipper);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyDownEvents(rightKeys, (event) {
flameTester.test(
'does nothing '
'when ${event.logicalKey.keyLabel} is pressed',
(game) async {
await game.ready();
await game.add(flipper);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
});
group('and Flipper is right', () {
late Flipper flipper;
late FlipperController controller;
setUp(() {
flipper = Flipper(side: BoardSide.right);
controller = FlipperController(flipper);
flipper.add(controller);
});
testRawKeyDownEvents(rightKeys, (event) {
flameTester.test(
'moves upwards '
'when ${event.logicalKey.keyLabel} is pressed',
(game) async {
await game.ready();
await game.add(flipper);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isPositive);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(rightKeys, (event) {
flameTester.test(
'moves downwards '
'when ${event.logicalKey.keyLabel} is released',
(game) async {
await game.ready();
await game.add(flipper);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isNegative);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(leftKeys, (event) {
flameTester.test(
'does nothing '
'when ${event.logicalKey.keyLabel} is released',
(game) async {
await game.ready();
await game.add(flipper);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyDownEvents(leftKeys, (event) {
flameTester.test(
'does nothing '
'when ${event.logicalKey.keyLabel} is pressed',
(game) async {
await game.ready();
await game.add(flipper);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
});
});
});
}

@ -1,275 +0,0 @@
// ignore_for_file: cascade_invocations
import 'dart:collection';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGameTest.create);
group(
'Flipper',
() {
// TODO(alestiago): Add golden tests.
flameTester.test(
'loads correctly',
(game) async {
final leftFlipper = Flipper(
side: BoardSide.left,
);
final rightFlipper = Flipper(
side: BoardSide.right,
);
await game.ready();
await game.ensureAddAll([leftFlipper, rightFlipper]);
expect(game.contains(leftFlipper), isTrue);
expect(game.contains(rightFlipper), isTrue);
},
);
group('constructor', () {
test('sets BoardSide', () {
final leftFlipper = Flipper(
side: BoardSide.left,
);
expect(leftFlipper.side, equals(leftFlipper.side));
final rightFlipper = Flipper(
side: BoardSide.right,
);
expect(rightFlipper.side, equals(rightFlipper.side));
});
});
group('body', () {
flameTester.test(
'is dynamic',
(game) async {
final flipper = Flipper(
side: BoardSide.left,
);
await game.ensureAdd(flipper);
expect(flipper.body.bodyType, equals(BodyType.dynamic));
},
);
flameTester.test(
'ignores gravity',
(game) async {
final flipper = Flipper(
side: BoardSide.left,
);
await game.ensureAdd(flipper);
expect(flipper.body.gravityScale, isZero);
},
);
flameTester.test(
'has greater mass than Ball',
(game) async {
final flipper = Flipper(
side: BoardSide.left,
);
final ball = Ball(baseColor: Colors.white);
await game.ready();
await game.ensureAddAll([flipper, ball]);
expect(
flipper.body.getMassData().mass,
greaterThan(ball.body.getMassData().mass),
);
},
);
});
group('fixtures', () {
flameTester.test(
'has three',
(game) async {
final flipper = Flipper(
side: BoardSide.left,
);
await game.ensureAdd(flipper);
expect(flipper.body.fixtures.length, equals(3));
},
);
flameTester.test(
'has density',
(game) async {
final flipper = Flipper(
side: BoardSide.left,
);
await game.ensureAdd(flipper);
final fixtures = flipper.body.fixtures;
final density = fixtures.fold<double>(
0,
(sum, fixture) => sum + fixture.density,
);
expect(density, greaterThan(0));
},
);
});
group('onKeyEvent', () {
final leftKeys = UnmodifiableListView([
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.keyA,
]);
final rightKeys = UnmodifiableListView([
LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.keyD,
]);
group('and Flipper is left', () {
late Flipper flipper;
setUp(() {
flipper = Flipper(
side: BoardSide.left,
);
});
testRawKeyDownEvents(leftKeys, (event) {
flameTester.test(
'moves upwards '
'when ${event.logicalKey.keyLabel} is pressed',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isPositive);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(leftKeys, (event) {
flameTester.test(
'moves downwards '
'when ${event.logicalKey.keyLabel} is released',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isNegative);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(rightKeys, (event) {
flameTester.test(
'does nothing '
'when ${event.logicalKey.keyLabel} is released',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyDownEvents(rightKeys, (event) {
flameTester.test(
'does nothing '
'when ${event.logicalKey.keyLabel} is pressed',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
});
group('and Flipper is right', () {
late Flipper flipper;
setUp(() {
flipper = Flipper(
side: BoardSide.right,
);
});
testRawKeyDownEvents(rightKeys, (event) {
flameTester.test(
'moves upwards '
'when ${event.logicalKey.keyLabel} is pressed',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isPositive);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(rightKeys, (event) {
flameTester.test(
'moves downwards '
'when ${event.logicalKey.keyLabel} is released',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isNegative);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(leftKeys, (event) {
flameTester.test(
'does nothing '
'when ${event.logicalKey.keyLabel} is released',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
testRawKeyDownEvents(leftKeys, (event) {
flameTester.test(
'does nothing '
'when ${event.logicalKey.keyLabel} is pressed',
(game) async {
await game.ensureAdd(flipper);
flipper.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
);
});
});
});
},
);
}

@ -4,6 +4,7 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
void main() { void main() {
group('Kicker', () { group('Kicker', () {

@ -1,44 +1,195 @@
// ignore_for_file: prefer_const_constructors // ignore_for_file: prefer_const_constructors
import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mockingjay/mockingjay.dart'; import 'package:mockingjay/mockingjay.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/leaderboard/leaderboard.dart';
import 'package:pinball_theme/pinball_theme.dart'; import 'package:pinball_theme/pinball_theme.dart';
import '../../../helpers/helpers.dart'; import '../../../helpers/helpers.dart';
void main() { void main() {
group('GameOverDialog', () { group('GameOverDialog', () {
testWidgets('renders correctly', (tester) async { testWidgets('renders GameOverDialogView', (tester) async {
final l10n = await AppLocalizations.delegate.load(Locale('en'));
await tester.pumpApp( await tester.pumpApp(
const GameOverDialog( GameOverDialog(
score: 1000,
theme: DashTheme(), theme: DashTheme(),
), ),
); );
expect(find.text(l10n.gameOver), findsOneWidget); expect(find.byType(GameOverDialogView), findsOneWidget);
expect(find.text(l10n.leaderboard), findsOneWidget);
}); });
testWidgets('tapping on leaderboard button navigates to LeaderBoardPage', group('GameOverDialogView', () {
(tester) async { late LeaderboardBloc leaderboardBloc;
final l10n = await AppLocalizations.delegate.load(Locale('en'));
final navigator = MockNavigator();
when(() => navigator.push<void>(any())).thenAnswer((_) async {});
await tester.pumpApp( final leaderboard = [
const GameOverDialog( LeaderboardEntry(
theme: DashTheme(), rank: '1',
playerInitials: 'ABC',
score: 5000,
character: DashTheme().characterAsset,
), ),
navigator: navigator, ];
final entryData = LeaderboardEntryData(
playerInitials: 'VGV',
score: 10000,
character: CharacterType.dash,
); );
await tester.tap(find.widgetWithText(TextButton, l10n.leaderboard)); setUp(() {
leaderboardBloc = MockLeaderboardBloc();
whenListen(
leaderboardBloc,
const Stream<LeaderboardState>.empty(),
initialState: const LeaderboardState.initial(),
);
});
testWidgets('renders input text view when bloc emits [loading]',
(tester) async {
final l10n = await AppLocalizations.delegate.load(Locale('en'));
await tester.pumpApp(
BlocProvider.value(
value: leaderboardBloc,
child: GameOverDialogView(
score: entryData.score,
theme: entryData.character.toTheme,
),
),
);
expect(find.widgetWithText(TextButton, l10n.addUser), findsOneWidget);
});
testWidgets('renders error view when bloc emits [error]', (tester) async {
final l10n = await AppLocalizations.delegate.load(Locale('en'));
whenListen(
leaderboardBloc,
const Stream<LeaderboardState>.empty(),
initialState: LeaderboardState.initial()
.copyWith(status: LeaderboardStatus.error),
);
await tester.pumpApp(
BlocProvider.value(
value: leaderboardBloc,
child: GameOverDialogView(
score: entryData.score,
theme: entryData.character.toTheme,
),
),
);
expect(find.text(l10n.error), findsOneWidget);
});
testWidgets('renders success view when bloc emits [success]',
(tester) async {
final l10n = await AppLocalizations.delegate.load(Locale('en'));
whenListen(
leaderboardBloc,
const Stream<LeaderboardState>.empty(),
initialState: LeaderboardState(
status: LeaderboardStatus.success,
ranking: LeaderboardRanking(ranking: 1, outOf: 2),
leaderboard: leaderboard,
),
);
await tester.pumpApp(
BlocProvider.value(
value: leaderboardBloc,
child: GameOverDialogView(
score: entryData.score,
theme: entryData.character.toTheme,
),
),
);
expect(
find.widgetWithText(TextButton, l10n.leaderboard),
findsOneWidget,
);
});
testWidgets('adds LeaderboardEntryAdded when tap on add user button',
(tester) async {
final l10n = await AppLocalizations.delegate.load(Locale('en'));
whenListen(
leaderboardBloc,
const Stream<LeaderboardState>.empty(),
initialState: LeaderboardState.initial(),
);
await tester.pumpApp(
BlocProvider.value(
value: leaderboardBloc,
child: GameOverDialogView(
score: entryData.score,
theme: entryData.character.toTheme,
),
),
);
await tester.enterText(
find.byKey(const Key('player_initials_text_field')),
entryData.playerInitials,
);
final button = find.widgetWithText(TextButton, l10n.addUser);
await tester.ensureVisible(button);
await tester.tap(button);
verify(
() => leaderboardBloc.add(LeaderboardEntryAdded(entry: entryData)),
).called(1);
});
testWidgets('navigates to LeaderboardPage when tap on leaderboard button',
(tester) async {
final l10n = await AppLocalizations.delegate.load(Locale('en'));
final navigator = MockNavigator();
when(() => navigator.push<void>(any())).thenAnswer((_) async {});
whenListen(
leaderboardBloc,
const Stream<LeaderboardState>.empty(),
initialState: LeaderboardState(
status: LeaderboardStatus.success,
ranking: LeaderboardRanking(ranking: 1, outOf: 2),
leaderboard: leaderboard,
),
);
await tester.pumpApp(
BlocProvider.value(
value: leaderboardBloc,
child: GameOverDialogView(
score: entryData.score,
theme: entryData.character.toTheme,
),
),
navigator: navigator,
);
final button = find.widgetWithText(TextButton, l10n.leaderboard);
await tester.ensureVisible(button);
await tester.tap(button);
verify(() => navigator.push<void>(any())).called(1); verify(() => navigator.push<void>(any())).called(1);
});
}); });
}); });
} }

@ -1,4 +1,3 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
@ -39,8 +38,7 @@ class MockGameState extends Mock implements GameState {}
class MockThemeCubit extends Mock implements ThemeCubit {} class MockThemeCubit extends Mock implements ThemeCubit {}
class MockLeaderboardBloc extends MockBloc<LeaderboardEvent, LeaderboardState> class MockLeaderboardBloc extends Mock implements LeaderboardBloc {}
implements LeaderboardBloc {}
class MockLeaderboardRepository extends Mock implements LeaderboardRepository {} class MockLeaderboardRepository extends Mock implements LeaderboardRepository {}

@ -1,5 +1,6 @@
// ignore_for_file: prefer_const_constructors // ignore_for_file: prefer_const_constructors
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -42,7 +43,11 @@ void main() {
testWidgets('renders correctly', (tester) async { testWidgets('renders correctly', (tester) async {
final l10n = await AppLocalizations.delegate.load(Locale('en')); final l10n = await AppLocalizations.delegate.load(Locale('en'));
when(() => leaderboardBloc.state).thenReturn(LeaderboardState.initial()); whenListen(
leaderboardBloc,
const Stream<LeaderboardState>.empty(),
initialState: LeaderboardState.initial(),
);
await tester.pumpApp( await tester.pumpApp(
BlocProvider.value( BlocProvider.value(
@ -59,7 +64,11 @@ void main() {
testWidgets('renders loading view when bloc emits [loading]', testWidgets('renders loading view when bloc emits [loading]',
(tester) async { (tester) async {
when(() => leaderboardBloc.state).thenReturn(LeaderboardState.initial()); whenListen(
leaderboardBloc,
const Stream<LeaderboardState>.empty(),
initialState: LeaderboardState.initial(),
);
await tester.pumpApp( await tester.pumpApp(
BlocProvider.value( BlocProvider.value(
@ -76,8 +85,12 @@ void main() {
}); });
testWidgets('renders error view when bloc emits [error]', (tester) async { testWidgets('renders error view when bloc emits [error]', (tester) async {
when(() => leaderboardBloc.state).thenReturn( whenListen(
LeaderboardState.initial().copyWith(status: LeaderboardStatus.error), leaderboardBloc,
const Stream<LeaderboardState>.empty(),
initialState: LeaderboardState.initial().copyWith(
status: LeaderboardStatus.error,
),
); );
await tester.pumpApp( await tester.pumpApp(
@ -97,8 +110,10 @@ void main() {
testWidgets('renders success view when bloc emits [success]', testWidgets('renders success view when bloc emits [success]',
(tester) async { (tester) async {
final l10n = await AppLocalizations.delegate.load(Locale('en')); final l10n = await AppLocalizations.delegate.load(Locale('en'));
when(() => leaderboardBloc.state).thenReturn( whenListen(
LeaderboardState( leaderboardBloc,
const Stream<LeaderboardState>.empty(),
initialState: LeaderboardState(
status: LeaderboardStatus.success, status: LeaderboardStatus.success,
ranking: LeaderboardRanking(ranking: 0, outOf: 0), ranking: LeaderboardRanking(ranking: 0, outOf: 0),
leaderboard: [ leaderboard: [

Loading…
Cancel
Save